From 82b1fd1326d82d2978bb96530168bcb8eb438370 Mon Sep 17 00:00:00 2001 From: Nick Williams <5554798+metanoic@users.noreply.github.com> Date: Sun, 22 Jul 2018 10:12:25 -0500 Subject: [PATCH 001/549] taskName is deprecated. Use 'label' instead. https://github.com/Microsoft/vscode/issues/29852 --- Firmware/.vscode/tasks.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Firmware/.vscode/tasks.json b/Firmware/.vscode/tasks.json index 2376b650..d5b7d969 100644 --- a/Firmware/.vscode/tasks.json +++ b/Firmware/.vscode/tasks.json @@ -4,7 +4,7 @@ "version": "2.0.0", "tasks": [ { - "taskName": "build", + "label": "build", "type": "shell", "command": "make", "group": { @@ -19,13 +19,13 @@ ] }, { - "taskName": "flash", + "label": "flash", "type": "shell", "command": "make flash", "problemMatcher": [] }, { - "taskName": "openocd", + "label": "openocd", "type": "shell", "command": "openocd -f \"interface/stlink-v2.cfg\" -f \"target/stm32f4x_stlink.cfg\" -c \"gdb_port 3333; log_output openocd.log\"", "problemMatcher": [] From f05cfa01a98134cd411c57caae740bfee65864b4 Mon Sep 17 00:00:00 2001 From: Nick Williams <5554798+metanoic@users.noreply.github.com> Date: Sun, 22 Jul 2018 10:31:11 -0500 Subject: [PATCH 002/549] add flashbmp to makefile and accompanying task --- Firmware/.vscode/launch.json | 18 ++++++++++++++++-- Firmware/.vscode/tasks.json | 8 +++++++- Firmware/Makefile | 9 +++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/Firmware/.vscode/launch.json b/Firmware/.vscode/launch.json index 71e7db4c..1b99a2a6 100644 --- a/Firmware/.vscode/launch.json +++ b/Firmware/.vscode/launch.json @@ -9,7 +9,7 @@ "type": "cortex-debug", "servertype": "openocd", "request": "launch", - "name": "Debug ODrive", + "name": "Debug ODrive - ST-Link", "executable": "${workspaceRoot}/build/ODriveFirmware.elf", "configFiles": [ "interface/stlink-v2.cfg", @@ -22,7 +22,7 @@ "type": "cortex-debug", "servertype": "openocd", "request": "launch", - "name": "Debug ODrive - FreeRTOS", + "name": "Debug ODrive - ST-Link - FreeRTOS", "executable": "${workspaceRoot}/build/ODriveFirmware.elf", "rtos": "FreeRTOS", "configFiles": [ @@ -31,5 +31,19 @@ ], "cwd": "${workspaceRoot}" }, + { + // For the Cortex-Debug extensions + "type": "cortex-debug", + "servertype": "bmp", + "request": "launch", + "name": "Debug ODrive - Black Magic Probe", + "executable": "${workspaceRoot}/build/ODriveFirmware.elf", + "device": "STM32F4xx", + "BMPGDBSerialPort": "${env:BMP_PORT}", + "interface": "swd", + "targetId": 1, + "armToolchainPath": "${env:ARM_GCC_ROOT}/bin/", + "cwd": "${workspaceRoot}" + } ] } \ No newline at end of file diff --git a/Firmware/.vscode/tasks.json b/Firmware/.vscode/tasks.json index d5b7d969..c82adc83 100644 --- a/Firmware/.vscode/tasks.json +++ b/Firmware/.vscode/tasks.json @@ -19,11 +19,17 @@ ] }, { - "label": "flash", + "label": "flash - ST-Link", "type": "shell", "command": "make flash", "problemMatcher": [] }, + { + "label": "flash - Black Magic Probe", + "type": "shell", + "command": "make flashbmp", + "problemMatcher": [] + }, { "label": "openocd", "type": "shell", diff --git a/Firmware/Makefile b/Firmware/Makefile index 82ae758c..b498343c 100644 --- a/Firmware/Makefile +++ b/Firmware/Makefile @@ -20,6 +20,15 @@ flash: all -c 'reset run' \ -c exit +flashbmp: all + arm-none-eabi-gdb --ex 'target extended-remote $(BMP_PORT)' \ + --ex 'monitor swdp_scan' \ + --ex 'attach 1' \ + --ex 'load' \ + --ex 'detach' \ + --ex 'quit' \ + $(FIRMWARE) + gdb: all arm-none-eabi-gdb $(FIRMWARE) -x openocd.gdbinit From 0600cb851c5539477937ca1db562dd43cca4c0a4 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 19 Aug 2018 20:51:41 -0400 Subject: [PATCH 003/549] Add selectable intterupt mode to gpio_subscribe --- Firmware/Board/v3/Inc/gpio.h | 4 ++-- Firmware/Board/v3/Src/gpio.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Firmware/Board/v3/Inc/gpio.h b/Firmware/Board/v3/Inc/gpio.h index 7c7a4e63..c7251132 100644 --- a/Firmware/Board/v3/Inc/gpio.h +++ b/Firmware/Board/v3/Inc/gpio.h @@ -72,8 +72,8 @@ void MX_GPIO_Init(void); void SetGPIO12toUART(); bool GPIO_subscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin, - uint32_t pull_up_down, - void (*callback)(void*), void* ctx); + uint32_t pull_up_down, uint32_t interrupt_mode, + void (*callback)(void*), void* ctx); void GPIO_unsubscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin); void GPIO_set_to_analog(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin); diff --git a/Firmware/Board/v3/Src/gpio.c b/Firmware/Board/v3/Src/gpio.c index 031158d5..dfec1cf2 100644 --- a/Firmware/Board/v3/Src/gpio.c +++ b/Firmware/Board/v3/Src/gpio.c @@ -205,8 +205,8 @@ size_t n_subscriptions = 0; // on a rising edge of the GPIO. // @param pull_up_down: one of GPIO_NOPULL, GPIO_PULLUP or GPIO_PULLDOWN bool GPIO_subscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin, - uint32_t pull_up_down, - void (*callback)(void*), void* ctx) { + uint32_t pull_up_down, uint32_t interrupt_mode, + void (*callback)(void*), void* ctx) { // Register handler (or reuse existing registration) // TODO: make thread safe @@ -232,7 +232,7 @@ bool GPIO_subscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin, // Set up GPIO GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = GPIO_pin; - GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; + GPIO_InitStruct.Mode = interrupt_mode; GPIO_InitStruct.Pull = pull_up_down; HAL_GPIO_Init(GPIO_port, &GPIO_InitStruct); From 92fe075b9eb76ee20d428b619878519261637359 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 19 Aug 2018 20:52:14 -0400 Subject: [PATCH 004/549] Fix existing calls to GPIO_Subscribe --- Firmware/MotorControl/axis.cpp | 4 ++-- Firmware/MotorControl/encoder.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 7d79b3d6..d560ae9d 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -81,8 +81,8 @@ void Axis::set_step_dir_enabled(bool enable) { HAL_GPIO_Init(hw_config_.dir_port, &GPIO_InitStruct); // Subscribe to rising edges of the step GPIO - GPIO_subscribe(hw_config_.step_port, hw_config_.step_pin, GPIO_PULLDOWN, - step_cb_wrapper, this); + GPIO_subscribe(hw_config_.step_port, hw_config_.step_pin, GPIO_PULLDOWN, + GPIO_MODE_IT_FALLING, step_cb_wrapper, this); enable_step_dir_ = true; } else { diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 5b80ed8d..0f2f838a 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -19,7 +19,7 @@ static void enc_index_cb_wrapper(void* ctx) { void Encoder::setup() { HAL_TIM_Encoder_Start(hw_config_.timer, TIM_CHANNEL_ALL); GPIO_subscribe(hw_config_.index_port, hw_config_.index_pin, GPIO_NOPULL, - enc_index_cb_wrapper, this); + GPIO_MODE_IT_RISING, enc_index_cb_wrapper, this); } void Encoder::set_error(Encoder::Error_t error) { From 6844cfb86239070f30bd92ed8fc76cbaa1a212e7 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 19 Aug 2018 20:52:55 -0400 Subject: [PATCH 005/549] Add configuration options for endstops --- Firmware/MotorControl/axis.hpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 111f9e26..11b3e269 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -19,6 +19,11 @@ enum AxisState_t { AXIS_STATE_CLOSED_LOOP_CONTROL = 8 // Date: Sun, 19 Aug 2018 20:53:26 -0400 Subject: [PATCH 006/549] Add setup and callback functions for endstops --- Firmware/MotorControl/axis.cpp | 69 ++++++++++++++++++++++++++++++++++ Firmware/MotorControl/axis.hpp | 8 ++++ 2 files changed, 77 insertions(+) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index d560ae9d..3bdb03ce 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -29,6 +29,14 @@ static void step_cb_wrapper(void* ctx) { reinterpret_cast(ctx)->step_cb(); } +static void min_endstop_cb_wrapper(void* ctx){ + reinterpret_cast(ctx)->min_endstop_cb(); +} + +static void max_endstop_cb_wrapper(void* ctx){ + reinterpret_cast(ctx)->max_endstop_cb(); +} + // @brief Sets up all components of the axis, // such as gate driver and encoder hardware. void Axis::setup() { @@ -93,6 +101,64 @@ void Axis::set_step_dir_enabled(bool enable) { } } +void Axis::min_endstop_cb(){ + uint16_t gpio_pin = get_gpio_pin_by_pin(config_.min_endstop.gpio_num); + GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.min_endstop.gpio_num); + + if(config_.min_endstop.enabled){ + min_endstop_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); + } else { + min_endstop_state_ = false; + } +} + +void Axis::set_min_endstop_enabled(bool enable){ + uint16_t gpio_pin = get_gpio_pin_by_pin(config_.min_endstop.gpio_num); + GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.min_endstop.gpio_num); + if(enable){ + GPIO_InitTypeDef GPIO_InitStruct; + GPIO_InitStruct.Pin = gpio_pin; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); + + GPIO_subscribe(gpio_port, gpio_pin, GPIO_PULLUP, GPIO_MODE_IT_RISING_FALLING, + min_endstop_cb_wrapper, this); + } + else { + GPIO_unsubscribe(gpio_port, gpio_pin); + } +} + +void Axis::max_endstop_cb(){ + uint16_t gpio_pin = get_gpio_pin_by_pin(config_.max_endstop.gpio_num); + GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.max_endstop.gpio_num); + + if(config_.max_endstop.enabled){ + max_endstop_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); + } else { + max_endstop_state_ = false; + } +} + +void Axis::set_max_endstop_enabled(bool enable){ + uint16_t gpio_pin = get_gpio_pin_by_pin(config_.max_endstop.gpio_num); + GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.max_endstop.gpio_num); + if(enable){ + GPIO_InitTypeDef GPIO_InitStruct; + GPIO_InitStruct.Pin = gpio_pin; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); + + GPIO_subscribe(gpio_port, gpio_pin, GPIO_PULLUP, GPIO_MODE_IT_RISING_FALLING, + max_endstop_cb_wrapper, this); + } + else { + GPIO_unsubscribe(gpio_port, gpio_pin); + } +} + bool Axis::check_for_errors() { // Maybe we should update this to only trigger on new errors? // The danger with that is we could fail to bail on uncleared errors that still prevent @@ -177,7 +243,10 @@ bool Axis::run_sensorless_spin_up() { // Note run_sensorless_control_loop and run_closed_loop_control_loop are very similar and differ only in where we get the estimate from. bool Axis::run_sensorless_control_loop() { + set_min_endstop_enabled(config_.min_endstop.enabled); + set_max_endstop_enabled(config_.max_endstop.enabled); set_step_dir_enabled(config_.enable_step_dir); + run_control_loop([this](){ if (controller_.config_.control_mode >= CTRL_MODE_POSITION_CONTROL) return error_ |= ERROR_POS_CTRL_DURING_SENSORLESS, false; diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 11b3e269..8b0095d1 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -81,6 +81,10 @@ public: void step_cb(); void set_step_dir_enabled(bool enable); + void min_endstop_cb(); + void set_min_endstop_enabled(bool enable); + void max_endstop_cb(); + void set_max_endstop_enabled(bool enable); bool check_DRV_fault(); bool check_PSU_brownout(); @@ -168,6 +172,8 @@ public: AxisState_t task_chain_[10] = { AXIS_STATE_UNDEFINED }; AxisState_t& current_state_ = task_chain_[0]; uint32_t loop_counter_ = 0; + bool min_endstop_state_ = false; + bool max_endstop_state_ = false; // Communication protocol definitions auto make_protocol_definitions() { @@ -177,6 +183,8 @@ public: make_protocol_ro_property("current_state", ¤t_state_), make_protocol_property("requested_state", &requested_state_), make_protocol_ro_property("loop_counter", &loop_counter_), + make_protocol_ro_property("min_endstop_state", &min_endstop_state_), + make_protocol_ro_property("max_endstop_state", &max_endstop_state_), make_protocol_object("config", make_protocol_property("startup_motor_calibration", &config_.startup_motor_calibration), make_protocol_property("startup_encoder_index_search", &config_.startup_encoder_index_search), From 41a590773ff558db25bd93e513bce8f461c9765e Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 19 Aug 2018 22:34:18 -0400 Subject: [PATCH 007/549] Add axis homing function and AXIS state --- Firmware/MotorControl/axis.cpp | 8 ++++++++ Firmware/MotorControl/axis.hpp | 10 +++++++--- Firmware/MotorControl/controller.cpp | 19 +++++++++++++++++++ Firmware/MotorControl/controller.hpp | 9 +++++++-- 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 3bdb03ce..dc57bdc4 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -321,6 +321,10 @@ void Axis::run_state_machine_loop() { else if (config_.startup_sensorless_control) task_chain_[pos++] = AXIS_STATE_SENSORLESS_CONTROL; task_chain_[pos++] = AXIS_STATE_IDLE; + } else if (requested_state_ == AXIS_STATE_HOMING){ + task_chain_[pos++] = AXIS_STATE_HOMING; + task_chain_[pos++] = AXIS_STATE_CLOSED_LOOP_CONTROL; + task_chain_[pos++] = AXIS_STATE_IDLE; } else if (requested_state_ == AXIS_STATE_FULL_CALIBRATION_SEQUENCE) { task_chain_[pos++] = AXIS_STATE_MOTOR_CALIBRATION; if (encoder_.config_.use_index) @@ -357,6 +361,10 @@ void Axis::run_state_machine_loop() { status = encoder_.run_index_search(); break; + case AXIS_STATE_HOMING: + status = controller_.home_axis(); + break; + case AXIS_STATE_ENCODER_OFFSET_CALIBRATION: status = encoder_.run_offset_calibration(); break; diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 8b0095d1..220a2874 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -16,12 +16,14 @@ enum AxisState_t { AXIS_STATE_SENSORLESS_CONTROL = 5, //config_.min_endstop.enabled) { + set_vel_setpoint(-config_.homing_speed, 0.0f); + } else { + return false; + } + + axis_->run_control_loop([&](){ + if(axis_->min_endstop_state_){ + axis_->encoder_.set_linear_count(axis_->config_.min_endstop.offset); + set_pos_setpoint(0.0f, 0.0f, 0.0f); + } + return !axis_->min_endstop_state_; + }); + return true; +} + /* * This anti-cogging implementation iterates through each encoder position, * waits for zero velocity & position error, diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index f10b6211..abc155ee 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -21,6 +21,7 @@ struct ControllerConfig_t { // float vel_gain = 5.0f / 200.0f, // [A/(rad/s)] float vel_integrator_gain = 10.0f / 10000.0f; // [A/(counts/s * s)] float vel_limit = 20000.0f; // [counts/s] + float homing_speed = 2000.0f; // [counts/s] }; class Controller { @@ -31,6 +32,8 @@ public: void set_pos_setpoint(float pos_setpoint, float vel_feed_forward, float current_feed_forward); void set_vel_setpoint(float vel_setpoint, float current_feed_forward); void set_current_setpoint(float current_setpoint); + + bool home_axis(); // TODO: make this more similar to other calibration loops void start_anticogging_calibration(); @@ -83,7 +86,8 @@ public: make_protocol_property("pos_gain", &config_.pos_gain), make_protocol_property("vel_gain", &config_.vel_gain), make_protocol_property("vel_integrator_gain", &config_.vel_integrator_gain), - make_protocol_property("vel_limit", &config_.vel_limit) + make_protocol_property("vel_limit", &config_.vel_limit), + make_protocol_property("homing_speed", &config_.homing_speed) ), make_protocol_function("set_pos_setpoint", *this, &Controller::set_pos_setpoint, "pos_setpoint", @@ -94,7 +98,8 @@ public: "current_feed_forward"), make_protocol_function("set_current_setpoint", *this, &Controller::set_current_setpoint, "current_setpoint"), - make_protocol_function("start_anticogging_calibration", *this, &Controller::start_anticogging_calibration) + make_protocol_function("start_anticogging_calibration", *this, &Controller::start_anticogging_calibration), + make_protocol_function("home_axis", *this, &Controller::home_axis) ); } }; From ef954aa84d1bd8b85b41480e763a28493c68aed9 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 19 Aug 2018 22:40:17 -0400 Subject: [PATCH 008/549] Add endstop pressed errors in closed_loop_control mode --- Firmware/MotorControl/axis.cpp | 8 ++++++++ Firmware/MotorControl/axis.hpp | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index dc57bdc4..dae77bd4 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -272,6 +272,14 @@ bool Axis::run_closed_loop_control_loop() { return error_ |= ERROR_CONTROLLER_FAILED, false; //TODO: Make controller.set_error if (!motor_.update(current_setpoint, encoder_.phase_)) return false; // set_error should update axis.error_ + + + // Check for endstop presses + if(config_.min_endstop.enabled && min_endstop_state_) { + return error_ |= ERROR_MIN_ENDSTOP_PRESSED, false; + } else if(config_.max_endstop.enabled && max_endstop_state_) { + return error_ |= ERROR_MAX_ENDSTOP_PRESSED, false; + } return true; }); set_step_dir_enabled(false); diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 220a2874..e78413e9 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -20,7 +20,7 @@ enum AxisState_t { AXIS_STATE_HOMING = 9 // Date: Sun, 19 Aug 2018 22:44:08 -0400 Subject: [PATCH 009/549] Add homing to python enums --- tools/odrive/enums.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 19572c90..5d4d759e 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -10,6 +10,7 @@ AXIS_STATE_SENSORLESS_CONTROL = 5 AXIS_STATE_ENCODER_INDEX_SEARCH = 6 AXIS_STATE_ENCODER_OFFSET_CALIBRATION = 7 AXIS_STATE_CLOSED_LOOP_CONTROL = 8 +AXIS_STATE_HOMING = 9 AXIS_ERROR_NONE = 0 AXIS_ERROR_INVALID_STATE = 1 From ccfdddbc807903cd20cf3392d966f6278144f6fd Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 19 Aug 2018 23:00:19 -0400 Subject: [PATCH 010/549] Add is_active_high config var to endstops --- Firmware/MotorControl/axis.cpp | 5 +++++ Firmware/MotorControl/axis.hpp | 9 ++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index dae77bd4..f4f2e116 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -110,6 +110,8 @@ void Axis::min_endstop_cb(){ } else { min_endstop_state_ = false; } + if(config_.min_endstop.is_active_high == false) + min_endstop_state_ = !min_endstop_state_; } void Axis::set_min_endstop_enabled(bool enable){ @@ -139,6 +141,9 @@ void Axis::max_endstop_cb(){ } else { max_endstop_state_ = false; } + + if(config_.max_endstop.is_active_high == false) + max_endstop_state_ = !max_endstop_state_; } void Axis::set_max_endstop_enabled(bool enable){ diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index e78413e9..0f4fd53f 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -22,8 +22,9 @@ enum AxisState_t { struct Endstop_t { uint16_t gpio_num; - bool enabled; + bool enabled = false; int32_t offset = 0; + bool is_active_high = false; }; struct AxisConfig_t { @@ -205,12 +206,14 @@ public: make_protocol_object("min_endstop", make_protocol_property("gpio_num", &config_.min_endstop.gpio_num), make_protocol_property("enabled", &config_.min_endstop.enabled), - make_protocol_property("offset", &config_.min_endstop.offset) + make_protocol_property("offset", &config_.min_endstop.offset), + make_protocol_property("is_active_high", &config_.min_endstop.is_active_high) ), make_protocol_object("max_endstop", make_protocol_property("gpio_num", &config_.max_endstop.gpio_num), make_protocol_property("enabled", &config_.max_endstop.enabled), - make_protocol_property("offset", &config_.max_endstop.offset) + make_protocol_property("offset", &config_.max_endstop.offset), + make_protocol_property("is_active_high", &config_.max_endstop.is_active_high) ) ), make_protocol_function("get_temp", *this, &Axis::get_temp), From 725f19dff0b8ecc3cbaf8390d862bf10373b5919 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 19 Aug 2018 23:01:55 -0400 Subject: [PATCH 011/549] Move endstop polarity check to avoid flipping a disabled endstop --- Firmware/MotorControl/axis.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index f4f2e116..655747f9 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -107,11 +107,11 @@ void Axis::min_endstop_cb(){ if(config_.min_endstop.enabled){ min_endstop_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); + if(config_.min_endstop.is_active_high == false) + min_endstop_state_ = !min_endstop_state_; } else { min_endstop_state_ = false; } - if(config_.min_endstop.is_active_high == false) - min_endstop_state_ = !min_endstop_state_; } void Axis::set_min_endstop_enabled(bool enable){ @@ -138,12 +138,11 @@ void Axis::max_endstop_cb(){ if(config_.max_endstop.enabled){ max_endstop_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); + if(config_.max_endstop.is_active_high == false) + max_endstop_state_ = !max_endstop_state_; } else { max_endstop_state_ = false; } - - if(config_.max_endstop.is_active_high == false) - max_endstop_state_ = !max_endstop_state_; } void Axis::set_max_endstop_enabled(bool enable){ From 965a42a928970857126dd7b12cf507e4f1f3f547 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 28 Aug 2018 23:33:11 -0400 Subject: [PATCH 012/549] Adjust pullup and interrupt mode based on endstop polarity. --- Firmware/MotorControl/axis.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 655747f9..6358137c 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -124,7 +124,9 @@ void Axis::set_min_endstop_enabled(bool enable){ GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); - GPIO_subscribe(gpio_port, gpio_pin, GPIO_PULLUP, GPIO_MODE_IT_RISING_FALLING, + uint32_t pull_up_down = config_.min_endstop.is_active_high ? GPIO_PULLDOWN : GPIO_PULLUP; + uint32_t interrupt_mode = config_.min_endstop.is_active_high ? GPIO_MODE_IT_RISING : GPIO_MODE_IT_FALLING; + GPIO_subscribe(gpio_port, gpio_pin, pull_up_down, interrupt_mode, min_endstop_cb_wrapper, this); } else { @@ -155,7 +157,9 @@ void Axis::set_max_endstop_enabled(bool enable){ GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); - GPIO_subscribe(gpio_port, gpio_pin, GPIO_PULLUP, GPIO_MODE_IT_RISING_FALLING, + uint32_t pull_up_down = config_.max_endstop.is_active_high ? GPIO_PULLDOWN : GPIO_PULLUP; + uint32_t interrupt_mode = config_.max_endstop.is_active_high ? GPIO_MODE_IT_RISING : GPIO_MODE_IT_FALLING; + GPIO_subscribe(gpio_port, gpio_pin, pull_up_down, interrupt_mode, max_endstop_cb_wrapper, this); } else { From 8df5e217b0d880d34ce51739dbb062ac62f3f7df Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 29 Aug 2018 22:39:32 -0400 Subject: [PATCH 013/549] Add missing pin #'s in get_gpio_by_x functions --- Firmware/Board/v3/Src/gpio.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Firmware/Board/v3/Src/gpio.c b/Firmware/Board/v3/Src/gpio.c index dfec1cf2..2a7133df 100644 --- a/Firmware/Board/v3/Src/gpio.c +++ b/Firmware/Board/v3/Src/gpio.c @@ -285,6 +285,15 @@ GPIO_TypeDef* get_gpio_port_by_pin(uint16_t GPIO_pin){ case 4: return GPIO_4_GPIO_Port; break; #ifdef GPIO_5_GPIO_Port case 5: return GPIO_5_GPIO_Port; break; +#endif +#ifdef GPIO_6_GPIO_Port + case 6: return GPIO_6_GPIO_Port; break; +#endif +#ifdef GPIO_7_GPIO_Port + case 7: return GPIO_7_GPIO_Port; break; +#endif +#ifdef GPIO_8_GPIO_Port + case 8: return GPIO_8_GPIO_Port; break; #endif default: return GPIO_1_GPIO_Port; } @@ -298,6 +307,15 @@ uint16_t get_gpio_pin_by_pin(uint16_t GPIO_pin){ case 4: return GPIO_4_Pin; break; #ifdef GPIO_5_Pin case 5: return GPIO_5_Pin; break; +#endif +#ifdef GPIO_6_Pin + case 6: return GPIO_6_Pin; break; +#endif +#ifdef GPIO_7_Pin + case 7: return GPIO_7_Pin; break; +#endif +#ifdef GPIO_8_Pin + case 8: return GPIO_8_Pin; break; #endif default: return GPIO_1_Pin; } From dd74eed54b140b3f21904fc02348c0292a2dde3c Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 29 Aug 2018 22:49:11 -0400 Subject: [PATCH 014/549] Add .startup_homing configuration value --- Firmware/MotorControl/axis.cpp | 5 ++++- Firmware/MotorControl/axis.hpp | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 6358137c..2275aa97 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -332,8 +332,11 @@ void Axis::run_state_machine_loop() { task_chain_[pos++] = AXIS_STATE_ENCODER_INDEX_SEARCH; if (config_.startup_encoder_offset_calibration) task_chain_[pos++] = AXIS_STATE_ENCODER_OFFSET_CALIBRATION; - if (config_.startup_closed_loop_control) + if (config_.startup_closed_loop_control){ task_chain_[pos++] = AXIS_STATE_CLOSED_LOOP_CONTROL; + if(config_.startup_homing) + task_chain_[pos++] = AXIS_STATE_HOMING; + } else if (config_.startup_sensorless_control) task_chain_[pos++] = AXIS_STATE_SENSORLESS_CONTROL; task_chain_[pos++] = AXIS_STATE_IDLE; diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 0f4fd53f..21c8ff16 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -34,6 +34,7 @@ struct AxisConfig_t { bool startup_encoder_offset_calibration = false; // Date: Sun, 2 Sep 2018 01:59:35 -0400 Subject: [PATCH 015/549] Fix bugs in homing by actually testing and using the debugger... --- Firmware/MotorControl/axis.cpp | 41 ++++++++++++++++++---------- Firmware/MotorControl/axis.hpp | 8 ++++++ Firmware/MotorControl/controller.cpp | 9 +----- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 2275aa97..7896f5b8 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -118,6 +118,7 @@ void Axis::set_min_endstop_enabled(bool enable){ uint16_t gpio_pin = get_gpio_pin_by_pin(config_.min_endstop.gpio_num); GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.min_endstop.gpio_num); if(enable){ + HAL_GPIO_DeInit(gpio_port, gpio_pin); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = gpio_pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; @@ -125,7 +126,7 @@ void Axis::set_min_endstop_enabled(bool enable){ HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); uint32_t pull_up_down = config_.min_endstop.is_active_high ? GPIO_PULLDOWN : GPIO_PULLUP; - uint32_t interrupt_mode = config_.min_endstop.is_active_high ? GPIO_MODE_IT_RISING : GPIO_MODE_IT_FALLING; + uint32_t interrupt_mode = GPIO_MODE_IT_RISING_FALLING; GPIO_subscribe(gpio_port, gpio_pin, pull_up_down, interrupt_mode, min_endstop_cb_wrapper, this); } @@ -158,7 +159,7 @@ void Axis::set_max_endstop_enabled(bool enable){ HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); uint32_t pull_up_down = config_.max_endstop.is_active_high ? GPIO_PULLDOWN : GPIO_PULLUP; - uint32_t interrupt_mode = config_.max_endstop.is_active_high ? GPIO_MODE_IT_RISING : GPIO_MODE_IT_FALLING; + uint32_t interrupt_mode = GPIO_MODE_IT_RISING_FALLING; // Need to track pin state, not just homing edges GPIO_subscribe(gpio_port, gpio_pin, pull_up_down, interrupt_mode, max_endstop_cb_wrapper, this); } @@ -251,8 +252,6 @@ bool Axis::run_sensorless_spin_up() { // Note run_sensorless_control_loop and run_closed_loop_control_loop are very similar and differ only in where we get the estimate from. bool Axis::run_sensorless_control_loop() { - set_min_endstop_enabled(config_.min_endstop.enabled); - set_max_endstop_enabled(config_.max_endstop.enabled); set_step_dir_enabled(config_.enable_step_dir); run_control_loop([this](){ @@ -273,20 +272,32 @@ bool Axis::run_sensorless_control_loop() { bool Axis::run_closed_loop_control_loop() { set_step_dir_enabled(config_.enable_step_dir); - run_control_loop([this](){ + run_control_loop([this]() { // Note that all estimators are updated in the loop prefix in run_control_loop float current_setpoint; if (!controller_.update(encoder_.pos_estimate_, encoder_.vel_estimate_, ¤t_setpoint)) - return error_ |= ERROR_CONTROLLER_FAILED, false; //TODO: Make controller.set_error + return error_ |= ERROR_CONTROLLER_FAILED, false; //TODO: Make controller.set_error if (!motor_.update(current_setpoint, encoder_.phase_)) - return false; // set_error should update axis.error_ - + return false; // set_error should update axis.error_ - // Check for endstop presses - if(config_.min_endstop.enabled && min_endstop_state_) { - return error_ |= ERROR_MIN_ENDSTOP_PRESSED, false; - } else if(config_.max_endstop.enabled && max_endstop_state_) { - return error_ |= ERROR_MAX_ENDSTOP_PRESSED, false; + // Handle the homing case + if (homing_state_ == HOMING_STATE_HOMING) { + if (min_endstop_state_) { + encoder_.set_linear_count(config_.min_endstop.offset); + controller_.set_pos_setpoint(0.0f, 0.0f, 0.0f); + homing_state_ = HOMING_STATE_MOVE_TO_ZERO; + } + } else if (homing_state_ == HOMING_STATE_MOVE_TO_ZERO) { + if(!min_endstop_state_){ + homing_state_ = HOMING_STATE_IDLE; + } + } else { + // Check for endstop presses + if (config_.min_endstop.enabled && min_endstop_state_) { + return error_ |= ERROR_MIN_ENDSTOP_PRESSED, false; + } else if (config_.max_endstop.enabled && max_endstop_state_) { + return error_ |= ERROR_MAX_ENDSTOP_PRESSED, false; + } } return true; }); @@ -306,6 +317,8 @@ bool Axis::run_idle_loop() { // Infinite loop that does calibration and enters main control loop as appropriate void Axis::run_state_machine_loop() { + set_min_endstop_enabled(config_.min_endstop.enabled); + set_max_endstop_enabled(config_.max_endstop.enabled); // Allocate the map for anti-cogging algorithm and initialize all values to 0.0f // TODO: Move this somewhere else @@ -333,9 +346,9 @@ void Axis::run_state_machine_loop() { if (config_.startup_encoder_offset_calibration) task_chain_[pos++] = AXIS_STATE_ENCODER_OFFSET_CALIBRATION; if (config_.startup_closed_loop_control){ - task_chain_[pos++] = AXIS_STATE_CLOSED_LOOP_CONTROL; if(config_.startup_homing) task_chain_[pos++] = AXIS_STATE_HOMING; + task_chain_[pos++] = AXIS_STATE_CLOSED_LOOP_CONTROL; } else if (config_.startup_sensorless_control) task_chain_[pos++] = AXIS_STATE_SENSORLESS_CONTROL; diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 21c8ff16..5cc2aab7 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -20,6 +20,12 @@ enum AxisState_t { AXIS_STATE_HOMING = 9 //config_.min_endstop.enabled) { set_vel_setpoint(-config_.homing_speed, 0.0f); + axis_->homing_state_ = HOMING_STATE_HOMING; } else { return false; } - - axis_->run_control_loop([&](){ - if(axis_->min_endstop_state_){ - axis_->encoder_.set_linear_count(axis_->config_.min_endstop.offset); - set_pos_setpoint(0.0f, 0.0f, 0.0f); - } - return !axis_->min_endstop_state_; - }); return true; } From 9b0bc6f8407bad998d0940483c834e3a1a335548 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 8 Sep 2018 18:25:51 -0400 Subject: [PATCH 016/549] Make Endstops objects instead of structs in Axis --- CHANGELOG.md | 2 + Firmware/MotorControl/axis.cpp | 97 ++++------------------------ Firmware/MotorControl/axis.hpp | 40 +++--------- Firmware/MotorControl/controller.cpp | 2 +- Firmware/MotorControl/endstop.cpp | 43 ++++++++++++ Firmware/MotorControl/endstop.hpp | 37 +++++++++++ Firmware/MotorControl/main.cpp | 20 ++++-- Firmware/MotorControl/odrive_main.h | 1 + Firmware/Tupfile.lua | 1 + 9 files changed, 123 insertions(+), 120 deletions(-) create mode 100644 Firmware/MotorControl/endstop.cpp create mode 100644 Firmware/MotorControl/endstop.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index a7b7fc42..2f71fc29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ Please add a note of your changes below this heading if you make a Pull Request. ## Unreleased ### Added +* `min_endstop` and `max_endstop` objects can be configured on GPIO +* Axes can be homed if `min_endstop` is enabled * Encoder position count "homed" to zero when index is found. ### Changed diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 7896f5b8..984aeb9f 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -11,31 +11,30 @@ Axis::Axis(const AxisHardwareConfig_t& hw_config, Encoder& encoder, SensorlessEstimator& sensorless_estimator, Controller& controller, - Motor& motor) + Motor& motor, + Endstop& min_endstop, + Endstop& max_endstop) : hw_config_(hw_config), config_(config), encoder_(encoder), sensorless_estimator_(sensorless_estimator), controller_(controller), - motor_(motor) + motor_(motor), + min_endstop_(min_endstop), + max_endstop_(max_endstop) { encoder_.axis_ = this; sensorless_estimator_.axis_ = this; controller_.axis_ = this; motor_.axis_ = this; + min_endstop_.axis_ = this; + max_endstop_.axis_ = this; } static void step_cb_wrapper(void* ctx) { reinterpret_cast(ctx)->step_cb(); } -static void min_endstop_cb_wrapper(void* ctx){ - reinterpret_cast(ctx)->min_endstop_cb(); -} - -static void max_endstop_cb_wrapper(void* ctx){ - reinterpret_cast(ctx)->max_endstop_cb(); -} // @brief Sets up all components of the axis, // such as gate driver and encoder hardware. @@ -101,73 +100,6 @@ void Axis::set_step_dir_enabled(bool enable) { } } -void Axis::min_endstop_cb(){ - uint16_t gpio_pin = get_gpio_pin_by_pin(config_.min_endstop.gpio_num); - GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.min_endstop.gpio_num); - - if(config_.min_endstop.enabled){ - min_endstop_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); - if(config_.min_endstop.is_active_high == false) - min_endstop_state_ = !min_endstop_state_; - } else { - min_endstop_state_ = false; - } -} - -void Axis::set_min_endstop_enabled(bool enable){ - uint16_t gpio_pin = get_gpio_pin_by_pin(config_.min_endstop.gpio_num); - GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.min_endstop.gpio_num); - if(enable){ - HAL_GPIO_DeInit(gpio_port, gpio_pin); - GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Pin = gpio_pin; - GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = GPIO_NOPULL; - HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); - - uint32_t pull_up_down = config_.min_endstop.is_active_high ? GPIO_PULLDOWN : GPIO_PULLUP; - uint32_t interrupt_mode = GPIO_MODE_IT_RISING_FALLING; - GPIO_subscribe(gpio_port, gpio_pin, pull_up_down, interrupt_mode, - min_endstop_cb_wrapper, this); - } - else { - GPIO_unsubscribe(gpio_port, gpio_pin); - } -} - -void Axis::max_endstop_cb(){ - uint16_t gpio_pin = get_gpio_pin_by_pin(config_.max_endstop.gpio_num); - GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.max_endstop.gpio_num); - - if(config_.max_endstop.enabled){ - max_endstop_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); - if(config_.max_endstop.is_active_high == false) - max_endstop_state_ = !max_endstop_state_; - } else { - max_endstop_state_ = false; - } -} - -void Axis::set_max_endstop_enabled(bool enable){ - uint16_t gpio_pin = get_gpio_pin_by_pin(config_.max_endstop.gpio_num); - GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.max_endstop.gpio_num); - if(enable){ - GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Pin = gpio_pin; - GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = GPIO_NOPULL; - HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); - - uint32_t pull_up_down = config_.max_endstop.is_active_high ? GPIO_PULLDOWN : GPIO_PULLUP; - uint32_t interrupt_mode = GPIO_MODE_IT_RISING_FALLING; // Need to track pin state, not just homing edges - GPIO_subscribe(gpio_port, gpio_pin, pull_up_down, interrupt_mode, - max_endstop_cb_wrapper, this); - } - else { - GPIO_unsubscribe(gpio_port, gpio_pin); - } -} - bool Axis::check_for_errors() { // Maybe we should update this to only trigger on new errors? // The danger with that is we could fail to bail on uncleared errors that still prevent @@ -282,20 +214,20 @@ bool Axis::run_closed_loop_control_loop() { // Handle the homing case if (homing_state_ == HOMING_STATE_HOMING) { - if (min_endstop_state_) { - encoder_.set_linear_count(config_.min_endstop.offset); + if (min_endstop_.endstop_state_) { + encoder_.set_linear_count(min_endstop_.config_.offset); controller_.set_pos_setpoint(0.0f, 0.0f, 0.0f); homing_state_ = HOMING_STATE_MOVE_TO_ZERO; } } else if (homing_state_ == HOMING_STATE_MOVE_TO_ZERO) { - if(!min_endstop_state_){ + if(!min_endstop_.endstop_state_){ homing_state_ = HOMING_STATE_IDLE; } } else { // Check for endstop presses - if (config_.min_endstop.enabled && min_endstop_state_) { + if (min_endstop_.config_.enabled && min_endstop_.endstop_state_) { return error_ |= ERROR_MIN_ENDSTOP_PRESSED, false; - } else if (config_.max_endstop.enabled && max_endstop_state_) { + } else if (max_endstop_.config_.enabled && max_endstop_.endstop_state_) { return error_ |= ERROR_MAX_ENDSTOP_PRESSED, false; } } @@ -317,9 +249,6 @@ bool Axis::run_idle_loop() { // Infinite loop that does calibration and enters main control loop as appropriate void Axis::run_state_machine_loop() { - set_min_endstop_enabled(config_.min_endstop.enabled); - set_max_endstop_enabled(config_.max_endstop.enabled); - // Allocate the map for anti-cogging algorithm and initialize all values to 0.0f // TODO: Move this somewhere else // TODO: respect changes of CPR diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 5cc2aab7..bf8cd5bf 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -26,13 +26,6 @@ enum HomingState_t { HOMING_STATE_MOVE_TO_ZERO }; -struct Endstop_t { - uint16_t gpio_num; - bool enabled = false; - int32_t offset = 0; - bool is_active_high = false; -}; - struct AxisConfig_t { bool startup_motor_calibration = false; //config_.min_endstop.enabled) { + if (axis_->min_endstop_.config_.enabled) { set_vel_setpoint(-config_.homing_speed, 0.0f); axis_->homing_state_ = HOMING_STATE_HOMING; } else { diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp new file mode 100644 index 00000000..d46caf0b --- /dev/null +++ b/Firmware/MotorControl/endstop.cpp @@ -0,0 +1,43 @@ +#include + +Endstop::Endstop(EndstopConfig_t &config) + : config_(config) { +} + +static void endstop_cb_wrapper(void* ctx){ + reinterpret_cast(ctx)->endstop_cb(); +} + +void Endstop::endstop_cb(){ + uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); + GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); + + if(config_.enabled){ + endstop_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); + if(config_.is_active_high == false) + endstop_state_ = !endstop_state_; + } else { + endstop_state_ = false; + } +} + +void Endstop::set_endstop_enabled(bool enable){ + uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); + GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); + if(enable){ + HAL_GPIO_DeInit(gpio_port, gpio_pin); + GPIO_InitTypeDef GPIO_InitStruct; + GPIO_InitStruct.Pin = gpio_pin; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); + + uint32_t pull_up_down = config_.is_active_high ? GPIO_PULLDOWN : GPIO_PULLUP; + uint32_t interrupt_mode = GPIO_MODE_IT_RISING_FALLING; + GPIO_subscribe(gpio_port, gpio_pin, pull_up_down, interrupt_mode, + endstop_cb_wrapper, this); + } + else { + GPIO_unsubscribe(gpio_port, gpio_pin); + } +} \ No newline at end of file diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp new file mode 100644 index 00000000..b0f02bab --- /dev/null +++ b/Firmware/MotorControl/endstop.hpp @@ -0,0 +1,37 @@ +#ifndef __ENDSTOP_HPP +#define __ENDSTOP_HPP + + +struct EndstopConfig_t { + uint16_t gpio_num; + bool enabled = false; + int32_t offset = 0; + bool is_active_high = false; +}; + +class Endstop { + public: + Endstop(EndstopConfig_t& config); + EndstopConfig_t config_; + Axis* axis_ = nullptr; + + bool endstop_state_ = false; + + void set_endstop_enabled(bool enable); + void endstop_cb(); + + auto make_protocol_definitions(){ + return make_protocol_member_list( + make_protocol_object("config", + make_protocol_property("gpio_num", &config_.gpio_num), + make_protocol_property("enabled", &config_.enabled), + make_protocol_property("offset", &config_.offset), + make_protocol_property("is_active_high", &config_.is_active_high) + ) + ); + } + + private: + uint16_t debounce_timer_ = 0; +}; +#endif \ No newline at end of file diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 18b88433..67ea382a 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -14,6 +14,8 @@ SensorlessEstimator::Config_t sensorless_configs[AXIS_COUNT]; ControllerConfig_t controller_configs[AXIS_COUNT]; MotorConfig_t motor_configs[AXIS_COUNT]; AxisConfig_t axis_configs[AXIS_COUNT]; +EndstopConfig_t min_endstop_configs[AXIS_COUNT]; +EndstopConfig_t max_endstop_configs[AXIS_COUNT]; bool user_config_loaded_; SystemStats_t system_stats_ = { 0 }; @@ -26,7 +28,9 @@ typedef Config< SensorlessEstimator::Config_t[AXIS_COUNT], ControllerConfig_t[AXIS_COUNT], MotorConfig_t[AXIS_COUNT], - AxisConfig_t[AXIS_COUNT]> ConfigFormat; + AxisConfig_t[AXIS_COUNT], + EndstopConfig_t[AXIS_COUNT], + EndstopConfig_t[AXIS_COUNT]> ConfigFormat; void save_configuration(void) { if (ConfigFormat::safe_store_config( @@ -35,7 +39,9 @@ void save_configuration(void) { &sensorless_configs, &controller_configs, &motor_configs, - &axis_configs)) { + &axis_configs, + &min_endstop_configs, + &max_endstop_configs)) { //printf("saving configuration failed\r\n"); osDelay(5); } else { user_config_loaded_ = true; @@ -51,7 +57,9 @@ void load_configuration(void) { &sensorless_configs, &controller_configs, &motor_configs, - &axis_configs)) { + &axis_configs, + &min_endstop_configs, + &max_endstop_configs)) { //If loading failed, restore defaults board_config = BoardConfig_t(); for (size_t i = 0; i < AXIS_COUNT; ++i) { @@ -60,6 +68,8 @@ void load_configuration(void) { controller_configs[i] = ControllerConfig_t(); motor_configs[i] = MotorConfig_t(); axis_configs[i] = AxisConfig_t(); + min_endstop_configs[i] = EndstopConfig_t(); + max_endstop_configs[i] = EndstopConfig_t(); } } else { user_config_loaded_ = true; @@ -162,8 +172,10 @@ int odrive_main(void) { Motor *motor = new Motor(hw_configs[i].motor_config, hw_configs[i].gate_driver_config, motor_configs[i]); + Endstop *min_endstop = new Endstop(min_endstop_configs[i]); + Endstop *max_endstop = new Endstop(max_endstop_configs[i]); axes[i] = new Axis(hw_configs[i].axis_config, axis_configs[i], - *encoder, *sensorless_estimator, *controller, *motor); + *encoder, *sensorless_estimator, *controller, *motor, *min_endstop, *max_endstop); } // Start ADC for temperature measurements and user measurements diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 4e4db160..65577760 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -109,6 +109,7 @@ inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast(~static_c #include #include #include +#include #include #include diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index f242b259..ce0a3158 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -153,6 +153,7 @@ build{ 'MotorControl/axis.cpp', 'MotorControl/motor.cpp', 'MotorControl/encoder.cpp', + 'MotorControl/endstop.cpp', 'MotorControl/controller.cpp', 'MotorControl/sensorless_estimator.cpp', 'MotorControl/main.cpp', From f7f2452973047fa546effb982c531d2102106234 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 8 Sep 2018 18:57:25 -0400 Subject: [PATCH 017/549] Add endstop debouncing --- Firmware/MotorControl/axis.cpp | 10 ++++++---- Firmware/MotorControl/endstop.cpp | 30 ++++++++++++++++++++++-------- Firmware/MotorControl/endstop.hpp | 13 +++++++++---- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 984aeb9f..a0e22d21 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -140,6 +140,8 @@ bool Axis::do_updates() { // Sub-components should use set_error which will propegate to this error_ encoder_.update(); sensorless_estimator_.update(); + min_endstop_.update(); + max_endstop_.update(); return check_for_errors(); } @@ -214,20 +216,20 @@ bool Axis::run_closed_loop_control_loop() { // Handle the homing case if (homing_state_ == HOMING_STATE_HOMING) { - if (min_endstop_.endstop_state_) { + if (min_endstop_.getEndstopState()) { encoder_.set_linear_count(min_endstop_.config_.offset); controller_.set_pos_setpoint(0.0f, 0.0f, 0.0f); homing_state_ = HOMING_STATE_MOVE_TO_ZERO; } } else if (homing_state_ == HOMING_STATE_MOVE_TO_ZERO) { - if(!min_endstop_.endstop_state_){ + if(!min_endstop_.getEndstopState()){ homing_state_ = HOMING_STATE_IDLE; } } else { // Check for endstop presses - if (min_endstop_.config_.enabled && min_endstop_.endstop_state_) { + if (min_endstop_.config_.enabled && min_endstop_.getEndstopState()) { return error_ |= ERROR_MIN_ENDSTOP_PRESSED, false; - } else if (max_endstop_.config_.enabled && max_endstop_.endstop_state_) { + } else if (max_endstop_.config_.enabled && max_endstop_.getEndstopState()) { return error_ |= ERROR_MAX_ENDSTOP_PRESSED, false; } } diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index d46caf0b..df4f65eb 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -1,4 +1,5 @@ #include +#include Endstop::Endstop(EndstopConfig_t &config) : config_(config) { @@ -8,19 +9,32 @@ static void endstop_cb_wrapper(void* ctx){ reinterpret_cast(ctx)->endstop_cb(); } -void Endstop::endstop_cb(){ - uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); - GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); - - if(config_.enabled){ - endstop_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); - if(config_.is_active_high == false) - endstop_state_ = !endstop_state_; +void Endstop::update() { + if (config_.enabled) { + float now = axis_->loop_counter_ * current_meas_period; + if((now - debounce_timer_) >= config_.debounce_ms) { // Debounce timer expired, take the new pin state + endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state + debounce_timer_ = now - config_.debounce_ms; // Ensure timer doesn't have overflow issues + } else { + endstop_state_ = endstop_state_; // Do nothing + } } else { endstop_state_ = false; } } +bool Endstop::getEndstopState() { + return endstop_state_; +} + +void Endstop::endstop_cb() { + uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); + GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); + + debounce_timer_ = axis_->loop_counter_ * current_meas_period; + pin_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); +} + void Endstop::set_endstop_enabled(bool enable){ uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index b0f02bab..dcaf0e27 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -7,6 +7,7 @@ struct EndstopConfig_t { bool enabled = false; int32_t offset = 0; bool is_active_high = false; + float debounce_ms = 100; }; class Endstop { @@ -15,10 +16,11 @@ class Endstop { EndstopConfig_t config_; Axis* axis_ = nullptr; - bool endstop_state_ = false; - void set_endstop_enabled(bool enable); void endstop_cb(); + void update(); + + bool getEndstopState(); auto make_protocol_definitions(){ return make_protocol_member_list( @@ -26,12 +28,15 @@ class Endstop { make_protocol_property("gpio_num", &config_.gpio_num), make_protocol_property("enabled", &config_.enabled), make_protocol_property("offset", &config_.offset), - make_protocol_property("is_active_high", &config_.is_active_high) + make_protocol_property("is_active_high", &config_.is_active_high), + make_protocol_property("debounce_ms", &config_.debounce_ms) ) ); } private: - uint16_t debounce_timer_ = 0; + bool endstop_state_ = false; + bool pin_state_ = false; + float debounce_timer_ = 0; }; #endif \ No newline at end of file From 8b7ac1bab2ab5f2ce238bccf41260a17b1ada90b Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 8 Sep 2018 19:03:09 -0400 Subject: [PATCH 018/549] Fix seconds -> ms conversion error --- Firmware/MotorControl/endstop.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index df4f65eb..b9730984 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -12,11 +12,11 @@ static void endstop_cb_wrapper(void* ctx){ void Endstop::update() { if (config_.enabled) { float now = axis_->loop_counter_ * current_meas_period; - if((now - debounce_timer_) >= config_.debounce_ms) { // Debounce timer expired, take the new pin state - endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state - debounce_timer_ = now - config_.debounce_ms; // Ensure timer doesn't have overflow issues + if ((now - debounce_timer_) >= (config_.debounce_ms * 0.001)) { // Debounce timer expired, take the new pin state + endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state + debounce_timer_ = now - (config_.debounce_ms * 0.001); // Ensure timer doesn't have overflow issues } else { - endstop_state_ = endstop_state_; // Do nothing + endstop_state_ = endstop_state_; // Do nothing } } else { endstop_state_ = false; From d169129434c89f68ea3b85554c4efe307682f4c1 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 21 Sep 2018 18:55:47 -0400 Subject: [PATCH 019/549] Fix float -> double conversion --- Firmware/MotorControl/endstop.cpp | 7 +++---- Firmware/MotorControl/endstop.hpp | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index b9730984..c4e4a9fa 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -1,5 +1,4 @@ #include -#include Endstop::Endstop(EndstopConfig_t &config) : config_(config) { @@ -12,9 +11,9 @@ static void endstop_cb_wrapper(void* ctx){ void Endstop::update() { if (config_.enabled) { float now = axis_->loop_counter_ * current_meas_period; - if ((now - debounce_timer_) >= (config_.debounce_ms * 0.001)) { // Debounce timer expired, take the new pin state - endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state - debounce_timer_ = now - (config_.debounce_ms * 0.001); // Ensure timer doesn't have overflow issues + if ((now - debounce_timer_) >= (config_.debounce_ms * 0.001f)) { // Debounce timer expired, take the new pin state + endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state + debounce_timer_ = now - (config_.debounce_ms * 0.001f); // Ensure timer doesn't have overflow issues } else { endstop_state_ = endstop_state_; // Do nothing } diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index dcaf0e27..6a13eb68 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -7,7 +7,7 @@ struct EndstopConfig_t { bool enabled = false; int32_t offset = 0; bool is_active_high = false; - float debounce_ms = 100; + float debounce_ms = 100.0f; }; class Endstop { From 9f9ee3f1a34dff1bf64815d5b9301277c7f375ea Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 21 Sep 2018 18:56:38 -0400 Subject: [PATCH 020/549] Rearrange configs so they match trapTraj --- Firmware/MotorControl/main.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 67ea382a..a816d920 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -28,9 +28,9 @@ typedef Config< SensorlessEstimator::Config_t[AXIS_COUNT], ControllerConfig_t[AXIS_COUNT], MotorConfig_t[AXIS_COUNT], - AxisConfig_t[AXIS_COUNT], EndstopConfig_t[AXIS_COUNT], - EndstopConfig_t[AXIS_COUNT]> ConfigFormat; + EndstopConfig_t[AXIS_COUNT], + AxisConfig_t[AXIS_COUNT]> ConfigFormat; void save_configuration(void) { if (ConfigFormat::safe_store_config( @@ -39,10 +39,10 @@ void save_configuration(void) { &sensorless_configs, &controller_configs, &motor_configs, - &axis_configs, &min_endstop_configs, - &max_endstop_configs)) { - //printf("saving configuration failed\r\n"); osDelay(5); + &max_endstop_configs, + &axis_configs)) { + printf("saving configuration failed\r\n"); osDelay(5); } else { user_config_loaded_ = true; } @@ -57,9 +57,9 @@ void load_configuration(void) { &sensorless_configs, &controller_configs, &motor_configs, - &axis_configs, &min_endstop_configs, - &max_endstop_configs)) { + &max_endstop_configs, + &axis_configs)) { //If loading failed, restore defaults board_config = BoardConfig_t(); for (size_t i = 0; i < AXIS_COUNT; ++i) { @@ -67,9 +67,9 @@ void load_configuration(void) { sensorless_configs[i] = SensorlessEstimator::Config_t(); controller_configs[i] = ControllerConfig_t(); motor_configs[i] = MotorConfig_t(); - axis_configs[i] = AxisConfig_t(); min_endstop_configs[i] = EndstopConfig_t(); max_endstop_configs[i] = EndstopConfig_t(); + axis_configs[i] = AxisConfig_t(); } } else { user_config_loaded_ = true; From 01f98f749e957aea40a02d85e902f89d133314d6 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 21 Sep 2018 19:17:11 -0400 Subject: [PATCH 021/549] Fix issue with saving endstop configs --- Firmware/MotorControl/endstop.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index 6a13eb68..8a90d23f 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -13,7 +13,8 @@ struct EndstopConfig_t { class Endstop { public: Endstop(EndstopConfig_t& config); - EndstopConfig_t config_; + + EndstopConfig_t& config_; Axis* axis_ = nullptr; void set_endstop_enabled(bool enable); From 4e317d1fbf14f1b6e49ac24548e3ed9f8ded4729 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 23 Sep 2018 14:21:04 -0400 Subject: [PATCH 022/549] Set endstop state according to config_ --- Firmware/MotorControl/endstop.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index c4e4a9fa..d0c4f981 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -2,6 +2,7 @@ Endstop::Endstop(EndstopConfig_t &config) : config_(config) { + set_endstop_enabled(config_.enabled); } static void endstop_cb_wrapper(void* ctx){ From 197d10f50267e4d6b260fb40d03841f21cb293b6 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 23 Sep 2018 14:21:34 -0400 Subject: [PATCH 023/549] Add endstop_state_ as protocol ro property --- Firmware/MotorControl/endstop.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index 8a90d23f..747422a8 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -23,8 +23,11 @@ class Endstop { bool getEndstopState(); + bool endstop_state_ = false; + auto make_protocol_definitions(){ return make_protocol_member_list( + make_protocol_ro_property("endstop_state_", &endstop_state_), make_protocol_object("config", make_protocol_property("gpio_num", &config_.gpio_num), make_protocol_property("enabled", &config_.enabled), @@ -36,7 +39,7 @@ class Endstop { } private: - bool endstop_state_ = false; + bool pin_state_ = false; float debounce_timer_ = 0; }; From d71364068131879c1d1f9f5a632e3c6d094e2155 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 23 Sep 2018 14:22:34 -0400 Subject: [PATCH 024/549] Change debounce logic so less is done in interrupts --- Firmware/MotorControl/endstop.cpp | 7 +++---- Firmware/MotorControl/endstop.hpp | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index d0c4f981..ac0ff9a1 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -10,6 +10,9 @@ static void endstop_cb_wrapper(void* ctx){ } void Endstop::update() { + uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); + GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); + pin_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); if (config_.enabled) { float now = axis_->loop_counter_ * current_meas_period; if ((now - debounce_timer_) >= (config_.debounce_ms * 0.001f)) { // Debounce timer expired, take the new pin state @@ -28,11 +31,7 @@ bool Endstop::getEndstopState() { } void Endstop::endstop_cb() { - uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); - GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); - debounce_timer_ = axis_->loop_counter_ * current_meas_period; - pin_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); } void Endstop::set_endstop_enabled(bool enable){ diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index 747422a8..6e9cb46f 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -23,6 +23,7 @@ class Endstop { bool getEndstopState(); + bool endstop_state_ = false; auto make_protocol_definitions(){ @@ -41,6 +42,6 @@ class Endstop { private: bool pin_state_ = false; - float debounce_timer_ = 0; + volatile float debounce_timer_ = 0; }; #endif \ No newline at end of file From 484da5515005149f9ec068dc056aad34d2f0d43d Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 22 Sep 2018 16:35:22 -0400 Subject: [PATCH 025/549] Make interface_can a class, so it can be instantiated multiple times --- Firmware/MotorControl/main.cpp | 11 + Firmware/MotorControl/odrive_main.h | 4 + Firmware/communication/communication.cpp | 10 +- Firmware/communication/interface_can.cpp | 264 +++-------------------- Firmware/communication/interface_can.hpp | 75 +++---- 5 files changed, 78 insertions(+), 286 deletions(-) diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 18b88433..3b91e075 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -7,8 +7,10 @@ #include #include #include +#include BoardConfig_t board_config; +CANConfig_t can_config; Encoder::Config_t encoder_configs[AXIS_COUNT]; SensorlessEstimator::Config_t sensorless_configs[AXIS_COUNT]; ControllerConfig_t controller_configs[AXIS_COUNT]; @@ -19,9 +21,13 @@ bool user_config_loaded_; SystemStats_t system_stats_ = { 0 }; Axis *axes[AXIS_COUNT]; +ODriveCAN *odCAN; + + typedef Config< BoardConfig_t, + CANConfig_t, Encoder::Config_t[AXIS_COUNT], SensorlessEstimator::Config_t[AXIS_COUNT], ControllerConfig_t[AXIS_COUNT], @@ -31,6 +37,7 @@ typedef Config< void save_configuration(void) { if (ConfigFormat::safe_store_config( &board_config, + &can_config, &encoder_configs, &sensorless_configs, &controller_configs, @@ -47,6 +54,7 @@ void load_configuration(void) { if (NVM_init() || ConfigFormat::safe_load_config( &board_config, + &can_config, &encoder_configs, &sensorless_configs, &controller_configs, @@ -54,6 +62,7 @@ void load_configuration(void) { &axis_configs)) { //If loading failed, restore defaults board_config = BoardConfig_t(); + can_config = CANConfig_t(); for (size_t i = 0; i < AXIS_COUNT; ++i) { encoder_configs[i] = Encoder::Config_t(); sensorless_configs[i] = SensorlessEstimator::Config_t(); @@ -104,6 +113,7 @@ void vApplicationIdleHook(void) { system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread) * sizeof(StackType_t); system_stats_.min_stack_space_usb_irq = uxTaskGetStackHighWaterMark(usb_irq_thread) * sizeof(StackType_t); system_stats_.min_stack_space_startup = uxTaskGetStackHighWaterMark(defaultTaskHandle) * sizeof(StackType_t); + system_stats_.min_stack_space_can = uxTaskGetStackHighWaterMark(odCAN->thread_id_) * sizeof(StackType_t); } } } @@ -154,6 +164,7 @@ int odrive_main(void) { #endif // Construct all objects. + odCAN = new ODriveCAN(&hcan1, can_config); for (size_t i = 0; i < AXIS_COUNT; ++i) { Encoder *encoder = new Encoder(hw_configs[i].encoder_config, encoder_configs[i]); diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 4e4db160..0df04433 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -47,6 +47,7 @@ typedef struct { uint32_t min_stack_space_uart; uint32_t min_stack_space_usb_irq; uint32_t min_stack_space_startup; + uint32_t min_stack_space_can; } SystemStats_t; extern SystemStats_t system_stats_; @@ -79,11 +80,14 @@ struct BoardConfig_t { extern BoardConfig_t board_config; extern bool user_config_loaded_; +// Forward Declarations class Axis; class Motor; +class ODriveCAN; constexpr size_t AXIS_COUNT = 2; extern Axis *axes[AXIS_COUNT]; +extern ODriveCAN *odCAN; // if you use the oscilloscope feature you can bump up this value #define OSCILLOSCOPE_SIZE 128 diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 622bdefb..843f46b3 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -91,13 +91,9 @@ void init_communication(void) { osDelay(1); } - float oscilloscope[OSCILLOSCOPE_SIZE] = {0}; size_t oscilloscope_pos = 0; - -static CAN_context can1_ctx; - // Helper class because the protocol library doesn't yet // support non-member functions // TODO: make this go away @@ -136,6 +132,7 @@ static inline auto make_obj_tree() { make_protocol_ro_property("min_stack_space_comms", &system_stats_.min_stack_space_comms), make_protocol_ro_property("min_stack_space_usb", &system_stats_.min_stack_space_usb), make_protocol_ro_property("min_stack_space_uart", &system_stats_.min_stack_space_uart), + make_protocol_ro_property("min_stack_space_can", &system_stats_.min_stack_space_can), make_protocol_ro_property("min_stack_space_usb_irq", &system_stats_.min_stack_space_usb_irq), make_protocol_ro_property("min_stack_space_startup", &system_stats_.min_stack_space_startup), make_protocol_object("usb", @@ -167,7 +164,7 @@ static inline auto make_obj_tree() { ), make_protocol_object("axis0", axes[0]->make_protocol_definitions()), make_protocol_object("axis1", axes[1]->make_protocol_definitions()), - make_protocol_object("can", can1_ctx.make_protocol_definitions()), + make_protocol_object("can", odCAN->make_protocol_definitions()), make_protocol_property("test_property", &test_property), make_protocol_function("test_function", static_functions, &StaticFunctions::test_function, "delta"), make_protocol_function("get_oscilloscope_val", static_functions, &StaticFunctions::get_oscilloscope_val, "index"), @@ -202,8 +199,7 @@ void communication_task(void * ctx) { if (board_config.enable_i2c_instead_of_can) { start_i2c_server(); } else { - // TODO: finish implementing CAN - // start_can_server(can1_ctx, CAN1, serial_number); + odCAN->start_can_server(); } for (;;) { diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 3bf822bd..b020f4c1 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -36,262 +36,48 @@ #include "utils.h" #include -#include #include +#include -#define CAN_HEARTBEAT_INTERVAL 1000 // [ms] -#define CAN_HEARTBEAT_MARGIN 10 // maximum time that a heartbeat message can be delayed until we stop sending other messages [ms] - -// defined in can.c -extern CAN_HandleTypeDef hcan1; -extern CAN_HandleTypeDef hcan2; -extern CAN_HandleTypeDef hcan3; - -static CAN_context* ctxs[3] = { nullptr, nullptr, nullptr }; - -struct CAN_context* get_can_ctx(CAN_HandleTypeDef *hcan) { -#if defined(CAN1) - if (hcan->Instance == CAN1) return ctxs[0]; -#endif -#if defined(CAN2) - if (hcan->Instance == CAN2) return ctxs[1]; -#endif -#if defined(CAN3) - if (hcan->Instance == CAN3) return ctxs[2]; -#endif - return nullptr; +// Constructor is called by communication.cpp and the handle is assigned appropriately +ODriveCAN::ODriveCAN(CAN_HandleTypeDef *handle, CANConfig_t &config) + : handle_{handle}, + config_{config} { } - -void consider_node_id_in_use(CAN_context* ctx, uint8_t node_id) { - ctx->node_ids_in_use_0[node_id >> 5] |= (1 << (node_id & 0x1f)); +static void can_server_thread_wrapper(void* ctx){ + reinterpret_cast(ctx)->can_server_thread(); + reinterpret_cast(ctx)->thread_id_valid_ = false; } -bool is_node_id_in_use(CAN_context* ctx, uint32_t node_id) { - if (node_id == 0) // node ID 0 is reserved (is it though?) - return true; - return (ctx->node_ids_in_use_0[node_id >> 5] & (1 << (node_id & 0x1f))) - || (ctx->node_ids_in_use_1[node_id >> 5] & (1 << (node_id & 0x1f))); -} - -bool select_another_node_id(CAN_context* ctx) { - ctx->node_id_expiry = osKernelSysTick() - 1; - - // Find a new node ID that is not in use - for (uint8_t i = 0; i < 32; i++) { - // Each time we select a new node ID, we use the next byte from the serial - // number to get advance the node ID. - uint8_t poor_mans_random_byte = ((uint8_t*)ctx->serial_number)[ctx->node_id_rng_state]; - if (++(ctx->node_id_rng_state) >= sizeof(ctx->serial_number)) - ctx->node_id_rng_state = 0; - ctx->node_id = calc_crc(ctx->node_id, poor_mans_random_byte); - if (!is_node_id_in_use(ctx, ctx->node_id)) - return true; - } - return false; -} - - -void server_thread(CAN_context* ctx) { - uint32_t next_1s_tick = osKernelSysTick() + 1000; +void ODriveCAN::can_server_thread() { for (;;) { - if (deadline_to_timeout(next_1s_tick) == 0) - - // wait until either the next heartbeat is due or a hearbeat was requested - // by releasing the semaphore - osSemaphoreWait(ctx->sem_send_heartbeat, deadline_to_timeout(next_1s_tick)); - if (!is_in_the_future(next_1s_tick)) - memcpy(ctx->node_ids_in_use_1, ctx->node_ids_in_use_0, sizeof(ctx->node_ids_in_use_1)); - next_1s_tick += 1000; - if (!is_in_the_future(next_1s_tick)) - next_1s_tick = osKernelSysTick(); // fast-forward if we missed several 1 second ticks - - if (is_node_id_in_use(ctx, ctx->node_id)) { - if (!select_another_node_id(ctx)) - continue; - else - next_1s_tick += ctx->node_id; // shift the 1s tick by a bit - } - - uint8_t data[8]; - //uint8_t data[] = { ctx->node_id }; // this would be the correct data for CANopen - TODO: make it compatible - *(uint64_t*)data = ctx->serial_number; - - CAN_TxHeaderTypeDef header = { - .StdId = 0x700u + ctx->node_id, - .ExtId = 0, - .IDE = CAN_ID_STD, - .RTR = CAN_RTR_DATA, - .DLC = sizeof(data), - .TransmitGlobalTime = DISABLE - }; - HAL_CAN_AddTxMessage(ctx->handle, &header, data, &ctx->last_heartbeat_mailbox); + // Do nothing } } -bool start_can_server(CAN_context& ctx, CAN_TypeDef *port, uint64_t serial_number) { - //MX_CAN1_Init(); // TODO: flatten -#if defined(CAN1) - if (port == CAN1) ctx.handle = &hcan1, ctxs[0] = &ctx; else -#endif -#if defined(CAN2) - // TODO: move CubeMX stuff into this file so all symbols are defined - //if (port == CAN2) ctx.handle = &hcan2, ctxs[1] = &ctx; else -#endif -#if defined(CAN3) - if (port == CAN3) ctx.handle = &hcan3, ctxs[2] = &ctx; else -#endif - return false; // fail if none of the above checks matched - +bool ODriveCAN::start_can_server() { HAL_StatusTypeDef status; - ctx.node_id = calc_crc(0, (const uint8_t*)UID_BASE, 12); - ctx.serial_number = serial_number; - osSemaphoreDef(sem_send_heartbeat); - ctx.sem_send_heartbeat = osSemaphoreCreate(osSemaphore(sem_send_heartbeat), 1); - osSemaphoreWait(ctx.sem_send_heartbeat, 0); - - //// Set up heartbeat filter - CAN_FilterTypeDef sFilterConfig = { - .FilterIdHigh = ((0x700u + ctx.node_id) << 5) | (0x0 << 2), // own heartbeat (standard ID, no RTR) - .FilterIdLow = (0x700u << 5) | (0x0 << 2), // any heartbeat (standard ID, no RTR) - .FilterMaskIdHigh = (0x7ffu << 5) | (0x3 << 2), - .FilterMaskIdLow = (0x780u << 5) | (0x3 << 2), - .FilterFIFOAssignment = CAN_RX_FIFO0, - .FilterBank = 0, - .FilterMode = CAN_FILTERMODE_IDMASK, - .FilterScale = CAN_FILTERSCALE_16BIT, // two 16-bit filters - .FilterActivation = ENABLE, - .SlaveStartFilterBank = 0 - }; - status = HAL_CAN_ConfigFilter(ctx.handle, &sFilterConfig); + status = HAL_CAN_Start(handle_); if (status != HAL_OK) return false; - status = HAL_CAN_Start(ctx.handle); + status = HAL_CAN_ActivateNotification(handle_, + CAN_IT_TX_MAILBOX_EMPTY | + CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_RX_FIFO1_MSG_PENDING | /* we probably only want this */ + CAN_IT_RX_FIFO0_FULL | CAN_IT_RX_FIFO1_FULL | + CAN_IT_RX_FIFO0_OVERRUN | CAN_IT_RX_FIFO1_OVERRUN | + CAN_IT_WAKEUP | CAN_IT_SLEEP_ACK | + CAN_IT_ERROR_WARNING | CAN_IT_ERROR_PASSIVE | + CAN_IT_BUSOFF | CAN_IT_LAST_ERROR_CODE | + CAN_IT_ERROR); if (status != HAL_OK) return false; - status = HAL_CAN_ActivateNotification(ctx.handle, - CAN_IT_TX_MAILBOX_EMPTY | - CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_RX_FIFO1_MSG_PENDING | /* we probably only want this */ - CAN_IT_RX_FIFO0_FULL | CAN_IT_RX_FIFO1_FULL | - CAN_IT_RX_FIFO0_OVERRUN | CAN_IT_RX_FIFO1_OVERRUN | - CAN_IT_WAKEUP | CAN_IT_SLEEP_ACK | - CAN_IT_ERROR_WARNING | CAN_IT_ERROR_PASSIVE | - CAN_IT_BUSOFF | CAN_IT_LAST_ERROR_CODE | - CAN_IT_ERROR); - if (status != HAL_OK) - return false; + osThreadDef(can_server_thread_def, can_server_thread_wrapper, osPriorityNormal, 0, 512); + thread_id_ = osThreadCreate(osThread(can_server_thread_def), this); + thread_id_valid_ = true; - server_thread(&ctx); return true; -} - -void tx_complete_callback(CAN_HandleTypeDef *hcan, uint8_t mailbox_idx) { - CAN_context *ctx = get_can_ctx(hcan); - if (!ctx) return; - ctx->tx_msg_cnt++; - if (mailbox_idx == ctx->last_heartbeat_mailbox) { - // we succeeded in sending a heartbeat - // now we're allowed to send messages for the next second plus a small margin - ctx->node_id_expiry = osKernelSysTick() + CAN_HEARTBEAT_INTERVAL + CAN_HEARTBEAT_MARGIN; - } -} - -void tx_aborted_callback(CAN_HandleTypeDef *hcan, uint8_t mailbox_idx) { - //__asm volatile ("bkpt"); - if (!get_can_ctx(hcan)) - return; - get_can_ctx(hcan)->TxMailboxAbortCallbackCnt++; -} - -void tx_error(CAN_context *ctx, uint8_t mailbox_idx) { - if (mailbox_idx == ctx->last_heartbeat_mailbox) { - // Consider the node ID in use - consider_node_id_in_use(ctx, ctx->node_id); - // Try to find a new node ID that is not in use and immediately - // resend heartbeat if we find one - if (select_another_node_id(ctx)) - osSemaphoreRelease(ctx->sem_send_heartbeat); - } -} - -void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan) { tx_complete_callback(hcan, 0); } -void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef *hcan) { tx_complete_callback(hcan, 1); } -void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef *hcan) { tx_complete_callback(hcan, 2); } -void HAL_CAN_TxMailbox0AbortCallback(CAN_HandleTypeDef *hcan) { tx_aborted_callback(hcan, 0); } -void HAL_CAN_TxMailbox1AbortCallback(CAN_HandleTypeDef *hcan) { tx_aborted_callback(hcan, 1); } -void HAL_CAN_TxMailbox2AbortCallback(CAN_HandleTypeDef *hcan) { tx_aborted_callback(hcan, 2); } - -void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { - CAN_context *ctx = get_can_ctx(hcan); - if (!ctx) return; - ctx->received_msg_cnt++; - - CAN_RxHeaderTypeDef header; - uint8_t data[8]; - HAL_StatusTypeDef status = HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &header, data); - if (status != HAL_OK) { - ctx->unexpected_errors++; - return; - } - - uint8_t node_id = header.StdId & 0x07fu; - if ((header.StdId & 0x780u) == 0x700u) { - ctx->received_ack++; - consider_node_id_in_use(ctx, node_id); - } else { - ctx->unhandled_messages++; - } -} - -void HAL_CAN_RxFifo0FullCallback(CAN_HandleTypeDef *hcan) { if (get_can_ctx(hcan)) get_can_ctx(hcan)->RxFifo0FullCallbackCnt++; } - -void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan) { if (get_can_ctx(hcan)) get_can_ctx(hcan)->RxFifo1MsgPendingCallbackCnt++; } -void HAL_CAN_RxFifo1FullCallback(CAN_HandleTypeDef *hcan) { if (get_can_ctx(hcan)) get_can_ctx(hcan)->RxFifo1FullCallbackCnt++; } -void HAL_CAN_SleepCallback(CAN_HandleTypeDef *hcan) { if (get_can_ctx(hcan)) get_can_ctx(hcan)->SleepCallbackCnt++; } -void HAL_CAN_WakeUpFromRxMsgCallback(CAN_HandleTypeDef *hcan) { if (get_can_ctx(hcan)) get_can_ctx(hcan)->WakeUpFromRxMsgCallbackCnt++; } - -void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) { - //__asm volatile ("bkpt"); - CAN_context *ctx = get_can_ctx(hcan); - if (!ctx) return; - volatile uint32_t original_error = hcan->ErrorCode; - (void) original_error; - - // handle transmit errors in all three mailboxes - if (hcan->ErrorCode & HAL_CAN_ERROR_TX_ALST0) { - SET_BIT(hcan->Instance->sTxMailBox[0].TIR, CAN_TI0R_TXRQ); - hcan->ErrorCode &= ~HAL_CAN_ERROR_TX_ALST0; - } else if (hcan->ErrorCode & HAL_CAN_ERROR_TX_TERR0) { - tx_error(ctx, 0); - hcan->ErrorCode &= ~HAL_CAN_ERROR_EWG; - hcan->ErrorCode &= ~HAL_CAN_ERROR_ACK; - hcan->ErrorCode &= ~HAL_CAN_ERROR_TX_TERR0; - } - - if (hcan->ErrorCode & HAL_CAN_ERROR_TX_ALST1) { - SET_BIT(hcan->Instance->sTxMailBox[1].TIR, CAN_TI1R_TXRQ); - hcan->ErrorCode &= ~HAL_CAN_ERROR_TX_ALST1; - } else if (hcan->ErrorCode & HAL_CAN_ERROR_TX_TERR1) { - tx_error(ctx, 1); - hcan->ErrorCode &= ~HAL_CAN_ERROR_EWG; - hcan->ErrorCode &= ~HAL_CAN_ERROR_ACK; - hcan->ErrorCode &= ~HAL_CAN_ERROR_TX_TERR1; - } - - if (hcan->ErrorCode & HAL_CAN_ERROR_TX_ALST2) { - SET_BIT(hcan->Instance->sTxMailBox[2].TIR, CAN_TI2R_TXRQ); - hcan->ErrorCode &= ~HAL_CAN_ERROR_TX_ALST2; - } else if (hcan->ErrorCode & HAL_CAN_ERROR_TX_TERR2) { - tx_error(ctx, 2); - hcan->ErrorCode &= ~HAL_CAN_ERROR_EWG; - hcan->ErrorCode &= ~HAL_CAN_ERROR_ACK; - hcan->ErrorCode &= ~HAL_CAN_ERROR_TX_TERR2; - } - - if (hcan->ErrorCode) - ctx->unexpected_errors++; -} - +} \ No newline at end of file diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 2cd487b0..548a444f 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -1,55 +1,50 @@ #ifndef __INTERFACE_CAN_HPP #define __INTERFACE_CAN_HPP -#include "fibre/protocol.hpp" -#include #include +#include +#include "fibre/protocol.hpp" -struct CAN_context { - CAN_HandleTypeDef *handle = nullptr; - uint8_t node_id = 0; - uint64_t serial_number = 0; +struct { + uint32_t id; + bool isExt; + uint8_t len; + uint8_t buf[8]; +} CAN_message_t; - uint32_t node_ids_in_use_0[4]; // 128 bits (indicate if a node ID was in use up to 1 second ago) - uint32_t node_ids_in_use_1[4]; // 128 bits (indicats if a node ID was in use 1-2 seconds ago) +// Anonymous enum for defining the most common CAN baud rates +enum { + CAN_BAUD_125K = 125000, + CAN_BAUD_250K = 250000, + CAN_BAUD_500K = 500000, + CAN_BAUD_1000K = 1000000, + CAN_BAUD_1M = 1000000 +}; +struct CANConfig_t { + uint8_t node_id; + uint32_t baud; +}; - uint32_t last_heartbeat_mailbox = 0; - uint32_t tx_msg_cnt = 0; - uint32_t node_id_expiry = 0; - - uint8_t node_id_rng_state = 0; +class ODriveCAN { + public: + ODriveCAN(CAN_HandleTypeDef *handle, CANConfig_t &config); - osSemaphoreId sem_send_heartbeat; + bool start_can_server(); + void can_server_thread(); - // count occurrence various callbacks - uint32_t TxMailboxCompleteCallbackCnt = 0; - uint32_t TxMailboxAbortCallbackCnt = 0; - int RxFifo0MsgPendingCallbackCnt = 0; - int RxFifo0FullCallbackCnt = 0; - int RxFifo1MsgPendingCallbackCnt = 0; - int RxFifo1FullCallbackCnt = 0; - int SleepCallbackCnt = 0; - int WakeUpFromRxMsgCallbackCnt = 0; - int ErrorCallbackCnt = 0; - - uint32_t received_msg_cnt = 0; - uint32_t received_ack = 0; - uint32_t unexpected_errors = 0; - uint32_t unhandled_messages = 0; + osThreadId thread_id_; + volatile bool thread_id_valid_ = false; auto make_protocol_definitions() { return make_protocol_member_list( - make_protocol_ro_property("node_id", &node_id), - make_protocol_ro_property("TxMailboxCompleteCallbackCnt", &TxMailboxCompleteCallbackCnt), - make_protocol_ro_property("TxMailboxAbortCallbackCnt", &TxMailboxAbortCallbackCnt), - make_protocol_ro_property("received_msg_cnt", &received_msg_cnt), - make_protocol_ro_property("received_ack", &received_ack), - make_protocol_ro_property("unexpected_errors", &unexpected_errors), - make_protocol_ro_property("unhandled_messages", &unhandled_messages) - ); + make_protocol_object("config", + make_protocol_property("node_id", &config_.node_id), + make_protocol_property("baud_rate", &config_.baud))); } + + private: + CAN_HandleTypeDef *handle_ = nullptr; + CANConfig_t &config_; }; -bool start_can_server(CAN_context& ctx, CAN_TypeDef *hcan, uint64_t serial_number); - -#endif // __INTERFACE_CAN_HPP +#endif // __INTERFACE_CAN_HPP From deb7a9adde6cc9163d64c9936a93255586a3cabc Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 22 Sep 2018 16:52:36 -0400 Subject: [PATCH 026/549] Add baud rate validation via set function --- Firmware/communication/interface_can.cpp | 20 +++++++++++++++++++- Firmware/communication/interface_can.hpp | 15 +++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index b020f4c1..6709daa5 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -52,7 +52,7 @@ static void can_server_thread_wrapper(void* ctx){ void ODriveCAN::can_server_thread() { for (;;) { - // Do nothing + osDelay(1000); } } @@ -80,4 +80,22 @@ bool ODriveCAN::start_can_server() { thread_id_valid_ = true; return true; +} + +void ODriveCAN::set_baud_rate(uint32_t baudRate){ + switch(baudRate){ + case CAN_BAUD_125K: + case CAN_BAUD_250K: + case CAN_BAUD_500K: + case CAN_BAUD_1000K: + config_.baud = baudRate; // baudRate is a valid CAN baud + break; + default: + break; // baudRate is invalid, so do nothing + } +} + +void ODriveCAN::set_node_id(uint8_t nodeID){ + // Allow for future nodeID validation by making this a set function + config_.node_id = nodeID; } \ No newline at end of file diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 548a444f..35b7c3b3 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -21,8 +21,8 @@ enum { CAN_BAUD_1M = 1000000 }; struct CANConfig_t { - uint8_t node_id; - uint32_t baud; + uint8_t node_id = 0; + uint32_t baud = CAN_BAUD_250K; }; class ODriveCAN { @@ -38,13 +38,20 @@ class ODriveCAN { auto make_protocol_definitions() { return make_protocol_member_list( make_protocol_object("config", - make_protocol_property("node_id", &config_.node_id), - make_protocol_property("baud_rate", &config_.baud))); + make_protocol_ro_property("node_id", &config_.node_id), + make_protocol_ro_property("baud_rate", &config_.baud) + ), + make_protocol_function("set_node_id", *this, &ODriveCAN::set_node_id, "nodeID"), + make_protocol_function("set_baud_rate", *this, &ODriveCAN::set_baud_rate, "baudRate") + ); } private: CAN_HandleTypeDef *handle_ = nullptr; CANConfig_t &config_; + + void set_node_id(uint8_t nodeID); + void set_baud_rate(uint32_t baudRate); }; #endif // __INTERFACE_CAN_HPP From 3ba3d4e4133263a0df01560b3e6624539db71810 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 22 Sep 2018 18:50:05 -0400 Subject: [PATCH 027/549] Allow for different baud rates by adjusting prescaler and time quanta --- Firmware/Board/v3/Src/can.c | 8 ++-- Firmware/MotorControl/main.cpp | 2 - Firmware/communication/interface_can.cpp | 60 ++++++++++++++++-------- Firmware/communication/interface_can.hpp | 3 ++ 4 files changed, 48 insertions(+), 25 deletions(-) diff --git a/Firmware/Board/v3/Src/can.c b/Firmware/Board/v3/Src/can.c index fc7bf388..d4cff30a 100644 --- a/Firmware/Board/v3/Src/can.c +++ b/Firmware/Board/v3/Src/can.c @@ -63,11 +63,11 @@ void MX_CAN1_Init(void) { hcan1.Instance = CAN1; - hcan1.Init.Prescaler = 7; + hcan1.Init.Prescaler = 8; hcan1.Init.Mode = CAN_MODE_NORMAL; - hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; - hcan1.Init.TimeSeg1 = CAN_BS1_6TQ; - hcan1.Init.TimeSeg2 = CAN_BS2_5TQ; + hcan1.Init.SyncJumpWidth = CAN_SJW_4TQ; + hcan1.Init.TimeSeg1 = CAN_BS1_16TQ; + hcan1.Init.TimeSeg2 = CAN_BS2_4TQ; hcan1.Init.TimeTriggeredMode = DISABLE; hcan1.Init.AutoBusOff = DISABLE; hcan1.Init.AutoWakeUp = ENABLE; diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 3b91e075..af9682d5 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -23,8 +23,6 @@ SystemStats_t system_stats_ = { 0 }; Axis *axes[AXIS_COUNT]; ODriveCAN *odCAN; - - typedef Config< BoardConfig_t, CANConfig_t, diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 6709daa5..985b33f6 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -45,57 +45,79 @@ ODriveCAN::ODriveCAN(CAN_HandleTypeDef *handle, CANConfig_t &config) config_{config} { } -static void can_server_thread_wrapper(void* ctx){ - reinterpret_cast(ctx)->can_server_thread(); - reinterpret_cast(ctx)->thread_id_valid_ = false; +static void can_server_thread_wrapper(void *ctx) { + reinterpret_cast(ctx)->can_server_thread(); + reinterpret_cast(ctx)->thread_id_valid_ = false; } void ODriveCAN::can_server_thread() { for (;;) { - osDelay(1000); + osDelay(10); } } bool ODriveCAN::start_can_server() { HAL_StatusTypeDef status; + set_baud_rate(config_.baud); + status = HAL_CAN_Init(handle_); + if (status != HAL_OK) + return false; + status = HAL_CAN_Start(handle_); if (status != HAL_OK) return false; status = HAL_CAN_ActivateNotification(handle_, - CAN_IT_TX_MAILBOX_EMPTY | - CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_RX_FIFO1_MSG_PENDING | /* we probably only want this */ - CAN_IT_RX_FIFO0_FULL | CAN_IT_RX_FIFO1_FULL | - CAN_IT_RX_FIFO0_OVERRUN | CAN_IT_RX_FIFO1_OVERRUN | - CAN_IT_WAKEUP | CAN_IT_SLEEP_ACK | - CAN_IT_ERROR_WARNING | CAN_IT_ERROR_PASSIVE | - CAN_IT_BUSOFF | CAN_IT_LAST_ERROR_CODE | - CAN_IT_ERROR); + CAN_IT_TX_MAILBOX_EMPTY | + CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_RX_FIFO1_MSG_PENDING | /* we probably only want this */ + CAN_IT_RX_FIFO0_FULL | CAN_IT_RX_FIFO1_FULL | + CAN_IT_RX_FIFO0_OVERRUN | CAN_IT_RX_FIFO1_OVERRUN | + CAN_IT_WAKEUP | CAN_IT_SLEEP_ACK | + CAN_IT_ERROR_WARNING | CAN_IT_ERROR_PASSIVE | + CAN_IT_BUSOFF | CAN_IT_LAST_ERROR_CODE | + CAN_IT_ERROR); if (status != HAL_OK) return false; osThreadDef(can_server_thread_def, can_server_thread_wrapper, osPriorityNormal, 0, 512); thread_id_ = osThreadCreate(osThread(can_server_thread_def), this); thread_id_valid_ = true; - + return true; } -void ODriveCAN::set_baud_rate(uint32_t baudRate){ - switch(baudRate){ - case CAN_BAUD_125K: +void ODriveCAN::set_baud_rate(uint32_t baudRate) { + switch (baudRate) { + case CAN_BAUD_125K: + handle_->Init.Prescaler = 21; // 16 TQ's + handle_->Init.TimeSeg1 = CAN_BS1_12TQ; + handle_->Init.TimeSeg2 = CAN_BS2_3TQ; + config_.baud = baudRate; + break; case CAN_BAUD_250K: + handle_->Init.Prescaler = 8; // 21 TQ's + handle_->Init.TimeSeg1 = CAN_BS1_16TQ; + handle_->Init.TimeSeg2 = CAN_BS2_4TQ; + config_.baud = baudRate; + break; case CAN_BAUD_500K: + handle_->Init.Prescaler = 4; // 21 TQ's + handle_->Init.TimeSeg1 = CAN_BS1_16TQ; + handle_->Init.TimeSeg2 = CAN_BS2_4TQ; + config_.baud = baudRate; case CAN_BAUD_1000K: - config_.baud = baudRate; // baudRate is a valid CAN baud + handle_->Init.Prescaler = 2; // 21 TQ's + handle_->Init.TimeSeg1 = CAN_BS1_16TQ; + handle_->Init.TimeSeg2 = CAN_BS2_4TQ; + config_.baud = baudRate; break; default: - break; // baudRate is invalid, so do nothing + break; // baudRate is invalid, so do nothing } } -void ODriveCAN::set_node_id(uint8_t nodeID){ +void ODriveCAN::set_node_id(uint8_t nodeID) { // Allow for future nodeID validation by making this a set function config_.node_id = nodeID; } \ No newline at end of file diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 35b7c3b3..2403023e 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -5,6 +5,9 @@ #include #include "fibre/protocol.hpp" +#define CAN_CLK_HZ (42000000) +#define CAN_CLK_MHZ (42) + struct { uint32_t id; bool isExt; From 6a87ca8e9171ba394cb6552c30ef6b8dcd4ac488 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 22 Sep 2018 19:11:35 -0400 Subject: [PATCH 028/549] Add write function for CAN class --- Firmware/communication/interface_can.cpp | 42 ++++++++++++++++++++---- Firmware/communication/interface_can.hpp | 5 ++- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 985b33f6..4902ca2f 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -39,23 +39,36 @@ #include #include +static uint32_t counter = 0; // Constructor is called by communication.cpp and the handle is assigned appropriately ODriveCAN::ODriveCAN(CAN_HandleTypeDef *handle, CANConfig_t &config) : handle_{handle}, config_{config} { } +void ODriveCAN::can_server_thread() { + for (;;) { + CAN_message_t txmsg; + txmsg.id = 0x100; + txmsg.len = 4; + txmsg.isExt = false; + + txmsg.buf[0] = counter >> 24; + txmsg.buf[1] = counter >> 16; + txmsg.buf[2] = counter >> 8; + txmsg.buf[3] = counter; + + write(txmsg); // Transmit message w/ counter at ID = 0x100 + + osDelay(10); + } +} + static void can_server_thread_wrapper(void *ctx) { reinterpret_cast(ctx)->can_server_thread(); reinterpret_cast(ctx)->thread_id_valid_ = false; } -void ODriveCAN::can_server_thread() { - for (;;) { - osDelay(10); - } -} - bool ODriveCAN::start_can_server() { HAL_StatusTypeDef status; @@ -120,4 +133,19 @@ void ODriveCAN::set_baud_rate(uint32_t baudRate) { void ODriveCAN::set_node_id(uint8_t nodeID) { // Allow for future nodeID validation by making this a set function config_.node_id = nodeID; -} \ No newline at end of file +} + +uint32_t ODriveCAN::write(CAN_message_t& txmsg) { + CAN_TxHeaderTypeDef header; + header.StdId = txmsg.id; + header.ExtId = txmsg.id; + header.IDE = txmsg.isExt ? CAN_ID_EXT : CAN_ID_STD; + header.RTR = CAN_RTR_DATA; + header.DLC = txmsg.len; + header.TransmitGlobalTime = FunctionalState::DISABLE; + + uint32_t retTxMailbox; + HAL_CAN_AddTxMessage(handle_, &header, txmsg.buf, &retTxMailbox); + + return retTxMailbox; +} diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 2403023e..0959d7c1 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -8,7 +8,7 @@ #define CAN_CLK_HZ (42000000) #define CAN_CLK_MHZ (42) -struct { +typedef struct { uint32_t id; bool isExt; uint8_t len; @@ -35,6 +35,9 @@ class ODriveCAN { bool start_can_server(); void can_server_thread(); + uint32_t write(CAN_message_t& txmsg); + int read(CAN_message_t& rxmsg); + osThreadId thread_id_; volatile bool thread_id_valid_ = false; From 41224a4a4105efce91b97b2fbe553ff898eac947 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 27 Sep 2018 20:45:39 -0400 Subject: [PATCH 029/549] Move Endstop's config struct into the class to unify the style --- Firmware/MotorControl/endstop.cpp | 2 +- Firmware/MotorControl/endstop.hpp | 33 +++++++++++++------------------ Firmware/MotorControl/main.cpp | 12 +++++------ 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index ac0ff9a1..4443fdd6 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -1,6 +1,6 @@ #include -Endstop::Endstop(EndstopConfig_t &config) +Endstop::Endstop(Endstop::Config_t &config) : config_(config) { set_endstop_enabled(config_.enabled); } diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index 6e9cb46f..92ff10d7 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -1,20 +1,19 @@ #ifndef __ENDSTOP_HPP #define __ENDSTOP_HPP - -struct EndstopConfig_t { - uint16_t gpio_num; - bool enabled = false; - int32_t offset = 0; - bool is_active_high = false; - float debounce_ms = 100.0f; -}; - class Endstop { public: - Endstop(EndstopConfig_t& config); - - EndstopConfig_t& config_; + struct Config_t { + uint16_t gpio_num; + bool enabled = false; + int32_t offset = 0; + bool is_active_high = false; + float debounce_ms = 100.0f; + }; + + Endstop(Endstop::Config_t& config); + + Endstop::Config_t& config_; Axis* axis_ = nullptr; void set_endstop_enabled(bool enable); @@ -23,10 +22,9 @@ class Endstop { bool getEndstopState(); - bool endstop_state_ = false; - - auto make_protocol_definitions(){ + + auto make_protocol_definitions() { return make_protocol_member_list( make_protocol_ro_property("endstop_state_", &endstop_state_), make_protocol_object("config", @@ -34,13 +32,10 @@ class Endstop { make_protocol_property("enabled", &config_.enabled), make_protocol_property("offset", &config_.offset), make_protocol_property("is_active_high", &config_.is_active_high), - make_protocol_property("debounce_ms", &config_.debounce_ms) - ) - ); + make_protocol_property("debounce_ms", &config_.debounce_ms))); } private: - bool pin_state_ = false; volatile float debounce_timer_ = 0; }; diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index ebfe25a6..39921fea 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -15,8 +15,8 @@ Controller::Config_t controller_configs[AXIS_COUNT]; Motor::Config_t motor_configs[AXIS_COUNT]; Axis::Config_t axis_configs[AXIS_COUNT]; TrapezoidalTrajectory::Config_t trap_configs[AXIS_COUNT]; -EndstopConfig_t min_endstop_configs[AXIS_COUNT]; -EndstopConfig_t max_endstop_configs[AXIS_COUNT]; +Endstop::Config_t min_endstop_configs[AXIS_COUNT]; +Endstop::Config_t max_endstop_configs[AXIS_COUNT]; bool user_config_loaded_; SystemStats_t system_stats_ = { 0 }; @@ -30,8 +30,8 @@ typedef Config< Controller::Config_t[AXIS_COUNT], Motor::Config_t[AXIS_COUNT], TrapezoidalTrajectory::Config_t[AXIS_COUNT], - EndstopConfig_t[AXIS_COUNT], - EndstopConfig_t[AXIS_COUNT], + Endstop::Config_t[AXIS_COUNT], + Endstop::Config_t[AXIS_COUNT], Axis::Config_t[AXIS_COUNT]> ConfigFormat; void save_configuration(void) { @@ -73,8 +73,8 @@ void load_configuration(void) { motor_configs[i] = Motor::Config_t(); trap_configs[i] = TrapezoidalTrajectory::Config_t(); axis_configs[i] = Axis::Config_t(); - min_endstop_configs[i] = EndstopConfig_t(); - max_endstop_configs[i] = EndstopConfig_t(); + min_endstop_configs[i] = Endstop::Config_t(); + max_endstop_configs[i] = Endstop::Config_t(); } } else { user_config_loaded_ = true; From 75dfb48238592ad30657e68aa77c85fddb0b8198 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 28 Sep 2018 18:19:34 -0400 Subject: [PATCH 030/549] Unify the Config_t style for ODriveCAN --- Firmware/MotorControl/main.cpp | 6 ++-- Firmware/communication/interface_can.cpp | 2 +- Firmware/communication/interface_can.hpp | 38 ++++++++++++------------ 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 8c7351ac..290172c7 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -10,7 +10,7 @@ #include BoardConfig_t board_config; -CANConfig_t can_config; +ODriveCAN::Config_t can_config; Encoder::Config_t encoder_configs[AXIS_COUNT]; SensorlessEstimator::Config_t sensorless_configs[AXIS_COUNT]; Controller::Config_t controller_configs[AXIS_COUNT]; @@ -26,7 +26,7 @@ ODriveCAN *odCAN; typedef Config< BoardConfig_t, - CANConfig_t, + ODriveCAN::Config_t, Encoder::Config_t[AXIS_COUNT], SensorlessEstimator::Config_t[AXIS_COUNT], Controller::Config_t[AXIS_COUNT], @@ -64,7 +64,7 @@ void load_configuration(void) { &axis_configs)) { //If loading failed, restore defaults board_config = BoardConfig_t(); - can_config = CANConfig_t(); + can_config = ODriveCAN::Config_t(); for (size_t i = 0; i < AXIS_COUNT; ++i) { encoder_configs[i] = Encoder::Config_t(); sensorless_configs[i] = SensorlessEstimator::Config_t(); diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 4902ca2f..f9a9ff6f 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -41,7 +41,7 @@ static uint32_t counter = 0; // Constructor is called by communication.cpp and the handle is assigned appropriately -ODriveCAN::ODriveCAN(CAN_HandleTypeDef *handle, CANConfig_t &config) +ODriveCAN::ODriveCAN(CAN_HandleTypeDef *handle, ODriveCAN::Config_t &config) : handle_{handle}, config_{config} { } diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 0959d7c1..dceb8120 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -16,27 +16,29 @@ typedef struct { } CAN_message_t; // Anonymous enum for defining the most common CAN baud rates -enum { - CAN_BAUD_125K = 125000, - CAN_BAUD_250K = 250000, - CAN_BAUD_500K = 500000, - CAN_BAUD_1000K = 1000000, - CAN_BAUD_1M = 1000000 -}; -struct CANConfig_t { - uint8_t node_id = 0; - uint32_t baud = CAN_BAUD_250K; -}; + + enum { + CAN_BAUD_125K = 125000, + CAN_BAUD_250K = 250000, + CAN_BAUD_500K = 500000, + CAN_BAUD_1000K = 1000000, + CAN_BAUD_1M = 1000000 + }; class ODriveCAN { public: - ODriveCAN(CAN_HandleTypeDef *handle, CANConfig_t &config); + struct Config_t { + uint8_t node_id = 0; + uint32_t baud = CAN_BAUD_250K; + }; + + ODriveCAN(CAN_HandleTypeDef *handle, ODriveCAN::Config_t &config); bool start_can_server(); void can_server_thread(); - uint32_t write(CAN_message_t& txmsg); - int read(CAN_message_t& rxmsg); + uint32_t write(CAN_message_t &txmsg); + int read(CAN_message_t &rxmsg); osThreadId thread_id_; volatile bool thread_id_valid_ = false; @@ -45,16 +47,14 @@ class ODriveCAN { return make_protocol_member_list( make_protocol_object("config", make_protocol_ro_property("node_id", &config_.node_id), - make_protocol_ro_property("baud_rate", &config_.baud) - ), + make_protocol_ro_property("baud_rate", &config_.baud)), make_protocol_function("set_node_id", *this, &ODriveCAN::set_node_id, "nodeID"), - make_protocol_function("set_baud_rate", *this, &ODriveCAN::set_baud_rate, "baudRate") - ); + make_protocol_function("set_baud_rate", *this, &ODriveCAN::set_baud_rate, "baudRate")); } private: CAN_HandleTypeDef *handle_ = nullptr; - CANConfig_t &config_; + ODriveCAN::Config_t &config_; void set_node_id(uint8_t nodeID); void set_baud_rate(uint32_t baudRate); From 071c0fc802bf79aadf19237564c4ab8ea94d1f65 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 29 Sep 2018 18:17:54 -0400 Subject: [PATCH 031/549] Tweak CAN messaging, get it working with PSoC --- Firmware/communication/interface_can.cpp | 39 ++++++++++++++---------- Firmware/communication/interface_can.hpp | 2 +- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index f9a9ff6f..5ebaf3c0 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -39,7 +39,6 @@ #include #include -static uint32_t counter = 0; // Constructor is called by communication.cpp and the handle is assigned appropriately ODriveCAN::ODriveCAN(CAN_HandleTypeDef *handle, ODriveCAN::Config_t &config) : handle_{handle}, @@ -47,6 +46,7 @@ ODriveCAN::ODriveCAN(CAN_HandleTypeDef *handle, ODriveCAN::Config_t &config) } void ODriveCAN::can_server_thread() { + static uint32_t counter = 0; for (;;) { CAN_message_t txmsg; txmsg.id = 0x100; @@ -58,8 +58,8 @@ void ODriveCAN::can_server_thread() { txmsg.buf[2] = counter >> 8; txmsg.buf[3] = counter; - write(txmsg); // Transmit message w/ counter at ID = 0x100 - + counter++; + write(txmsg); osDelay(10); } } @@ -103,28 +103,33 @@ bool ODriveCAN::start_can_server() { void ODriveCAN::set_baud_rate(uint32_t baudRate) { switch (baudRate) { case CAN_BAUD_125K: - handle_->Init.Prescaler = 21; // 16 TQ's - handle_->Init.TimeSeg1 = CAN_BS1_12TQ; - handle_->Init.TimeSeg2 = CAN_BS2_3TQ; + handle_->Init.Prescaler = 16; // 21 TQ's + handle_->Init.TimeSeg1 = CAN_BS1_16TQ; + handle_->Init.TimeSeg2 = CAN_BS2_4TQ; config_.baud = baudRate; break; + case CAN_BAUD_250K: - handle_->Init.Prescaler = 8; // 21 TQ's + handle_->Init.Prescaler = 8; // 21 TQ's handle_->Init.TimeSeg1 = CAN_BS1_16TQ; handle_->Init.TimeSeg2 = CAN_BS2_4TQ; config_.baud = baudRate; break; + case CAN_BAUD_500K: - handle_->Init.Prescaler = 4; // 21 TQ's - handle_->Init.TimeSeg1 = CAN_BS1_16TQ; - handle_->Init.TimeSeg2 = CAN_BS2_4TQ; - config_.baud = baudRate; - case CAN_BAUD_1000K: - handle_->Init.Prescaler = 2; // 21 TQ's + handle_->Init.Prescaler = 4; // 21 TQ's handle_->Init.TimeSeg1 = CAN_BS1_16TQ; handle_->Init.TimeSeg2 = CAN_BS2_4TQ; config_.baud = baudRate; break; + + case CAN_BAUD_1000K: + handle_->Init.Prescaler = 2; // 21 TQ's + handle_->Init.TimeSeg1 = CAN_BS1_16TQ; + handle_->Init.TimeSeg2 = CAN_BS2_4TQ; + config_.baud = baudRate; + break; + default: break; // baudRate is invalid, so do nothing } @@ -135,7 +140,8 @@ void ODriveCAN::set_node_id(uint8_t nodeID) { config_.node_id = nodeID; } -uint32_t ODriveCAN::write(CAN_message_t& txmsg) { +// Send a CAN message on the bus +uint32_t ODriveCAN::write(CAN_message_t &txmsg) { CAN_TxHeaderTypeDef header; header.StdId = txmsg.id; header.ExtId = txmsg.id; @@ -145,7 +151,8 @@ uint32_t ODriveCAN::write(CAN_message_t& txmsg) { header.TransmitGlobalTime = FunctionalState::DISABLE; uint32_t retTxMailbox; - HAL_CAN_AddTxMessage(handle_, &header, txmsg.buf, &retTxMailbox); + if(HAL_CAN_GetTxMailboxesFreeLevel(handle_) > 0) + HAL_CAN_AddTxMessage(handle_, &header, txmsg.buf, &retTxMailbox); return retTxMailbox; -} +} \ No newline at end of file diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index dceb8120..6187440a 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -36,7 +36,7 @@ class ODriveCAN { bool start_can_server(); void can_server_thread(); - + uint32_t write(CAN_message_t &txmsg); int read(CAN_message_t &rxmsg); From 41998b67a6bcf58811b623998c761df1e9faf019 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 30 Sep 2018 01:49:34 -0400 Subject: [PATCH 032/549] Let the hardware handle re-transmission and reinit from busoff --- Firmware/Board/v3/Src/can.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/Board/v3/Src/can.c b/Firmware/Board/v3/Src/can.c index d4cff30a..011150ff 100644 --- a/Firmware/Board/v3/Src/can.c +++ b/Firmware/Board/v3/Src/can.c @@ -69,9 +69,9 @@ void MX_CAN1_Init(void) hcan1.Init.TimeSeg1 = CAN_BS1_16TQ; hcan1.Init.TimeSeg2 = CAN_BS2_4TQ; hcan1.Init.TimeTriggeredMode = DISABLE; - hcan1.Init.AutoBusOff = DISABLE; + hcan1.Init.AutoBusOff = ENABLE; hcan1.Init.AutoWakeUp = ENABLE; - hcan1.Init.AutoRetransmission = DISABLE; + hcan1.Init.AutoRetransmission = ENABLE; hcan1.Init.ReceiveFifoLocked = DISABLE; hcan1.Init.TransmitFifoPriority = DISABLE; if (HAL_CAN_Init(&hcan1) != HAL_OK) From 72fc50db6f50d67c205d7abb0d36e765248dfab0 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 30 Sep 2018 01:50:30 -0400 Subject: [PATCH 033/549] Setup semaphore for can --- Firmware/Board/v3/Inc/freertos_vars.h | 1 + Firmware/Board/v3/Src/freertos.c | 5 +++++ Firmware/communication/interface_can.cpp | 3 ++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Firmware/Board/v3/Inc/freertos_vars.h b/Firmware/Board/v3/Inc/freertos_vars.h index 6982ee28..6e4c0696 100644 --- a/Firmware/Board/v3/Inc/freertos_vars.h +++ b/Firmware/Board/v3/Inc/freertos_vars.h @@ -7,6 +7,7 @@ extern osSemaphoreId sem_usb_irq; extern osSemaphoreId sem_uart_dma; extern osSemaphoreId sem_usb_rx; extern osSemaphoreId sem_usb_tx; +extern osSemaphoreId sem_can; extern osThreadId defaultTaskHandle; extern osThreadId usb_irq_thread; diff --git a/Firmware/Board/v3/Src/freertos.c b/Firmware/Board/v3/Src/freertos.c index 524cd6cb..5a078f79 100644 --- a/Firmware/Board/v3/Src/freertos.c +++ b/Firmware/Board/v3/Src/freertos.c @@ -67,6 +67,7 @@ osSemaphoreId sem_usb_irq; osSemaphoreId sem_uart_dma; osSemaphoreId sem_usb_rx; osSemaphoreId sem_usb_tx; +osSemaphoreId sem_can; osThreadId usb_irq_thread; @@ -165,6 +166,10 @@ void MX_FREERTOS_Init(void) { osSemaphoreDef(sem_usb_tx); sem_usb_tx = osSemaphoreCreate(osSemaphore(sem_usb_tx), 1); + osSemaphoreDef(sem_can); + sem_can = osSemaphoreCreate(osSemaphore(sem_can), 1); + osSemaphoreWait(sem_can, 0); + init_deferred_interrupts(); /* USER CODE END RTOS_SEMAPHORES */ diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 5ebaf3c0..29a03a24 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -33,6 +33,7 @@ #include "interface_can.hpp" #include "fibre/crc.hpp" +#include "freertos_vars.h" #include "utils.h" #include @@ -50,7 +51,7 @@ void ODriveCAN::can_server_thread() { for (;;) { CAN_message_t txmsg; txmsg.id = 0x100; - txmsg.len = 4; + osSemaphoreWait(sem_can, 10); txmsg.isExt = false; txmsg.buf[0] = counter >> 24; From 3a460385b4002447b31adcb73f8489128f44001a Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 30 Sep 2018 01:51:09 -0400 Subject: [PATCH 034/549] Messages should have clean default parameters --- Firmware/communication/interface_can.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 6187440a..a309658b 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -9,10 +9,10 @@ #define CAN_CLK_MHZ (42) typedef struct { - uint32_t id; - bool isExt; - uint8_t len; - uint8_t buf[8]; + uint32_t id = 0x000; // 11-bit max is 0x7ff, 29-bit max is 0x1FFFFFFF + bool isExt = false; + uint8_t len = 8; + uint8_t buf[8] = {0, 0, 0, 0, 0, 0, 0, 0}; } CAN_message_t; // Anonymous enum for defining the most common CAN baud rates From d38f1c8464eff65879cf5232156279f18bd3a9e6 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 30 Sep 2018 01:51:36 -0400 Subject: [PATCH 035/549] Use an STM32F4 SVD for debugging so we can see registers --- Firmware/.vscode/launch.json | 1 + Firmware/Board/v3/STM32F40x.svd | 54146 ++++++++++++++++++++++++++++++ 2 files changed, 54147 insertions(+) create mode 100644 Firmware/Board/v3/STM32F40x.svd diff --git a/Firmware/.vscode/launch.json b/Firmware/.vscode/launch.json index 71e7db4c..48fecec2 100644 --- a/Firmware/.vscode/launch.json +++ b/Firmware/.vscode/launch.json @@ -15,6 +15,7 @@ "interface/stlink-v2.cfg", "target/stm32f4x_stlink.cfg", ], + "svdFile": "${workspaceRoot}/Board/v3/STM32F40x.svd", "cwd": "${workspaceRoot}" }, { diff --git a/Firmware/Board/v3/STM32F40x.svd b/Firmware/Board/v3/STM32F40x.svd new file mode 100644 index 00000000..3ead4727 --- /dev/null +++ b/Firmware/Board/v3/STM32F40x.svd @@ -0,0 +1,54146 @@ + + + STM32F40x + 1.5 + STM32F40x + + + 8 + + 32 + + 0x20 + 0x0 + 0xFFFFFFFF + + + RNG + Random number generator + RNG + 0x50060800 + + 0x0 + 0x400 + registers + + +FPU +FPU interrupt +81 + + + + CR + CR + control register + 0x0 + 0x20 + read-write + 0x00000000 + + + IE + Interrupt enable + 3 + 1 + + + RNGEN + Random number generator + enable + 2 + 1 + + + + + SR + SR + status register + 0x4 + 0x20 + 0x00000000 + + + SEIS + Seed error interrupt + status + 6 + 1 + read-write + + + CEIS + Clock error interrupt + status + 5 + 1 + read-write + + + SECS + Seed error current status + 2 + 1 + read-only + + + CECS + Clock error current status + 1 + 1 + read-only + + + DRDY + Data ready + 0 + 1 + read-only + + + + + DR + DR + data register + 0x8 + 0x20 + read-only + 0x00000000 + + + RNDATA + Random data + 0 + 32 + + + + + + + DCMI + Digital camera interface + DCMI + 0x50050000 + + 0x0 + 0x400 + registers + + + DCMI + DCMI global interrupt + 78 + + + + CR + CR + control register 1 + 0x0 + 0x20 + read-write + 0x0000 + + + ENABLE + DCMI enable + 14 + 1 + + + EDM + Extended data mode + 10 + 2 + + + FCRC + Frame capture rate control + 8 + 2 + + + VSPOL + Vertical synchronization + polarity + 7 + 1 + + + HSPOL + Horizontal synchronization + polarity + 6 + 1 + + + PCKPOL + Pixel clock polarity + 5 + 1 + + + ESS + Embedded synchronization + select + 4 + 1 + + + JPEG + JPEG format + 3 + 1 + + + CROP + Crop feature + 2 + 1 + + + CM + Capture mode + 1 + 1 + + + CAPTURE + Capture enable + 0 + 1 + + + + + SR + SR + status register + 0x4 + 0x20 + read-only + 0x0000 + + + FNE + FIFO not empty + 2 + 1 + + + VSYNC + VSYNC + 1 + 1 + + + HSYNC + HSYNC + 0 + 1 + + + + + RIS + RIS + raw interrupt status register + 0x8 + 0x20 + read-only + 0x0000 + + + LINE_RIS + Line raw interrupt status + 4 + 1 + + + VSYNC_RIS + VSYNC raw interrupt status + 3 + 1 + + + ERR_RIS + Synchronization error raw interrupt + status + 2 + 1 + + + OVR_RIS + Overrun raw interrupt + status + 1 + 1 + + + FRAME_RIS + Capture complete raw interrupt + status + 0 + 1 + + + + + IER + IER + interrupt enable register + 0xC + 0x20 + read-write + 0x0000 + + + LINE_IE + Line interrupt enable + 4 + 1 + + + VSYNC_IE + VSYNC interrupt enable + 3 + 1 + + + ERR_IE + Synchronization error interrupt + enable + 2 + 1 + + + OVR_IE + Overrun interrupt enable + 1 + 1 + + + FRAME_IE + Capture complete interrupt + enable + 0 + 1 + + + + + MIS + MIS + masked interrupt status + register + 0x10 + 0x20 + read-only + 0x0000 + + + LINE_MIS + Line masked interrupt + status + 4 + 1 + + + VSYNC_MIS + VSYNC masked interrupt + status + 3 + 1 + + + ERR_MIS + Synchronization error masked interrupt + status + 2 + 1 + + + OVR_MIS + Overrun masked interrupt + status + 1 + 1 + + + FRAME_MIS + Capture complete masked interrupt + status + 0 + 1 + + + + + ICR + ICR + interrupt clear register + 0x14 + 0x20 + write-only + 0x0000 + + + LINE_ISC + line interrupt status + clear + 4 + 1 + + + VSYNC_ISC + Vertical synch interrupt status + clear + 3 + 1 + + + ERR_ISC + Synchronization error interrupt status + clear + 2 + 1 + + + OVR_ISC + Overrun interrupt status + clear + 1 + 1 + + + FRAME_ISC + Capture complete interrupt status + clear + 0 + 1 + + + + + ESCR + ESCR + embedded synchronization code + register + 0x18 + 0x20 + read-write + 0x0000 + + + FEC + Frame end delimiter code + 24 + 8 + + + LEC + Line end delimiter code + 16 + 8 + + + LSC + Line start delimiter code + 8 + 8 + + + FSC + Frame start delimiter code + 0 + 8 + + + + + ESUR + ESUR + embedded synchronization unmask + register + 0x1C + 0x20 + read-write + 0x0000 + + + FEU + Frame end delimiter unmask + 24 + 8 + + + LEU + Line end delimiter unmask + 16 + 8 + + + LSU + Line start delimiter + unmask + 8 + 8 + + + FSU + Frame start delimiter + unmask + 0 + 8 + + + + + CWSTRT + CWSTRT + crop window start + 0x20 + 0x20 + read-write + 0x0000 + + + VST + Vertical start line count + 16 + 13 + + + HOFFCNT + Horizontal offset count + 0 + 14 + + + + + CWSIZE + CWSIZE + crop window size + 0x24 + 0x20 + read-write + 0x0000 + + + VLINE + Vertical line count + 16 + 14 + + + CAPCNT + Capture count + 0 + 14 + + + + + DR + DR + data register + 0x28 + 0x20 + read-only + 0x0000 + + + Byte3 + Data byte 3 + 24 + 8 + + + Byte2 + Data byte 2 + 16 + 8 + + + Byte1 + Data byte 1 + 8 + 8 + + + Byte0 + Data byte 0 + 0 + 8 + + + + + + + FSMC + Flexible static memory controller + FSMC + 0xA0000000 + + 0x0 + 0x400 + registers + + + FSMC + FSMC global interrupt + 48 + + + + BCR1 + BCR1 + SRAM/NOR-Flash chip-select control register + 1 + 0x0 + 0x20 + read-write + 0x000030D0 + + + CBURSTRW + CBURSTRW + 19 + 1 + + + ASYNCWAIT + ASYNCWAIT + 15 + 1 + + + EXTMOD + EXTMOD + 14 + 1 + + + WAITEN + WAITEN + 13 + 1 + + + WREN + WREN + 12 + 1 + + + WAITCFG + WAITCFG + 11 + 1 + + + WAITPOL + WAITPOL + 9 + 1 + + + BURSTEN + BURSTEN + 8 + 1 + + + FACCEN + FACCEN + 6 + 1 + + + MWID + MWID + 4 + 2 + + + MTYP + MTYP + 2 + 2 + + + MUXEN + MUXEN + 1 + 1 + + + MBKEN + MBKEN + 0 + 1 + + + + + BTR1 + BTR1 + SRAM/NOR-Flash chip-select timing register + 1 + 0x4 + 0x20 + read-write + 0xFFFFFFFF + + + ACCMOD + ACCMOD + 28 + 2 + + + DATLAT + DATLAT + 24 + 4 + + + CLKDIV + CLKDIV + 20 + 4 + + + BUSTURN + BUSTURN + 16 + 4 + + + DATAST + DATAST + 8 + 8 + + + ADDHLD + ADDHLD + 4 + 4 + + + ADDSET + ADDSET + 0 + 4 + + + + + BCR2 + BCR2 + SRAM/NOR-Flash chip-select control register + 2 + 0x8 + 0x20 + read-write + 0x000030D0 + + + CBURSTRW + CBURSTRW + 19 + 1 + + + ASYNCWAIT + ASYNCWAIT + 15 + 1 + + + EXTMOD + EXTMOD + 14 + 1 + + + WAITEN + WAITEN + 13 + 1 + + + WREN + WREN + 12 + 1 + + + WAITCFG + WAITCFG + 11 + 1 + + + WRAPMOD + WRAPMOD + 10 + 1 + + + WAITPOL + WAITPOL + 9 + 1 + + + BURSTEN + BURSTEN + 8 + 1 + + + FACCEN + FACCEN + 6 + 1 + + + MWID + MWID + 4 + 2 + + + MTYP + MTYP + 2 + 2 + + + MUXEN + MUXEN + 1 + 1 + + + MBKEN + MBKEN + 0 + 1 + + + + + BTR2 + BTR2 + SRAM/NOR-Flash chip-select timing register + 2 + 0xC + 0x20 + read-write + 0xFFFFFFFF + + + ACCMOD + ACCMOD + 28 + 2 + + + DATLAT + DATLAT + 24 + 4 + + + CLKDIV + CLKDIV + 20 + 4 + + + BUSTURN + BUSTURN + 16 + 4 + + + DATAST + DATAST + 8 + 8 + + + ADDHLD + ADDHLD + 4 + 4 + + + ADDSET + ADDSET + 0 + 4 + + + + + BCR3 + BCR3 + SRAM/NOR-Flash chip-select control register + 3 + 0x10 + 0x20 + read-write + 0x000030D0 + + + CBURSTRW + CBURSTRW + 19 + 1 + + + ASYNCWAIT + ASYNCWAIT + 15 + 1 + + + EXTMOD + EXTMOD + 14 + 1 + + + WAITEN + WAITEN + 13 + 1 + + + WREN + WREN + 12 + 1 + + + WAITCFG + WAITCFG + 11 + 1 + + + WRAPMOD + WRAPMOD + 10 + 1 + + + WAITPOL + WAITPOL + 9 + 1 + + + BURSTEN + BURSTEN + 8 + 1 + + + FACCEN + FACCEN + 6 + 1 + + + MWID + MWID + 4 + 2 + + + MTYP + MTYP + 2 + 2 + + + MUXEN + MUXEN + 1 + 1 + + + MBKEN + MBKEN + 0 + 1 + + + + + BTR3 + BTR3 + SRAM/NOR-Flash chip-select timing register + 3 + 0x14 + 0x20 + read-write + 0xFFFFFFFF + + + ACCMOD + ACCMOD + 28 + 2 + + + DATLAT + DATLAT + 24 + 4 + + + CLKDIV + CLKDIV + 20 + 4 + + + BUSTURN + BUSTURN + 16 + 4 + + + DATAST + DATAST + 8 + 8 + + + ADDHLD + ADDHLD + 4 + 4 + + + ADDSET + ADDSET + 0 + 4 + + + + + BCR4 + BCR4 + SRAM/NOR-Flash chip-select control register + 4 + 0x18 + 0x20 + read-write + 0x000030D0 + + + CBURSTRW + CBURSTRW + 19 + 1 + + + ASYNCWAIT + ASYNCWAIT + 15 + 1 + + + EXTMOD + EXTMOD + 14 + 1 + + + WAITEN + WAITEN + 13 + 1 + + + WREN + WREN + 12 + 1 + + + WAITCFG + WAITCFG + 11 + 1 + + + WRAPMOD + WRAPMOD + 10 + 1 + + + WAITPOL + WAITPOL + 9 + 1 + + + BURSTEN + BURSTEN + 8 + 1 + + + FACCEN + FACCEN + 6 + 1 + + + MWID + MWID + 4 + 2 + + + MTYP + MTYP + 2 + 2 + + + MUXEN + MUXEN + 1 + 1 + + + MBKEN + MBKEN + 0 + 1 + + + + + BTR4 + BTR4 + SRAM/NOR-Flash chip-select timing register + 4 + 0x1C + 0x20 + read-write + 0xFFFFFFFF + + + ACCMOD + ACCMOD + 28 + 2 + + + DATLAT + DATLAT + 24 + 4 + + + CLKDIV + CLKDIV + 20 + 4 + + + BUSTURN + BUSTURN + 16 + 4 + + + DATAST + DATAST + 8 + 8 + + + ADDHLD + ADDHLD + 4 + 4 + + + ADDSET + ADDSET + 0 + 4 + + + + + PCR2 + PCR2 + PC Card/NAND Flash control register + 2 + 0x60 + 0x20 + read-write + 0x00000018 + + + ECCPS + ECCPS + 17 + 3 + + + TAR + TAR + 13 + 4 + + + TCLR + TCLR + 9 + 4 + + + ECCEN + ECCEN + 6 + 1 + + + PWID + PWID + 4 + 2 + + + PTYP + PTYP + 3 + 1 + + + PBKEN + PBKEN + 2 + 1 + + + PWAITEN + PWAITEN + 1 + 1 + + + + + SR2 + SR2 + FIFO status and interrupt register + 2 + 0x64 + 0x20 + 0x00000040 + + + FEMPT + FEMPT + 6 + 1 + read-only + + + IFEN + IFEN + 5 + 1 + read-write + + + ILEN + ILEN + 4 + 1 + read-write + + + IREN + IREN + 3 + 1 + read-write + + + IFS + IFS + 2 + 1 + read-write + + + ILS + ILS + 1 + 1 + read-write + + + IRS + IRS + 0 + 1 + read-write + + + + + PMEM2 + PMEM2 + Common memory space timing register + 2 + 0x68 + 0x20 + read-write + 0xFCFCFCFC + + + MEMHIZx + MEMHIZx + 24 + 8 + + + MEMHOLDx + MEMHOLDx + 16 + 8 + + + MEMWAITx + MEMWAITx + 8 + 8 + + + MEMSETx + MEMSETx + 0 + 8 + + + + + PATT2 + PATT2 + Attribute memory space timing register + 2 + 0x6C + 0x20 + read-write + 0xFCFCFCFC + + + ATTHIZx + ATTHIZx + 24 + 8 + + + ATTHOLDx + ATTHOLDx + 16 + 8 + + + ATTWAITx + ATTWAITx + 8 + 8 + + + ATTSETx + ATTSETx + 0 + 8 + + + + + ECCR2 + ECCR2 + ECC result register 2 + 0x74 + 0x20 + read-only + 0x00000000 + + + ECCx + ECCx + 0 + 32 + + + + + PCR3 + PCR3 + PC Card/NAND Flash control register + 3 + 0x80 + 0x20 + read-write + 0x00000018 + + + ECCPS + ECCPS + 17 + 3 + + + TAR + TAR + 13 + 4 + + + TCLR + TCLR + 9 + 4 + + + ECCEN + ECCEN + 6 + 1 + + + PWID + PWID + 4 + 2 + + + PTYP + PTYP + 3 + 1 + + + PBKEN + PBKEN + 2 + 1 + + + PWAITEN + PWAITEN + 1 + 1 + + + + + SR3 + SR3 + FIFO status and interrupt register + 3 + 0x84 + 0x20 + 0x00000040 + + + FEMPT + FEMPT + 6 + 1 + read-only + + + IFEN + IFEN + 5 + 1 + read-write + + + ILEN + ILEN + 4 + 1 + read-write + + + IREN + IREN + 3 + 1 + read-write + + + IFS + IFS + 2 + 1 + read-write + + + ILS + ILS + 1 + 1 + read-write + + + IRS + IRS + 0 + 1 + read-write + + + + + PMEM3 + PMEM3 + Common memory space timing register + 3 + 0x88 + 0x20 + read-write + 0xFCFCFCFC + + + MEMHIZx + MEMHIZx + 24 + 8 + + + MEMHOLDx + MEMHOLDx + 16 + 8 + + + MEMWAITx + MEMWAITx + 8 + 8 + + + MEMSETx + MEMSETx + 0 + 8 + + + + + PATT3 + PATT3 + Attribute memory space timing register + 3 + 0x8C + 0x20 + read-write + 0xFCFCFCFC + + + ATTHIZx + ATTHIZx + 24 + 8 + + + ATTHOLDx + ATTHOLDx + 16 + 8 + + + ATTWAITx + ATTWAITx + 8 + 8 + + + ATTSETx + ATTSETx + 0 + 8 + + + + + ECCR3 + ECCR3 + ECC result register 3 + 0x94 + 0x20 + read-only + 0x00000000 + + + ECCx + ECCx + 0 + 32 + + + + + PCR4 + PCR4 + PC Card/NAND Flash control register + 4 + 0xA0 + 0x20 + read-write + 0x00000018 + + + ECCPS + ECCPS + 17 + 3 + + + TAR + TAR + 13 + 4 + + + TCLR + TCLR + 9 + 4 + + + ECCEN + ECCEN + 6 + 1 + + + PWID + PWID + 4 + 2 + + + PTYP + PTYP + 3 + 1 + + + PBKEN + PBKEN + 2 + 1 + + + PWAITEN + PWAITEN + 1 + 1 + + + + + SR4 + SR4 + FIFO status and interrupt register + 4 + 0xA4 + 0x20 + 0x00000040 + + + FEMPT + FEMPT + 6 + 1 + read-only + + + IFEN + IFEN + 5 + 1 + read-write + + + ILEN + ILEN + 4 + 1 + read-write + + + IREN + IREN + 3 + 1 + read-write + + + IFS + IFS + 2 + 1 + read-write + + + ILS + ILS + 1 + 1 + read-write + + + IRS + IRS + 0 + 1 + read-write + + + + + PMEM4 + PMEM4 + Common memory space timing register + 4 + 0xA8 + 0x20 + read-write + 0xFCFCFCFC + + + MEMHIZx + MEMHIZx + 24 + 8 + + + MEMHOLDx + MEMHOLDx + 16 + 8 + + + MEMWAITx + MEMWAITx + 8 + 8 + + + MEMSETx + MEMSETx + 0 + 8 + + + + + PATT4 + PATT4 + Attribute memory space timing register + 4 + 0xAC + 0x20 + read-write + 0xFCFCFCFC + + + ATTHIZx + ATTHIZx + 24 + 8 + + + ATTHOLDx + ATTHOLDx + 16 + 8 + + + ATTWAITx + ATTWAITx + 8 + 8 + + + ATTSETx + ATTSETx + 0 + 8 + + + + + PIO4 + PIO4 + I/O space timing register 4 + 0xB0 + 0x20 + read-write + 0xFCFCFCFC + + + IOHIZx + IOHIZx + 24 + 8 + + + IOHOLDx + IOHOLDx + 16 + 8 + + + IOWAITx + IOWAITx + 8 + 8 + + + IOSETx + IOSETx + 0 + 8 + + + + + BWTR1 + BWTR1 + SRAM/NOR-Flash write timing registers + 1 + 0x104 + 0x20 + read-write + 0x0FFFFFFF + + + ACCMOD + ACCMOD + 28 + 2 + + + DATLAT + DATLAT + 24 + 4 + + + CLKDIV + CLKDIV + 20 + 4 + + + DATAST + DATAST + 8 + 8 + + + ADDHLD + ADDHLD + 4 + 4 + + + ADDSET + ADDSET + 0 + 4 + + + + + BWTR2 + BWTR2 + SRAM/NOR-Flash write timing registers + 2 + 0x10C + 0x20 + read-write + 0x0FFFFFFF + + + ACCMOD + ACCMOD + 28 + 2 + + + DATLAT + DATLAT + 24 + 4 + + + CLKDIV + CLKDIV + 20 + 4 + + + DATAST + DATAST + 8 + 8 + + + ADDHLD + ADDHLD + 4 + 4 + + + ADDSET + ADDSET + 0 + 4 + + + + + BWTR3 + BWTR3 + SRAM/NOR-Flash write timing registers + 3 + 0x114 + 0x20 + read-write + 0x0FFFFFFF + + + ACCMOD + ACCMOD + 28 + 2 + + + DATLAT + DATLAT + 24 + 4 + + + CLKDIV + CLKDIV + 20 + 4 + + + DATAST + DATAST + 8 + 8 + + + ADDHLD + ADDHLD + 4 + 4 + + + ADDSET + ADDSET + 0 + 4 + + + + + BWTR4 + BWTR4 + SRAM/NOR-Flash write timing registers + 4 + 0x11C + 0x20 + read-write + 0x0FFFFFFF + + + ACCMOD + ACCMOD + 28 + 2 + + + DATLAT + DATLAT + 24 + 4 + + + CLKDIV + CLKDIV + 20 + 4 + + + DATAST + DATAST + 8 + 8 + + + ADDHLD + ADDHLD + 4 + 4 + + + ADDSET + ADDSET + 0 + 4 + + + + + + + DBG + Debug support + DBG + 0xE0042000 + + 0x0 + 0x400 + registers + + + + DBGMCU_IDCODE + DBGMCU_IDCODE + IDCODE + 0x0 + 0x20 + read-only + 0x10006411 + + + DEV_ID + DEV_ID + 0 + 12 + + + REV_ID + REV_ID + 16 + 16 + + + + + DBGMCU_CR + DBGMCU_CR + Control Register + 0x4 + 0x20 + read-write + 0x00000000 + + + DBG_SLEEP + DBG_SLEEP + 0 + 1 + + + DBG_STOP + DBG_STOP + 1 + 1 + + + DBG_STANDBY + DBG_STANDBY + 2 + 1 + + + TRACE_IOEN + TRACE_IOEN + 5 + 1 + + + TRACE_MODE + TRACE_MODE + 6 + 2 + + + DBG_I2C2_SMBUS_TIMEOUT + DBG_I2C2_SMBUS_TIMEOUT + 16 + 1 + + + DBG_TIM8_STOP + DBG_TIM8_STOP + 17 + 1 + + + DBG_TIM5_STOP + DBG_TIM5_STOP + 18 + 1 + + + DBG_TIM6_STOP + DBG_TIM6_STOP + 19 + 1 + + + DBG_TIM7_STOP + DBG_TIM7_STOP + 20 + 1 + + + + + DBGMCU_APB1_FZ + DBGMCU_APB1_FZ + Debug MCU APB1 Freeze registe + 0x8 + 0x20 + read-write + 0x00000000 + + + DBG_TIM2_STOP + DBG_TIM2_STOP + 0 + 1 + + + DBG_TIM3_STOP + DBG_TIM3 _STOP + 1 + 1 + + + DBG_TIM4_STOP + DBG_TIM4_STOP + 2 + 1 + + + DBG_TIM5_STOP + DBG_TIM5_STOP + 3 + 1 + + + DBG_TIM6_STOP + DBG_TIM6_STOP + 4 + 1 + + + DBG_TIM7_STOP + DBG_TIM7_STOP + 5 + 1 + + + DBG_TIM12_STOP + DBG_TIM12_STOP + 6 + 1 + + + DBG_TIM13_STOP + DBG_TIM13_STOP + 7 + 1 + + + DBG_TIM14_STOP + DBG_TIM14_STOP + 8 + 1 + + + DBG_WWDG_STOP + DBG_WWDG_STOP + 11 + 1 + + + DBG_IWDEG_STOP + DBG_IWDEG_STOP + 12 + 1 + + + DBG_J2C1_SMBUS_TIMEOUT + DBG_J2C1_SMBUS_TIMEOUT + 21 + 1 + + + DBG_J2C2_SMBUS_TIMEOUT + DBG_J2C2_SMBUS_TIMEOUT + 22 + 1 + + + DBG_J2C3SMBUS_TIMEOUT + DBG_J2C3SMBUS_TIMEOUT + 23 + 1 + + + DBG_CAN1_STOP + DBG_CAN1_STOP + 25 + 1 + + + DBG_CAN2_STOP + DBG_CAN2_STOP + 26 + 1 + + + + + DBGMCU_APB2_FZ + DBGMCU_APB2_FZ + Debug MCU APB2 Freeze registe + 0xC + 0x20 + read-write + 0x00000000 + + + DBG_TIM1_STOP + TIM1 counter stopped when core is + halted + 0 + 1 + + + DBG_TIM8_STOP + TIM8 counter stopped when core is + halted + 1 + 1 + + + DBG_TIM9_STOP + TIM9 counter stopped when core is + halted + 16 + 1 + + + DBG_TIM10_STOP + TIM10 counter stopped when core is + halted + 17 + 1 + + + DBG_TIM11_STOP + TIM11 counter stopped when core is + halted + 18 + 1 + + + + + + + DMA2 + DMA controller + DMA + 0x40026400 + + 0x0 + 0x400 + registers + + + DMA2_Stream0 + DMA2 Stream0 global interrupt + 56 + + + DMA2_Stream1 + DMA2 Stream1 global interrupt + 57 + + + DMA2_Stream2 + DMA2 Stream2 global interrupt + 58 + + + DMA2_Stream3 + DMA2 Stream3 global interrupt + 59 + + + DMA2_Stream4 + DMA2 Stream4 global interrupt + 60 + + + DMA2_Stream5 + DMA2 Stream5 global interrupt + 68 + + + DMA2_Stream6 + DMA2 Stream6 global interrupt + 69 + + + DMA2_Stream7 + DMA2 Stream7 global interrupt + 70 + + + + LISR + LISR + low interrupt status register + 0x0 + 0x20 + read-only + 0x00000000 + + + TCIF3 + Stream x transfer complete interrupt + flag (x = 3..0) + 27 + 1 + + + HTIF3 + Stream x half transfer interrupt flag + (x=3..0) + 26 + 1 + + + TEIF3 + Stream x transfer error interrupt flag + (x=3..0) + 25 + 1 + + + DMEIF3 + Stream x direct mode error interrupt + flag (x=3..0) + 24 + 1 + + + FEIF3 + Stream x FIFO error interrupt flag + (x=3..0) + 22 + 1 + + + TCIF2 + Stream x transfer complete interrupt + flag (x = 3..0) + 21 + 1 + + + HTIF2 + Stream x half transfer interrupt flag + (x=3..0) + 20 + 1 + + + TEIF2 + Stream x transfer error interrupt flag + (x=3..0) + 19 + 1 + + + DMEIF2 + Stream x direct mode error interrupt + flag (x=3..0) + 18 + 1 + + + FEIF2 + Stream x FIFO error interrupt flag + (x=3..0) + 16 + 1 + + + TCIF1 + Stream x transfer complete interrupt + flag (x = 3..0) + 11 + 1 + + + HTIF1 + Stream x half transfer interrupt flag + (x=3..0) + 10 + 1 + + + TEIF1 + Stream x transfer error interrupt flag + (x=3..0) + 9 + 1 + + + DMEIF1 + Stream x direct mode error interrupt + flag (x=3..0) + 8 + 1 + + + FEIF1 + Stream x FIFO error interrupt flag + (x=3..0) + 6 + 1 + + + TCIF0 + Stream x transfer complete interrupt + flag (x = 3..0) + 5 + 1 + + + HTIF0 + Stream x half transfer interrupt flag + (x=3..0) + 4 + 1 + + + TEIF0 + Stream x transfer error interrupt flag + (x=3..0) + 3 + 1 + + + DMEIF0 + Stream x direct mode error interrupt + flag (x=3..0) + 2 + 1 + + + FEIF0 + Stream x FIFO error interrupt flag + (x=3..0) + 0 + 1 + + + + + HISR + HISR + high interrupt status register + 0x4 + 0x20 + read-only + 0x00000000 + + + TCIF7 + Stream x transfer complete interrupt + flag (x=7..4) + 27 + 1 + + + HTIF7 + Stream x half transfer interrupt flag + (x=7..4) + 26 + 1 + + + TEIF7 + Stream x transfer error interrupt flag + (x=7..4) + 25 + 1 + + + DMEIF7 + Stream x direct mode error interrupt + flag (x=7..4) + 24 + 1 + + + FEIF7 + Stream x FIFO error interrupt flag + (x=7..4) + 22 + 1 + + + TCIF6 + Stream x transfer complete interrupt + flag (x=7..4) + 21 + 1 + + + HTIF6 + Stream x half transfer interrupt flag + (x=7..4) + 20 + 1 + + + TEIF6 + Stream x transfer error interrupt flag + (x=7..4) + 19 + 1 + + + DMEIF6 + Stream x direct mode error interrupt + flag (x=7..4) + 18 + 1 + + + FEIF6 + Stream x FIFO error interrupt flag + (x=7..4) + 16 + 1 + + + TCIF5 + Stream x transfer complete interrupt + flag (x=7..4) + 11 + 1 + + + HTIF5 + Stream x half transfer interrupt flag + (x=7..4) + 10 + 1 + + + TEIF5 + Stream x transfer error interrupt flag + (x=7..4) + 9 + 1 + + + DMEIF5 + Stream x direct mode error interrupt + flag (x=7..4) + 8 + 1 + + + FEIF5 + Stream x FIFO error interrupt flag + (x=7..4) + 6 + 1 + + + TCIF4 + Stream x transfer complete interrupt + flag (x=7..4) + 5 + 1 + + + HTIF4 + Stream x half transfer interrupt flag + (x=7..4) + 4 + 1 + + + TEIF4 + Stream x transfer error interrupt flag + (x=7..4) + 3 + 1 + + + DMEIF4 + Stream x direct mode error interrupt + flag (x=7..4) + 2 + 1 + + + FEIF4 + Stream x FIFO error interrupt flag + (x=7..4) + 0 + 1 + + + + + LIFCR + LIFCR + low interrupt flag clear + register + 0x8 + 0x20 + read-write + 0x00000000 + + + CTCIF3 + Stream x clear transfer complete + interrupt flag (x = 3..0) + 27 + 1 + + + CHTIF3 + Stream x clear half transfer interrupt + flag (x = 3..0) + 26 + 1 + + + CTEIF3 + Stream x clear transfer error interrupt + flag (x = 3..0) + 25 + 1 + + + CDMEIF3 + Stream x clear direct mode error + interrupt flag (x = 3..0) + 24 + 1 + + + CFEIF3 + Stream x clear FIFO error interrupt flag + (x = 3..0) + 22 + 1 + + + CTCIF2 + Stream x clear transfer complete + interrupt flag (x = 3..0) + 21 + 1 + + + CHTIF2 + Stream x clear half transfer interrupt + flag (x = 3..0) + 20 + 1 + + + CTEIF2 + Stream x clear transfer error interrupt + flag (x = 3..0) + 19 + 1 + + + CDMEIF2 + Stream x clear direct mode error + interrupt flag (x = 3..0) + 18 + 1 + + + CFEIF2 + Stream x clear FIFO error interrupt flag + (x = 3..0) + 16 + 1 + + + CTCIF1 + Stream x clear transfer complete + interrupt flag (x = 3..0) + 11 + 1 + + + CHTIF1 + Stream x clear half transfer interrupt + flag (x = 3..0) + 10 + 1 + + + CTEIF1 + Stream x clear transfer error interrupt + flag (x = 3..0) + 9 + 1 + + + CDMEIF1 + Stream x clear direct mode error + interrupt flag (x = 3..0) + 8 + 1 + + + CFEIF1 + Stream x clear FIFO error interrupt flag + (x = 3..0) + 6 + 1 + + + CTCIF0 + Stream x clear transfer complete + interrupt flag (x = 3..0) + 5 + 1 + + + CHTIF0 + Stream x clear half transfer interrupt + flag (x = 3..0) + 4 + 1 + + + CTEIF0 + Stream x clear transfer error interrupt + flag (x = 3..0) + 3 + 1 + + + CDMEIF0 + Stream x clear direct mode error + interrupt flag (x = 3..0) + 2 + 1 + + + CFEIF0 + Stream x clear FIFO error interrupt flag + (x = 3..0) + 0 + 1 + + + + + HIFCR + HIFCR + high interrupt flag clear + register + 0xC + 0x20 + read-write + 0x00000000 + + + CTCIF7 + Stream x clear transfer complete + interrupt flag (x = 7..4) + 27 + 1 + + + CHTIF7 + Stream x clear half transfer interrupt + flag (x = 7..4) + 26 + 1 + + + CTEIF7 + Stream x clear transfer error interrupt + flag (x = 7..4) + 25 + 1 + + + CDMEIF7 + Stream x clear direct mode error + interrupt flag (x = 7..4) + 24 + 1 + + + CFEIF7 + Stream x clear FIFO error interrupt flag + (x = 7..4) + 22 + 1 + + + CTCIF6 + Stream x clear transfer complete + interrupt flag (x = 7..4) + 21 + 1 + + + CHTIF6 + Stream x clear half transfer interrupt + flag (x = 7..4) + 20 + 1 + + + CTEIF6 + Stream x clear transfer error interrupt + flag (x = 7..4) + 19 + 1 + + + CDMEIF6 + Stream x clear direct mode error + interrupt flag (x = 7..4) + 18 + 1 + + + CFEIF6 + Stream x clear FIFO error interrupt flag + (x = 7..4) + 16 + 1 + + + CTCIF5 + Stream x clear transfer complete + interrupt flag (x = 7..4) + 11 + 1 + + + CHTIF5 + Stream x clear half transfer interrupt + flag (x = 7..4) + 10 + 1 + + + CTEIF5 + Stream x clear transfer error interrupt + flag (x = 7..4) + 9 + 1 + + + CDMEIF5 + Stream x clear direct mode error + interrupt flag (x = 7..4) + 8 + 1 + + + CFEIF5 + Stream x clear FIFO error interrupt flag + (x = 7..4) + 6 + 1 + + + CTCIF4 + Stream x clear transfer complete + interrupt flag (x = 7..4) + 5 + 1 + + + CHTIF4 + Stream x clear half transfer interrupt + flag (x = 7..4) + 4 + 1 + + + CTEIF4 + Stream x clear transfer error interrupt + flag (x = 7..4) + 3 + 1 + + + CDMEIF4 + Stream x clear direct mode error + interrupt flag (x = 7..4) + 2 + 1 + + + CFEIF4 + Stream x clear FIFO error interrupt flag + (x = 7..4) + 0 + 1 + + + + + S0CR + S0CR + stream x configuration + register + 0x10 + 0x20 + read-write + 0x00000000 + + + CHSEL + Channel selection + 25 + 3 + + + MBURST + Memory burst transfer + configuration + 23 + 2 + + + PBURST + Peripheral burst transfer + configuration + 21 + 2 + + + CT + Current target (only in double buffer + mode) + 19 + 1 + + + DBM + Double buffer mode + 18 + 1 + + + PL + Priority level + 16 + 2 + + + PINCOS + Peripheral increment offset + size + 15 + 1 + + + MSIZE + Memory data size + 13 + 2 + + + PSIZE + Peripheral data size + 11 + 2 + + + MINC + Memory increment mode + 10 + 1 + + + PINC + Peripheral increment mode + 9 + 1 + + + CIRC + Circular mode + 8 + 1 + + + DIR + Data transfer direction + 6 + 2 + + + PFCTRL + Peripheral flow controller + 5 + 1 + + + TCIE + Transfer complete interrupt + enable + 4 + 1 + + + HTIE + Half transfer interrupt + enable + 3 + 1 + + + TEIE + Transfer error interrupt + enable + 2 + 1 + + + DMEIE + Direct mode error interrupt + enable + 1 + 1 + + + EN + Stream enable / flag stream ready when + read low + 0 + 1 + + + + + S0NDTR + S0NDTR + stream x number of data + register + 0x14 + 0x20 + read-write + 0x00000000 + + + NDT + Number of data items to + transfer + 0 + 16 + + + + + S0PAR + S0PAR + stream x peripheral address + register + 0x18 + 0x20 + read-write + 0x00000000 + + + PA + Peripheral address + 0 + 32 + + + + + S0M0AR + S0M0AR + stream x memory 0 address + register + 0x1C + 0x20 + read-write + 0x00000000 + + + M0A + Memory 0 address + 0 + 32 + + + + + S0M1AR + S0M1AR + stream x memory 1 address + register + 0x20 + 0x20 + read-write + 0x00000000 + + + M1A + Memory 1 address (used in case of Double + buffer mode) + 0 + 32 + + + + + S0FCR + S0FCR + stream x FIFO control register + 0x24 + 0x20 + 0x00000021 + + + FEIE + FIFO error interrupt + enable + 7 + 1 + read-write + + + FS + FIFO status + 3 + 3 + read-only + + + DMDIS + Direct mode disable + 2 + 1 + read-write + + + FTH + FIFO threshold selection + 0 + 2 + read-write + + + + + S1CR + S1CR + stream x configuration + register + 0x28 + 0x20 + read-write + 0x00000000 + + + CHSEL + Channel selection + 25 + 3 + + + MBURST + Memory burst transfer + configuration + 23 + 2 + + + PBURST + Peripheral burst transfer + configuration + 21 + 2 + + + ACK + ACK + 20 + 1 + + + CT + Current target (only in double buffer + mode) + 19 + 1 + + + DBM + Double buffer mode + 18 + 1 + + + PL + Priority level + 16 + 2 + + + PINCOS + Peripheral increment offset + size + 15 + 1 + + + MSIZE + Memory data size + 13 + 2 + + + PSIZE + Peripheral data size + 11 + 2 + + + MINC + Memory increment mode + 10 + 1 + + + PINC + Peripheral increment mode + 9 + 1 + + + CIRC + Circular mode + 8 + 1 + + + DIR + Data transfer direction + 6 + 2 + + + PFCTRL + Peripheral flow controller + 5 + 1 + + + TCIE + Transfer complete interrupt + enable + 4 + 1 + + + HTIE + Half transfer interrupt + enable + 3 + 1 + + + TEIE + Transfer error interrupt + enable + 2 + 1 + + + DMEIE + Direct mode error interrupt + enable + 1 + 1 + + + EN + Stream enable / flag stream ready when + read low + 0 + 1 + + + + + S1NDTR + S1NDTR + stream x number of data + register + 0x2C + 0x20 + read-write + 0x00000000 + + + NDT + Number of data items to + transfer + 0 + 16 + + + + + S1PAR + S1PAR + stream x peripheral address + register + 0x30 + 0x20 + read-write + 0x00000000 + + + PA + Peripheral address + 0 + 32 + + + + + S1M0AR + S1M0AR + stream x memory 0 address + register + 0x34 + 0x20 + read-write + 0x00000000 + + + M0A + Memory 0 address + 0 + 32 + + + + + S1M1AR + S1M1AR + stream x memory 1 address + register + 0x38 + 0x20 + read-write + 0x00000000 + + + M1A + Memory 1 address (used in case of Double + buffer mode) + 0 + 32 + + + + + S1FCR + S1FCR + stream x FIFO control register + 0x3C + 0x20 + 0x00000021 + + + FEIE + FIFO error interrupt + enable + 7 + 1 + read-write + + + FS + FIFO status + 3 + 3 + read-only + + + DMDIS + Direct mode disable + 2 + 1 + read-write + + + FTH + FIFO threshold selection + 0 + 2 + read-write + + + + + S2CR + S2CR + stream x configuration + register + 0x40 + 0x20 + read-write + 0x00000000 + + + CHSEL + Channel selection + 25 + 3 + + + MBURST + Memory burst transfer + configuration + 23 + 2 + + + PBURST + Peripheral burst transfer + configuration + 21 + 2 + + + ACK + ACK + 20 + 1 + + + CT + Current target (only in double buffer + mode) + 19 + 1 + + + DBM + Double buffer mode + 18 + 1 + + + PL + Priority level + 16 + 2 + + + PINCOS + Peripheral increment offset + size + 15 + 1 + + + MSIZE + Memory data size + 13 + 2 + + + PSIZE + Peripheral data size + 11 + 2 + + + MINC + Memory increment mode + 10 + 1 + + + PINC + Peripheral increment mode + 9 + 1 + + + CIRC + Circular mode + 8 + 1 + + + DIR + Data transfer direction + 6 + 2 + + + PFCTRL + Peripheral flow controller + 5 + 1 + + + TCIE + Transfer complete interrupt + enable + 4 + 1 + + + HTIE + Half transfer interrupt + enable + 3 + 1 + + + TEIE + Transfer error interrupt + enable + 2 + 1 + + + DMEIE + Direct mode error interrupt + enable + 1 + 1 + + + EN + Stream enable / flag stream ready when + read low + 0 + 1 + + + + + S2NDTR + S2NDTR + stream x number of data + register + 0x44 + 0x20 + read-write + 0x00000000 + + + NDT + Number of data items to + transfer + 0 + 16 + + + + + S2PAR + S2PAR + stream x peripheral address + register + 0x48 + 0x20 + read-write + 0x00000000 + + + PA + Peripheral address + 0 + 32 + + + + + S2M0AR + S2M0AR + stream x memory 0 address + register + 0x4C + 0x20 + read-write + 0x00000000 + + + M0A + Memory 0 address + 0 + 32 + + + + + S2M1AR + S2M1AR + stream x memory 1 address + register + 0x50 + 0x20 + read-write + 0x00000000 + + + M1A + Memory 1 address (used in case of Double + buffer mode) + 0 + 32 + + + + + S2FCR + S2FCR + stream x FIFO control register + 0x54 + 0x20 + 0x00000021 + + + FEIE + FIFO error interrupt + enable + 7 + 1 + read-write + + + FS + FIFO status + 3 + 3 + read-only + + + DMDIS + Direct mode disable + 2 + 1 + read-write + + + FTH + FIFO threshold selection + 0 + 2 + read-write + + + + + S3CR + S3CR + stream x configuration + register + 0x58 + 0x20 + read-write + 0x00000000 + + + CHSEL + Channel selection + 25 + 3 + + + MBURST + Memory burst transfer + configuration + 23 + 2 + + + PBURST + Peripheral burst transfer + configuration + 21 + 2 + + + ACK + ACK + 20 + 1 + + + CT + Current target (only in double buffer + mode) + 19 + 1 + + + DBM + Double buffer mode + 18 + 1 + + + PL + Priority level + 16 + 2 + + + PINCOS + Peripheral increment offset + size + 15 + 1 + + + MSIZE + Memory data size + 13 + 2 + + + PSIZE + Peripheral data size + 11 + 2 + + + MINC + Memory increment mode + 10 + 1 + + + PINC + Peripheral increment mode + 9 + 1 + + + CIRC + Circular mode + 8 + 1 + + + DIR + Data transfer direction + 6 + 2 + + + PFCTRL + Peripheral flow controller + 5 + 1 + + + TCIE + Transfer complete interrupt + enable + 4 + 1 + + + HTIE + Half transfer interrupt + enable + 3 + 1 + + + TEIE + Transfer error interrupt + enable + 2 + 1 + + + DMEIE + Direct mode error interrupt + enable + 1 + 1 + + + EN + Stream enable / flag stream ready when + read low + 0 + 1 + + + + + S3NDTR + S3NDTR + stream x number of data + register + 0x5C + 0x20 + read-write + 0x00000000 + + + NDT + Number of data items to + transfer + 0 + 16 + + + + + S3PAR + S3PAR + stream x peripheral address + register + 0x60 + 0x20 + read-write + 0x00000000 + + + PA + Peripheral address + 0 + 32 + + + + + S3M0AR + S3M0AR + stream x memory 0 address + register + 0x64 + 0x20 + read-write + 0x00000000 + + + M0A + Memory 0 address + 0 + 32 + + + + + S3M1AR + S3M1AR + stream x memory 1 address + register + 0x68 + 0x20 + read-write + 0x00000000 + + + M1A + Memory 1 address (used in case of Double + buffer mode) + 0 + 32 + + + + + S3FCR + S3FCR + stream x FIFO control register + 0x6C + 0x20 + 0x00000021 + + + FEIE + FIFO error interrupt + enable + 7 + 1 + read-write + + + FS + FIFO status + 3 + 3 + read-only + + + DMDIS + Direct mode disable + 2 + 1 + read-write + + + FTH + FIFO threshold selection + 0 + 2 + read-write + + + + + S4CR + S4CR + stream x configuration + register + 0x70 + 0x20 + read-write + 0x00000000 + + + CHSEL + Channel selection + 25 + 3 + + + MBURST + Memory burst transfer + configuration + 23 + 2 + + + PBURST + Peripheral burst transfer + configuration + 21 + 2 + + + ACK + ACK + 20 + 1 + + + CT + Current target (only in double buffer + mode) + 19 + 1 + + + DBM + Double buffer mode + 18 + 1 + + + PL + Priority level + 16 + 2 + + + PINCOS + Peripheral increment offset + size + 15 + 1 + + + MSIZE + Memory data size + 13 + 2 + + + PSIZE + Peripheral data size + 11 + 2 + + + MINC + Memory increment mode + 10 + 1 + + + PINC + Peripheral increment mode + 9 + 1 + + + CIRC + Circular mode + 8 + 1 + + + DIR + Data transfer direction + 6 + 2 + + + PFCTRL + Peripheral flow controller + 5 + 1 + + + TCIE + Transfer complete interrupt + enable + 4 + 1 + + + HTIE + Half transfer interrupt + enable + 3 + 1 + + + TEIE + Transfer error interrupt + enable + 2 + 1 + + + DMEIE + Direct mode error interrupt + enable + 1 + 1 + + + EN + Stream enable / flag stream ready when + read low + 0 + 1 + + + + + S4NDTR + S4NDTR + stream x number of data + register + 0x74 + 0x20 + read-write + 0x00000000 + + + NDT + Number of data items to + transfer + 0 + 16 + + + + + S4PAR + S4PAR + stream x peripheral address + register + 0x78 + 0x20 + read-write + 0x00000000 + + + PA + Peripheral address + 0 + 32 + + + + + S4M0AR + S4M0AR + stream x memory 0 address + register + 0x7C + 0x20 + read-write + 0x00000000 + + + M0A + Memory 0 address + 0 + 32 + + + + + S4M1AR + S4M1AR + stream x memory 1 address + register + 0x80 + 0x20 + read-write + 0x00000000 + + + M1A + Memory 1 address (used in case of Double + buffer mode) + 0 + 32 + + + + + S4FCR + S4FCR + stream x FIFO control register + 0x84 + 0x20 + 0x00000021 + + + FEIE + FIFO error interrupt + enable + 7 + 1 + read-write + + + FS + FIFO status + 3 + 3 + read-only + + + DMDIS + Direct mode disable + 2 + 1 + read-write + + + FTH + FIFO threshold selection + 0 + 2 + read-write + + + + + S5CR + S5CR + stream x configuration + register + 0x88 + 0x20 + read-write + 0x00000000 + + + CHSEL + Channel selection + 25 + 3 + + + MBURST + Memory burst transfer + configuration + 23 + 2 + + + PBURST + Peripheral burst transfer + configuration + 21 + 2 + + + ACK + ACK + 20 + 1 + + + CT + Current target (only in double buffer + mode) + 19 + 1 + + + DBM + Double buffer mode + 18 + 1 + + + PL + Priority level + 16 + 2 + + + PINCOS + Peripheral increment offset + size + 15 + 1 + + + MSIZE + Memory data size + 13 + 2 + + + PSIZE + Peripheral data size + 11 + 2 + + + MINC + Memory increment mode + 10 + 1 + + + PINC + Peripheral increment mode + 9 + 1 + + + CIRC + Circular mode + 8 + 1 + + + DIR + Data transfer direction + 6 + 2 + + + PFCTRL + Peripheral flow controller + 5 + 1 + + + TCIE + Transfer complete interrupt + enable + 4 + 1 + + + HTIE + Half transfer interrupt + enable + 3 + 1 + + + TEIE + Transfer error interrupt + enable + 2 + 1 + + + DMEIE + Direct mode error interrupt + enable + 1 + 1 + + + EN + Stream enable / flag stream ready when + read low + 0 + 1 + + + + + S5NDTR + S5NDTR + stream x number of data + register + 0x8C + 0x20 + read-write + 0x00000000 + + + NDT + Number of data items to + transfer + 0 + 16 + + + + + S5PAR + S5PAR + stream x peripheral address + register + 0x90 + 0x20 + read-write + 0x00000000 + + + PA + Peripheral address + 0 + 32 + + + + + S5M0AR + S5M0AR + stream x memory 0 address + register + 0x94 + 0x20 + read-write + 0x00000000 + + + M0A + Memory 0 address + 0 + 32 + + + + + S5M1AR + S5M1AR + stream x memory 1 address + register + 0x98 + 0x20 + read-write + 0x00000000 + + + M1A + Memory 1 address (used in case of Double + buffer mode) + 0 + 32 + + + + + S5FCR + S5FCR + stream x FIFO control register + 0x9C + 0x20 + 0x00000021 + + + FEIE + FIFO error interrupt + enable + 7 + 1 + read-write + + + FS + FIFO status + 3 + 3 + read-only + + + DMDIS + Direct mode disable + 2 + 1 + read-write + + + FTH + FIFO threshold selection + 0 + 2 + read-write + + + + + S6CR + S6CR + stream x configuration + register + 0xA0 + 0x20 + read-write + 0x00000000 + + + CHSEL + Channel selection + 25 + 3 + + + MBURST + Memory burst transfer + configuration + 23 + 2 + + + PBURST + Peripheral burst transfer + configuration + 21 + 2 + + + ACK + ACK + 20 + 1 + + + CT + Current target (only in double buffer + mode) + 19 + 1 + + + DBM + Double buffer mode + 18 + 1 + + + PL + Priority level + 16 + 2 + + + PINCOS + Peripheral increment offset + size + 15 + 1 + + + MSIZE + Memory data size + 13 + 2 + + + PSIZE + Peripheral data size + 11 + 2 + + + MINC + Memory increment mode + 10 + 1 + + + PINC + Peripheral increment mode + 9 + 1 + + + CIRC + Circular mode + 8 + 1 + + + DIR + Data transfer direction + 6 + 2 + + + PFCTRL + Peripheral flow controller + 5 + 1 + + + TCIE + Transfer complete interrupt + enable + 4 + 1 + + + HTIE + Half transfer interrupt + enable + 3 + 1 + + + TEIE + Transfer error interrupt + enable + 2 + 1 + + + DMEIE + Direct mode error interrupt + enable + 1 + 1 + + + EN + Stream enable / flag stream ready when + read low + 0 + 1 + + + + + S6NDTR + S6NDTR + stream x number of data + register + 0xA4 + 0x20 + read-write + 0x00000000 + + + NDT + Number of data items to + transfer + 0 + 16 + + + + + S6PAR + S6PAR + stream x peripheral address + register + 0xA8 + 0x20 + read-write + 0x00000000 + + + PA + Peripheral address + 0 + 32 + + + + + S6M0AR + S6M0AR + stream x memory 0 address + register + 0xAC + 0x20 + read-write + 0x00000000 + + + M0A + Memory 0 address + 0 + 32 + + + + + S6M1AR + S6M1AR + stream x memory 1 address + register + 0xB0 + 0x20 + read-write + 0x00000000 + + + M1A + Memory 1 address (used in case of Double + buffer mode) + 0 + 32 + + + + + S6FCR + S6FCR + stream x FIFO control register + 0xB4 + 0x20 + 0x00000021 + + + FEIE + FIFO error interrupt + enable + 7 + 1 + read-write + + + FS + FIFO status + 3 + 3 + read-only + + + DMDIS + Direct mode disable + 2 + 1 + read-write + + + FTH + FIFO threshold selection + 0 + 2 + read-write + + + + + S7CR + S7CR + stream x configuration + register + 0xB8 + 0x20 + read-write + 0x00000000 + + + CHSEL + Channel selection + 25 + 3 + + + MBURST + Memory burst transfer + configuration + 23 + 2 + + + PBURST + Peripheral burst transfer + configuration + 21 + 2 + + + ACK + ACK + 20 + 1 + + + CT + Current target (only in double buffer + mode) + 19 + 1 + + + DBM + Double buffer mode + 18 + 1 + + + PL + Priority level + 16 + 2 + + + PINCOS + Peripheral increment offset + size + 15 + 1 + + + MSIZE + Memory data size + 13 + 2 + + + PSIZE + Peripheral data size + 11 + 2 + + + MINC + Memory increment mode + 10 + 1 + + + PINC + Peripheral increment mode + 9 + 1 + + + CIRC + Circular mode + 8 + 1 + + + DIR + Data transfer direction + 6 + 2 + + + PFCTRL + Peripheral flow controller + 5 + 1 + + + TCIE + Transfer complete interrupt + enable + 4 + 1 + + + HTIE + Half transfer interrupt + enable + 3 + 1 + + + TEIE + Transfer error interrupt + enable + 2 + 1 + + + DMEIE + Direct mode error interrupt + enable + 1 + 1 + + + EN + Stream enable / flag stream ready when + read low + 0 + 1 + + + + + S7NDTR + S7NDTR + stream x number of data + register + 0xBC + 0x20 + read-write + 0x00000000 + + + NDT + Number of data items to + transfer + 0 + 16 + + + + + S7PAR + S7PAR + stream x peripheral address + register + 0xC0 + 0x20 + read-write + 0x00000000 + + + PA + Peripheral address + 0 + 32 + + + + + S7M0AR + S7M0AR + stream x memory 0 address + register + 0xC4 + 0x20 + read-write + 0x00000000 + + + M0A + Memory 0 address + 0 + 32 + + + + + S7M1AR + S7M1AR + stream x memory 1 address + register + 0xC8 + 0x20 + read-write + 0x00000000 + + + M1A + Memory 1 address (used in case of Double + buffer mode) + 0 + 32 + + + + + S7FCR + S7FCR + stream x FIFO control register + 0xCC + 0x20 + 0x00000021 + + + FEIE + FIFO error interrupt + enable + 7 + 1 + read-write + + + FS + FIFO status + 3 + 3 + read-only + + + DMDIS + Direct mode disable + 2 + 1 + read-write + + + FTH + FIFO threshold selection + 0 + 2 + read-write + + + + + + + DMA1 + 0x40026000 + + DMA1_Stream0 + DMA1 Stream0 global interrupt + 11 + + + DMA1_Stream1 + DMA1 Stream1 global interrupt + 12 + + + DMA1_Stream2 + DMA1 Stream2 global interrupt + 13 + + + DMA1_Stream3 + DMA1 Stream3 global interrupt + 14 + + + DMA1_Stream4 + DMA1 Stream4 global interrupt + 15 + + + DMA1_Stream5 + DMA1 Stream5 global interrupt + 16 + + + DMA1_Stream6 + DMA1 Stream6 global interrupt + 17 + + + DMA1_Stream7 + DMA1 Stream7 global interrupt + 47 + + + + RCC + Reset and clock control + RCC + 0x40023800 + + 0x0 + 0x400 + registers + + + RCC + RCC global interrupt + 5 + + + + CR + CR + clock control register + 0x0 + 0x20 + 0x00000083 + + + PLLI2SRDY + PLLI2S clock ready flag + 27 + 1 + read-only + + + PLLI2SON + PLLI2S enable + 26 + 1 + read-write + + + PLLRDY + Main PLL (PLL) clock ready + flag + 25 + 1 + read-only + + + PLLON + Main PLL (PLL) enable + 24 + 1 + read-write + + + CSSON + Clock security system + enable + 19 + 1 + read-write + + + HSEBYP + HSE clock bypass + 18 + 1 + read-write + + + HSERDY + HSE clock ready flag + 17 + 1 + read-only + + + HSEON + HSE clock enable + 16 + 1 + read-write + + + HSICAL + Internal high-speed clock + calibration + 8 + 8 + read-only + + + HSITRIM + Internal high-speed clock + trimming + 3 + 5 + read-write + + + HSIRDY + Internal high-speed clock ready + flag + 1 + 1 + read-only + + + HSION + Internal high-speed clock + enable + 0 + 1 + read-write + + + + + PLLCFGR + PLLCFGR + PLL configuration register + 0x4 + 0x20 + read-write + 0x24003010 + + + PLLQ3 + Main PLL (PLL) division factor for USB + OTG FS, SDIO and random number generator + clocks + 27 + 1 + + + PLLQ2 + Main PLL (PLL) division factor for USB + OTG FS, SDIO and random number generator + clocks + 26 + 1 + + + PLLQ1 + Main PLL (PLL) division factor for USB + OTG FS, SDIO and random number generator + clocks + 25 + 1 + + + PLLQ0 + Main PLL (PLL) division factor for USB + OTG FS, SDIO and random number generator + clocks + 24 + 1 + + + PLLSRC + Main PLL(PLL) and audio PLL (PLLI2S) + entry clock source + 22 + 1 + + + PLLP1 + Main PLL (PLL) division factor for main + system clock + 17 + 1 + + + PLLP0 + Main PLL (PLL) division factor for main + system clock + 16 + 1 + + + PLLN8 + Main PLL (PLL) multiplication factor for + VCO + 14 + 1 + + + PLLN7 + Main PLL (PLL) multiplication factor for + VCO + 13 + 1 + + + PLLN6 + Main PLL (PLL) multiplication factor for + VCO + 12 + 1 + + + PLLN5 + Main PLL (PLL) multiplication factor for + VCO + 11 + 1 + + + PLLN4 + Main PLL (PLL) multiplication factor for + VCO + 10 + 1 + + + PLLN3 + Main PLL (PLL) multiplication factor for + VCO + 9 + 1 + + + PLLN2 + Main PLL (PLL) multiplication factor for + VCO + 8 + 1 + + + PLLN1 + Main PLL (PLL) multiplication factor for + VCO + 7 + 1 + + + PLLN0 + Main PLL (PLL) multiplication factor for + VCO + 6 + 1 + + + PLLM5 + Division factor for the main PLL (PLL) + and audio PLL (PLLI2S) input clock + 5 + 1 + + + PLLM4 + Division factor for the main PLL (PLL) + and audio PLL (PLLI2S) input clock + 4 + 1 + + + PLLM3 + Division factor for the main PLL (PLL) + and audio PLL (PLLI2S) input clock + 3 + 1 + + + PLLM2 + Division factor for the main PLL (PLL) + and audio PLL (PLLI2S) input clock + 2 + 1 + + + PLLM1 + Division factor for the main PLL (PLL) + and audio PLL (PLLI2S) input clock + 1 + 1 + + + PLLM0 + Division factor for the main PLL (PLL) + and audio PLL (PLLI2S) input clock + 0 + 1 + + + + + CFGR + CFGR + clock configuration register + 0x8 + 0x20 + 0x00000000 + + + MCO2 + Microcontroller clock output + 2 + 30 + 2 + read-write + + + MCO2PRE + MCO2 prescaler + 27 + 3 + read-write + + + MCO1PRE + MCO1 prescaler + 24 + 3 + read-write + + + I2SSRC + I2S clock selection + 23 + 1 + read-write + + + MCO1 + Microcontroller clock output + 1 + 21 + 2 + read-write + + + RTCPRE + HSE division factor for RTC + clock + 16 + 5 + read-write + + + PPRE2 + APB high-speed prescaler + (APB2) + 13 + 3 + read-write + + + PPRE1 + APB Low speed prescaler + (APB1) + 10 + 3 + read-write + + + HPRE + AHB prescaler + 4 + 4 + read-write + + + SWS1 + System clock switch status + 3 + 1 + read-only + + + SWS0 + System clock switch status + 2 + 1 + read-only + + + SW1 + System clock switch + 1 + 1 + read-write + + + SW0 + System clock switch + 0 + 1 + read-write + + + + + CIR + CIR + clock interrupt register + 0xC + 0x20 + 0x00000000 + + + CSSC + Clock security system interrupt + clear + 23 + 1 + write-only + + + PLLI2SRDYC + PLLI2S ready interrupt + clear + 21 + 1 + write-only + + + PLLRDYC + Main PLL(PLL) ready interrupt + clear + 20 + 1 + write-only + + + HSERDYC + HSE ready interrupt clear + 19 + 1 + write-only + + + HSIRDYC + HSI ready interrupt clear + 18 + 1 + write-only + + + LSERDYC + LSE ready interrupt clear + 17 + 1 + write-only + + + LSIRDYC + LSI ready interrupt clear + 16 + 1 + write-only + + + PLLI2SRDYIE + PLLI2S ready interrupt + enable + 13 + 1 + read-write + + + PLLRDYIE + Main PLL (PLL) ready interrupt + enable + 12 + 1 + read-write + + + HSERDYIE + HSE ready interrupt enable + 11 + 1 + read-write + + + HSIRDYIE + HSI ready interrupt enable + 10 + 1 + read-write + + + LSERDYIE + LSE ready interrupt enable + 9 + 1 + read-write + + + LSIRDYIE + LSI ready interrupt enable + 8 + 1 + read-write + + + CSSF + Clock security system interrupt + flag + 7 + 1 + read-only + + + PLLI2SRDYF + PLLI2S ready interrupt + flag + 5 + 1 + read-only + + + PLLRDYF + Main PLL (PLL) ready interrupt + flag + 4 + 1 + read-only + + + HSERDYF + HSE ready interrupt flag + 3 + 1 + read-only + + + HSIRDYF + HSI ready interrupt flag + 2 + 1 + read-only + + + LSERDYF + LSE ready interrupt flag + 1 + 1 + read-only + + + LSIRDYF + LSI ready interrupt flag + 0 + 1 + read-only + + + + + AHB1RSTR + AHB1RSTR + AHB1 peripheral reset register + 0x10 + 0x20 + read-write + 0x00000000 + + + OTGHSRST + USB OTG HS module reset + 29 + 1 + + + ETHMACRST + Ethernet MAC reset + 25 + 1 + + + DMA2RST + DMA2 reset + 22 + 1 + + + DMA1RST + DMA2 reset + 21 + 1 + + + CRCRST + CRC reset + 12 + 1 + + + GPIOIRST + IO port I reset + 8 + 1 + + + GPIOHRST + IO port H reset + 7 + 1 + + + GPIOGRST + IO port G reset + 6 + 1 + + + GPIOFRST + IO port F reset + 5 + 1 + + + GPIOERST + IO port E reset + 4 + 1 + + + GPIODRST + IO port D reset + 3 + 1 + + + GPIOCRST + IO port C reset + 2 + 1 + + + GPIOBRST + IO port B reset + 1 + 1 + + + GPIOARST + IO port A reset + 0 + 1 + + + + + AHB2RSTR + AHB2RSTR + AHB2 peripheral reset register + 0x14 + 0x20 + read-write + 0x00000000 + + + OTGFSRST + USB OTG FS module reset + 7 + 1 + + + RNGRST + Random number generator module + reset + 6 + 1 + + + DCMIRST + Camera interface reset + 0 + 1 + + + + + AHB3RSTR + AHB3RSTR + AHB3 peripheral reset register + 0x18 + 0x20 + read-write + 0x00000000 + + + FSMCRST + Flexible static memory controller module + reset + 0 + 1 + + + + + APB1RSTR + APB1RSTR + APB1 peripheral reset register + 0x20 + 0x20 + read-write + 0x00000000 + + + DACRST + DAC reset + 29 + 1 + + + PWRRST + Power interface reset + 28 + 1 + + + CAN2RST + CAN2 reset + 26 + 1 + + + CAN1RST + CAN1 reset + 25 + 1 + + + I2C3RST + I2C3 reset + 23 + 1 + + + I2C2RST + I2C 2 reset + 22 + 1 + + + I2C1RST + I2C 1 reset + 21 + 1 + + + UART5RST + USART 5 reset + 20 + 1 + + + UART4RST + USART 4 reset + 19 + 1 + + + UART3RST + USART 3 reset + 18 + 1 + + + UART2RST + USART 2 reset + 17 + 1 + + + SPI3RST + SPI 3 reset + 15 + 1 + + + SPI2RST + SPI 2 reset + 14 + 1 + + + WWDGRST + Window watchdog reset + 11 + 1 + + + TIM14RST + TIM14 reset + 8 + 1 + + + TIM13RST + TIM13 reset + 7 + 1 + + + TIM12RST + TIM12 reset + 6 + 1 + + + TIM7RST + TIM7 reset + 5 + 1 + + + TIM6RST + TIM6 reset + 4 + 1 + + + TIM5RST + TIM5 reset + 3 + 1 + + + TIM4RST + TIM4 reset + 2 + 1 + + + TIM3RST + TIM3 reset + 1 + 1 + + + TIM2RST + TIM2 reset + 0 + 1 + + + + + APB2RSTR + APB2RSTR + APB2 peripheral reset register + 0x24 + 0x20 + read-write + 0x00000000 + + + TIM11RST + TIM11 reset + 18 + 1 + + + TIM10RST + TIM10 reset + 17 + 1 + + + TIM9RST + TIM9 reset + 16 + 1 + + + SYSCFGRST + System configuration controller + reset + 14 + 1 + + + SPI1RST + SPI 1 reset + 12 + 1 + + + SDIORST + SDIO reset + 11 + 1 + + + ADCRST + ADC interface reset (common to all + ADCs) + 8 + 1 + + + USART6RST + USART6 reset + 5 + 1 + + + USART1RST + USART1 reset + 4 + 1 + + + TIM8RST + TIM8 reset + 1 + 1 + + + TIM1RST + TIM1 reset + 0 + 1 + + + + + AHB1ENR + AHB1ENR + AHB1 peripheral clock register + 0x30 + 0x20 + read-write + 0x00100000 + + + OTGHSULPIEN + USB OTG HSULPI clock + enable + 30 + 1 + + + OTGHSEN + USB OTG HS clock enable + 29 + 1 + + + ETHMACPTPEN + Ethernet PTP clock enable + 28 + 1 + + + ETHMACRXEN + Ethernet Reception clock + enable + 27 + 1 + + + ETHMACTXEN + Ethernet Transmission clock + enable + 26 + 1 + + + ETHMACEN + Ethernet MAC clock enable + 25 + 1 + + + DMA2EN + DMA2 clock enable + 22 + 1 + + + DMA1EN + DMA1 clock enable + 21 + 1 + + + BKPSRAMEN + Backup SRAM interface clock + enable + 18 + 1 + + + CRCEN + CRC clock enable + 12 + 1 + + + GPIOIEN + IO port I clock enable + 8 + 1 + + + GPIOHEN + IO port H clock enable + 7 + 1 + + + GPIOGEN + IO port G clock enable + 6 + 1 + + + GPIOFEN + IO port F clock enable + 5 + 1 + + + GPIOEEN + IO port E clock enable + 4 + 1 + + + GPIODEN + IO port D clock enable + 3 + 1 + + + GPIOCEN + IO port C clock enable + 2 + 1 + + + GPIOBEN + IO port B clock enable + 1 + 1 + + + GPIOAEN + IO port A clock enable + 0 + 1 + + + + + AHB2ENR + AHB2ENR + AHB2 peripheral clock enable + register + 0x34 + 0x20 + read-write + 0x00000000 + + + OTGFSEN + USB OTG FS clock enable + 7 + 1 + + + RNGEN + Random number generator clock + enable + 6 + 1 + + + DCMIEN + Camera interface enable + 0 + 1 + + + + + AHB3ENR + AHB3ENR + AHB3 peripheral clock enable + register + 0x38 + 0x20 + read-write + 0x00000000 + + + FSMCEN + Flexible static memory controller module + clock enable + 0 + 1 + + + + + APB1ENR + APB1ENR + APB1 peripheral clock enable + register + 0x40 + 0x20 + read-write + 0x00000000 + + + DACEN + DAC interface clock enable + 29 + 1 + + + PWREN + Power interface clock + enable + 28 + 1 + + + CAN2EN + CAN 2 clock enable + 26 + 1 + + + CAN1EN + CAN 1 clock enable + 25 + 1 + + + I2C3EN + I2C3 clock enable + 23 + 1 + + + I2C2EN + I2C2 clock enable + 22 + 1 + + + I2C1EN + I2C1 clock enable + 21 + 1 + + + UART5EN + UART5 clock enable + 20 + 1 + + + UART4EN + UART4 clock enable + 19 + 1 + + + USART3EN + USART3 clock enable + 18 + 1 + + + USART2EN + USART 2 clock enable + 17 + 1 + + + SPI3EN + SPI3 clock enable + 15 + 1 + + + SPI2EN + SPI2 clock enable + 14 + 1 + + + WWDGEN + Window watchdog clock + enable + 11 + 1 + + + TIM14EN + TIM14 clock enable + 8 + 1 + + + TIM13EN + TIM13 clock enable + 7 + 1 + + + TIM12EN + TIM12 clock enable + 6 + 1 + + + TIM7EN + TIM7 clock enable + 5 + 1 + + + TIM6EN + TIM6 clock enable + 4 + 1 + + + TIM5EN + TIM5 clock enable + 3 + 1 + + + TIM4EN + TIM4 clock enable + 2 + 1 + + + TIM3EN + TIM3 clock enable + 1 + 1 + + + TIM2EN + TIM2 clock enable + 0 + 1 + + + + + APB2ENR + APB2ENR + APB2 peripheral clock enable + register + 0x44 + 0x20 + read-write + 0x00000000 + + + TIM11EN + TIM11 clock enable + 18 + 1 + + + TIM10EN + TIM10 clock enable + 17 + 1 + + + TIM9EN + TIM9 clock enable + 16 + 1 + + + SYSCFGEN + System configuration controller clock + enable + 14 + 1 + + + SPI1EN + SPI1 clock enable + 12 + 1 + + + SDIOEN + SDIO clock enable + 11 + 1 + + + ADC3EN + ADC3 clock enable + 10 + 1 + + + ADC2EN + ADC2 clock enable + 9 + 1 + + + ADC1EN + ADC1 clock enable + 8 + 1 + + + USART6EN + USART6 clock enable + 5 + 1 + + + USART1EN + USART1 clock enable + 4 + 1 + + + TIM8EN + TIM8 clock enable + 1 + 1 + + + TIM1EN + TIM1 clock enable + 0 + 1 + + + + + AHB1LPENR + AHB1LPENR + AHB1 peripheral clock enable in low power + mode register + 0x50 + 0x20 + read-write + 0x7E6791FF + + + OTGHSULPILPEN + USB OTG HS ULPI clock enable during + Sleep mode + 30 + 1 + + + OTGHSLPEN + USB OTG HS clock enable during Sleep + mode + 29 + 1 + + + ETHMACPTPLPEN + Ethernet PTP clock enable during Sleep + mode + 28 + 1 + + + ETHMACRXLPEN + Ethernet reception clock enable during + Sleep mode + 27 + 1 + + + ETHMACTXLPEN + Ethernet transmission clock enable + during Sleep mode + 26 + 1 + + + ETHMACLPEN + Ethernet MAC clock enable during Sleep + mode + 25 + 1 + + + DMA2LPEN + DMA2 clock enable during Sleep + mode + 22 + 1 + + + DMA1LPEN + DMA1 clock enable during Sleep + mode + 21 + 1 + + + BKPSRAMLPEN + Backup SRAM interface clock enable + during Sleep mode + 18 + 1 + + + SRAM2LPEN + SRAM 2 interface clock enable during + Sleep mode + 17 + 1 + + + SRAM1LPEN + SRAM 1interface clock enable during + Sleep mode + 16 + 1 + + + FLITFLPEN + Flash interface clock enable during + Sleep mode + 15 + 1 + + + CRCLPEN + CRC clock enable during Sleep + mode + 12 + 1 + + + GPIOILPEN + IO port I clock enable during Sleep + mode + 8 + 1 + + + GPIOHLPEN + IO port H clock enable during Sleep + mode + 7 + 1 + + + GPIOGLPEN + IO port G clock enable during Sleep + mode + 6 + 1 + + + GPIOFLPEN + IO port F clock enable during Sleep + mode + 5 + 1 + + + GPIOELPEN + IO port E clock enable during Sleep + mode + 4 + 1 + + + GPIODLPEN + IO port D clock enable during Sleep + mode + 3 + 1 + + + GPIOCLPEN + IO port C clock enable during Sleep + mode + 2 + 1 + + + GPIOBLPEN + IO port B clock enable during Sleep + mode + 1 + 1 + + + GPIOALPEN + IO port A clock enable during sleep + mode + 0 + 1 + + + + + AHB2LPENR + AHB2LPENR + AHB2 peripheral clock enable in low power + mode register + 0x54 + 0x20 + read-write + 0x000000F1 + + + OTGFSLPEN + USB OTG FS clock enable during Sleep + mode + 7 + 1 + + + RNGLPEN + Random number generator clock enable + during Sleep mode + 6 + 1 + + + DCMILPEN + Camera interface enable during Sleep + mode + 0 + 1 + + + + + AHB3LPENR + AHB3LPENR + AHB3 peripheral clock enable in low power + mode register + 0x58 + 0x20 + read-write + 0x00000001 + + + FSMCLPEN + Flexible static memory controller module + clock enable during Sleep mode + 0 + 1 + + + + + APB1LPENR + APB1LPENR + APB1 peripheral clock enable in low power + mode register + 0x60 + 0x20 + read-write + 0x36FEC9FF + + + DACLPEN + DAC interface clock enable during Sleep + mode + 29 + 1 + + + PWRLPEN + Power interface clock enable during + Sleep mode + 28 + 1 + + + CAN2LPEN + CAN 2 clock enable during Sleep + mode + 26 + 1 + + + CAN1LPEN + CAN 1 clock enable during Sleep + mode + 25 + 1 + + + I2C3LPEN + I2C3 clock enable during Sleep + mode + 23 + 1 + + + I2C2LPEN + I2C2 clock enable during Sleep + mode + 22 + 1 + + + I2C1LPEN + I2C1 clock enable during Sleep + mode + 21 + 1 + + + UART5LPEN + UART5 clock enable during Sleep + mode + 20 + 1 + + + UART4LPEN + UART4 clock enable during Sleep + mode + 19 + 1 + + + USART3LPEN + USART3 clock enable during Sleep + mode + 18 + 1 + + + USART2LPEN + USART2 clock enable during Sleep + mode + 17 + 1 + + + SPI3LPEN + SPI3 clock enable during Sleep + mode + 15 + 1 + + + SPI2LPEN + SPI2 clock enable during Sleep + mode + 14 + 1 + + + WWDGLPEN + Window watchdog clock enable during + Sleep mode + 11 + 1 + + + TIM14LPEN + TIM14 clock enable during Sleep + mode + 8 + 1 + + + TIM13LPEN + TIM13 clock enable during Sleep + mode + 7 + 1 + + + TIM12LPEN + TIM12 clock enable during Sleep + mode + 6 + 1 + + + TIM7LPEN + TIM7 clock enable during Sleep + mode + 5 + 1 + + + TIM6LPEN + TIM6 clock enable during Sleep + mode + 4 + 1 + + + TIM5LPEN + TIM5 clock enable during Sleep + mode + 3 + 1 + + + TIM4LPEN + TIM4 clock enable during Sleep + mode + 2 + 1 + + + TIM3LPEN + TIM3 clock enable during Sleep + mode + 1 + 1 + + + TIM2LPEN + TIM2 clock enable during Sleep + mode + 0 + 1 + + + + + APB2LPENR + APB2LPENR + APB2 peripheral clock enabled in low power + mode register + 0x64 + 0x20 + read-write + 0x00075F33 + + + TIM11LPEN + TIM11 clock enable during Sleep + mode + 18 + 1 + + + TIM10LPEN + TIM10 clock enable during Sleep + mode + 17 + 1 + + + TIM9LPEN + TIM9 clock enable during sleep + mode + 16 + 1 + + + SYSCFGLPEN + System configuration controller clock + enable during Sleep mode + 14 + 1 + + + SPI1LPEN + SPI 1 clock enable during Sleep + mode + 12 + 1 + + + SDIOLPEN + SDIO clock enable during Sleep + mode + 11 + 1 + + + ADC3LPEN + ADC 3 clock enable during Sleep + mode + 10 + 1 + + + ADC2LPEN + ADC2 clock enable during Sleep + mode + 9 + 1 + + + ADC1LPEN + ADC1 clock enable during Sleep + mode + 8 + 1 + + + USART6LPEN + USART6 clock enable during Sleep + mode + 5 + 1 + + + USART1LPEN + USART1 clock enable during Sleep + mode + 4 + 1 + + + TIM8LPEN + TIM8 clock enable during Sleep + mode + 1 + 1 + + + TIM1LPEN + TIM1 clock enable during Sleep + mode + 0 + 1 + + + + + BDCR + BDCR + Backup domain control register + 0x70 + 0x20 + 0x00000000 + + + BDRST + Backup domain software + reset + 16 + 1 + read-write + + + RTCEN + RTC clock enable + 15 + 1 + read-write + + + RTCSEL1 + RTC clock source selection + 9 + 1 + read-write + + + RTCSEL0 + RTC clock source selection + 8 + 1 + read-write + + + LSEBYP + External low-speed oscillator + bypass + 2 + 1 + read-write + + + LSERDY + External low-speed oscillator + ready + 1 + 1 + read-only + + + LSEON + External low-speed oscillator + enable + 0 + 1 + read-write + + + + + CSR + CSR + clock control & status + register + 0x74 + 0x20 + 0x0E000000 + + + LPWRRSTF + Low-power reset flag + 31 + 1 + read-write + + + WWDGRSTF + Window watchdog reset flag + 30 + 1 + read-write + + + WDGRSTF + Independent watchdog reset + flag + 29 + 1 + read-write + + + SFTRSTF + Software reset flag + 28 + 1 + read-write + + + PORRSTF + POR/PDR reset flag + 27 + 1 + read-write + + + PADRSTF + PIN reset flag + 26 + 1 + read-write + + + BORRSTF + BOR reset flag + 25 + 1 + read-write + + + RMVF + Remove reset flag + 24 + 1 + read-write + + + LSIRDY + Internal low-speed oscillator + ready + 1 + 1 + read-only + + + LSION + Internal low-speed oscillator + enable + 0 + 1 + read-write + + + + + SSCGR + SSCGR + spread spectrum clock generation + register + 0x80 + 0x20 + read-write + 0x00000000 + + + SSCGEN + Spread spectrum modulation + enable + 31 + 1 + + + SPREADSEL + Spread Select + 30 + 1 + + + INCSTEP + Incrementation step + 13 + 15 + + + MODPER + Modulation period + 0 + 13 + + + + + PLLI2SCFGR + PLLI2SCFGR + PLLI2S configuration register + 0x84 + 0x20 + read-write + 0x20003000 + + + PLLI2SRx + PLLI2S division factor for I2S + clocks + 28 + 3 + + + PLLI2SNx + PLLI2S multiplication factor for + VCO + 6 + 9 + + + + + + + GPIOI + General-purpose I/Os + GPIO + 0x40022000 + + 0x0 + 0x400 + registers + + + + MODER + MODER + GPIO port mode register + 0x0 + 0x20 + read-write + 0x00000000 + + + MODER15 + Port x configuration bits (y = + 0..15) + 30 + 2 + + + MODER14 + Port x configuration bits (y = + 0..15) + 28 + 2 + + + MODER13 + Port x configuration bits (y = + 0..15) + 26 + 2 + + + MODER12 + Port x configuration bits (y = + 0..15) + 24 + 2 + + + MODER11 + Port x configuration bits (y = + 0..15) + 22 + 2 + + + MODER10 + Port x configuration bits (y = + 0..15) + 20 + 2 + + + MODER9 + Port x configuration bits (y = + 0..15) + 18 + 2 + + + MODER8 + Port x configuration bits (y = + 0..15) + 16 + 2 + + + MODER7 + Port x configuration bits (y = + 0..15) + 14 + 2 + + + MODER6 + Port x configuration bits (y = + 0..15) + 12 + 2 + + + MODER5 + Port x configuration bits (y = + 0..15) + 10 + 2 + + + MODER4 + Port x configuration bits (y = + 0..15) + 8 + 2 + + + MODER3 + Port x configuration bits (y = + 0..15) + 6 + 2 + + + MODER2 + Port x configuration bits (y = + 0..15) + 4 + 2 + + + MODER1 + Port x configuration bits (y = + 0..15) + 2 + 2 + + + MODER0 + Port x configuration bits (y = + 0..15) + 0 + 2 + + + + + OTYPER + OTYPER + GPIO port output type register + 0x4 + 0x20 + read-write + 0x00000000 + + + OT15 + Port x configuration bits (y = + 0..15) + 15 + 1 + + + OT14 + Port x configuration bits (y = + 0..15) + 14 + 1 + + + OT13 + Port x configuration bits (y = + 0..15) + 13 + 1 + + + OT12 + Port x configuration bits (y = + 0..15) + 12 + 1 + + + OT11 + Port x configuration bits (y = + 0..15) + 11 + 1 + + + OT10 + Port x configuration bits (y = + 0..15) + 10 + 1 + + + OT9 + Port x configuration bits (y = + 0..15) + 9 + 1 + + + OT8 + Port x configuration bits (y = + 0..15) + 8 + 1 + + + OT7 + Port x configuration bits (y = + 0..15) + 7 + 1 + + + OT6 + Port x configuration bits (y = + 0..15) + 6 + 1 + + + OT5 + Port x configuration bits (y = + 0..15) + 5 + 1 + + + OT4 + Port x configuration bits (y = + 0..15) + 4 + 1 + + + OT3 + Port x configuration bits (y = + 0..15) + 3 + 1 + + + OT2 + Port x configuration bits (y = + 0..15) + 2 + 1 + + + OT1 + Port x configuration bits (y = + 0..15) + 1 + 1 + + + OT0 + Port x configuration bits (y = + 0..15) + 0 + 1 + + + + + OSPEEDR + OSPEEDR + GPIO port output speed + register + 0x8 + 0x20 + read-write + 0x00000000 + + + OSPEEDR15 + Port x configuration bits (y = + 0..15) + 30 + 2 + + + OSPEEDR14 + Port x configuration bits (y = + 0..15) + 28 + 2 + + + OSPEEDR13 + Port x configuration bits (y = + 0..15) + 26 + 2 + + + OSPEEDR12 + Port x configuration bits (y = + 0..15) + 24 + 2 + + + OSPEEDR11 + Port x configuration bits (y = + 0..15) + 22 + 2 + + + OSPEEDR10 + Port x configuration bits (y = + 0..15) + 20 + 2 + + + OSPEEDR9 + Port x configuration bits (y = + 0..15) + 18 + 2 + + + OSPEEDR8 + Port x configuration bits (y = + 0..15) + 16 + 2 + + + OSPEEDR7 + Port x configuration bits (y = + 0..15) + 14 + 2 + + + OSPEEDR6 + Port x configuration bits (y = + 0..15) + 12 + 2 + + + OSPEEDR5 + Port x configuration bits (y = + 0..15) + 10 + 2 + + + OSPEEDR4 + Port x configuration bits (y = + 0..15) + 8 + 2 + + + OSPEEDR3 + Port x configuration bits (y = + 0..15) + 6 + 2 + + + OSPEEDR2 + Port x configuration bits (y = + 0..15) + 4 + 2 + + + OSPEEDR1 + Port x configuration bits (y = + 0..15) + 2 + 2 + + + OSPEEDR0 + Port x configuration bits (y = + 0..15) + 0 + 2 + + + + + PUPDR + PUPDR + GPIO port pull-up/pull-down + register + 0xC + 0x20 + read-write + 0x00000000 + + + PUPDR15 + Port x configuration bits (y = + 0..15) + 30 + 2 + + + PUPDR14 + Port x configuration bits (y = + 0..15) + 28 + 2 + + + PUPDR13 + Port x configuration bits (y = + 0..15) + 26 + 2 + + + PUPDR12 + Port x configuration bits (y = + 0..15) + 24 + 2 + + + PUPDR11 + Port x configuration bits (y = + 0..15) + 22 + 2 + + + PUPDR10 + Port x configuration bits (y = + 0..15) + 20 + 2 + + + PUPDR9 + Port x configuration bits (y = + 0..15) + 18 + 2 + + + PUPDR8 + Port x configuration bits (y = + 0..15) + 16 + 2 + + + PUPDR7 + Port x configuration bits (y = + 0..15) + 14 + 2 + + + PUPDR6 + Port x configuration bits (y = + 0..15) + 12 + 2 + + + PUPDR5 + Port x configuration bits (y = + 0..15) + 10 + 2 + + + PUPDR4 + Port x configuration bits (y = + 0..15) + 8 + 2 + + + PUPDR3 + Port x configuration bits (y = + 0..15) + 6 + 2 + + + PUPDR2 + Port x configuration bits (y = + 0..15) + 4 + 2 + + + PUPDR1 + Port x configuration bits (y = + 0..15) + 2 + 2 + + + PUPDR0 + Port x configuration bits (y = + 0..15) + 0 + 2 + + + + + IDR + IDR + GPIO port input data register + 0x10 + 0x20 + read-only + 0x00000000 + + + IDR15 + Port input data (y = + 0..15) + 15 + 1 + + + IDR14 + Port input data (y = + 0..15) + 14 + 1 + + + IDR13 + Port input data (y = + 0..15) + 13 + 1 + + + IDR12 + Port input data (y = + 0..15) + 12 + 1 + + + IDR11 + Port input data (y = + 0..15) + 11 + 1 + + + IDR10 + Port input data (y = + 0..15) + 10 + 1 + + + IDR9 + Port input data (y = + 0..15) + 9 + 1 + + + IDR8 + Port input data (y = + 0..15) + 8 + 1 + + + IDR7 + Port input data (y = + 0..15) + 7 + 1 + + + IDR6 + Port input data (y = + 0..15) + 6 + 1 + + + IDR5 + Port input data (y = + 0..15) + 5 + 1 + + + IDR4 + Port input data (y = + 0..15) + 4 + 1 + + + IDR3 + Port input data (y = + 0..15) + 3 + 1 + + + IDR2 + Port input data (y = + 0..15) + 2 + 1 + + + IDR1 + Port input data (y = + 0..15) + 1 + 1 + + + IDR0 + Port input data (y = + 0..15) + 0 + 1 + + + + + ODR + ODR + GPIO port output data register + 0x14 + 0x20 + read-write + 0x00000000 + + + ODR15 + Port output data (y = + 0..15) + 15 + 1 + + + ODR14 + Port output data (y = + 0..15) + 14 + 1 + + + ODR13 + Port output data (y = + 0..15) + 13 + 1 + + + ODR12 + Port output data (y = + 0..15) + 12 + 1 + + + ODR11 + Port output data (y = + 0..15) + 11 + 1 + + + ODR10 + Port output data (y = + 0..15) + 10 + 1 + + + ODR9 + Port output data (y = + 0..15) + 9 + 1 + + + ODR8 + Port output data (y = + 0..15) + 8 + 1 + + + ODR7 + Port output data (y = + 0..15) + 7 + 1 + + + ODR6 + Port output data (y = + 0..15) + 6 + 1 + + + ODR5 + Port output data (y = + 0..15) + 5 + 1 + + + ODR4 + Port output data (y = + 0..15) + 4 + 1 + + + ODR3 + Port output data (y = + 0..15) + 3 + 1 + + + ODR2 + Port output data (y = + 0..15) + 2 + 1 + + + ODR1 + Port output data (y = + 0..15) + 1 + 1 + + + ODR0 + Port output data (y = + 0..15) + 0 + 1 + + + + + BSRR + BSRR + GPIO port bit set/reset + register + 0x18 + 0x20 + write-only + 0x00000000 + + + BR15 + Port x reset bit y (y = + 0..15) + 31 + 1 + + + BR14 + Port x reset bit y (y = + 0..15) + 30 + 1 + + + BR13 + Port x reset bit y (y = + 0..15) + 29 + 1 + + + BR12 + Port x reset bit y (y = + 0..15) + 28 + 1 + + + BR11 + Port x reset bit y (y = + 0..15) + 27 + 1 + + + BR10 + Port x reset bit y (y = + 0..15) + 26 + 1 + + + BR9 + Port x reset bit y (y = + 0..15) + 25 + 1 + + + BR8 + Port x reset bit y (y = + 0..15) + 24 + 1 + + + BR7 + Port x reset bit y (y = + 0..15) + 23 + 1 + + + BR6 + Port x reset bit y (y = + 0..15) + 22 + 1 + + + BR5 + Port x reset bit y (y = + 0..15) + 21 + 1 + + + BR4 + Port x reset bit y (y = + 0..15) + 20 + 1 + + + BR3 + Port x reset bit y (y = + 0..15) + 19 + 1 + + + BR2 + Port x reset bit y (y = + 0..15) + 18 + 1 + + + BR1 + Port x reset bit y (y = + 0..15) + 17 + 1 + + + BR0 + Port x set bit y (y= + 0..15) + 16 + 1 + + + BS15 + Port x set bit y (y= + 0..15) + 15 + 1 + + + BS14 + Port x set bit y (y= + 0..15) + 14 + 1 + + + BS13 + Port x set bit y (y= + 0..15) + 13 + 1 + + + BS12 + Port x set bit y (y= + 0..15) + 12 + 1 + + + BS11 + Port x set bit y (y= + 0..15) + 11 + 1 + + + BS10 + Port x set bit y (y= + 0..15) + 10 + 1 + + + BS9 + Port x set bit y (y= + 0..15) + 9 + 1 + + + BS8 + Port x set bit y (y= + 0..15) + 8 + 1 + + + BS7 + Port x set bit y (y= + 0..15) + 7 + 1 + + + BS6 + Port x set bit y (y= + 0..15) + 6 + 1 + + + BS5 + Port x set bit y (y= + 0..15) + 5 + 1 + + + BS4 + Port x set bit y (y= + 0..15) + 4 + 1 + + + BS3 + Port x set bit y (y= + 0..15) + 3 + 1 + + + BS2 + Port x set bit y (y= + 0..15) + 2 + 1 + + + BS1 + Port x set bit y (y= + 0..15) + 1 + 1 + + + BS0 + Port x set bit y (y= + 0..15) + 0 + 1 + + + + + LCKR + LCKR + GPIO port configuration lock + register + 0x1C + 0x20 + read-write + 0x00000000 + + + LCKK + Port x lock bit y (y= + 0..15) + 16 + 1 + + + LCK15 + Port x lock bit y (y= + 0..15) + 15 + 1 + + + LCK14 + Port x lock bit y (y= + 0..15) + 14 + 1 + + + LCK13 + Port x lock bit y (y= + 0..15) + 13 + 1 + + + LCK12 + Port x lock bit y (y= + 0..15) + 12 + 1 + + + LCK11 + Port x lock bit y (y= + 0..15) + 11 + 1 + + + LCK10 + Port x lock bit y (y= + 0..15) + 10 + 1 + + + LCK9 + Port x lock bit y (y= + 0..15) + 9 + 1 + + + LCK8 + Port x lock bit y (y= + 0..15) + 8 + 1 + + + LCK7 + Port x lock bit y (y= + 0..15) + 7 + 1 + + + LCK6 + Port x lock bit y (y= + 0..15) + 6 + 1 + + + LCK5 + Port x lock bit y (y= + 0..15) + 5 + 1 + + + LCK4 + Port x lock bit y (y= + 0..15) + 4 + 1 + + + LCK3 + Port x lock bit y (y= + 0..15) + 3 + 1 + + + LCK2 + Port x lock bit y (y= + 0..15) + 2 + 1 + + + LCK1 + Port x lock bit y (y= + 0..15) + 1 + 1 + + + LCK0 + Port x lock bit y (y= + 0..15) + 0 + 1 + + + + + AFRL + AFRL + GPIO alternate function low + register + 0x20 + 0x20 + read-write + 0x00000000 + + + AFRL7 + Alternate function selection for port x + bit y (y = 0..7) + 28 + 4 + + + AFRL6 + Alternate function selection for port x + bit y (y = 0..7) + 24 + 4 + + + AFRL5 + Alternate function selection for port x + bit y (y = 0..7) + 20 + 4 + + + AFRL4 + Alternate function selection for port x + bit y (y = 0..7) + 16 + 4 + + + AFRL3 + Alternate function selection for port x + bit y (y = 0..7) + 12 + 4 + + + AFRL2 + Alternate function selection for port x + bit y (y = 0..7) + 8 + 4 + + + AFRL1 + Alternate function selection for port x + bit y (y = 0..7) + 4 + 4 + + + AFRL0 + Alternate function selection for port x + bit y (y = 0..7) + 0 + 4 + + + + + AFRH + AFRH + GPIO alternate function high + register + 0x24 + 0x20 + read-write + 0x00000000 + + + AFRH15 + Alternate function selection for port x + bit y (y = 8..15) + 28 + 4 + + + AFRH14 + Alternate function selection for port x + bit y (y = 8..15) + 24 + 4 + + + AFRH13 + Alternate function selection for port x + bit y (y = 8..15) + 20 + 4 + + + AFRH12 + Alternate function selection for port x + bit y (y = 8..15) + 16 + 4 + + + AFRH11 + Alternate function selection for port x + bit y (y = 8..15) + 12 + 4 + + + AFRH10 + Alternate function selection for port x + bit y (y = 8..15) + 8 + 4 + + + AFRH9 + Alternate function selection for port x + bit y (y = 8..15) + 4 + 4 + + + AFRH8 + Alternate function selection for port x + bit y (y = 8..15) + 0 + 4 + + + + + + + GPIOH + 0x40021C00 + + + GPIOG + 0x40021800 + + + GPIOF + 0x40021400 + + + GPIOE + 0x40021000 + + + GPIOD + 0X40020C00 + + + GPIOC + 0x40020800 + + + GPIOB + General-purpose I/Os + GPIO + 0x40020400 + + 0x0 + 0x400 + registers + + + + MODER + MODER + GPIO port mode register + 0x0 + 0x20 + read-write + 0x00000280 + + + MODER15 + Port x configuration bits (y = + 0..15) + 30 + 2 + + + MODER14 + Port x configuration bits (y = + 0..15) + 28 + 2 + + + MODER13 + Port x configuration bits (y = + 0..15) + 26 + 2 + + + MODER12 + Port x configuration bits (y = + 0..15) + 24 + 2 + + + MODER11 + Port x configuration bits (y = + 0..15) + 22 + 2 + + + MODER10 + Port x configuration bits (y = + 0..15) + 20 + 2 + + + MODER9 + Port x configuration bits (y = + 0..15) + 18 + 2 + + + MODER8 + Port x configuration bits (y = + 0..15) + 16 + 2 + + + MODER7 + Port x configuration bits (y = + 0..15) + 14 + 2 + + + MODER6 + Port x configuration bits (y = + 0..15) + 12 + 2 + + + MODER5 + Port x configuration bits (y = + 0..15) + 10 + 2 + + + MODER4 + Port x configuration bits (y = + 0..15) + 8 + 2 + + + MODER3 + Port x configuration bits (y = + 0..15) + 6 + 2 + + + MODER2 + Port x configuration bits (y = + 0..15) + 4 + 2 + + + MODER1 + Port x configuration bits (y = + 0..15) + 2 + 2 + + + MODER0 + Port x configuration bits (y = + 0..15) + 0 + 2 + + + + + OTYPER + OTYPER + GPIO port output type register + 0x4 + 0x20 + read-write + 0x00000000 + + + OT15 + Port x configuration bits (y = + 0..15) + 15 + 1 + + + OT14 + Port x configuration bits (y = + 0..15) + 14 + 1 + + + OT13 + Port x configuration bits (y = + 0..15) + 13 + 1 + + + OT12 + Port x configuration bits (y = + 0..15) + 12 + 1 + + + OT11 + Port x configuration bits (y = + 0..15) + 11 + 1 + + + OT10 + Port x configuration bits (y = + 0..15) + 10 + 1 + + + OT9 + Port x configuration bits (y = + 0..15) + 9 + 1 + + + OT8 + Port x configuration bits (y = + 0..15) + 8 + 1 + + + OT7 + Port x configuration bits (y = + 0..15) + 7 + 1 + + + OT6 + Port x configuration bits (y = + 0..15) + 6 + 1 + + + OT5 + Port x configuration bits (y = + 0..15) + 5 + 1 + + + OT4 + Port x configuration bits (y = + 0..15) + 4 + 1 + + + OT3 + Port x configuration bits (y = + 0..15) + 3 + 1 + + + OT2 + Port x configuration bits (y = + 0..15) + 2 + 1 + + + OT1 + Port x configuration bits (y = + 0..15) + 1 + 1 + + + OT0 + Port x configuration bits (y = + 0..15) + 0 + 1 + + + + + OSPEEDR + OSPEEDR + GPIO port output speed + register + 0x8 + 0x20 + read-write + 0x000000C0 + + + OSPEEDR15 + Port x configuration bits (y = + 0..15) + 30 + 2 + + + OSPEEDR14 + Port x configuration bits (y = + 0..15) + 28 + 2 + + + OSPEEDR13 + Port x configuration bits (y = + 0..15) + 26 + 2 + + + OSPEEDR12 + Port x configuration bits (y = + 0..15) + 24 + 2 + + + OSPEEDR11 + Port x configuration bits (y = + 0..15) + 22 + 2 + + + OSPEEDR10 + Port x configuration bits (y = + 0..15) + 20 + 2 + + + OSPEEDR9 + Port x configuration bits (y = + 0..15) + 18 + 2 + + + OSPEEDR8 + Port x configuration bits (y = + 0..15) + 16 + 2 + + + OSPEEDR7 + Port x configuration bits (y = + 0..15) + 14 + 2 + + + OSPEEDR6 + Port x configuration bits (y = + 0..15) + 12 + 2 + + + OSPEEDR5 + Port x configuration bits (y = + 0..15) + 10 + 2 + + + OSPEEDR4 + Port x configuration bits (y = + 0..15) + 8 + 2 + + + OSPEEDR3 + Port x configuration bits (y = + 0..15) + 6 + 2 + + + OSPEEDR2 + Port x configuration bits (y = + 0..15) + 4 + 2 + + + OSPEEDR1 + Port x configuration bits (y = + 0..15) + 2 + 2 + + + OSPEEDR0 + Port x configuration bits (y = + 0..15) + 0 + 2 + + + + + PUPDR + PUPDR + GPIO port pull-up/pull-down + register + 0xC + 0x20 + read-write + 0x00000100 + + + PUPDR15 + Port x configuration bits (y = + 0..15) + 30 + 2 + + + PUPDR14 + Port x configuration bits (y = + 0..15) + 28 + 2 + + + PUPDR13 + Port x configuration bits (y = + 0..15) + 26 + 2 + + + PUPDR12 + Port x configuration bits (y = + 0..15) + 24 + 2 + + + PUPDR11 + Port x configuration bits (y = + 0..15) + 22 + 2 + + + PUPDR10 + Port x configuration bits (y = + 0..15) + 20 + 2 + + + PUPDR9 + Port x configuration bits (y = + 0..15) + 18 + 2 + + + PUPDR8 + Port x configuration bits (y = + 0..15) + 16 + 2 + + + PUPDR7 + Port x configuration bits (y = + 0..15) + 14 + 2 + + + PUPDR6 + Port x configuration bits (y = + 0..15) + 12 + 2 + + + PUPDR5 + Port x configuration bits (y = + 0..15) + 10 + 2 + + + PUPDR4 + Port x configuration bits (y = + 0..15) + 8 + 2 + + + PUPDR3 + Port x configuration bits (y = + 0..15) + 6 + 2 + + + PUPDR2 + Port x configuration bits (y = + 0..15) + 4 + 2 + + + PUPDR1 + Port x configuration bits (y = + 0..15) + 2 + 2 + + + PUPDR0 + Port x configuration bits (y = + 0..15) + 0 + 2 + + + + + IDR + IDR + GPIO port input data register + 0x10 + 0x20 + read-only + 0x00000000 + + + IDR15 + Port input data (y = + 0..15) + 15 + 1 + + + IDR14 + Port input data (y = + 0..15) + 14 + 1 + + + IDR13 + Port input data (y = + 0..15) + 13 + 1 + + + IDR12 + Port input data (y = + 0..15) + 12 + 1 + + + IDR11 + Port input data (y = + 0..15) + 11 + 1 + + + IDR10 + Port input data (y = + 0..15) + 10 + 1 + + + IDR9 + Port input data (y = + 0..15) + 9 + 1 + + + IDR8 + Port input data (y = + 0..15) + 8 + 1 + + + IDR7 + Port input data (y = + 0..15) + 7 + 1 + + + IDR6 + Port input data (y = + 0..15) + 6 + 1 + + + IDR5 + Port input data (y = + 0..15) + 5 + 1 + + + IDR4 + Port input data (y = + 0..15) + 4 + 1 + + + IDR3 + Port input data (y = + 0..15) + 3 + 1 + + + IDR2 + Port input data (y = + 0..15) + 2 + 1 + + + IDR1 + Port input data (y = + 0..15) + 1 + 1 + + + IDR0 + Port input data (y = + 0..15) + 0 + 1 + + + + + ODR + ODR + GPIO port output data register + 0x14 + 0x20 + read-write + 0x00000000 + + + ODR15 + Port output data (y = + 0..15) + 15 + 1 + + + ODR14 + Port output data (y = + 0..15) + 14 + 1 + + + ODR13 + Port output data (y = + 0..15) + 13 + 1 + + + ODR12 + Port output data (y = + 0..15) + 12 + 1 + + + ODR11 + Port output data (y = + 0..15) + 11 + 1 + + + ODR10 + Port output data (y = + 0..15) + 10 + 1 + + + ODR9 + Port output data (y = + 0..15) + 9 + 1 + + + ODR8 + Port output data (y = + 0..15) + 8 + 1 + + + ODR7 + Port output data (y = + 0..15) + 7 + 1 + + + ODR6 + Port output data (y = + 0..15) + 6 + 1 + + + ODR5 + Port output data (y = + 0..15) + 5 + 1 + + + ODR4 + Port output data (y = + 0..15) + 4 + 1 + + + ODR3 + Port output data (y = + 0..15) + 3 + 1 + + + ODR2 + Port output data (y = + 0..15) + 2 + 1 + + + ODR1 + Port output data (y = + 0..15) + 1 + 1 + + + ODR0 + Port output data (y = + 0..15) + 0 + 1 + + + + + BSRR + BSRR + GPIO port bit set/reset + register + 0x18 + 0x20 + write-only + 0x00000000 + + + BR15 + Port x reset bit y (y = + 0..15) + 31 + 1 + + + BR14 + Port x reset bit y (y = + 0..15) + 30 + 1 + + + BR13 + Port x reset bit y (y = + 0..15) + 29 + 1 + + + BR12 + Port x reset bit y (y = + 0..15) + 28 + 1 + + + BR11 + Port x reset bit y (y = + 0..15) + 27 + 1 + + + BR10 + Port x reset bit y (y = + 0..15) + 26 + 1 + + + BR9 + Port x reset bit y (y = + 0..15) + 25 + 1 + + + BR8 + Port x reset bit y (y = + 0..15) + 24 + 1 + + + BR7 + Port x reset bit y (y = + 0..15) + 23 + 1 + + + BR6 + Port x reset bit y (y = + 0..15) + 22 + 1 + + + BR5 + Port x reset bit y (y = + 0..15) + 21 + 1 + + + BR4 + Port x reset bit y (y = + 0..15) + 20 + 1 + + + BR3 + Port x reset bit y (y = + 0..15) + 19 + 1 + + + BR2 + Port x reset bit y (y = + 0..15) + 18 + 1 + + + BR1 + Port x reset bit y (y = + 0..15) + 17 + 1 + + + BR0 + Port x set bit y (y= + 0..15) + 16 + 1 + + + BS15 + Port x set bit y (y= + 0..15) + 15 + 1 + + + BS14 + Port x set bit y (y= + 0..15) + 14 + 1 + + + BS13 + Port x set bit y (y= + 0..15) + 13 + 1 + + + BS12 + Port x set bit y (y= + 0..15) + 12 + 1 + + + BS11 + Port x set bit y (y= + 0..15) + 11 + 1 + + + BS10 + Port x set bit y (y= + 0..15) + 10 + 1 + + + BS9 + Port x set bit y (y= + 0..15) + 9 + 1 + + + BS8 + Port x set bit y (y= + 0..15) + 8 + 1 + + + BS7 + Port x set bit y (y= + 0..15) + 7 + 1 + + + BS6 + Port x set bit y (y= + 0..15) + 6 + 1 + + + BS5 + Port x set bit y (y= + 0..15) + 5 + 1 + + + BS4 + Port x set bit y (y= + 0..15) + 4 + 1 + + + BS3 + Port x set bit y (y= + 0..15) + 3 + 1 + + + BS2 + Port x set bit y (y= + 0..15) + 2 + 1 + + + BS1 + Port x set bit y (y= + 0..15) + 1 + 1 + + + BS0 + Port x set bit y (y= + 0..15) + 0 + 1 + + + + + LCKR + LCKR + GPIO port configuration lock + register + 0x1C + 0x20 + read-write + 0x00000000 + + + LCKK + Port x lock bit y (y= + 0..15) + 16 + 1 + + + LCK15 + Port x lock bit y (y= + 0..15) + 15 + 1 + + + LCK14 + Port x lock bit y (y= + 0..15) + 14 + 1 + + + LCK13 + Port x lock bit y (y= + 0..15) + 13 + 1 + + + LCK12 + Port x lock bit y (y= + 0..15) + 12 + 1 + + + LCK11 + Port x lock bit y (y= + 0..15) + 11 + 1 + + + LCK10 + Port x lock bit y (y= + 0..15) + 10 + 1 + + + LCK9 + Port x lock bit y (y= + 0..15) + 9 + 1 + + + LCK8 + Port x lock bit y (y= + 0..15) + 8 + 1 + + + LCK7 + Port x lock bit y (y= + 0..15) + 7 + 1 + + + LCK6 + Port x lock bit y (y= + 0..15) + 6 + 1 + + + LCK5 + Port x lock bit y (y= + 0..15) + 5 + 1 + + + LCK4 + Port x lock bit y (y= + 0..15) + 4 + 1 + + + LCK3 + Port x lock bit y (y= + 0..15) + 3 + 1 + + + LCK2 + Port x lock bit y (y= + 0..15) + 2 + 1 + + + LCK1 + Port x lock bit y (y= + 0..15) + 1 + 1 + + + LCK0 + Port x lock bit y (y= + 0..15) + 0 + 1 + + + + + AFRL + AFRL + GPIO alternate function low + register + 0x20 + 0x20 + read-write + 0x00000000 + + + AFRL7 + Alternate function selection for port x + bit y (y = 0..7) + 28 + 4 + + + AFRL6 + Alternate function selection for port x + bit y (y = 0..7) + 24 + 4 + + + AFRL5 + Alternate function selection for port x + bit y (y = 0..7) + 20 + 4 + + + AFRL4 + Alternate function selection for port x + bit y (y = 0..7) + 16 + 4 + + + AFRL3 + Alternate function selection for port x + bit y (y = 0..7) + 12 + 4 + + + AFRL2 + Alternate function selection for port x + bit y (y = 0..7) + 8 + 4 + + + AFRL1 + Alternate function selection for port x + bit y (y = 0..7) + 4 + 4 + + + AFRL0 + Alternate function selection for port x + bit y (y = 0..7) + 0 + 4 + + + + + AFRH + AFRH + GPIO alternate function high + register + 0x24 + 0x20 + read-write + 0x00000000 + + + AFRH15 + Alternate function selection for port x + bit y (y = 8..15) + 28 + 4 + + + AFRH14 + Alternate function selection for port x + bit y (y = 8..15) + 24 + 4 + + + AFRH13 + Alternate function selection for port x + bit y (y = 8..15) + 20 + 4 + + + AFRH12 + Alternate function selection for port x + bit y (y = 8..15) + 16 + 4 + + + AFRH11 + Alternate function selection for port x + bit y (y = 8..15) + 12 + 4 + + + AFRH10 + Alternate function selection for port x + bit y (y = 8..15) + 8 + 4 + + + AFRH9 + Alternate function selection for port x + bit y (y = 8..15) + 4 + 4 + + + AFRH8 + Alternate function selection for port x + bit y (y = 8..15) + 0 + 4 + + + + + + + GPIOA + General-purpose I/Os + GPIO + 0x40020000 + + 0x0 + 0x400 + registers + + + + MODER + MODER + GPIO port mode register + 0x0 + 0x20 + read-write + 0xA8000000 + + + MODER15 + Port x configuration bits (y = + 0..15) + 30 + 2 + + + MODER14 + Port x configuration bits (y = + 0..15) + 28 + 2 + + + MODER13 + Port x configuration bits (y = + 0..15) + 26 + 2 + + + MODER12 + Port x configuration bits (y = + 0..15) + 24 + 2 + + + MODER11 + Port x configuration bits (y = + 0..15) + 22 + 2 + + + MODER10 + Port x configuration bits (y = + 0..15) + 20 + 2 + + + MODER9 + Port x configuration bits (y = + 0..15) + 18 + 2 + + + MODER8 + Port x configuration bits (y = + 0..15) + 16 + 2 + + + MODER7 + Port x configuration bits (y = + 0..15) + 14 + 2 + + + MODER6 + Port x configuration bits (y = + 0..15) + 12 + 2 + + + MODER5 + Port x configuration bits (y = + 0..15) + 10 + 2 + + + MODER4 + Port x configuration bits (y = + 0..15) + 8 + 2 + + + MODER3 + Port x configuration bits (y = + 0..15) + 6 + 2 + + + MODER2 + Port x configuration bits (y = + 0..15) + 4 + 2 + + + MODER1 + Port x configuration bits (y = + 0..15) + 2 + 2 + + + MODER0 + Port x configuration bits (y = + 0..15) + 0 + 2 + + + + + OTYPER + OTYPER + GPIO port output type register + 0x4 + 0x20 + read-write + 0x00000000 + + + OT15 + Port x configuration bits (y = + 0..15) + 15 + 1 + + + OT14 + Port x configuration bits (y = + 0..15) + 14 + 1 + + + OT13 + Port x configuration bits (y = + 0..15) + 13 + 1 + + + OT12 + Port x configuration bits (y = + 0..15) + 12 + 1 + + + OT11 + Port x configuration bits (y = + 0..15) + 11 + 1 + + + OT10 + Port x configuration bits (y = + 0..15) + 10 + 1 + + + OT9 + Port x configuration bits (y = + 0..15) + 9 + 1 + + + OT8 + Port x configuration bits (y = + 0..15) + 8 + 1 + + + OT7 + Port x configuration bits (y = + 0..15) + 7 + 1 + + + OT6 + Port x configuration bits (y = + 0..15) + 6 + 1 + + + OT5 + Port x configuration bits (y = + 0..15) + 5 + 1 + + + OT4 + Port x configuration bits (y = + 0..15) + 4 + 1 + + + OT3 + Port x configuration bits (y = + 0..15) + 3 + 1 + + + OT2 + Port x configuration bits (y = + 0..15) + 2 + 1 + + + OT1 + Port x configuration bits (y = + 0..15) + 1 + 1 + + + OT0 + Port x configuration bits (y = + 0..15) + 0 + 1 + + + + + OSPEEDR + OSPEEDR + GPIO port output speed + register + 0x8 + 0x20 + read-write + 0x00000000 + + + OSPEEDR15 + Port x configuration bits (y = + 0..15) + 30 + 2 + + + OSPEEDR14 + Port x configuration bits (y = + 0..15) + 28 + 2 + + + OSPEEDR13 + Port x configuration bits (y = + 0..15) + 26 + 2 + + + OSPEEDR12 + Port x configuration bits (y = + 0..15) + 24 + 2 + + + OSPEEDR11 + Port x configuration bits (y = + 0..15) + 22 + 2 + + + OSPEEDR10 + Port x configuration bits (y = + 0..15) + 20 + 2 + + + OSPEEDR9 + Port x configuration bits (y = + 0..15) + 18 + 2 + + + OSPEEDR8 + Port x configuration bits (y = + 0..15) + 16 + 2 + + + OSPEEDR7 + Port x configuration bits (y = + 0..15) + 14 + 2 + + + OSPEEDR6 + Port x configuration bits (y = + 0..15) + 12 + 2 + + + OSPEEDR5 + Port x configuration bits (y = + 0..15) + 10 + 2 + + + OSPEEDR4 + Port x configuration bits (y = + 0..15) + 8 + 2 + + + OSPEEDR3 + Port x configuration bits (y = + 0..15) + 6 + 2 + + + OSPEEDR2 + Port x configuration bits (y = + 0..15) + 4 + 2 + + + OSPEEDR1 + Port x configuration bits (y = + 0..15) + 2 + 2 + + + OSPEEDR0 + Port x configuration bits (y = + 0..15) + 0 + 2 + + + + + PUPDR + PUPDR + GPIO port pull-up/pull-down + register + 0xC + 0x20 + read-write + 0x64000000 + + + PUPDR15 + Port x configuration bits (y = + 0..15) + 30 + 2 + + + PUPDR14 + Port x configuration bits (y = + 0..15) + 28 + 2 + + + PUPDR13 + Port x configuration bits (y = + 0..15) + 26 + 2 + + + PUPDR12 + Port x configuration bits (y = + 0..15) + 24 + 2 + + + PUPDR11 + Port x configuration bits (y = + 0..15) + 22 + 2 + + + PUPDR10 + Port x configuration bits (y = + 0..15) + 20 + 2 + + + PUPDR9 + Port x configuration bits (y = + 0..15) + 18 + 2 + + + PUPDR8 + Port x configuration bits (y = + 0..15) + 16 + 2 + + + PUPDR7 + Port x configuration bits (y = + 0..15) + 14 + 2 + + + PUPDR6 + Port x configuration bits (y = + 0..15) + 12 + 2 + + + PUPDR5 + Port x configuration bits (y = + 0..15) + 10 + 2 + + + PUPDR4 + Port x configuration bits (y = + 0..15) + 8 + 2 + + + PUPDR3 + Port x configuration bits (y = + 0..15) + 6 + 2 + + + PUPDR2 + Port x configuration bits (y = + 0..15) + 4 + 2 + + + PUPDR1 + Port x configuration bits (y = + 0..15) + 2 + 2 + + + PUPDR0 + Port x configuration bits (y = + 0..15) + 0 + 2 + + + + + IDR + IDR + GPIO port input data register + 0x10 + 0x20 + read-only + 0x00000000 + + + IDR15 + Port input data (y = + 0..15) + 15 + 1 + + + IDR14 + Port input data (y = + 0..15) + 14 + 1 + + + IDR13 + Port input data (y = + 0..15) + 13 + 1 + + + IDR12 + Port input data (y = + 0..15) + 12 + 1 + + + IDR11 + Port input data (y = + 0..15) + 11 + 1 + + + IDR10 + Port input data (y = + 0..15) + 10 + 1 + + + IDR9 + Port input data (y = + 0..15) + 9 + 1 + + + IDR8 + Port input data (y = + 0..15) + 8 + 1 + + + IDR7 + Port input data (y = + 0..15) + 7 + 1 + + + IDR6 + Port input data (y = + 0..15) + 6 + 1 + + + IDR5 + Port input data (y = + 0..15) + 5 + 1 + + + IDR4 + Port input data (y = + 0..15) + 4 + 1 + + + IDR3 + Port input data (y = + 0..15) + 3 + 1 + + + IDR2 + Port input data (y = + 0..15) + 2 + 1 + + + IDR1 + Port input data (y = + 0..15) + 1 + 1 + + + IDR0 + Port input data (y = + 0..15) + 0 + 1 + + + + + ODR + ODR + GPIO port output data register + 0x14 + 0x20 + read-write + 0x00000000 + + + ODR15 + Port output data (y = + 0..15) + 15 + 1 + + + ODR14 + Port output data (y = + 0..15) + 14 + 1 + + + ODR13 + Port output data (y = + 0..15) + 13 + 1 + + + ODR12 + Port output data (y = + 0..15) + 12 + 1 + + + ODR11 + Port output data (y = + 0..15) + 11 + 1 + + + ODR10 + Port output data (y = + 0..15) + 10 + 1 + + + ODR9 + Port output data (y = + 0..15) + 9 + 1 + + + ODR8 + Port output data (y = + 0..15) + 8 + 1 + + + ODR7 + Port output data (y = + 0..15) + 7 + 1 + + + ODR6 + Port output data (y = + 0..15) + 6 + 1 + + + ODR5 + Port output data (y = + 0..15) + 5 + 1 + + + ODR4 + Port output data (y = + 0..15) + 4 + 1 + + + ODR3 + Port output data (y = + 0..15) + 3 + 1 + + + ODR2 + Port output data (y = + 0..15) + 2 + 1 + + + ODR1 + Port output data (y = + 0..15) + 1 + 1 + + + ODR0 + Port output data (y = + 0..15) + 0 + 1 + + + + + BSRR + BSRR + GPIO port bit set/reset + register + 0x18 + 0x20 + write-only + 0x00000000 + + + BR15 + Port x reset bit y (y = + 0..15) + 31 + 1 + + + BR14 + Port x reset bit y (y = + 0..15) + 30 + 1 + + + BR13 + Port x reset bit y (y = + 0..15) + 29 + 1 + + + BR12 + Port x reset bit y (y = + 0..15) + 28 + 1 + + + BR11 + Port x reset bit y (y = + 0..15) + 27 + 1 + + + BR10 + Port x reset bit y (y = + 0..15) + 26 + 1 + + + BR9 + Port x reset bit y (y = + 0..15) + 25 + 1 + + + BR8 + Port x reset bit y (y = + 0..15) + 24 + 1 + + + BR7 + Port x reset bit y (y = + 0..15) + 23 + 1 + + + BR6 + Port x reset bit y (y = + 0..15) + 22 + 1 + + + BR5 + Port x reset bit y (y = + 0..15) + 21 + 1 + + + BR4 + Port x reset bit y (y = + 0..15) + 20 + 1 + + + BR3 + Port x reset bit y (y = + 0..15) + 19 + 1 + + + BR2 + Port x reset bit y (y = + 0..15) + 18 + 1 + + + BR1 + Port x reset bit y (y = + 0..15) + 17 + 1 + + + BR0 + Port x set bit y (y= + 0..15) + 16 + 1 + + + BS15 + Port x set bit y (y= + 0..15) + 15 + 1 + + + BS14 + Port x set bit y (y= + 0..15) + 14 + 1 + + + BS13 + Port x set bit y (y= + 0..15) + 13 + 1 + + + BS12 + Port x set bit y (y= + 0..15) + 12 + 1 + + + BS11 + Port x set bit y (y= + 0..15) + 11 + 1 + + + BS10 + Port x set bit y (y= + 0..15) + 10 + 1 + + + BS9 + Port x set bit y (y= + 0..15) + 9 + 1 + + + BS8 + Port x set bit y (y= + 0..15) + 8 + 1 + + + BS7 + Port x set bit y (y= + 0..15) + 7 + 1 + + + BS6 + Port x set bit y (y= + 0..15) + 6 + 1 + + + BS5 + Port x set bit y (y= + 0..15) + 5 + 1 + + + BS4 + Port x set bit y (y= + 0..15) + 4 + 1 + + + BS3 + Port x set bit y (y= + 0..15) + 3 + 1 + + + BS2 + Port x set bit y (y= + 0..15) + 2 + 1 + + + BS1 + Port x set bit y (y= + 0..15) + 1 + 1 + + + BS0 + Port x set bit y (y= + 0..15) + 0 + 1 + + + + + LCKR + LCKR + GPIO port configuration lock + register + 0x1C + 0x20 + read-write + 0x00000000 + + + LCKK + Port x lock bit y (y= + 0..15) + 16 + 1 + + + LCK15 + Port x lock bit y (y= + 0..15) + 15 + 1 + + + LCK14 + Port x lock bit y (y= + 0..15) + 14 + 1 + + + LCK13 + Port x lock bit y (y= + 0..15) + 13 + 1 + + + LCK12 + Port x lock bit y (y= + 0..15) + 12 + 1 + + + LCK11 + Port x lock bit y (y= + 0..15) + 11 + 1 + + + LCK10 + Port x lock bit y (y= + 0..15) + 10 + 1 + + + LCK9 + Port x lock bit y (y= + 0..15) + 9 + 1 + + + LCK8 + Port x lock bit y (y= + 0..15) + 8 + 1 + + + LCK7 + Port x lock bit y (y= + 0..15) + 7 + 1 + + + LCK6 + Port x lock bit y (y= + 0..15) + 6 + 1 + + + LCK5 + Port x lock bit y (y= + 0..15) + 5 + 1 + + + LCK4 + Port x lock bit y (y= + 0..15) + 4 + 1 + + + LCK3 + Port x lock bit y (y= + 0..15) + 3 + 1 + + + LCK2 + Port x lock bit y (y= + 0..15) + 2 + 1 + + + LCK1 + Port x lock bit y (y= + 0..15) + 1 + 1 + + + LCK0 + Port x lock bit y (y= + 0..15) + 0 + 1 + + + + + AFRL + AFRL + GPIO alternate function low + register + 0x20 + 0x20 + read-write + 0x00000000 + + + AFRL7 + Alternate function selection for port x + bit y (y = 0..7) + 28 + 4 + + + AFRL6 + Alternate function selection for port x + bit y (y = 0..7) + 24 + 4 + + + AFRL5 + Alternate function selection for port x + bit y (y = 0..7) + 20 + 4 + + + AFRL4 + Alternate function selection for port x + bit y (y = 0..7) + 16 + 4 + + + AFRL3 + Alternate function selection for port x + bit y (y = 0..7) + 12 + 4 + + + AFRL2 + Alternate function selection for port x + bit y (y = 0..7) + 8 + 4 + + + AFRL1 + Alternate function selection for port x + bit y (y = 0..7) + 4 + 4 + + + AFRL0 + Alternate function selection for port x + bit y (y = 0..7) + 0 + 4 + + + + + AFRH + AFRH + GPIO alternate function high + register + 0x24 + 0x20 + read-write + 0x00000000 + + + AFRH15 + Alternate function selection for port x + bit y (y = 8..15) + 28 + 4 + + + AFRH14 + Alternate function selection for port x + bit y (y = 8..15) + 24 + 4 + + + AFRH13 + Alternate function selection for port x + bit y (y = 8..15) + 20 + 4 + + + AFRH12 + Alternate function selection for port x + bit y (y = 8..15) + 16 + 4 + + + AFRH11 + Alternate function selection for port x + bit y (y = 8..15) + 12 + 4 + + + AFRH10 + Alternate function selection for port x + bit y (y = 8..15) + 8 + 4 + + + AFRH9 + Alternate function selection for port x + bit y (y = 8..15) + 4 + 4 + + + AFRH8 + Alternate function selection for port x + bit y (y = 8..15) + 0 + 4 + + + + + + + SYSCFG + System configuration controller + SYSCFG + 0x40013800 + + 0x0 + 0x400 + registers + + + + MEMRM + MEMRM + memory remap register + 0x0 + 0x20 + read-write + 0x00000000 + + + MEM_MODE + MEM_MODE + 0 + 2 + + + + + PMC + PMC + peripheral mode configuration + register + 0x4 + 0x20 + read-write + 0x00000000 + + + MII_RMII_SEL + Ethernet PHY interface + selection + 23 + 1 + + + + + EXTICR1 + EXTICR1 + external interrupt configuration register + 1 + 0x8 + 0x20 + read-write + 0x0000 + + + EXTI3 + EXTI x configuration (x = 0 to + 3) + 12 + 4 + + + EXTI2 + EXTI x configuration (x = 0 to + 3) + 8 + 4 + + + EXTI1 + EXTI x configuration (x = 0 to + 3) + 4 + 4 + + + EXTI0 + EXTI x configuration (x = 0 to + 3) + 0 + 4 + + + + + EXTICR2 + EXTICR2 + external interrupt configuration register + 2 + 0xC + 0x20 + read-write + 0x0000 + + + EXTI7 + EXTI x configuration (x = 4 to + 7) + 12 + 4 + + + EXTI6 + EXTI x configuration (x = 4 to + 7) + 8 + 4 + + + EXTI5 + EXTI x configuration (x = 4 to + 7) + 4 + 4 + + + EXTI4 + EXTI x configuration (x = 4 to + 7) + 0 + 4 + + + + + EXTICR3 + EXTICR3 + external interrupt configuration register + 3 + 0x10 + 0x20 + read-write + 0x0000 + + + EXTI11 + EXTI x configuration (x = 8 to + 11) + 12 + 4 + + + EXTI10 + EXTI10 + 8 + 4 + + + EXTI9 + EXTI x configuration (x = 8 to + 11) + 4 + 4 + + + EXTI8 + EXTI x configuration (x = 8 to + 11) + 0 + 4 + + + + + EXTICR4 + EXTICR4 + external interrupt configuration register + 4 + 0x14 + 0x20 + read-write + 0x0000 + + + EXTI15 + EXTI x configuration (x = 12 to + 15) + 12 + 4 + + + EXTI14 + EXTI x configuration (x = 12 to + 15) + 8 + 4 + + + EXTI13 + EXTI x configuration (x = 12 to + 15) + 4 + 4 + + + EXTI12 + EXTI x configuration (x = 12 to + 15) + 0 + 4 + + + + + CMPCR + CMPCR + Compensation cell control + register + 0x20 + 0x20 + read-only + 0x00000000 + + + READY + READY + 8 + 1 + + + CMP_PD + Compensation cell + power-down + 0 + 1 + + + + + + + SPI1 + Serial peripheral interface + SPI + 0x40013000 + + 0x0 + 0x400 + registers + + + SPI1 + SPI1 global interrupt + 35 + + + + CR1 + CR1 + control register 1 + 0x0 + 0x20 + read-write + 0x0000 + + + BIDIMODE + Bidirectional data mode + enable + 15 + 1 + + + BIDIOE + Output enable in bidirectional + mode + 14 + 1 + + + CRCEN + Hardware CRC calculation + enable + 13 + 1 + + + CRCNEXT + CRC transfer next + 12 + 1 + + + DFF + Data frame format + 11 + 1 + + + RXONLY + Receive only + 10 + 1 + + + SSM + Software slave management + 9 + 1 + + + SSI + Internal slave select + 8 + 1 + + + LSBFIRST + Frame format + 7 + 1 + + + SPE + SPI enable + 6 + 1 + + + BR + Baud rate control + 3 + 3 + + + MSTR + Master selection + 2 + 1 + + + CPOL + Clock polarity + 1 + 1 + + + CPHA + Clock phase + 0 + 1 + + + + + CR2 + CR2 + control register 2 + 0x4 + 0x20 + read-write + 0x0000 + + + TXEIE + Tx buffer empty interrupt + enable + 7 + 1 + + + RXNEIE + RX buffer not empty interrupt + enable + 6 + 1 + + + ERRIE + Error interrupt enable + 5 + 1 + + + FRF + Frame format + 4 + 1 + + + SSOE + SS output enable + 2 + 1 + + + TXDMAEN + Tx buffer DMA enable + 1 + 1 + + + RXDMAEN + Rx buffer DMA enable + 0 + 1 + + + + + SR + SR + status register + 0x8 + 0x20 + 0x0002 + + + TIFRFE + TI frame format error + 8 + 1 + read-only + + + BSY + Busy flag + 7 + 1 + read-only + + + OVR + Overrun flag + 6 + 1 + read-only + + + MODF + Mode fault + 5 + 1 + read-only + + + CRCERR + CRC error flag + 4 + 1 + read-write + + + UDR + Underrun flag + 3 + 1 + read-only + + + CHSIDE + Channel side + 2 + 1 + read-only + + + TXE + Transmit buffer empty + 1 + 1 + read-only + + + RXNE + Receive buffer not empty + 0 + 1 + read-only + + + + + DR + DR + data register + 0xC + 0x20 + read-write + 0x0000 + + + DR + Data register + 0 + 16 + + + + + CRCPR + CRCPR + CRC polynomial register + 0x10 + 0x20 + read-write + 0x0007 + + + CRCPOLY + CRC polynomial register + 0 + 16 + + + + + RXCRCR + RXCRCR + RX CRC register + 0x14 + 0x20 + read-only + 0x0000 + + + RxCRC + Rx CRC register + 0 + 16 + + + + + TXCRCR + TXCRCR + TX CRC register + 0x18 + 0x20 + read-only + 0x0000 + + + TxCRC + Tx CRC register + 0 + 16 + + + + + I2SCFGR + I2SCFGR + I2S configuration register + 0x1C + 0x20 + read-write + 0x0000 + + + I2SMOD + I2S mode selection + 11 + 1 + + + I2SE + I2S Enable + 10 + 1 + + + I2SCFG + I2S configuration mode + 8 + 2 + + + PCMSYNC + PCM frame synchronization + 7 + 1 + + + I2SSTD + I2S standard selection + 4 + 2 + + + CKPOL + Steady state clock + polarity + 3 + 1 + + + DATLEN + Data length to be + transferred + 1 + 2 + + + CHLEN + Channel length (number of bits per audio + channel) + 0 + 1 + + + + + I2SPR + I2SPR + I2S prescaler register + 0x20 + 0x20 + read-write + 00000010 + + + MCKOE + Master clock output enable + 9 + 1 + + + ODD + Odd factor for the + prescaler + 8 + 1 + + + I2SDIV + I2S Linear prescaler + 0 + 8 + + + + + + + SPI2 + 0x40003800 + + SPI2 + SPI2 global interrupt + 36 + + + + SPI3 + 0x40003C00 + + SPI3 + SPI3 global interrupt + 51 + + + + I2S2ext + 0x40003400 + + + I2S3ext + 0x40004000 + + + SDIO + Secure digital input/output + interface + SDIO + 0x40012C00 + + 0x0 + 0x400 + registers + + + SDIO + SDIO global interrupt + 49 + + + + POWER + POWER + power control register + 0x0 + 0x20 + read-write + 0x00000000 + + + PWRCTRL + PWRCTRL + 0 + 2 + + + + + CLKCR + CLKCR + SDI clock control register + 0x4 + 0x20 + read-write + 0x00000000 + + + HWFC_EN + HW Flow Control enable + 14 + 1 + + + NEGEDGE + SDIO_CK dephasing selection + bit + 13 + 1 + + + WIDBUS + Wide bus mode enable bit + 11 + 2 + + + BYPASS + Clock divider bypass enable + bit + 10 + 1 + + + PWRSAV + Power saving configuration + bit + 9 + 1 + + + CLKEN + Clock enable bit + 8 + 1 + + + CLKDIV + Clock divide factor + 0 + 8 + + + + + ARG + ARG + argument register + 0x8 + 0x20 + read-write + 0x00000000 + + + CMDARG + Command argument + 0 + 32 + + + + + CMD + CMD + command register + 0xC + 0x20 + read-write + 0x00000000 + + + CE_ATACMD + CE-ATA command + 14 + 1 + + + nIEN + not Interrupt Enable + 13 + 1 + + + ENCMDcompl + Enable CMD completion + 12 + 1 + + + SDIOSuspend + SD I/O suspend command + 11 + 1 + + + CPSMEN + Command path state machine (CPSM) Enable + bit + 10 + 1 + + + WAITPEND + CPSM Waits for ends of data transfer + (CmdPend internal signal). + 9 + 1 + + + WAITINT + CPSM waits for interrupt + request + 8 + 1 + + + WAITRESP + Wait for response bits + 6 + 2 + + + CMDINDEX + Command index + 0 + 6 + + + + + RESPCMD + RESPCMD + command response register + 0x10 + 0x20 + read-only + 0x00000000 + + + RESPCMD + Response command index + 0 + 6 + + + + + RESP1 + RESP1 + response 1..4 register + 0x14 + 0x20 + read-only + 0x00000000 + + + CARDSTATUS1 + see Table 132. + 0 + 32 + + + + + RESP2 + RESP2 + response 1..4 register + 0x18 + 0x20 + read-only + 0x00000000 + + + CARDSTATUS2 + see Table 132. + 0 + 32 + + + + + RESP3 + RESP3 + response 1..4 register + 0x1C + 0x20 + read-only + 0x00000000 + + + CARDSTATUS3 + see Table 132. + 0 + 32 + + + + + RESP4 + RESP4 + response 1..4 register + 0x20 + 0x20 + read-only + 0x00000000 + + + CARDSTATUS4 + see Table 132. + 0 + 32 + + + + + DTIMER + DTIMER + data timer register + 0x24 + 0x20 + read-write + 0x00000000 + + + DATATIME + Data timeout period + 0 + 32 + + + + + DLEN + DLEN + data length register + 0x28 + 0x20 + read-write + 0x00000000 + + + DATALENGTH + Data length value + 0 + 25 + + + + + DCTRL + DCTRL + data control register + 0x2C + 0x20 + read-write + 0x00000000 + + + SDIOEN + SD I/O enable functions + 11 + 1 + + + RWMOD + Read wait mode + 10 + 1 + + + RWSTOP + Read wait stop + 9 + 1 + + + RWSTART + Read wait start + 8 + 1 + + + DBLOCKSIZE + Data block size + 4 + 4 + + + DMAEN + DMA enable bit + 3 + 1 + + + DTMODE + Data transfer mode selection 1: Stream + or SDIO multibyte data transfer. + 2 + 1 + + + DTDIR + Data transfer direction + selection + 1 + 1 + + + DTEN + DTEN + 0 + 1 + + + + + DCOUNT + DCOUNT + data counter register + 0x30 + 0x20 + read-only + 0x00000000 + + + DATACOUNT + Data count value + 0 + 25 + + + + + STA + STA + status register + 0x34 + 0x20 + read-only + 0x00000000 + + + CEATAEND + CE-ATA command completion signal + received for CMD61 + 23 + 1 + + + SDIOIT + SDIO interrupt received + 22 + 1 + + + RXDAVL + Data available in receive + FIFO + 21 + 1 + + + TXDAVL + Data available in transmit + FIFO + 20 + 1 + + + RXFIFOE + Receive FIFO empty + 19 + 1 + + + TXFIFOE + Transmit FIFO empty + 18 + 1 + + + RXFIFOF + Receive FIFO full + 17 + 1 + + + TXFIFOF + Transmit FIFO full + 16 + 1 + + + RXFIFOHF + Receive FIFO half full: there are at + least 8 words in the FIFO + 15 + 1 + + + TXFIFOHE + Transmit FIFO half empty: at least 8 + words can be written into the FIFO + 14 + 1 + + + RXACT + Data receive in progress + 13 + 1 + + + TXACT + Data transmit in progress + 12 + 1 + + + CMDACT + Command transfer in + progress + 11 + 1 + + + DBCKEND + Data block sent/received (CRC check + passed) + 10 + 1 + + + STBITERR + Start bit not detected on all data + signals in wide bus mode + 9 + 1 + + + DATAEND + Data end (data counter, SDIDCOUNT, is + zero) + 8 + 1 + + + CMDSENT + Command sent (no response + required) + 7 + 1 + + + CMDREND + Command response received (CRC check + passed) + 6 + 1 + + + RXOVERR + Received FIFO overrun + error + 5 + 1 + + + TXUNDERR + Transmit FIFO underrun + error + 4 + 1 + + + DTIMEOUT + Data timeout + 3 + 1 + + + CTIMEOUT + Command response timeout + 2 + 1 + + + DCRCFAIL + Data block sent/received (CRC check + failed) + 1 + 1 + + + CCRCFAIL + Command response received (CRC check + failed) + 0 + 1 + + + + + ICR + ICR + interrupt clear register + 0x38 + 0x20 + read-write + 0x00000000 + + + CEATAENDC + CEATAEND flag clear bit + 23 + 1 + + + SDIOITC + SDIOIT flag clear bit + 22 + 1 + + + DBCKENDC + DBCKEND flag clear bit + 10 + 1 + + + STBITERRC + STBITERR flag clear bit + 9 + 1 + + + DATAENDC + DATAEND flag clear bit + 8 + 1 + + + CMDSENTC + CMDSENT flag clear bit + 7 + 1 + + + CMDRENDC + CMDREND flag clear bit + 6 + 1 + + + RXOVERRC + RXOVERR flag clear bit + 5 + 1 + + + TXUNDERRC + TXUNDERR flag clear bit + 4 + 1 + + + DTIMEOUTC + DTIMEOUT flag clear bit + 3 + 1 + + + CTIMEOUTC + CTIMEOUT flag clear bit + 2 + 1 + + + DCRCFAILC + DCRCFAIL flag clear bit + 1 + 1 + + + CCRCFAILC + CCRCFAIL flag clear bit + 0 + 1 + + + + + MASK + MASK + mask register + 0x3C + 0x20 + read-write + 0x00000000 + + + CEATAENDIE + CE-ATA command completion signal + received interrupt enable + 23 + 1 + + + SDIOITIE + SDIO mode interrupt received interrupt + enable + 22 + 1 + + + RXDAVLIE + Data available in Rx FIFO interrupt + enable + 21 + 1 + + + TXDAVLIE + Data available in Tx FIFO interrupt + enable + 20 + 1 + + + RXFIFOEIE + Rx FIFO empty interrupt + enable + 19 + 1 + + + TXFIFOEIE + Tx FIFO empty interrupt + enable + 18 + 1 + + + RXFIFOFIE + Rx FIFO full interrupt + enable + 17 + 1 + + + TXFIFOFIE + Tx FIFO full interrupt + enable + 16 + 1 + + + RXFIFOHFIE + Rx FIFO half full interrupt + enable + 15 + 1 + + + TXFIFOHEIE + Tx FIFO half empty interrupt + enable + 14 + 1 + + + RXACTIE + Data receive acting interrupt + enable + 13 + 1 + + + TXACTIE + Data transmit acting interrupt + enable + 12 + 1 + + + CMDACTIE + Command acting interrupt + enable + 11 + 1 + + + DBCKENDIE + Data block end interrupt + enable + 10 + 1 + + + STBITERRIE + Start bit error interrupt + enable + 9 + 1 + + + DATAENDIE + Data end interrupt enable + 8 + 1 + + + CMDSENTIE + Command sent interrupt + enable + 7 + 1 + + + CMDRENDIE + Command response received interrupt + enable + 6 + 1 + + + RXOVERRIE + Rx FIFO overrun error interrupt + enable + 5 + 1 + + + TXUNDERRIE + Tx FIFO underrun error interrupt + enable + 4 + 1 + + + DTIMEOUTIE + Data timeout interrupt + enable + 3 + 1 + + + CTIMEOUTIE + Command timeout interrupt + enable + 2 + 1 + + + DCRCFAILIE + Data CRC fail interrupt + enable + 1 + 1 + + + CCRCFAILIE + Command CRC fail interrupt + enable + 0 + 1 + + + + + FIFOCNT + FIFOCNT + FIFO counter register + 0x48 + 0x20 + read-only + 0x00000000 + + + FIFOCOUNT + Remaining number of words to be written + to or read from the FIFO. + 0 + 24 + + + + + FIFO + FIFO + data FIFO register + 0x80 + 0x20 + read-write + 0x00000000 + + + FIFOData + Receive and transmit FIFO + data + 0 + 32 + + + + + + + ADC1 + Analog-to-digital converter + ADC + 0x40012000 + + 0x0 + 0x400 + registers + + + ADC + ADC1 global interrupt + 18 + + + + SR + SR + status register + 0x0 + 0x20 + read-write + 0x00000000 + + + OVR + Overrun + 5 + 1 + + + STRT + Regular channel start flag + 4 + 1 + + + JSTRT + Injected channel start + flag + 3 + 1 + + + JEOC + Injected channel end of + conversion + 2 + 1 + + + EOC + Regular channel end of + conversion + 1 + 1 + + + AWD + Analog watchdog flag + 0 + 1 + + + + + CR1 + CR1 + control register 1 + 0x4 + 0x20 + read-write + 0x00000000 + + + OVRIE + Overrun interrupt enable + 26 + 1 + + + RES + Resolution + 24 + 2 + + + AWDEN + Analog watchdog enable on regular + channels + 23 + 1 + + + JAWDEN + Analog watchdog enable on injected + channels + 22 + 1 + + + DISCNUM + Discontinuous mode channel + count + 13 + 3 + + + JDISCEN + Discontinuous mode on injected + channels + 12 + 1 + + + DISCEN + Discontinuous mode on regular + channels + 11 + 1 + + + JAUTO + Automatic injected group + conversion + 10 + 1 + + + AWDSGL + Enable the watchdog on a single channel + in scan mode + 9 + 1 + + + SCAN + Scan mode + 8 + 1 + + + JEOCIE + Interrupt enable for injected + channels + 7 + 1 + + + AWDIE + Analog watchdog interrupt + enable + 6 + 1 + + + EOCIE + Interrupt enable for EOC + 5 + 1 + + + AWDCH + Analog watchdog channel select + bits + 0 + 5 + + + + + CR2 + CR2 + control register 2 + 0x8 + 0x20 + read-write + 0x00000000 + + + SWSTART + Start conversion of regular + channels + 30 + 1 + + + EXTEN + External trigger enable for regular + channels + 28 + 2 + + + EXTSEL + External event select for regular + group + 24 + 4 + + + JSWSTART + Start conversion of injected + channels + 22 + 1 + + + JEXTEN + External trigger enable for injected + channels + 20 + 2 + + + JEXTSEL + External event select for injected + group + 16 + 4 + + + ALIGN + Data alignment + 11 + 1 + + + EOCS + End of conversion + selection + 10 + 1 + + + DDS + DMA disable selection (for single ADC + mode) + 9 + 1 + + + DMA + Direct memory access mode (for single + ADC mode) + 8 + 1 + + + CONT + Continuous conversion + 1 + 1 + + + ADON + A/D Converter ON / OFF + 0 + 1 + + + + + SMPR1 + SMPR1 + sample time register 1 + 0xC + 0x20 + read-write + 0x00000000 + + + SMPx_x + Sample time bits + 0 + 32 + + + + + SMPR2 + SMPR2 + sample time register 2 + 0x10 + 0x20 + read-write + 0x00000000 + + + SMPx_x + Sample time bits + 0 + 32 + + + + + JOFR1 + JOFR1 + injected channel data offset register + x + 0x14 + 0x20 + read-write + 0x00000000 + + + JOFFSET1 + Data offset for injected channel + x + 0 + 12 + + + + + JOFR2 + JOFR2 + injected channel data offset register + x + 0x18 + 0x20 + read-write + 0x00000000 + + + JOFFSET2 + Data offset for injected channel + x + 0 + 12 + + + + + JOFR3 + JOFR3 + injected channel data offset register + x + 0x1C + 0x20 + read-write + 0x00000000 + + + JOFFSET3 + Data offset for injected channel + x + 0 + 12 + + + + + JOFR4 + JOFR4 + injected channel data offset register + x + 0x20 + 0x20 + read-write + 0x00000000 + + + JOFFSET4 + Data offset for injected channel + x + 0 + 12 + + + + + HTR + HTR + watchdog higher threshold + register + 0x24 + 0x20 + read-write + 0x00000FFF + + + HT + Analog watchdog higher + threshold + 0 + 12 + + + + + LTR + LTR + watchdog lower threshold + register + 0x28 + 0x20 + read-write + 0x00000000 + + + LT + Analog watchdog lower + threshold + 0 + 12 + + + + + SQR1 + SQR1 + regular sequence register 1 + 0x2C + 0x20 + read-write + 0x00000000 + + + L + Regular channel sequence + length + 20 + 4 + + + SQ16 + 16th conversion in regular + sequence + 15 + 5 + + + SQ15 + 15th conversion in regular + sequence + 10 + 5 + + + SQ14 + 14th conversion in regular + sequence + 5 + 5 + + + SQ13 + 13th conversion in regular + sequence + 0 + 5 + + + + + SQR2 + SQR2 + regular sequence register 2 + 0x30 + 0x20 + read-write + 0x00000000 + + + SQ12 + 12th conversion in regular + sequence + 25 + 5 + + + SQ11 + 11th conversion in regular + sequence + 20 + 5 + + + SQ10 + 10th conversion in regular + sequence + 15 + 5 + + + SQ9 + 9th conversion in regular + sequence + 10 + 5 + + + SQ8 + 8th conversion in regular + sequence + 5 + 5 + + + SQ7 + 7th conversion in regular + sequence + 0 + 5 + + + + + SQR3 + SQR3 + regular sequence register 3 + 0x34 + 0x20 + read-write + 0x00000000 + + + SQ6 + 6th conversion in regular + sequence + 25 + 5 + + + SQ5 + 5th conversion in regular + sequence + 20 + 5 + + + SQ4 + 4th conversion in regular + sequence + 15 + 5 + + + SQ3 + 3rd conversion in regular + sequence + 10 + 5 + + + SQ2 + 2nd conversion in regular + sequence + 5 + 5 + + + SQ1 + 1st conversion in regular + sequence + 0 + 5 + + + + + JSQR + JSQR + injected sequence register + 0x38 + 0x20 + read-write + 0x00000000 + + + JL + Injected sequence length + 20 + 2 + + + JSQ4 + 4th conversion in injected + sequence + 15 + 5 + + + JSQ3 + 3rd conversion in injected + sequence + 10 + 5 + + + JSQ2 + 2nd conversion in injected + sequence + 5 + 5 + + + JSQ1 + 1st conversion in injected + sequence + 0 + 5 + + + + + JDR1 + JDR1 + injected data register x + 0x3C + 0x20 + read-only + 0x00000000 + + + JDATA + Injected data + 0 + 16 + + + + + JDR2 + JDR2 + injected data register x + 0x40 + 0x20 + read-only + 0x00000000 + + + JDATA + Injected data + 0 + 16 + + + + + JDR3 + JDR3 + injected data register x + 0x44 + 0x20 + read-only + 0x00000000 + + + JDATA + Injected data + 0 + 16 + + + + + JDR4 + JDR4 + injected data register x + 0x48 + 0x20 + read-only + 0x00000000 + + + JDATA + Injected data + 0 + 16 + + + + + DR + DR + regular data register + 0x4C + 0x20 + read-only + 0x00000000 + + + DATA + Regular data + 0 + 16 + + + + + + + ADC2 + 0x40012100 + + ADC + ADC2 global interrupts + 18 + + + + ADC3 + 0x40012200 + + ADC + ADC3 global interrupts + 18 + + + + USART6 + Universal synchronous asynchronous receiver + transmitter + USART + 0x40011400 + + 0x0 + 0x400 + registers + + + USART6 + USART6 global interrupt + 71 + + + + SR + SR + Status register + 0x0 + 0x20 + 0x00C00000 + + + CTS + CTS flag + 9 + 1 + read-write + + + LBD + LIN break detection flag + 8 + 1 + read-write + + + TXE + Transmit data register + empty + 7 + 1 + read-only + + + TC + Transmission complete + 6 + 1 + read-write + + + RXNE + Read data register not + empty + 5 + 1 + read-write + + + IDLE + IDLE line detected + 4 + 1 + read-only + + + ORE + Overrun error + 3 + 1 + read-only + + + NF + Noise detected flag + 2 + 1 + read-only + + + FE + Framing error + 1 + 1 + read-only + + + PE + Parity error + 0 + 1 + read-only + + + + + DR + DR + Data register + 0x4 + 0x20 + read-write + 0x00000000 + + + DR + Data value + 0 + 9 + + + + + BRR + BRR + Baud rate register + 0x8 + 0x20 + read-write + 0x0000 + + + DIV_Mantissa + mantissa of USARTDIV + 4 + 12 + + + DIV_Fraction + fraction of USARTDIV + 0 + 4 + + + + + CR1 + CR1 + Control register 1 + 0xC + 0x20 + read-write + 0x0000 + + + OVER8 + Oversampling mode + 15 + 1 + + + UE + USART enable + 13 + 1 + + + M + Word length + 12 + 1 + + + WAKE + Wakeup method + 11 + 1 + + + PCE + Parity control enable + 10 + 1 + + + PS + Parity selection + 9 + 1 + + + PEIE + PE interrupt enable + 8 + 1 + + + TXEIE + TXE interrupt enable + 7 + 1 + + + TCIE + Transmission complete interrupt + enable + 6 + 1 + + + RXNEIE + RXNE interrupt enable + 5 + 1 + + + IDLEIE + IDLE interrupt enable + 4 + 1 + + + TE + Transmitter enable + 3 + 1 + + + RE + Receiver enable + 2 + 1 + + + RWU + Receiver wakeup + 1 + 1 + + + SBK + Send break + 0 + 1 + + + + + CR2 + CR2 + Control register 2 + 0x10 + 0x20 + read-write + 0x0000 + + + LINEN + LIN mode enable + 14 + 1 + + + STOP + STOP bits + 12 + 2 + + + CLKEN + Clock enable + 11 + 1 + + + CPOL + Clock polarity + 10 + 1 + + + CPHA + Clock phase + 9 + 1 + + + LBCL + Last bit clock pulse + 8 + 1 + + + LBDIE + LIN break detection interrupt + enable + 6 + 1 + + + LBDL + lin break detection length + 5 + 1 + + + ADD + Address of the USART node + 0 + 4 + + + + + CR3 + CR3 + Control register 3 + 0x14 + 0x20 + read-write + 0x0000 + + + ONEBIT + One sample bit method + enable + 11 + 1 + + + CTSIE + CTS interrupt enable + 10 + 1 + + + CTSE + CTS enable + 9 + 1 + + + RTSE + RTS enable + 8 + 1 + + + DMAT + DMA enable transmitter + 7 + 1 + + + DMAR + DMA enable receiver + 6 + 1 + + + SCEN + Smartcard mode enable + 5 + 1 + + + NACK + Smartcard NACK enable + 4 + 1 + + + HDSEL + Half-duplex selection + 3 + 1 + + + IRLP + IrDA low-power + 2 + 1 + + + IREN + IrDA mode enable + 1 + 1 + + + EIE + Error interrupt enable + 0 + 1 + + + + + GTPR + GTPR + Guard time and prescaler + register + 0x18 + 0x20 + read-write + 0x0000 + + + GT + Guard time value + 8 + 8 + + + PSC + Prescaler value + 0 + 8 + + + + + + + USART1 + 0x40011000 + + USART1 + USART1 global interrupt + 37 + + + + USART2 + 0x40004400 + + USART2 + USART2 global interrupt + 38 + + + + USART3 + 0x40004800 + + USART3 + USART3 global interrupt + 39 + + + + DAC + Digital-to-analog converter + DAC + 0x40007400 + + 0x0 + 0x400 + registers + + + TIM6_DAC + TIM6 global interrupt, DAC1 and DAC2 underrun + error interrupt + 54 + + + + CR + CR + control register + 0x0 + 0x20 + read-write + 0x00000000 + + + DMAUDRIE2 + DAC channel2 DMA underrun interrupt + enable + 29 + 1 + + + DMAEN2 + DAC channel2 DMA enable + 28 + 1 + + + MAMP2 + DAC channel2 mask/amplitude + selector + 24 + 4 + + + WAVE2 + DAC channel2 noise/triangle wave + generation enable + 22 + 2 + + + TSEL2 + DAC channel2 trigger + selection + 19 + 3 + + + TEN2 + DAC channel2 trigger + enable + 18 + 1 + + + BOFF2 + DAC channel2 output buffer + disable + 17 + 1 + + + EN2 + DAC channel2 enable + 16 + 1 + + + DMAUDRIE1 + DAC channel1 DMA Underrun Interrupt + enable + 13 + 1 + + + DMAEN1 + DAC channel1 DMA enable + 12 + 1 + + + MAMP1 + DAC channel1 mask/amplitude + selector + 8 + 4 + + + WAVE1 + DAC channel1 noise/triangle wave + generation enable + 6 + 2 + + + TSEL1 + DAC channel1 trigger + selection + 3 + 3 + + + TEN1 + DAC channel1 trigger + enable + 2 + 1 + + + BOFF1 + DAC channel1 output buffer + disable + 1 + 1 + + + EN1 + DAC channel1 enable + 0 + 1 + + + + + SWTRIGR + SWTRIGR + software trigger register + 0x4 + 0x20 + write-only + 0x00000000 + + + SWTRIG2 + DAC channel2 software + trigger + 1 + 1 + + + SWTRIG1 + DAC channel1 software + trigger + 0 + 1 + + + + + DHR12R1 + DHR12R1 + channel1 12-bit right-aligned data holding + register + 0x8 + 0x20 + read-write + 0x00000000 + + + DACC1DHR + DAC channel1 12-bit right-aligned + data + 0 + 12 + + + + + DHR12L1 + DHR12L1 + channel1 12-bit left aligned data holding + register + 0xC + 0x20 + read-write + 0x00000000 + + + DACC1DHR + DAC channel1 12-bit left-aligned + data + 4 + 12 + + + + + DHR8R1 + DHR8R1 + channel1 8-bit right aligned data holding + register + 0x10 + 0x20 + read-write + 0x00000000 + + + DACC1DHR + DAC channel1 8-bit right-aligned + data + 0 + 8 + + + + + DHR12R2 + DHR12R2 + channel2 12-bit right aligned data holding + register + 0x14 + 0x20 + read-write + 0x00000000 + + + DACC2DHR + DAC channel2 12-bit right-aligned + data + 0 + 12 + + + + + DHR12L2 + DHR12L2 + channel2 12-bit left aligned data holding + register + 0x18 + 0x20 + read-write + 0x00000000 + + + DACC2DHR + DAC channel2 12-bit left-aligned + data + 4 + 12 + + + + + DHR8R2 + DHR8R2 + channel2 8-bit right-aligned data holding + register + 0x1C + 0x20 + read-write + 0x00000000 + + + DACC2DHR + DAC channel2 8-bit right-aligned + data + 0 + 8 + + + + + DHR12RD + DHR12RD + Dual DAC 12-bit right-aligned data holding + register + 0x20 + 0x20 + read-write + 0x00000000 + + + DACC2DHR + DAC channel2 12-bit right-aligned + data + 16 + 12 + + + DACC1DHR + DAC channel1 12-bit right-aligned + data + 0 + 12 + + + + + DHR12LD + DHR12LD + DUAL DAC 12-bit left aligned data holding + register + 0x24 + 0x20 + read-write + 0x00000000 + + + DACC2DHR + DAC channel2 12-bit left-aligned + data + 20 + 12 + + + DACC1DHR + DAC channel1 12-bit left-aligned + data + 4 + 12 + + + + + DHR8RD + DHR8RD + DUAL DAC 8-bit right aligned data holding + register + 0x28 + 0x20 + read-write + 0x00000000 + + + DACC2DHR + DAC channel2 8-bit right-aligned + data + 8 + 8 + + + DACC1DHR + DAC channel1 8-bit right-aligned + data + 0 + 8 + + + + + DOR1 + DOR1 + channel1 data output register + 0x2C + 0x20 + read-only + 0x00000000 + + + DACC1DOR + DAC channel1 data output + 0 + 12 + + + + + DOR2 + DOR2 + channel2 data output register + 0x30 + 0x20 + read-only + 0x00000000 + + + DACC2DOR + DAC channel2 data output + 0 + 12 + + + + + SR + SR + status register + 0x34 + 0x20 + read-write + 0x00000000 + + + DMAUDR2 + DAC channel2 DMA underrun + flag + 29 + 1 + + + DMAUDR1 + DAC channel1 DMA underrun + flag + 13 + 1 + + + + + + + PWR + Power control + PWR + 0x40007000 + + 0x0 + 0x400 + registers + + + PVD + PVD through EXTI line detection + interrupt + 1 + + + + CR + CR + power control register + 0x0 + 0x20 + read-write + 0x00000000 + + + FPDS + Flash power down in Stop + mode + 9 + 1 + + + DBP + Disable backup domain write + protection + 8 + 1 + + + PLS + PVD level selection + 5 + 3 + + + PVDE + Power voltage detector + enable + 4 + 1 + + + CSBF + Clear standby flag + 3 + 1 + + + CWUF + Clear wakeup flag + 2 + 1 + + + PDDS + Power down deepsleep + 1 + 1 + + + LPDS + Low-power deep sleep + 0 + 1 + + + + + CSR + CSR + power control/status register + 0x4 + 0x20 + 0x00000000 + + + WUF + Wakeup flag + 0 + 1 + read-only + + + SBF + Standby flag + 1 + 1 + read-only + + + PVDO + PVD output + 2 + 1 + read-only + + + BRR + Backup regulator ready + 3 + 1 + read-only + + + EWUP + Enable WKUP pin + 8 + 1 + read-write + + + BRE + Backup regulator enable + 9 + 1 + read-write + + + VOSRDY + Regulator voltage scaling output + selection ready bit + 14 + 1 + read-write + + + + + + + I2C3 + Inter-integrated circuit + I2C + 0x40005C00 + + 0x0 + 0x400 + registers + + + I2C3_EV + I2C3 event interrupt + 72 + + + I2C3_ER + I2C3 error interrupt + 73 + + + + CR1 + CR1 + Control register 1 + 0x0 + 0x20 + read-write + 0x0000 + + + SWRST + Software reset + 15 + 1 + + + ALERT + SMBus alert + 13 + 1 + + + PEC + Packet error checking + 12 + 1 + + + POS + Acknowledge/PEC Position (for data + reception) + 11 + 1 + + + ACK + Acknowledge enable + 10 + 1 + + + STOP + Stop generation + 9 + 1 + + + START + Start generation + 8 + 1 + + + NOSTRETCH + Clock stretching disable (Slave + mode) + 7 + 1 + + + ENGC + General call enable + 6 + 1 + + + ENPEC + PEC enable + 5 + 1 + + + ENARP + ARP enable + 4 + 1 + + + SMBTYPE + SMBus type + 3 + 1 + + + SMBUS + SMBus mode + 1 + 1 + + + PE + Peripheral enable + 0 + 1 + + + + + CR2 + CR2 + Control register 2 + 0x4 + 0x20 + read-write + 0x0000 + + + LAST + DMA last transfer + 12 + 1 + + + DMAEN + DMA requests enable + 11 + 1 + + + ITBUFEN + Buffer interrupt enable + 10 + 1 + + + ITEVTEN + Event interrupt enable + 9 + 1 + + + ITERREN + Error interrupt enable + 8 + 1 + + + FREQ + Peripheral clock frequency + 0 + 6 + + + + + OAR1 + OAR1 + Own address register 1 + 0x8 + 0x20 + read-write + 0x0000 + + + ADDMODE + Addressing mode (slave + mode) + 15 + 1 + + + ADD10 + Interface address + 8 + 2 + + + ADD7 + Interface address + 1 + 7 + + + ADD0 + Interface address + 0 + 1 + + + + + OAR2 + OAR2 + Own address register 2 + 0xC + 0x20 + read-write + 0x0000 + + + ADD2 + Interface address + 1 + 7 + + + ENDUAL + Dual addressing mode + enable + 0 + 1 + + + + + DR + DR + Data register + 0x10 + 0x20 + read-write + 0x0000 + + + DR + 8-bit data register + 0 + 8 + + + + + SR1 + SR1 + Status register 1 + 0x14 + 0x20 + 0x0000 + + + SMBALERT + SMBus alert + 15 + 1 + read-write + + + TIMEOUT + Timeout or Tlow error + 14 + 1 + read-write + + + PECERR + PEC Error in reception + 12 + 1 + read-write + + + OVR + Overrun/Underrun + 11 + 1 + read-write + + + AF + Acknowledge failure + 10 + 1 + read-write + + + ARLO + Arbitration lost (master + mode) + 9 + 1 + read-write + + + BERR + Bus error + 8 + 1 + read-write + + + TxE + Data register empty + (transmitters) + 7 + 1 + read-only + + + RxNE + Data register not empty + (receivers) + 6 + 1 + read-only + + + STOPF + Stop detection (slave + mode) + 4 + 1 + read-only + + + ADD10 + 10-bit header sent (Master + mode) + 3 + 1 + read-only + + + BTF + Byte transfer finished + 2 + 1 + read-only + + + ADDR + Address sent (master mode)/matched + (slave mode) + 1 + 1 + read-only + + + SB + Start bit (Master mode) + 0 + 1 + read-only + + + + + SR2 + SR2 + Status register 2 + 0x18 + 0x20 + read-only + 0x0000 + + + PEC + acket error checking + register + 8 + 8 + + + DUALF + Dual flag (Slave mode) + 7 + 1 + + + SMBHOST + SMBus host header (Slave + mode) + 6 + 1 + + + SMBDEFAULT + SMBus device default address (Slave + mode) + 5 + 1 + + + GENCALL + General call address (Slave + mode) + 4 + 1 + + + TRA + Transmitter/receiver + 2 + 1 + + + BUSY + Bus busy + 1 + 1 + + + MSL + Master/slave + 0 + 1 + + + + + CCR + CCR + Clock control register + 0x1C + 0x20 + read-write + 0x0000 + + + F_S + I2C master mode selection + 15 + 1 + + + DUTY + Fast mode duty cycle + 14 + 1 + + + CCR + Clock control register in Fast/Standard + mode (Master mode) + 0 + 12 + + + + + TRISE + TRISE + TRISE register + 0x20 + 0x20 + read-write + 0x0002 + + + TRISE + Maximum rise time in Fast/Standard mode + (Master mode) + 0 + 6 + + + + + + + I2C2 + 0x40005800 + + I2C2_EV + I2C2 event interrupt + 33 + + + I2C2_ER + I2C2 error interrupt + 34 + + + + I2C1 + 0x40005400 + + I2C1_EV + I2C1 event interrupt + 31 + + + I2C1_ER + I2C1 error interrupt + 32 + + + + IWDG + Independent watchdog + IWDG + 0x40003000 + + 0x0 + 0x400 + registers + + + + KR + KR + Key register + 0x0 + 0x20 + write-only + 0x00000000 + + + KEY + Key value (write only, read + 0000h) + 0 + 16 + + + + + PR + PR + Prescaler register + 0x4 + 0x20 + read-write + 0x00000000 + + + PR + Prescaler divider + 0 + 3 + + + + + RLR + RLR + Reload register + 0x8 + 0x20 + read-write + 0x00000FFF + + + RL + Watchdog counter reload + value + 0 + 12 + + + + + SR + SR + Status register + 0xC + 0x20 + read-only + 0x00000000 + + + RVU + Watchdog counter reload value + update + 1 + 1 + + + PVU + Watchdog prescaler value + update + 0 + 1 + + + + + + + WWDG + Window watchdog + WWDG + 0x40002C00 + + 0x0 + 0x400 + registers + + + WWDG + Window Watchdog interrupt + 0 + + + + CR + CR + Control register + 0x0 + 0x20 + read-write + 0x7F + + + WDGA + Activation bit + 7 + 1 + + + T + 7-bit counter (MSB to LSB) + 0 + 7 + + + + + CFR + CFR + Configuration register + 0x4 + 0x20 + read-write + 0x7F + + + EWI + Early wakeup interrupt + 9 + 1 + + + WDGTB1 + Timer base + 8 + 1 + + + WDGTB0 + Timer base + 7 + 1 + + + W + 7-bit window value + 0 + 7 + + + + + SR + SR + Status register + 0x8 + 0x20 + read-write + 0x00 + + + EWIF + Early wakeup interrupt + flag + 0 + 1 + + + + + + + RTC + Real-time clock + RTC + 0x40002800 + + 0x0 + 0x400 + registers + + + RTC_WKUP + RTC Wakeup interrupt through the EXTI + line + 3 + + + RTC_Alarm + RTC Alarms (A and B) through EXTI line + interrupt + 41 + + + + TR + TR + time register + 0x0 + 0x20 + read-write + 0x00000000 + + + PM + AM/PM notation + 22 + 1 + + + HT + Hour tens in BCD format + 20 + 2 + + + HU + Hour units in BCD format + 16 + 4 + + + MNT + Minute tens in BCD format + 12 + 3 + + + MNU + Minute units in BCD format + 8 + 4 + + + ST + Second tens in BCD format + 4 + 3 + + + SU + Second units in BCD format + 0 + 4 + + + + + DR + DR + date register + 0x4 + 0x20 + read-write + 0x00002101 + + + YT + Year tens in BCD format + 20 + 4 + + + YU + Year units in BCD format + 16 + 4 + + + WDU + Week day units + 13 + 3 + + + MT + Month tens in BCD format + 12 + 1 + + + MU + Month units in BCD format + 8 + 4 + + + DT + Date tens in BCD format + 4 + 2 + + + DU + Date units in BCD format + 0 + 4 + + + + + CR + CR + control register + 0x8 + 0x20 + read-write + 0x00000000 + + + COE + Calibration output enable + 23 + 1 + + + OSEL + Output selection + 21 + 2 + + + POL + Output polarity + 20 + 1 + + + BKP + Backup + 18 + 1 + + + SUB1H + Subtract 1 hour (winter time + change) + 17 + 1 + + + ADD1H + Add 1 hour (summer time + change) + 16 + 1 + + + TSIE + Time-stamp interrupt + enable + 15 + 1 + + + WUTIE + Wakeup timer interrupt + enable + 14 + 1 + + + ALRBIE + Alarm B interrupt enable + 13 + 1 + + + ALRAIE + Alarm A interrupt enable + 12 + 1 + + + TSE + Time stamp enable + 11 + 1 + + + WUTE + Wakeup timer enable + 10 + 1 + + + ALRBE + Alarm B enable + 9 + 1 + + + ALRAE + Alarm A enable + 8 + 1 + + + DCE + Coarse digital calibration + enable + 7 + 1 + + + FMT + Hour format + 6 + 1 + + + REFCKON + Reference clock detection enable (50 or + 60 Hz) + 4 + 1 + + + TSEDGE + Time-stamp event active + edge + 3 + 1 + + + WCKSEL + Wakeup clock selection + 0 + 3 + + + + + ISR + ISR + initialization and status + register + 0xC + 0x20 + 0x00000007 + + + ALRAWF + Alarm A write flag + 0 + 1 + read-only + + + ALRBWF + Alarm B write flag + 1 + 1 + read-only + + + WUTWF + Wakeup timer write flag + 2 + 1 + read-only + + + SHPF + Shift operation pending + 3 + 1 + read-write + + + INITS + Initialization status flag + 4 + 1 + read-only + + + RSF + Registers synchronization + flag + 5 + 1 + read-write + + + INITF + Initialization flag + 6 + 1 + read-only + + + INIT + Initialization mode + 7 + 1 + read-write + + + ALRAF + Alarm A flag + 8 + 1 + read-write + + + ALRBF + Alarm B flag + 9 + 1 + read-write + + + WUTF + Wakeup timer flag + 10 + 1 + read-write + + + TSF + Time-stamp flag + 11 + 1 + read-write + + + TSOVF + Time-stamp overflow flag + 12 + 1 + read-write + + + TAMP1F + Tamper detection flag + 13 + 1 + read-write + + + TAMP2F + TAMPER2 detection flag + 14 + 1 + read-write + + + RECALPF + Recalibration pending Flag + 16 + 1 + read-only + + + + + PRER + PRER + prescaler register + 0x10 + 0x20 + read-write + 0x007F00FF + + + PREDIV_A + Asynchronous prescaler + factor + 16 + 7 + + + PREDIV_S + Synchronous prescaler + factor + 0 + 15 + + + + + WUTR + WUTR + wakeup timer register + 0x14 + 0x20 + read-write + 0x0000FFFF + + + WUT + Wakeup auto-reload value + bits + 0 + 16 + + + + + CALIBR + CALIBR + calibration register + 0x18 + 0x20 + read-write + 0x00000000 + + + DCS + Digital calibration sign + 7 + 1 + + + DC + Digital calibration + 0 + 5 + + + + + ALRMAR + ALRMAR + alarm A register + 0x1C + 0x20 + read-write + 0x00000000 + + + MSK4 + Alarm A date mask + 31 + 1 + + + WDSEL + Week day selection + 30 + 1 + + + DT + Date tens in BCD format + 28 + 2 + + + DU + Date units or day in BCD + format + 24 + 4 + + + MSK3 + Alarm A hours mask + 23 + 1 + + + PM + AM/PM notation + 22 + 1 + + + HT + Hour tens in BCD format + 20 + 2 + + + HU + Hour units in BCD format + 16 + 4 + + + MSK2 + Alarm A minutes mask + 15 + 1 + + + MNT + Minute tens in BCD format + 12 + 3 + + + MNU + Minute units in BCD format + 8 + 4 + + + MSK1 + Alarm A seconds mask + 7 + 1 + + + ST + Second tens in BCD format + 4 + 3 + + + SU + Second units in BCD format + 0 + 4 + + + + + ALRMBR + ALRMBR + alarm B register + 0x20 + 0x20 + read-write + 0x00000000 + + + MSK4 + Alarm B date mask + 31 + 1 + + + WDSEL + Week day selection + 30 + 1 + + + DT + Date tens in BCD format + 28 + 2 + + + DU + Date units or day in BCD + format + 24 + 4 + + + MSK3 + Alarm B hours mask + 23 + 1 + + + PM + AM/PM notation + 22 + 1 + + + HT + Hour tens in BCD format + 20 + 2 + + + HU + Hour units in BCD format + 16 + 4 + + + MSK2 + Alarm B minutes mask + 15 + 1 + + + MNT + Minute tens in BCD format + 12 + 3 + + + MNU + Minute units in BCD format + 8 + 4 + + + MSK1 + Alarm B seconds mask + 7 + 1 + + + ST + Second tens in BCD format + 4 + 3 + + + SU + Second units in BCD format + 0 + 4 + + + + + WPR + WPR + write protection register + 0x24 + 0x20 + write-only + 0x00000000 + + + KEY + Write protection key + 0 + 8 + + + + + SSR + SSR + sub second register + 0x28 + 0x20 + read-only + 0x00000000 + + + SS + Sub second value + 0 + 16 + + + + + SHIFTR + SHIFTR + shift control register + 0x2C + 0x20 + write-only + 0x00000000 + + + ADD1S + Add one second + 31 + 1 + + + SUBFS + Subtract a fraction of a + second + 0 + 15 + + + + + TSTR + TSTR + time stamp time register + 0x30 + 0x20 + read-only + 0x00000000 + + + ALARMOUTTYPE + AFO_ALARM output type + 18 + 1 + + + TSINSEL + TIMESTAMP mapping + 17 + 1 + + + TAMP1INSEL + TAMPER1 mapping + 16 + 1 + + + TAMPIE + Tamper interrupt enable + 2 + 1 + + + TAMP1TRG + Active level for tamper 1 + 1 + 1 + + + TAMP1E + Tamper 1 detection enable + 0 + 1 + + + + + TSDR + TSDR + time stamp date register + 0x34 + 0x20 + read-only + 0x00000000 + + + WDU + Week day units + 13 + 3 + + + MT + Month tens in BCD format + 12 + 1 + + + MU + Month units in BCD format + 8 + 4 + + + DT + Date tens in BCD format + 4 + 2 + + + DU + Date units in BCD format + 0 + 4 + + + + + TSSSR + TSSSR + timestamp sub second register + 0x38 + 0x20 + read-only + 0x00000000 + + + SS + Sub second value + 0 + 16 + + + + + CALR + CALR + calibration register + 0x3C + 0x20 + read-write + 0x00000000 + + + CALP + Increase frequency of RTC by 488.5 + ppm + 15 + 1 + + + CALW8 + Use an 8-second calibration cycle + period + 14 + 1 + + + CALW16 + Use a 16-second calibration cycle + period + 13 + 1 + + + CALM + Calibration minus + 0 + 9 + + + + + TAFCR + TAFCR + tamper and alternate function configuration + register + 0x40 + 0x20 + read-write + 0x00000000 + + + ALARMOUTTYPE + AFO_ALARM output type + 18 + 1 + + + TSINSEL + TIMESTAMP mapping + 17 + 1 + + + TAMP1INSEL + TAMPER1 mapping + 16 + 1 + + + TAMPPUDIS + TAMPER pull-up disable + 15 + 1 + + + TAMPPRCH + Tamper precharge duration + 13 + 2 + + + TAMPFLT + Tamper filter count + 11 + 2 + + + TAMPFREQ + Tamper sampling frequency + 8 + 3 + + + TAMPTS + Activate timestamp on tamper detection + event + 7 + 1 + + + TAMP2TRG + Active level for tamper 2 + 4 + 1 + + + TAMP2E + Tamper 2 detection enable + 3 + 1 + + + TAMPIE + Tamper interrupt enable + 2 + 1 + + + TAMP1TRG + Active level for tamper 1 + 1 + 1 + + + TAMP1E + Tamper 1 detection enable + 0 + 1 + + + + + ALRMASSR + ALRMASSR + alarm A sub second register + 0x44 + 0x20 + read-write + 0x00000000 + + + MASKSS + Mask the most-significant bits starting + at this bit + 24 + 4 + + + SS + Sub seconds value + 0 + 15 + + + + + ALRMBSSR + ALRMBSSR + alarm B sub second register + 0x48 + 0x20 + read-write + 0x00000000 + + + MASKSS + Mask the most-significant bits starting + at this bit + 24 + 4 + + + SS + Sub seconds value + 0 + 15 + + + + + BKP0R + BKP0R + backup register + 0x50 + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP1R + BKP1R + backup register + 0x54 + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP2R + BKP2R + backup register + 0x58 + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP3R + BKP3R + backup register + 0x5C + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP4R + BKP4R + backup register + 0x60 + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP5R + BKP5R + backup register + 0x64 + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP6R + BKP6R + backup register + 0x68 + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP7R + BKP7R + backup register + 0x6C + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP8R + BKP8R + backup register + 0x70 + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP9R + BKP9R + backup register + 0x74 + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP10R + BKP10R + backup register + 0x78 + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP11R + BKP11R + backup register + 0x7C + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP12R + BKP12R + backup register + 0x80 + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP13R + BKP13R + backup register + 0x84 + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP14R + BKP14R + backup register + 0x88 + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP15R + BKP15R + backup register + 0x8C + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP16R + BKP16R + backup register + 0x90 + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP17R + BKP17R + backup register + 0x94 + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP18R + BKP18R + backup register + 0x98 + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + BKP19R + BKP19R + backup register + 0x9C + 0x20 + read-write + 0x00000000 + + + BKP + BKP + 0 + 32 + + + + + + + UART4 + Universal synchronous asynchronous receiver + transmitter + USART + 0x40004C00 + + 0x0 + 0x400 + registers + + + UART4 + UART4 global interrupt + 52 + + + + SR + SR + Status register + 0x0 + 0x20 + 0x00C00000 + + + LBD + LIN break detection flag + 8 + 1 + read-write + + + TXE + Transmit data register + empty + 7 + 1 + read-only + + + TC + Transmission complete + 6 + 1 + read-write + + + RXNE + Read data register not + empty + 5 + 1 + read-write + + + IDLE + IDLE line detected + 4 + 1 + read-only + + + ORE + Overrun error + 3 + 1 + read-only + + + NF + Noise detected flag + 2 + 1 + read-only + + + FE + Framing error + 1 + 1 + read-only + + + PE + Parity error + 0 + 1 + read-only + + + + + DR + DR + Data register + 0x4 + 0x20 + read-write + 0x00000000 + + + DR + Data value + 0 + 9 + + + + + BRR + BRR + Baud rate register + 0x8 + 0x20 + read-write + 0x0000 + + + DIV_Mantissa + mantissa of USARTDIV + 4 + 12 + + + DIV_Fraction + fraction of USARTDIV + 0 + 4 + + + + + CR1 + CR1 + Control register 1 + 0xC + 0x20 + read-write + 0x0000 + + + OVER8 + Oversampling mode + 15 + 1 + + + UE + USART enable + 13 + 1 + + + M + Word length + 12 + 1 + + + WAKE + Wakeup method + 11 + 1 + + + PCE + Parity control enable + 10 + 1 + + + PS + Parity selection + 9 + 1 + + + PEIE + PE interrupt enable + 8 + 1 + + + TXEIE + TXE interrupt enable + 7 + 1 + + + TCIE + Transmission complete interrupt + enable + 6 + 1 + + + RXNEIE + RXNE interrupt enable + 5 + 1 + + + IDLEIE + IDLE interrupt enable + 4 + 1 + + + TE + Transmitter enable + 3 + 1 + + + RE + Receiver enable + 2 + 1 + + + RWU + Receiver wakeup + 1 + 1 + + + SBK + Send break + 0 + 1 + + + + + CR2 + CR2 + Control register 2 + 0x10 + 0x20 + read-write + 0x0000 + + + LINEN + LIN mode enable + 14 + 1 + + + STOP + STOP bits + 12 + 2 + + + LBDIE + LIN break detection interrupt + enable + 6 + 1 + + + LBDL + lin break detection length + 5 + 1 + + + ADD + Address of the USART node + 0 + 4 + + + + + CR3 + CR3 + Control register 3 + 0x14 + 0x20 + read-write + 0x0000 + + + ONEBIT + One sample bit method + enable + 11 + 1 + + + DMAT + DMA enable transmitter + 7 + 1 + + + DMAR + DMA enable receiver + 6 + 1 + + + HDSEL + Half-duplex selection + 3 + 1 + + + IRLP + IrDA low-power + 2 + 1 + + + IREN + IrDA mode enable + 1 + 1 + + + EIE + Error interrupt enable + 0 + 1 + + + + + + + UART5 + 0x40005000 + + UART5 + UART5 global interrupt + 53 + + + + C_ADC + Common ADC registers + ADC + 0x40012300 + + 0x0 + 0x400 + registers + + + + CSR + CSR + ADC Common status register + 0x0 + 0x20 + read-only + 0x00000000 + + + OVR3 + Overrun flag of ADC3 + 21 + 1 + + + STRT3 + Regular channel Start flag of ADC + 3 + 20 + 1 + + + JSTRT3 + Injected channel Start flag of ADC + 3 + 19 + 1 + + + JEOC3 + Injected channel end of conversion of + ADC 3 + 18 + 1 + + + EOC3 + End of conversion of ADC 3 + 17 + 1 + + + AWD3 + Analog watchdog flag of ADC + 3 + 16 + 1 + + + OVR2 + Overrun flag of ADC 2 + 13 + 1 + + + STRT2 + Regular channel Start flag of ADC + 2 + 12 + 1 + + + JSTRT2 + Injected channel Start flag of ADC + 2 + 11 + 1 + + + JEOC2 + Injected channel end of conversion of + ADC 2 + 10 + 1 + + + EOC2 + End of conversion of ADC 2 + 9 + 1 + + + AWD2 + Analog watchdog flag of ADC + 2 + 8 + 1 + + + OVR1 + Overrun flag of ADC 1 + 5 + 1 + + + STRT1 + Regular channel Start flag of ADC + 1 + 4 + 1 + + + JSTRT1 + Injected channel Start flag of ADC + 1 + 3 + 1 + + + JEOC1 + Injected channel end of conversion of + ADC 1 + 2 + 1 + + + EOC1 + End of conversion of ADC 1 + 1 + 1 + + + AWD1 + Analog watchdog flag of ADC + 1 + 0 + 1 + + + + + CCR + CCR + ADC common control register + 0x4 + 0x20 + read-write + 0x00000000 + + + TSVREFE + Temperature sensor and VREFINT + enable + 23 + 1 + + + VBATE + VBAT enable + 22 + 1 + + + ADCPRE + ADC prescaler + 16 + 2 + + + DMA + Direct memory access mode for multi ADC + mode + 14 + 2 + + + DDS + DMA disable selection for multi-ADC + mode + 13 + 1 + + + DELAY + Delay between 2 sampling + phases + 8 + 4 + + + MULT + Multi ADC mode selection + 0 + 5 + + + + + CDR + CDR + ADC common regular data register for dual + and triple modes + 0x8 + 0x20 + read-only + 0x00000000 + + + DATA2 + 2nd data item of a pair of regular + conversions + 16 + 16 + + + DATA1 + 1st data item of a pair of regular + conversions + 0 + 16 + + + + + + + TIM1 + Advanced-timers + TIM + 0x40010000 + + 0x0 + 0x400 + registers + + + TIM1_BRK_TIM9 + TIM1 Break interrupt and TIM9 global + interrupt + 24 + + + TIM1_UP_TIM10 + TIM1 Update interrupt and TIM10 global + interrupt + 25 + + + TIM1_TRG_COM_TIM11 + TIM1 Trigger and Commutation interrupts and + TIM11 global interrupt + 26 + + + TIM1_CC + TIM1 Capture Compare interrupt + 27 + + + + CR1 + CR1 + control register 1 + 0x0 + 0x20 + read-write + 0x0000 + + + CKD + Clock division + 8 + 2 + + + ARPE + Auto-reload preload enable + 7 + 1 + + + CMS + Center-aligned mode + selection + 5 + 2 + + + DIR + Direction + 4 + 1 + + + OPM + One-pulse mode + 3 + 1 + + + URS + Update request source + 2 + 1 + + + UDIS + Update disable + 1 + 1 + + + CEN + Counter enable + 0 + 1 + + + + + CR2 + CR2 + control register 2 + 0x4 + 0x20 + read-write + 0x0000 + + + OIS4 + Output Idle state 4 + 14 + 1 + + + OIS3N + Output Idle state 3 + 13 + 1 + + + OIS3 + Output Idle state 3 + 12 + 1 + + + OIS2N + Output Idle state 2 + 11 + 1 + + + OIS2 + Output Idle state 2 + 10 + 1 + + + OIS1N + Output Idle state 1 + 9 + 1 + + + OIS1 + Output Idle state 1 + 8 + 1 + + + TI1S + TI1 selection + 7 + 1 + + + MMS + Master mode selection + 4 + 3 + + + CCDS + Capture/compare DMA + selection + 3 + 1 + + + CCUS + Capture/compare control update + selection + 2 + 1 + + + CCPC + Capture/compare preloaded + control + 0 + 1 + + + + + SMCR + SMCR + slave mode control register + 0x8 + 0x20 + read-write + 0x0000 + + + ETP + External trigger polarity + 15 + 1 + + + ECE + External clock enable + 14 + 1 + + + ETPS + External trigger prescaler + 12 + 2 + + + ETF + External trigger filter + 8 + 4 + + + MSM + Master/Slave mode + 7 + 1 + + + TS + Trigger selection + 4 + 3 + + + SMS + Slave mode selection + 0 + 3 + + + + + DIER + DIER + DMA/Interrupt enable register + 0xC + 0x20 + read-write + 0x0000 + + + TDE + Trigger DMA request enable + 14 + 1 + + + COMDE + COM DMA request enable + 13 + 1 + + + CC4DE + Capture/Compare 4 DMA request + enable + 12 + 1 + + + CC3DE + Capture/Compare 3 DMA request + enable + 11 + 1 + + + CC2DE + Capture/Compare 2 DMA request + enable + 10 + 1 + + + CC1DE + Capture/Compare 1 DMA request + enable + 9 + 1 + + + UDE + Update DMA request enable + 8 + 1 + + + TIE + Trigger interrupt enable + 6 + 1 + + + CC4IE + Capture/Compare 4 interrupt + enable + 4 + 1 + + + CC3IE + Capture/Compare 3 interrupt + enable + 3 + 1 + + + CC2IE + Capture/Compare 2 interrupt + enable + 2 + 1 + + + CC1IE + Capture/Compare 1 interrupt + enable + 1 + 1 + + + UIE + Update interrupt enable + 0 + 1 + + + BIE + Break interrupt enable + 7 + 1 + + + COMIE + COM interrupt enable + 5 + 1 + + + + + SR + SR + status register + 0x10 + 0x20 + read-write + 0x0000 + + + CC4OF + Capture/Compare 4 overcapture + flag + 12 + 1 + + + CC3OF + Capture/Compare 3 overcapture + flag + 11 + 1 + + + CC2OF + Capture/compare 2 overcapture + flag + 10 + 1 + + + CC1OF + Capture/Compare 1 overcapture + flag + 9 + 1 + + + BIF + Break interrupt flag + 7 + 1 + + + TIF + Trigger interrupt flag + 6 + 1 + + + COMIF + COM interrupt flag + 5 + 1 + + + CC4IF + Capture/Compare 4 interrupt + flag + 4 + 1 + + + CC3IF + Capture/Compare 3 interrupt + flag + 3 + 1 + + + CC2IF + Capture/Compare 2 interrupt + flag + 2 + 1 + + + CC1IF + Capture/compare 1 interrupt + flag + 1 + 1 + + + UIF + Update interrupt flag + 0 + 1 + + + + + EGR + EGR + event generation register + 0x14 + 0x20 + write-only + 0x0000 + + + BG + Break generation + 7 + 1 + + + TG + Trigger generation + 6 + 1 + + + COMG + Capture/Compare control update + generation + 5 + 1 + + + CC4G + Capture/compare 4 + generation + 4 + 1 + + + CC3G + Capture/compare 3 + generation + 3 + 1 + + + CC2G + Capture/compare 2 + generation + 2 + 1 + + + CC1G + Capture/compare 1 + generation + 1 + 1 + + + UG + Update generation + 0 + 1 + + + + + CCMR1_Output + CCMR1_Output + capture/compare mode register 1 (output + mode) + 0x18 + 0x20 + read-write + 0x00000000 + + + OC2CE + Output Compare 2 clear + enable + 15 + 1 + + + OC2M + Output Compare 2 mode + 12 + 3 + + + OC2PE + Output Compare 2 preload + enable + 11 + 1 + + + OC2FE + Output Compare 2 fast + enable + 10 + 1 + + + CC2S + Capture/Compare 2 + selection + 8 + 2 + + + OC1CE + Output Compare 1 clear + enable + 7 + 1 + + + OC1M + Output Compare 1 mode + 4 + 3 + + + OC1PE + Output Compare 1 preload + enable + 3 + 1 + + + OC1FE + Output Compare 1 fast + enable + 2 + 1 + + + CC1S + Capture/Compare 1 + selection + 0 + 2 + + + + + CCMR1_Input + CCMR1_Input + capture/compare mode register 1 (input + mode) + CCMR1_Output + 0x18 + 0x20 + read-write + 0x00000000 + + + IC2F + Input capture 2 filter + 12 + 4 + + + IC2PCS + Input capture 2 prescaler + 10 + 2 + + + CC2S + Capture/Compare 2 + selection + 8 + 2 + + + IC1F + Input capture 1 filter + 4 + 4 + + + ICPCS + Input capture 1 prescaler + 2 + 2 + + + CC1S + Capture/Compare 1 + selection + 0 + 2 + + + + + CCMR2_Output + CCMR2_Output + capture/compare mode register 2 (output + mode) + 0x1C + 0x20 + read-write + 0x00000000 + + + OC4CE + Output compare 4 clear + enable + 15 + 1 + + + OC4M + Output compare 4 mode + 12 + 3 + + + OC4PE + Output compare 4 preload + enable + 11 + 1 + + + OC4FE + Output compare 4 fast + enable + 10 + 1 + + + CC4S + Capture/Compare 4 + selection + 8 + 2 + + + OC3CE + Output compare 3 clear + enable + 7 + 1 + + + OC3M + Output compare 3 mode + 4 + 3 + + + OC3PE + Output compare 3 preload + enable + 3 + 1 + + + OC3FE + Output compare 3 fast + enable + 2 + 1 + + + CC3S + Capture/Compare 3 + selection + 0 + 2 + + + + + CCMR2_Input + CCMR2_Input + capture/compare mode register 2 (input + mode) + CCMR2_Output + 0x1C + 0x20 + read-write + 0x00000000 + + + IC4F + Input capture 4 filter + 12 + 4 + + + IC4PSC + Input capture 4 prescaler + 10 + 2 + + + CC4S + Capture/Compare 4 + selection + 8 + 2 + + + IC3F + Input capture 3 filter + 4 + 4 + + + IC3PSC + Input capture 3 prescaler + 2 + 2 + + + CC3S + Capture/compare 3 + selection + 0 + 2 + + + + + CCER + CCER + capture/compare enable + register + 0x20 + 0x20 + read-write + 0x0000 + + + CC4P + Capture/Compare 3 output + Polarity + 13 + 1 + + + CC4E + Capture/Compare 4 output + enable + 12 + 1 + + + CC3NP + Capture/Compare 3 output + Polarity + 11 + 1 + + + CC3NE + Capture/Compare 3 complementary output + enable + 10 + 1 + + + CC3P + Capture/Compare 3 output + Polarity + 9 + 1 + + + CC3E + Capture/Compare 3 output + enable + 8 + 1 + + + CC2NP + Capture/Compare 2 output + Polarity + 7 + 1 + + + CC2NE + Capture/Compare 2 complementary output + enable + 6 + 1 + + + CC2P + Capture/Compare 2 output + Polarity + 5 + 1 + + + CC2E + Capture/Compare 2 output + enable + 4 + 1 + + + CC1NP + Capture/Compare 1 output + Polarity + 3 + 1 + + + CC1NE + Capture/Compare 1 complementary output + enable + 2 + 1 + + + CC1P + Capture/Compare 1 output + Polarity + 1 + 1 + + + CC1E + Capture/Compare 1 output + enable + 0 + 1 + + + + + CNT + CNT + counter + 0x24 + 0x20 + read-write + 0x00000000 + + + CNT + counter value + 0 + 16 + + + + + PSC + PSC + prescaler + 0x28 + 0x20 + read-write + 0x0000 + + + PSC + Prescaler value + 0 + 16 + + + + + ARR + ARR + auto-reload register + 0x2C + 0x20 + read-write + 0x00000000 + + + ARR + Auto-reload value + 0 + 16 + + + + + CCR1 + CCR1 + capture/compare register 1 + 0x34 + 0x20 + read-write + 0x00000000 + + + CCR1 + Capture/Compare 1 value + 0 + 16 + + + + + CCR2 + CCR2 + capture/compare register 2 + 0x38 + 0x20 + read-write + 0x00000000 + + + CCR2 + Capture/Compare 2 value + 0 + 16 + + + + + CCR3 + CCR3 + capture/compare register 3 + 0x3C + 0x20 + read-write + 0x00000000 + + + CCR3 + Capture/Compare value + 0 + 16 + + + + + CCR4 + CCR4 + capture/compare register 4 + 0x40 + 0x20 + read-write + 0x00000000 + + + CCR4 + Capture/Compare value + 0 + 16 + + + + + DCR + DCR + DMA control register + 0x48 + 0x20 + read-write + 0x0000 + + + DBL + DMA burst length + 8 + 5 + + + DBA + DMA base address + 0 + 5 + + + + + DMAR + DMAR + DMA address for full transfer + 0x4C + 0x20 + read-write + 0x0000 + + + DMAB + DMA register for burst + accesses + 0 + 16 + + + + + RCR + RCR + repetition counter register + 0x30 + 0x20 + read-write + 0x0000 + + + REP + Repetition counter value + 0 + 8 + + + + + BDTR + BDTR + break and dead-time register + 0x44 + 0x20 + read-write + 0x0000 + + + MOE + Main output enable + 15 + 1 + + + AOE + Automatic output enable + 14 + 1 + + + BKP + Break polarity + 13 + 1 + + + BKE + Break enable + 12 + 1 + + + OSSR + Off-state selection for Run + mode + 11 + 1 + + + OSSI + Off-state selection for Idle + mode + 10 + 1 + + + LOCK + Lock configuration + 8 + 2 + + + DTG + Dead-time generator setup + 0 + 8 + + + + + + + TIM8 + 0x40010400 + + TIM8_BRK_TIM12 + TIM8 Break interrupt and TIM12 global + interrupt + 43 + + + TIM8_UP_TIM13 + TIM8 Update interrupt and TIM13 global + interrupt + 44 + + + TIM8_TRG_COM_TIM14 + TIM8 Trigger and Commutation interrupts and + TIM14 global interrupt + 45 + + + TIM8_CC + TIM8 Capture Compare interrupt + 46 + + + + TIM2 + General purpose timers + TIM + 0x40000000 + + 0x0 + 0x400 + registers + + + TIM2 + TIM2 global interrupt + 28 + + + + CR1 + CR1 + control register 1 + 0x0 + 0x20 + read-write + 0x0000 + + + CKD + Clock division + 8 + 2 + + + ARPE + Auto-reload preload enable + 7 + 1 + + + CMS + Center-aligned mode + selection + 5 + 2 + + + DIR + Direction + 4 + 1 + + + OPM + One-pulse mode + 3 + 1 + + + URS + Update request source + 2 + 1 + + + UDIS + Update disable + 1 + 1 + + + CEN + Counter enable + 0 + 1 + + + + + CR2 + CR2 + control register 2 + 0x4 + 0x20 + read-write + 0x0000 + + + TI1S + TI1 selection + 7 + 1 + + + MMS + Master mode selection + 4 + 3 + + + CCDS + Capture/compare DMA + selection + 3 + 1 + + + + + SMCR + SMCR + slave mode control register + 0x8 + 0x20 + read-write + 0x0000 + + + ETP + External trigger polarity + 15 + 1 + + + ECE + External clock enable + 14 + 1 + + + ETPS + External trigger prescaler + 12 + 2 + + + ETF + External trigger filter + 8 + 4 + + + MSM + Master/Slave mode + 7 + 1 + + + TS + Trigger selection + 4 + 3 + + + SMS + Slave mode selection + 0 + 3 + + + + + DIER + DIER + DMA/Interrupt enable register + 0xC + 0x20 + read-write + 0x0000 + + + TDE + Trigger DMA request enable + 14 + 1 + + + CC4DE + Capture/Compare 4 DMA request + enable + 12 + 1 + + + CC3DE + Capture/Compare 3 DMA request + enable + 11 + 1 + + + CC2DE + Capture/Compare 2 DMA request + enable + 10 + 1 + + + CC1DE + Capture/Compare 1 DMA request + enable + 9 + 1 + + + UDE + Update DMA request enable + 8 + 1 + + + TIE + Trigger interrupt enable + 6 + 1 + + + CC4IE + Capture/Compare 4 interrupt + enable + 4 + 1 + + + CC3IE + Capture/Compare 3 interrupt + enable + 3 + 1 + + + CC2IE + Capture/Compare 2 interrupt + enable + 2 + 1 + + + CC1IE + Capture/Compare 1 interrupt + enable + 1 + 1 + + + UIE + Update interrupt enable + 0 + 1 + + + + + SR + SR + status register + 0x10 + 0x20 + read-write + 0x0000 + + + CC4OF + Capture/Compare 4 overcapture + flag + 12 + 1 + + + CC3OF + Capture/Compare 3 overcapture + flag + 11 + 1 + + + CC2OF + Capture/compare 2 overcapture + flag + 10 + 1 + + + CC1OF + Capture/Compare 1 overcapture + flag + 9 + 1 + + + TIF + Trigger interrupt flag + 6 + 1 + + + CC4IF + Capture/Compare 4 interrupt + flag + 4 + 1 + + + CC3IF + Capture/Compare 3 interrupt + flag + 3 + 1 + + + CC2IF + Capture/Compare 2 interrupt + flag + 2 + 1 + + + CC1IF + Capture/compare 1 interrupt + flag + 1 + 1 + + + UIF + Update interrupt flag + 0 + 1 + + + + + EGR + EGR + event generation register + 0x14 + 0x20 + write-only + 0x0000 + + + TG + Trigger generation + 6 + 1 + + + CC4G + Capture/compare 4 + generation + 4 + 1 + + + CC3G + Capture/compare 3 + generation + 3 + 1 + + + CC2G + Capture/compare 2 + generation + 2 + 1 + + + CC1G + Capture/compare 1 + generation + 1 + 1 + + + UG + Update generation + 0 + 1 + + + + + CCMR1_Output + CCMR1_Output + capture/compare mode register 1 (output + mode) + 0x18 + 0x20 + read-write + 0x00000000 + + + OC2CE + OC2CE + 15 + 1 + + + OC2M + OC2M + 12 + 3 + + + OC2PE + OC2PE + 11 + 1 + + + OC2FE + OC2FE + 10 + 1 + + + CC2S + CC2S + 8 + 2 + + + OC1CE + OC1CE + 7 + 1 + + + OC1M + OC1M + 4 + 3 + + + OC1PE + OC1PE + 3 + 1 + + + OC1FE + OC1FE + 2 + 1 + + + CC1S + CC1S + 0 + 2 + + + + + CCMR1_Input + CCMR1_Input + capture/compare mode register 1 (input + mode) + CCMR1_Output + 0x18 + 0x20 + read-write + 0x00000000 + + + IC2F + Input capture 2 filter + 12 + 4 + + + IC2PCS + Input capture 2 prescaler + 10 + 2 + + + CC2S + Capture/Compare 2 + selection + 8 + 2 + + + IC1F + Input capture 1 filter + 4 + 4 + + + ICPCS + Input capture 1 prescaler + 2 + 2 + + + CC1S + Capture/Compare 1 + selection + 0 + 2 + + + + + CCMR2_Output + CCMR2_Output + capture/compare mode register 2 (output + mode) + 0x1C + 0x20 + read-write + 0x00000000 + + + O24CE + O24CE + 15 + 1 + + + OC4M + OC4M + 12 + 3 + + + OC4PE + OC4PE + 11 + 1 + + + OC4FE + OC4FE + 10 + 1 + + + CC4S + CC4S + 8 + 2 + + + OC3CE + OC3CE + 7 + 1 + + + OC3M + OC3M + 4 + 3 + + + OC3PE + OC3PE + 3 + 1 + + + OC3FE + OC3FE + 2 + 1 + + + CC3S + CC3S + 0 + 2 + + + + + CCMR2_Input + CCMR2_Input + capture/compare mode register 2 (input + mode) + CCMR2_Output + 0x1C + 0x20 + read-write + 0x00000000 + + + IC4F + Input capture 4 filter + 12 + 4 + + + IC4PSC + Input capture 4 prescaler + 10 + 2 + + + CC4S + Capture/Compare 4 + selection + 8 + 2 + + + IC3F + Input capture 3 filter + 4 + 4 + + + IC3PSC + Input capture 3 prescaler + 2 + 2 + + + CC3S + Capture/compare 3 + selection + 0 + 2 + + + + + CCER + CCER + capture/compare enable + register + 0x20 + 0x20 + read-write + 0x0000 + + + CC4NP + Capture/Compare 4 output + Polarity + 15 + 1 + + + CC4P + Capture/Compare 3 output + Polarity + 13 + 1 + + + CC4E + Capture/Compare 4 output + enable + 12 + 1 + + + CC3NP + Capture/Compare 3 output + Polarity + 11 + 1 + + + CC3P + Capture/Compare 3 output + Polarity + 9 + 1 + + + CC3E + Capture/Compare 3 output + enable + 8 + 1 + + + CC2NP + Capture/Compare 2 output + Polarity + 7 + 1 + + + CC2P + Capture/Compare 2 output + Polarity + 5 + 1 + + + CC2E + Capture/Compare 2 output + enable + 4 + 1 + + + CC1NP + Capture/Compare 1 output + Polarity + 3 + 1 + + + CC1P + Capture/Compare 1 output + Polarity + 1 + 1 + + + CC1E + Capture/Compare 1 output + enable + 0 + 1 + + + + + CNT + CNT + counter + 0x24 + 0x20 + read-write + 0x00000000 + + + CNT_H + High counter value + 16 + 16 + + + CNT_L + Low counter value + 0 + 16 + + + + + PSC + PSC + prescaler + 0x28 + 0x20 + read-write + 0x0000 + + + PSC + Prescaler value + 0 + 16 + + + + + ARR + ARR + auto-reload register + 0x2C + 0x20 + read-write + 0x00000000 + + + ARR_H + High Auto-reload value + 16 + 16 + + + ARR_L + Low Auto-reload value + 0 + 16 + + + + + CCR1 + CCR1 + capture/compare register 1 + 0x34 + 0x20 + read-write + 0x00000000 + + + CCR1_H + High Capture/Compare 1 + value + 16 + 16 + + + CCR1_L + Low Capture/Compare 1 + value + 0 + 16 + + + + + CCR2 + CCR2 + capture/compare register 2 + 0x38 + 0x20 + read-write + 0x00000000 + + + CCR2_H + High Capture/Compare 2 + value + 16 + 16 + + + CCR2_L + Low Capture/Compare 2 + value + 0 + 16 + + + + + CCR3 + CCR3 + capture/compare register 3 + 0x3C + 0x20 + read-write + 0x00000000 + + + CCR3_H + High Capture/Compare value + 16 + 16 + + + CCR3_L + Low Capture/Compare value + 0 + 16 + + + + + CCR4 + CCR4 + capture/compare register 4 + 0x40 + 0x20 + read-write + 0x00000000 + + + CCR4_H + High Capture/Compare value + 16 + 16 + + + CCR4_L + Low Capture/Compare value + 0 + 16 + + + + + DCR + DCR + DMA control register + 0x48 + 0x20 + read-write + 0x0000 + + + DBL + DMA burst length + 8 + 5 + + + DBA + DMA base address + 0 + 5 + + + + + DMAR + DMAR + DMA address for full transfer + 0x4C + 0x20 + read-write + 0x0000 + + + DMAB + DMA register for burst + accesses + 0 + 16 + + + + + OR + OR + TIM5 option register + 0x50 + 0x20 + read-write + 0x0000 + + + ITR1_RMP + Timer Input 4 remap + 10 + 2 + + + + + + + TIM3 + General purpose timers + TIM + 0x40000400 + + 0x0 + 0x400 + registers + + + TIM3 + TIM3 global interrupt + 29 + + + + CR1 + CR1 + control register 1 + 0x0 + 0x20 + read-write + 0x0000 + + + CKD + Clock division + 8 + 2 + + + ARPE + Auto-reload preload enable + 7 + 1 + + + CMS + Center-aligned mode + selection + 5 + 2 + + + DIR + Direction + 4 + 1 + + + OPM + One-pulse mode + 3 + 1 + + + URS + Update request source + 2 + 1 + + + UDIS + Update disable + 1 + 1 + + + CEN + Counter enable + 0 + 1 + + + + + CR2 + CR2 + control register 2 + 0x4 + 0x20 + read-write + 0x0000 + + + TI1S + TI1 selection + 7 + 1 + + + MMS + Master mode selection + 4 + 3 + + + CCDS + Capture/compare DMA + selection + 3 + 1 + + + + + SMCR + SMCR + slave mode control register + 0x8 + 0x20 + read-write + 0x0000 + + + ETP + External trigger polarity + 15 + 1 + + + ECE + External clock enable + 14 + 1 + + + ETPS + External trigger prescaler + 12 + 2 + + + ETF + External trigger filter + 8 + 4 + + + MSM + Master/Slave mode + 7 + 1 + + + TS + Trigger selection + 4 + 3 + + + SMS + Slave mode selection + 0 + 3 + + + + + DIER + DIER + DMA/Interrupt enable register + 0xC + 0x20 + read-write + 0x0000 + + + TDE + Trigger DMA request enable + 14 + 1 + + + CC4DE + Capture/Compare 4 DMA request + enable + 12 + 1 + + + CC3DE + Capture/Compare 3 DMA request + enable + 11 + 1 + + + CC2DE + Capture/Compare 2 DMA request + enable + 10 + 1 + + + CC1DE + Capture/Compare 1 DMA request + enable + 9 + 1 + + + UDE + Update DMA request enable + 8 + 1 + + + TIE + Trigger interrupt enable + 6 + 1 + + + CC4IE + Capture/Compare 4 interrupt + enable + 4 + 1 + + + CC3IE + Capture/Compare 3 interrupt + enable + 3 + 1 + + + CC2IE + Capture/Compare 2 interrupt + enable + 2 + 1 + + + CC1IE + Capture/Compare 1 interrupt + enable + 1 + 1 + + + UIE + Update interrupt enable + 0 + 1 + + + + + SR + SR + status register + 0x10 + 0x20 + read-write + 0x0000 + + + CC4OF + Capture/Compare 4 overcapture + flag + 12 + 1 + + + CC3OF + Capture/Compare 3 overcapture + flag + 11 + 1 + + + CC2OF + Capture/compare 2 overcapture + flag + 10 + 1 + + + CC1OF + Capture/Compare 1 overcapture + flag + 9 + 1 + + + TIF + Trigger interrupt flag + 6 + 1 + + + CC4IF + Capture/Compare 4 interrupt + flag + 4 + 1 + + + CC3IF + Capture/Compare 3 interrupt + flag + 3 + 1 + + + CC2IF + Capture/Compare 2 interrupt + flag + 2 + 1 + + + CC1IF + Capture/compare 1 interrupt + flag + 1 + 1 + + + UIF + Update interrupt flag + 0 + 1 + + + + + EGR + EGR + event generation register + 0x14 + 0x20 + write-only + 0x0000 + + + TG + Trigger generation + 6 + 1 + + + CC4G + Capture/compare 4 + generation + 4 + 1 + + + CC3G + Capture/compare 3 + generation + 3 + 1 + + + CC2G + Capture/compare 2 + generation + 2 + 1 + + + CC1G + Capture/compare 1 + generation + 1 + 1 + + + UG + Update generation + 0 + 1 + + + + + CCMR1_Output + CCMR1_Output + capture/compare mode register 1 (output + mode) + 0x18 + 0x20 + read-write + 0x00000000 + + + OC2CE + OC2CE + 15 + 1 + + + OC2M + OC2M + 12 + 3 + + + OC2PE + OC2PE + 11 + 1 + + + OC2FE + OC2FE + 10 + 1 + + + CC2S + CC2S + 8 + 2 + + + OC1CE + OC1CE + 7 + 1 + + + OC1M + OC1M + 4 + 3 + + + OC1PE + OC1PE + 3 + 1 + + + OC1FE + OC1FE + 2 + 1 + + + CC1S + CC1S + 0 + 2 + + + + + CCMR1_Input + CCMR1_Input + capture/compare mode register 1 (input + mode) + CCMR1_Output + 0x18 + 0x20 + read-write + 0x00000000 + + + IC2F + Input capture 2 filter + 12 + 4 + + + IC2PCS + Input capture 2 prescaler + 10 + 2 + + + CC2S + Capture/Compare 2 + selection + 8 + 2 + + + IC1F + Input capture 1 filter + 4 + 4 + + + ICPCS + Input capture 1 prescaler + 2 + 2 + + + CC1S + Capture/Compare 1 + selection + 0 + 2 + + + + + CCMR2_Output + CCMR2_Output + capture/compare mode register 2 (output + mode) + 0x1C + 0x20 + read-write + 0x00000000 + + + O24CE + O24CE + 15 + 1 + + + OC4M + OC4M + 12 + 3 + + + OC4PE + OC4PE + 11 + 1 + + + OC4FE + OC4FE + 10 + 1 + + + CC4S + CC4S + 8 + 2 + + + OC3CE + OC3CE + 7 + 1 + + + OC3M + OC3M + 4 + 3 + + + OC3PE + OC3PE + 3 + 1 + + + OC3FE + OC3FE + 2 + 1 + + + CC3S + CC3S + 0 + 2 + + + + + CCMR2_Input + CCMR2_Input + capture/compare mode register 2 (input + mode) + CCMR2_Output + 0x1C + 0x20 + read-write + 0x00000000 + + + IC4F + Input capture 4 filter + 12 + 4 + + + IC4PSC + Input capture 4 prescaler + 10 + 2 + + + CC4S + Capture/Compare 4 + selection + 8 + 2 + + + IC3F + Input capture 3 filter + 4 + 4 + + + IC3PSC + Input capture 3 prescaler + 2 + 2 + + + CC3S + Capture/compare 3 + selection + 0 + 2 + + + + + CCER + CCER + capture/compare enable + register + 0x20 + 0x20 + read-write + 0x0000 + + + CC4NP + Capture/Compare 4 output + Polarity + 15 + 1 + + + CC4P + Capture/Compare 3 output + Polarity + 13 + 1 + + + CC4E + Capture/Compare 4 output + enable + 12 + 1 + + + CC3NP + Capture/Compare 3 output + Polarity + 11 + 1 + + + CC3P + Capture/Compare 3 output + Polarity + 9 + 1 + + + CC3E + Capture/Compare 3 output + enable + 8 + 1 + + + CC2NP + Capture/Compare 2 output + Polarity + 7 + 1 + + + CC2P + Capture/Compare 2 output + Polarity + 5 + 1 + + + CC2E + Capture/Compare 2 output + enable + 4 + 1 + + + CC1NP + Capture/Compare 1 output + Polarity + 3 + 1 + + + CC1P + Capture/Compare 1 output + Polarity + 1 + 1 + + + CC1E + Capture/Compare 1 output + enable + 0 + 1 + + + + + CNT + CNT + counter + 0x24 + 0x20 + read-write + 0x00000000 + + + CNT_H + High counter value + 16 + 16 + + + CNT_L + Low counter value + 0 + 16 + + + + + PSC + PSC + prescaler + 0x28 + 0x20 + read-write + 0x0000 + + + PSC + Prescaler value + 0 + 16 + + + + + ARR + ARR + auto-reload register + 0x2C + 0x20 + read-write + 0x00000000 + + + ARR_H + High Auto-reload value + 16 + 16 + + + ARR_L + Low Auto-reload value + 0 + 16 + + + + + CCR1 + CCR1 + capture/compare register 1 + 0x34 + 0x20 + read-write + 0x00000000 + + + CCR1_H + High Capture/Compare 1 + value + 16 + 16 + + + CCR1_L + Low Capture/Compare 1 + value + 0 + 16 + + + + + CCR2 + CCR2 + capture/compare register 2 + 0x38 + 0x20 + read-write + 0x00000000 + + + CCR2_H + High Capture/Compare 2 + value + 16 + 16 + + + CCR2_L + Low Capture/Compare 2 + value + 0 + 16 + + + + + CCR3 + CCR3 + capture/compare register 3 + 0x3C + 0x20 + read-write + 0x00000000 + + + CCR3_H + High Capture/Compare value + 16 + 16 + + + CCR3_L + Low Capture/Compare value + 0 + 16 + + + + + CCR4 + CCR4 + capture/compare register 4 + 0x40 + 0x20 + read-write + 0x00000000 + + + CCR4_H + High Capture/Compare value + 16 + 16 + + + CCR4_L + Low Capture/Compare value + 0 + 16 + + + + + DCR + DCR + DMA control register + 0x48 + 0x20 + read-write + 0x0000 + + + DBL + DMA burst length + 8 + 5 + + + DBA + DMA base address + 0 + 5 + + + + + DMAR + DMAR + DMA address for full transfer + 0x4C + 0x20 + read-write + 0x0000 + + + DMAB + DMA register for burst + accesses + 0 + 16 + + + + + + + TIM4 + 0x40000800 + + TIM4 + TIM4 global interrupt + 30 + + + + TIM5 + General-purpose-timers + TIM + 0x40000C00 + + 0x0 + 0x400 + registers + + + TIM5 + TIM5 global interrupt + 50 + + + + CR1 + CR1 + control register 1 + 0x0 + 0x20 + read-write + 0x0000 + + + CKD + Clock division + 8 + 2 + + + ARPE + Auto-reload preload enable + 7 + 1 + + + CMS + Center-aligned mode + selection + 5 + 2 + + + DIR + Direction + 4 + 1 + + + OPM + One-pulse mode + 3 + 1 + + + URS + Update request source + 2 + 1 + + + UDIS + Update disable + 1 + 1 + + + CEN + Counter enable + 0 + 1 + + + + + CR2 + CR2 + control register 2 + 0x4 + 0x20 + read-write + 0x0000 + + + TI1S + TI1 selection + 7 + 1 + + + MMS + Master mode selection + 4 + 3 + + + CCDS + Capture/compare DMA + selection + 3 + 1 + + + + + SMCR + SMCR + slave mode control register + 0x8 + 0x20 + read-write + 0x0000 + + + ETP + External trigger polarity + 15 + 1 + + + ECE + External clock enable + 14 + 1 + + + ETPS + External trigger prescaler + 12 + 2 + + + ETF + External trigger filter + 8 + 4 + + + MSM + Master/Slave mode + 7 + 1 + + + TS + Trigger selection + 4 + 3 + + + SMS + Slave mode selection + 0 + 3 + + + + + DIER + DIER + DMA/Interrupt enable register + 0xC + 0x20 + read-write + 0x0000 + + + TDE + Trigger DMA request enable + 14 + 1 + + + CC4DE + Capture/Compare 4 DMA request + enable + 12 + 1 + + + CC3DE + Capture/Compare 3 DMA request + enable + 11 + 1 + + + CC2DE + Capture/Compare 2 DMA request + enable + 10 + 1 + + + CC1DE + Capture/Compare 1 DMA request + enable + 9 + 1 + + + UDE + Update DMA request enable + 8 + 1 + + + TIE + Trigger interrupt enable + 6 + 1 + + + CC4IE + Capture/Compare 4 interrupt + enable + 4 + 1 + + + CC3IE + Capture/Compare 3 interrupt + enable + 3 + 1 + + + CC2IE + Capture/Compare 2 interrupt + enable + 2 + 1 + + + CC1IE + Capture/Compare 1 interrupt + enable + 1 + 1 + + + UIE + Update interrupt enable + 0 + 1 + + + + + SR + SR + status register + 0x10 + 0x20 + read-write + 0x0000 + + + CC4OF + Capture/Compare 4 overcapture + flag + 12 + 1 + + + CC3OF + Capture/Compare 3 overcapture + flag + 11 + 1 + + + CC2OF + Capture/compare 2 overcapture + flag + 10 + 1 + + + CC1OF + Capture/Compare 1 overcapture + flag + 9 + 1 + + + TIF + Trigger interrupt flag + 6 + 1 + + + CC4IF + Capture/Compare 4 interrupt + flag + 4 + 1 + + + CC3IF + Capture/Compare 3 interrupt + flag + 3 + 1 + + + CC2IF + Capture/Compare 2 interrupt + flag + 2 + 1 + + + CC1IF + Capture/compare 1 interrupt + flag + 1 + 1 + + + UIF + Update interrupt flag + 0 + 1 + + + + + EGR + EGR + event generation register + 0x14 + 0x20 + write-only + 0x0000 + + + TG + Trigger generation + 6 + 1 + + + CC4G + Capture/compare 4 + generation + 4 + 1 + + + CC3G + Capture/compare 3 + generation + 3 + 1 + + + CC2G + Capture/compare 2 + generation + 2 + 1 + + + CC1G + Capture/compare 1 + generation + 1 + 1 + + + UG + Update generation + 0 + 1 + + + + + CCMR1_Output + CCMR1_Output + capture/compare mode register 1 (output + mode) + 0x18 + 0x20 + read-write + 0x00000000 + + + OC2CE + OC2CE + 15 + 1 + + + OC2M + OC2M + 12 + 3 + + + OC2PE + OC2PE + 11 + 1 + + + OC2FE + OC2FE + 10 + 1 + + + CC2S + CC2S + 8 + 2 + + + OC1CE + OC1CE + 7 + 1 + + + OC1M + OC1M + 4 + 3 + + + OC1PE + OC1PE + 3 + 1 + + + OC1FE + OC1FE + 2 + 1 + + + CC1S + CC1S + 0 + 2 + + + + + CCMR1_Input + CCMR1_Input + capture/compare mode register 1 (input + mode) + CCMR1_Output + 0x18 + 0x20 + read-write + 0x00000000 + + + IC2F + Input capture 2 filter + 12 + 4 + + + IC2PCS + Input capture 2 prescaler + 10 + 2 + + + CC2S + Capture/Compare 2 + selection + 8 + 2 + + + IC1F + Input capture 1 filter + 4 + 4 + + + ICPCS + Input capture 1 prescaler + 2 + 2 + + + CC1S + Capture/Compare 1 + selection + 0 + 2 + + + + + CCMR2_Output + CCMR2_Output + capture/compare mode register 2 (output + mode) + 0x1C + 0x20 + read-write + 0x00000000 + + + O24CE + O24CE + 15 + 1 + + + OC4M + OC4M + 12 + 3 + + + OC4PE + OC4PE + 11 + 1 + + + OC4FE + OC4FE + 10 + 1 + + + CC4S + CC4S + 8 + 2 + + + OC3CE + OC3CE + 7 + 1 + + + OC3M + OC3M + 4 + 3 + + + OC3PE + OC3PE + 3 + 1 + + + OC3FE + OC3FE + 2 + 1 + + + CC3S + CC3S + 0 + 2 + + + + + CCMR2_Input + CCMR2_Input + capture/compare mode register 2 (input + mode) + CCMR2_Output + 0x1C + 0x20 + read-write + 0x00000000 + + + IC4F + Input capture 4 filter + 12 + 4 + + + IC4PSC + Input capture 4 prescaler + 10 + 2 + + + CC4S + Capture/Compare 4 + selection + 8 + 2 + + + IC3F + Input capture 3 filter + 4 + 4 + + + IC3PSC + Input capture 3 prescaler + 2 + 2 + + + CC3S + Capture/compare 3 + selection + 0 + 2 + + + + + CCER + CCER + capture/compare enable + register + 0x20 + 0x20 + read-write + 0x0000 + + + CC4NP + Capture/Compare 4 output + Polarity + 15 + 1 + + + CC4P + Capture/Compare 3 output + Polarity + 13 + 1 + + + CC4E + Capture/Compare 4 output + enable + 12 + 1 + + + CC3NP + Capture/Compare 3 output + Polarity + 11 + 1 + + + CC3P + Capture/Compare 3 output + Polarity + 9 + 1 + + + CC3E + Capture/Compare 3 output + enable + 8 + 1 + + + CC2NP + Capture/Compare 2 output + Polarity + 7 + 1 + + + CC2P + Capture/Compare 2 output + Polarity + 5 + 1 + + + CC2E + Capture/Compare 2 output + enable + 4 + 1 + + + CC1NP + Capture/Compare 1 output + Polarity + 3 + 1 + + + CC1P + Capture/Compare 1 output + Polarity + 1 + 1 + + + CC1E + Capture/Compare 1 output + enable + 0 + 1 + + + + + CNT + CNT + counter + 0x24 + 0x20 + read-write + 0x00000000 + + + CNT_H + High counter value + 16 + 16 + + + CNT_L + Low counter value + 0 + 16 + + + + + PSC + PSC + prescaler + 0x28 + 0x20 + read-write + 0x0000 + + + PSC + Prescaler value + 0 + 16 + + + + + ARR + ARR + auto-reload register + 0x2C + 0x20 + read-write + 0x00000000 + + + ARR_H + High Auto-reload value + 16 + 16 + + + ARR_L + Low Auto-reload value + 0 + 16 + + + + + CCR1 + CCR1 + capture/compare register 1 + 0x34 + 0x20 + read-write + 0x00000000 + + + CCR1_H + High Capture/Compare 1 + value + 16 + 16 + + + CCR1_L + Low Capture/Compare 1 + value + 0 + 16 + + + + + CCR2 + CCR2 + capture/compare register 2 + 0x38 + 0x20 + read-write + 0x00000000 + + + CCR2_H + High Capture/Compare 2 + value + 16 + 16 + + + CCR2_L + Low Capture/Compare 2 + value + 0 + 16 + + + + + CCR3 + CCR3 + capture/compare register 3 + 0x3C + 0x20 + read-write + 0x00000000 + + + CCR3_H + High Capture/Compare value + 16 + 16 + + + CCR3_L + Low Capture/Compare value + 0 + 16 + + + + + CCR4 + CCR4 + capture/compare register 4 + 0x40 + 0x20 + read-write + 0x00000000 + + + CCR4_H + High Capture/Compare value + 16 + 16 + + + CCR4_L + Low Capture/Compare value + 0 + 16 + + + + + DCR + DCR + DMA control register + 0x48 + 0x20 + read-write + 0x0000 + + + DBL + DMA burst length + 8 + 5 + + + DBA + DMA base address + 0 + 5 + + + + + DMAR + DMAR + DMA address for full transfer + 0x4C + 0x20 + read-write + 0x0000 + + + DMAB + DMA register for burst + accesses + 0 + 16 + + + + + OR + OR + TIM5 option register + 0x50 + 0x20 + read-write + 0x0000 + + + IT4_RMP + Timer Input 4 remap + 6 + 2 + + + + + + + TIM9 + General purpose timers + TIM + 0x40014000 + + 0x0 + 0x400 + registers + + + TIM1_BRK_TIM9 + TIM1 Break interrupt and TIM9 global + interrupt + 24 + + + + CR1 + CR1 + control register 1 + 0x0 + 0x20 + read-write + 0x0000 + + + CKD + Clock division + 8 + 2 + + + ARPE + Auto-reload preload enable + 7 + 1 + + + OPM + One-pulse mode + 3 + 1 + + + URS + Update request source + 2 + 1 + + + UDIS + Update disable + 1 + 1 + + + CEN + Counter enable + 0 + 1 + + + + + CR2 + CR2 + control register 2 + 0x4 + 0x20 + read-write + 0x0000 + + + MMS + Master mode selection + 4 + 3 + + + + + SMCR + SMCR + slave mode control register + 0x8 + 0x20 + read-write + 0x0000 + + + MSM + Master/Slave mode + 7 + 1 + + + TS + Trigger selection + 4 + 3 + + + SMS + Slave mode selection + 0 + 3 + + + + + DIER + DIER + DMA/Interrupt enable register + 0xC + 0x20 + read-write + 0x0000 + + + TIE + Trigger interrupt enable + 6 + 1 + + + CC2IE + Capture/Compare 2 interrupt + enable + 2 + 1 + + + CC1IE + Capture/Compare 1 interrupt + enable + 1 + 1 + + + UIE + Update interrupt enable + 0 + 1 + + + + + SR + SR + status register + 0x10 + 0x20 + read-write + 0x0000 + + + CC2OF + Capture/compare 2 overcapture + flag + 10 + 1 + + + CC1OF + Capture/Compare 1 overcapture + flag + 9 + 1 + + + TIF + Trigger interrupt flag + 6 + 1 + + + CC2IF + Capture/Compare 2 interrupt + flag + 2 + 1 + + + CC1IF + Capture/compare 1 interrupt + flag + 1 + 1 + + + UIF + Update interrupt flag + 0 + 1 + + + + + EGR + EGR + event generation register + 0x14 + 0x20 + write-only + 0x0000 + + + TG + Trigger generation + 6 + 1 + + + CC2G + Capture/compare 2 + generation + 2 + 1 + + + CC1G + Capture/compare 1 + generation + 1 + 1 + + + UG + Update generation + 0 + 1 + + + + + CCMR1_Output + CCMR1_Output + capture/compare mode register 1 (output + mode) + 0x18 + 0x20 + read-write + 0x00000000 + + + OC2M + Output Compare 2 mode + 12 + 3 + + + OC2PE + Output Compare 2 preload + enable + 11 + 1 + + + OC2FE + Output Compare 2 fast + enable + 10 + 1 + + + CC2S + Capture/Compare 2 + selection + 8 + 2 + + + OC1M + Output Compare 1 mode + 4 + 3 + + + OC1PE + Output Compare 1 preload + enable + 3 + 1 + + + OC1FE + Output Compare 1 fast + enable + 2 + 1 + + + CC1S + Capture/Compare 1 + selection + 0 + 2 + + + + + CCMR1_Input + CCMR1_Input + capture/compare mode register 1 (input + mode) + CCMR1_Output + 0x18 + 0x20 + read-write + 0x00000000 + + + IC2F + Input capture 2 filter + 12 + 3 + + + IC2PCS + Input capture 2 prescaler + 10 + 2 + + + CC2S + Capture/Compare 2 + selection + 8 + 2 + + + IC1F + Input capture 1 filter + 4 + 3 + + + ICPCS + Input capture 1 prescaler + 2 + 2 + + + CC1S + Capture/Compare 1 + selection + 0 + 2 + + + + + CCER + CCER + capture/compare enable + register + 0x20 + 0x20 + read-write + 0x0000 + + + CC2NP + Capture/Compare 2 output + Polarity + 7 + 1 + + + CC2P + Capture/Compare 2 output + Polarity + 5 + 1 + + + CC2E + Capture/Compare 2 output + enable + 4 + 1 + + + CC1NP + Capture/Compare 1 output + Polarity + 3 + 1 + + + CC1P + Capture/Compare 1 output + Polarity + 1 + 1 + + + CC1E + Capture/Compare 1 output + enable + 0 + 1 + + + + + CNT + CNT + counter + 0x24 + 0x20 + read-write + 0x00000000 + + + CNT + counter value + 0 + 16 + + + + + PSC + PSC + prescaler + 0x28 + 0x20 + read-write + 0x0000 + + + PSC + Prescaler value + 0 + 16 + + + + + ARR + ARR + auto-reload register + 0x2C + 0x20 + read-write + 0x00000000 + + + ARR + Auto-reload value + 0 + 16 + + + + + CCR1 + CCR1 + capture/compare register 1 + 0x34 + 0x20 + read-write + 0x00000000 + + + CCR1 + Capture/Compare 1 value + 0 + 16 + + + + + CCR2 + CCR2 + capture/compare register 2 + 0x38 + 0x20 + read-write + 0x00000000 + + + CCR2 + Capture/Compare 2 value + 0 + 16 + + + + + + + TIM12 + 0x40001800 + + TIM8_BRK_TIM12 + TIM8 Break interrupt and TIM12 global + interrupt + 43 + + + + TIM10 + General-purpose-timers + TIM + 0x40014400 + + 0x0 + 0x400 + registers + + + TIM1_UP_TIM10 + TIM1 Update interrupt and TIM10 global + interrupt + 25 + + + + CR1 + CR1 + control register 1 + 0x0 + 0x20 + read-write + 0x0000 + + + CKD + Clock division + 8 + 2 + + + ARPE + Auto-reload preload enable + 7 + 1 + + + URS + Update request source + 2 + 1 + + + UDIS + Update disable + 1 + 1 + + + CEN + Counter enable + 0 + 1 + + + + + DIER + DIER + DMA/Interrupt enable register + 0xC + 0x20 + read-write + 0x0000 + + + CC1IE + Capture/Compare 1 interrupt + enable + 1 + 1 + + + UIE + Update interrupt enable + 0 + 1 + + + + + SR + SR + status register + 0x10 + 0x20 + read-write + 0x0000 + + + CC1OF + Capture/Compare 1 overcapture + flag + 9 + 1 + + + CC1IF + Capture/compare 1 interrupt + flag + 1 + 1 + + + UIF + Update interrupt flag + 0 + 1 + + + + + EGR + EGR + event generation register + 0x14 + 0x20 + write-only + 0x0000 + + + CC1G + Capture/compare 1 + generation + 1 + 1 + + + UG + Update generation + 0 + 1 + + + + + CCMR1_Output + CCMR1_Output + capture/compare mode register 1 (output + mode) + 0x18 + 0x20 + read-write + 0x00000000 + + + OC1M + Output Compare 1 mode + 4 + 3 + + + OC1PE + Output Compare 1 preload + enable + 3 + 1 + + + OC1FE + Output Compare 1 fast + enable + 2 + 1 + + + CC1S + Capture/Compare 1 + selection + 0 + 2 + + + + + CCMR1_Input + CCMR1_Input + capture/compare mode register 1 (input + mode) + CCMR1_Output + 0x18 + 0x20 + read-write + 0x00000000 + + + IC1F + Input capture 1 filter + 4 + 4 + + + ICPCS + Input capture 1 prescaler + 2 + 2 + + + CC1S + Capture/Compare 1 + selection + 0 + 2 + + + + + CCER + CCER + capture/compare enable + register + 0x20 + 0x20 + read-write + 0x0000 + + + CC1NP + Capture/Compare 1 output + Polarity + 3 + 1 + + + CC1P + Capture/Compare 1 output + Polarity + 1 + 1 + + + CC1E + Capture/Compare 1 output + enable + 0 + 1 + + + + + CNT + CNT + counter + 0x24 + 0x20 + read-write + 0x00000000 + + + CNT + counter value + 0 + 16 + + + + + PSC + PSC + prescaler + 0x28 + 0x20 + read-write + 0x0000 + + + PSC + Prescaler value + 0 + 16 + + + + + ARR + ARR + auto-reload register + 0x2C + 0x20 + read-write + 0x00000000 + + + ARR + Auto-reload value + 0 + 16 + + + + + CCR1 + CCR1 + capture/compare register 1 + 0x34 + 0x20 + read-write + 0x00000000 + + + CCR1 + Capture/Compare 1 value + 0 + 16 + + + + + + + TIM13 + 0x40001C00 + + TIM8_UP_TIM13 + TIM8 Update interrupt and TIM13 global + interrupt + 44 + + + + TIM14 + 0x40002000 + + TIM8_TRG_COM_TIM14 + TIM8 Trigger and Commutation interrupts and + TIM14 global interrupt + 45 + + + + TIM11 + General-purpose-timers + TIM + 0x40014800 + + 0x0 + 0x400 + registers + + + TIM1_TRG_COM_TIM11 + TIM1 Trigger and Commutation interrupts and + TIM11 global interrupt + 26 + + + + CR1 + CR1 + control register 1 + 0x0 + 0x20 + read-write + 0x0000 + + + CKD + Clock division + 8 + 2 + + + ARPE + Auto-reload preload enable + 7 + 1 + + + URS + Update request source + 2 + 1 + + + UDIS + Update disable + 1 + 1 + + + CEN + Counter enable + 0 + 1 + + + + + DIER + DIER + DMA/Interrupt enable register + 0xC + 0x20 + read-write + 0x0000 + + + CC1IE + Capture/Compare 1 interrupt + enable + 1 + 1 + + + UIE + Update interrupt enable + 0 + 1 + + + + + SR + SR + status register + 0x10 + 0x20 + read-write + 0x0000 + + + CC1OF + Capture/Compare 1 overcapture + flag + 9 + 1 + + + CC1IF + Capture/compare 1 interrupt + flag + 1 + 1 + + + UIF + Update interrupt flag + 0 + 1 + + + + + EGR + EGR + event generation register + 0x14 + 0x20 + write-only + 0x0000 + + + CC1G + Capture/compare 1 + generation + 1 + 1 + + + UG + Update generation + 0 + 1 + + + + + CCMR1_Output + CCMR1_Output + capture/compare mode register 1 (output + mode) + 0x18 + 0x20 + read-write + 0x00000000 + + + OC1M + Output Compare 1 mode + 4 + 3 + + + OC1PE + Output Compare 1 preload + enable + 3 + 1 + + + OC1FE + Output Compare 1 fast + enable + 2 + 1 + + + CC1S + Capture/Compare 1 + selection + 0 + 2 + + + + + CCMR1_Input + CCMR1_Input + capture/compare mode register 1 (input + mode) + CCMR1_Output + 0x18 + 0x20 + read-write + 0x00000000 + + + IC1F + Input capture 1 filter + 4 + 4 + + + ICPCS + Input capture 1 prescaler + 2 + 2 + + + CC1S + Capture/Compare 1 + selection + 0 + 2 + + + + + CCER + CCER + capture/compare enable + register + 0x20 + 0x20 + read-write + 0x0000 + + + CC1NP + Capture/Compare 1 output + Polarity + 3 + 1 + + + CC1P + Capture/Compare 1 output + Polarity + 1 + 1 + + + CC1E + Capture/Compare 1 output + enable + 0 + 1 + + + + + CNT + CNT + counter + 0x24 + 0x20 + read-write + 0x00000000 + + + CNT + counter value + 0 + 16 + + + + + PSC + PSC + prescaler + 0x28 + 0x20 + read-write + 0x0000 + + + PSC + Prescaler value + 0 + 16 + + + + + ARR + ARR + auto-reload register + 0x2C + 0x20 + read-write + 0x00000000 + + + ARR + Auto-reload value + 0 + 16 + + + + + CCR1 + CCR1 + capture/compare register 1 + 0x34 + 0x20 + read-write + 0x00000000 + + + CCR1 + Capture/Compare 1 value + 0 + 16 + + + + + OR + OR + option register + 0x50 + 0x20 + read-write + 0x00000000 + + + RMP + Input 1 remapping + capability + 0 + 2 + + + + + + + TIM6 + Basic timers + TIM + 0x40001000 + + 0x0 + 0x400 + registers + + + TIM6_DAC + TIM6 global interrupt, DAC1 and DAC2 underrun + error interrupt + 54 + + + + CR1 + CR1 + control register 1 + 0x0 + 0x20 + read-write + 0x0000 + + + ARPE + Auto-reload preload enable + 7 + 1 + + + OPM + One-pulse mode + 3 + 1 + + + URS + Update request source + 2 + 1 + + + UDIS + Update disable + 1 + 1 + + + CEN + Counter enable + 0 + 1 + + + + + CR2 + CR2 + control register 2 + 0x4 + 0x20 + read-write + 0x0000 + + + MMS + Master mode selection + 4 + 3 + + + + + DIER + DIER + DMA/Interrupt enable register + 0xC + 0x20 + read-write + 0x0000 + + + UDE + Update DMA request enable + 8 + 1 + + + UIE + Update interrupt enable + 0 + 1 + + + + + SR + SR + status register + 0x10 + 0x20 + read-write + 0x0000 + + + UIF + Update interrupt flag + 0 + 1 + + + + + EGR + EGR + event generation register + 0x14 + 0x20 + write-only + 0x0000 + + + UG + Update generation + 0 + 1 + + + + + CNT + CNT + counter + 0x24 + 0x20 + read-write + 0x00000000 + + + CNT + Low counter value + 0 + 16 + + + + + PSC + PSC + prescaler + 0x28 + 0x20 + read-write + 0x0000 + + + PSC + Prescaler value + 0 + 16 + + + + + ARR + ARR + auto-reload register + 0x2C + 0x20 + read-write + 0x00000000 + + + ARR + Low Auto-reload value + 0 + 16 + + + + + + + TIM7 + 0x40001400 + + TIM7 + TIM7 global interrupt + 55 + + + + Ethernet_MAC + Ethernet: media access control + (MAC) + Ethernet + 0x40028000 + + 0x0 + 0x400 + registers + + + ETH + Ethernet global interrupt + 61 + + + ETH_WKUP + Ethernet Wakeup through EXTI line + interrupt + 62 + + + + MACCR + MACCR + Ethernet MAC configuration + register + 0x0 + 0x20 + read-write + 0x0008000 + + + RE + RE + 2 + 1 + + + TE + TE + 3 + 1 + + + DC + DC + 4 + 1 + + + BL + BL + 5 + 2 + + + APCS + APCS + 7 + 1 + + + RD + RD + 9 + 1 + + + IPCO + IPCO + 10 + 1 + + + DM + DM + 11 + 1 + + + LM + LM + 12 + 1 + + + ROD + ROD + 13 + 1 + + + FES + FES + 14 + 1 + + + CSD + CSD + 16 + 1 + + + IFG + IFG + 17 + 3 + + + JD + JD + 22 + 1 + + + WD + WD + 23 + 1 + + + CSTF + CSTF + 25 + 1 + + + + + MACFFR + MACFFR + Ethernet MAC frame filter + register + 0x4 + 0x20 + read-write + 0x00000000 + + + PM + no description available + 0 + 1 + + + HU + no description available + 1 + 1 + + + HM + no description available + 2 + 1 + + + DAIF + no description available + 3 + 1 + + + RAM + no description available + 4 + 1 + + + BFD + no description available + 5 + 1 + + + PCF + no description available + 6 + 1 + + + SAIF + no description available + 7 + 1 + + + SAF + no description available + 8 + 1 + + + HPF + no description available + 9 + 1 + + + RA + no description available + 31 + 1 + + + + + MACHTHR + MACHTHR + Ethernet MAC hash table high + register + 0x8 + 0x20 + read-write + 0x00000000 + + + HTH + no description available + 0 + 32 + + + + + MACHTLR + MACHTLR + Ethernet MAC hash table low + register + 0xC + 0x20 + read-write + 0x00000000 + + + HTL + no description available + 0 + 32 + + + + + MACMIIAR + MACMIIAR + Ethernet MAC MII address + register + 0x10 + 0x20 + read-write + 0x00000000 + + + MB + no description available + 0 + 1 + + + MW + no description available + 1 + 1 + + + CR + no description available + 2 + 3 + + + MR + no description available + 6 + 5 + + + PA + no description available + 11 + 5 + + + + + MACMIIDR + MACMIIDR + Ethernet MAC MII data register + 0x14 + 0x20 + read-write + 0x00000000 + + + TD + no description available + 0 + 16 + + + + + MACFCR + MACFCR + Ethernet MAC flow control + register + 0x18 + 0x20 + read-write + 0x00000000 + + + FCB + no description available + 0 + 1 + + + TFCE + no description available + 1 + 1 + + + RFCE + no description available + 2 + 1 + + + UPFD + no description available + 3 + 1 + + + PLT + no description available + 4 + 2 + + + ZQPD + no description available + 7 + 1 + + + PT + no description available + 16 + 16 + + + + + MACVLANTR + MACVLANTR + Ethernet MAC VLAN tag register + 0x1C + 0x20 + read-write + 0x00000000 + + + VLANTI + no description available + 0 + 16 + + + VLANTC + no description available + 16 + 1 + + + + + MACPMTCSR + MACPMTCSR + Ethernet MAC PMT control and status + register + 0x2C + 0x20 + read-write + 0x00000000 + + + PD + no description available + 0 + 1 + + + MPE + no description available + 1 + 1 + + + WFE + no description available + 2 + 1 + + + MPR + no description available + 5 + 1 + + + WFR + no description available + 6 + 1 + + + GU + no description available + 9 + 1 + + + WFFRPR + no description available + 31 + 1 + + + + + MACDBGR + MACDBGR + Ethernet MAC debug register + 0x34 + 0x20 + read-only + 0x00000000 + + + CR + CR + 0 + 1 + + + CSR + CSR + 1 + 1 + + + ROR + ROR + 2 + 1 + + + MCF + MCF + 3 + 1 + + + MCP + MCP + 4 + 1 + + + MCFHP + MCFHP + 5 + 1 + + + + + MACSR + MACSR + Ethernet MAC interrupt status + register + 0x38 + 0x20 + 0x00000000 + + + PMTS + no description available + 3 + 1 + read-only + + + MMCS + no description available + 4 + 1 + read-only + + + MMCRS + no description available + 5 + 1 + read-only + + + MMCTS + no description available + 6 + 1 + read-only + + + TSTS + no description available + 9 + 1 + read-write + + + + + MACIMR + MACIMR + Ethernet MAC interrupt mask + register + 0x3C + 0x20 + read-write + 0x00000000 + + + PMTIM + no description available + 3 + 1 + + + TSTIM + no description available + 9 + 1 + + + + + MACA0HR + MACA0HR + Ethernet MAC address 0 high + register + 0x40 + 0x20 + 0x0010FFFF + + + MACA0H + MAC address0 high + 0 + 16 + read-write + + + MO + Always 1 + 31 + 1 + read-only + + + + + MACA0LR + MACA0LR + Ethernet MAC address 0 low + register + 0x44 + 0x20 + read-write + 0xFFFFFFFF + + + MACA0L + 0 + 0 + 32 + + + + + MACA1HR + MACA1HR + Ethernet MAC address 1 high + register + 0x48 + 0x20 + read-write + 0x0000FFFF + + + MACA1H + no description available + 0 + 16 + + + MBC + no description available + 24 + 6 + + + SA + no description available + 30 + 1 + + + AE + no description available + 31 + 1 + + + + + MACA1LR + MACA1LR + Ethernet MAC address1 low + register + 0x4C + 0x20 + read-write + 0xFFFFFFFF + + + MACA1LR + no description available + 0 + 32 + + + + + MACA2HR + MACA2HR + Ethernet MAC address 2 high + register + 0x50 + 0x20 + read-write + 0x0000FFFF + + + MAC2AH + no description available + 0 + 16 + + + MBC + no description available + 24 + 6 + + + SA + no description available + 30 + 1 + + + AE + no description available + 31 + 1 + + + + + MACA2LR + MACA2LR + Ethernet MAC address 2 low + register + 0x54 + 0x20 + read-write + 0xFFFFFFFF + + + MACA2L + no description available + 0 + 31 + + + + + MACA3HR + MACA3HR + Ethernet MAC address 3 high + register + 0x58 + 0x20 + read-write + 0x0000FFFF + + + MACA3H + no description available + 0 + 16 + + + MBC + no description available + 24 + 6 + + + SA + no description available + 30 + 1 + + + AE + no description available + 31 + 1 + + + + + MACA3LR + MACA3LR + Ethernet MAC address 3 low + register + 0x5C + 0x20 + read-write + 0xFFFFFFFF + + + MBCA3L + no description available + 0 + 32 + + + + + + + Ethernet_MMC + Ethernet: MAC management counters + Ethernet + 0x40028100 + + 0x0 + 0x400 + registers + + + + MMCCR + MMCCR + Ethernet MMC control register + 0x0 + 0x20 + read-write + 0x00000000 + + + CR + no description available + 0 + 1 + + + CSR + no description available + 1 + 1 + + + ROR + no description available + 2 + 1 + + + MCF + no description available + 3 + 1 + + + MCP + no description available + 4 + 1 + + + MCFHP + no description available + 5 + 1 + + + + + MMCRIR + MMCRIR + Ethernet MMC receive interrupt + register + 0x4 + 0x20 + read-write + 0x00000000 + + + RFCES + no description available + 5 + 1 + + + RFAES + no description available + 6 + 1 + + + RGUFS + no description available + 17 + 1 + + + + + MMCTIR + MMCTIR + Ethernet MMC transmit interrupt + register + 0x8 + 0x20 + read-only + 0x00000000 + + + TGFSCS + no description available + 14 + 1 + + + TGFMSCS + no description available + 15 + 1 + + + TGFS + no description available + 21 + 1 + + + + + MMCRIMR + MMCRIMR + Ethernet MMC receive interrupt mask + register + 0xC + 0x20 + read-write + 0x00000000 + + + RFCEM + no description available + 5 + 1 + + + RFAEM + no description available + 6 + 1 + + + RGUFM + no description available + 17 + 1 + + + + + MMCTIMR + MMCTIMR + Ethernet MMC transmit interrupt mask + register + 0x10 + 0x20 + read-write + 0x00000000 + + + TGFSCM + no description available + 14 + 1 + + + TGFMSCM + no description available + 15 + 1 + + + TGFM + no description available + 16 + 1 + + + + + MMCTGFSCCR + MMCTGFSCCR + Ethernet MMC transmitted good frames after a + single collision counter + 0x4C + 0x20 + read-only + 0x00000000 + + + TGFSCC + no description available + 0 + 32 + + + + + MMCTGFMSCCR + MMCTGFMSCCR + Ethernet MMC transmitted good frames after + more than a single collision + 0x50 + 0x20 + read-only + 0x00000000 + + + TGFMSCC + no description available + 0 + 32 + + + + + MMCTGFCR + MMCTGFCR + Ethernet MMC transmitted good frames counter + register + 0x68 + 0x20 + read-only + 0x00000000 + + + TGFC + HTL + 0 + 32 + + + + + MMCRFCECR + MMCRFCECR + Ethernet MMC received frames with CRC error + counter register + 0x94 + 0x20 + read-only + 0x00000000 + + + RFCFC + no description available + 0 + 32 + + + + + MMCRFAECR + MMCRFAECR + Ethernet MMC received frames with alignment + error counter register + 0x98 + 0x20 + read-only + 0x00000000 + + + RFAEC + no description available + 0 + 32 + + + + + MMCRGUFCR + MMCRGUFCR + MMC received good unicast frames counter + register + 0xC4 + 0x20 + read-only + 0x00000000 + + + RGUFC + no description available + 0 + 32 + + + + + + + Ethernet_PTP + Ethernet: Precision time protocol + Ethernet + 0x40028700 + + 0x0 + 0x400 + registers + + + + PTPTSCR + PTPTSCR + Ethernet PTP time stamp control + register + 0x0 + 0x20 + read-write + 0x00002000 + + + TSE + no description available + 0 + 1 + + + TSFCU + no description available + 1 + 1 + + + TSPTPPSV2E + no description available + 10 + 1 + + + TSSPTPOEFE + no description available + 11 + 1 + + + TSSIPV6FE + no description available + 12 + 1 + + + TSSIPV4FE + no description available + 13 + 1 + + + TSSEME + no description available + 14 + 1 + + + TSSMRME + no description available + 15 + 1 + + + TSCNT + no description available + 16 + 2 + + + TSPFFMAE + no description available + 18 + 1 + + + TSSTI + no description available + 2 + 1 + + + TSSTU + no description available + 3 + 1 + + + TSITE + no description available + 4 + 1 + + + TTSARU + no description available + 5 + 1 + + + TSSARFE + no description available + 8 + 1 + + + TSSSR + no description available + 9 + 1 + + + + + PTPSSIR + PTPSSIR + Ethernet PTP subsecond increment + register + 0x4 + 0x20 + read-write + 0x00000000 + + + STSSI + no description available + 0 + 8 + + + + + PTPTSHR + PTPTSHR + Ethernet PTP time stamp high + register + 0x8 + 0x20 + read-only + 0x00000000 + + + STS + no description available + 0 + 32 + + + + + PTPTSLR + PTPTSLR + Ethernet PTP time stamp low + register + 0xC + 0x20 + read-only + 0x00000000 + + + STSS + no description available + 0 + 31 + + + STPNS + no description available + 31 + 1 + + + + + PTPTSHUR + PTPTSHUR + Ethernet PTP time stamp high update + register + 0x10 + 0x20 + read-write + 0x00000000 + + + TSUS + no description available + 0 + 32 + + + + + PTPTSLUR + PTPTSLUR + Ethernet PTP time stamp low update + register + 0x14 + 0x20 + read-write + 0x00000000 + + + TSUSS + no description available + 0 + 31 + + + TSUPNS + no description available + 31 + 1 + + + + + PTPTSAR + PTPTSAR + Ethernet PTP time stamp addend + register + 0x18 + 0x20 + read-write + 0x00000000 + + + TSA + no description available + 0 + 32 + + + + + PTPTTHR + PTPTTHR + Ethernet PTP target time high + register + 0x1C + 0x20 + read-write + 0x00000000 + + + TTSH + 0 + 0 + 32 + + + + + PTPTTLR + PTPTTLR + Ethernet PTP target time low + register + 0x20 + 0x20 + read-write + 0x00000000 + + + TTSL + no description available + 0 + 32 + + + + + PTPTSSR + PTPTSSR + Ethernet PTP time stamp status + register + 0x28 + 0x20 + read-only + 0x00000000 + + + TSSO + no description available + 0 + 1 + + + TSTTR + no description available + 1 + 1 + + + + + PTPPPSCR + PTPPPSCR + Ethernet PTP PPS control + register + 0x2C + 0x20 + read-only + 0x00000000 + + + TSSO + TSSO + 0 + 1 + + + TSTTR + TSTTR + 1 + 1 + + + + + + + Ethernet_DMA + Ethernet: DMA controller operation + Ethernet + 0x40029000 + + 0x0 + 0x400 + registers + + + + DMABMR + DMABMR + Ethernet DMA bus mode register + 0x0 + 0x20 + read-write + 0x00002101 + + + SR + no description available + 0 + 1 + + + DA + no description available + 1 + 1 + + + DSL + no description available + 2 + 5 + + + EDFE + no description available + 7 + 1 + + + PBL + no description available + 8 + 6 + + + RTPR + no description available + 14 + 2 + + + FB + no description available + 16 + 1 + + + RDP + no description available + 17 + 6 + + + USP + no description available + 23 + 1 + + + FPM + no description available + 24 + 1 + + + AAB + no description available + 25 + 1 + + + MB + no description available + 26 + 1 + + + + + DMATPDR + DMATPDR + Ethernet DMA transmit poll demand + register + 0x4 + 0x20 + read-write + 0x00000000 + + + TPD + no description available + 0 + 32 + + + + + DMARPDR + DMARPDR + EHERNET DMA receive poll demand + register + 0x8 + 0x20 + read-write + 0x00000000 + + + RPD + RPD + 0 + 32 + + + + + DMARDLAR + DMARDLAR + Ethernet DMA receive descriptor list address + register + 0xC + 0x20 + read-write + 0x00000000 + + + SRL + no description available + 0 + 32 + + + + + DMATDLAR + DMATDLAR + Ethernet DMA transmit descriptor list + address register + 0x10 + 0x20 + read-write + 0x00000000 + + + STL + no description available + 0 + 32 + + + + + DMASR + DMASR + Ethernet DMA status register + 0x14 + 0x20 + 0x00000000 + + + TS + no description available + 0 + 1 + read-write + + + TPSS + no description available + 1 + 1 + read-write + + + TBUS + no description available + 2 + 1 + read-write + + + TJTS + no description available + 3 + 1 + read-write + + + ROS + no description available + 4 + 1 + read-write + + + TUS + no description available + 5 + 1 + read-write + + + RS + no description available + 6 + 1 + read-write + + + RBUS + no description available + 7 + 1 + read-write + + + RPSS + no description available + 8 + 1 + read-write + + + PWTS + no description available + 9 + 1 + read-write + + + ETS + no description available + 10 + 1 + read-write + + + FBES + no description available + 13 + 1 + read-write + + + ERS + no description available + 14 + 1 + read-write + + + AIS + no description available + 15 + 1 + read-write + + + NIS + no description available + 16 + 1 + read-write + + + RPS + no description available + 17 + 3 + read-only + + + TPS + no description available + 20 + 3 + read-only + + + EBS + no description available + 23 + 3 + read-only + + + MMCS + no description available + 27 + 1 + read-only + + + PMTS + no description available + 28 + 1 + read-only + + + TSTS + no description available + 29 + 1 + read-only + + + + + DMAOMR + DMAOMR + Ethernet DMA operation mode + register + 0x18 + 0x20 + read-write + 0x00000000 + + + SR + SR + 1 + 1 + + + OSF + OSF + 2 + 1 + + + RTC + RTC + 3 + 2 + + + FUGF + FUGF + 6 + 1 + + + FEF + FEF + 7 + 1 + + + ST + ST + 13 + 1 + + + TTC + TTC + 14 + 3 + + + FTF + FTF + 20 + 1 + + + TSF + TSF + 21 + 1 + + + DFRF + DFRF + 24 + 1 + + + RSF + RSF + 25 + 1 + + + DTCEFD + DTCEFD + 26 + 1 + + + + + DMAIER + DMAIER + Ethernet DMA interrupt enable + register + 0x1C + 0x20 + read-write + 0x00000000 + + + TIE + no description available + 0 + 1 + + + TPSIE + no description available + 1 + 1 + + + TBUIE + no description available + 2 + 1 + + + TJTIE + no description available + 3 + 1 + + + ROIE + no description available + 4 + 1 + + + TUIE + no description available + 5 + 1 + + + RIE + no description available + 6 + 1 + + + RBUIE + no description available + 7 + 1 + + + RPSIE + no description available + 8 + 1 + + + RWTIE + no description available + 9 + 1 + + + ETIE + no description available + 10 + 1 + + + FBEIE + no description available + 13 + 1 + + + ERIE + no description available + 14 + 1 + + + AISE + no description available + 15 + 1 + + + NISE + no description available + 16 + 1 + + + + + DMAMFBOCR + DMAMFBOCR + Ethernet DMA missed frame and buffer + overflow counter register + 0x20 + 0x20 + read-write + 0x00000000 + + + MFC + no description available + 0 + 16 + + + OMFC + no description available + 16 + 1 + + + MFA + no description available + 17 + 11 + + + OFOC + no description available + 28 + 1 + + + + + DMARSWTR + DMARSWTR + Ethernet DMA receive status watchdog timer + register + 0x24 + 0x20 + read-write + 0x00000000 + + + RSWTC + RSWTC + 0 + 8 + + + + + DMACHTDR + DMACHTDR + Ethernet DMA current host transmit + descriptor register + 0x48 + 0x20 + read-only + 0x00000000 + + + HTDAP + HTDAP + 0 + 32 + + + + + DMACHRDR + DMACHRDR + Ethernet DMA current host receive descriptor + register + 0x4C + 0x20 + read-only + 0x00000000 + + + HRDAP + HRDAP + 0 + 32 + + + + + DMACHTBAR + DMACHTBAR + Ethernet DMA current host transmit buffer + address register + 0x50 + 0x20 + read-only + 0x00000000 + + + HTBAP + no description available + 0 + 32 + + + + + DMACHRBAR + DMACHRBAR + Ethernet DMA current host receive buffer + address register + 0x54 + 0x20 + read-only + 0x00000000 + + + HRBAP + no description available + 0 + 32 + + + + + + + CRC + Cryptographic processor + CRC + 0x40023000 + + 0x0 + 0x400 + registers + + + + DR + DR + Data register + 0x0 + 0x20 + read-write + 0xFFFFFFFF + + + DR + Data Register + 0 + 32 + + + + + IDR + IDR + Independent Data register + 0x4 + 0x20 + read-write + 0x00000000 + + + IDR + Independent Data register + 0 + 8 + + + + + CR + CR + Control register + 0x8 + 0x20 + write-only + 0x00000000 + + + CR + Control regidter + 0 + 1 + + + + + + + OTG_FS_GLOBAL + USB on the go full speed + USB_OTG_FS + 0x50000000 + + 0x0 + 0x400 + registers + + + OTG_FS_WKUP + USB On-The-Go FS Wakeup through EXTI line + interrupt + 42 + + + OTG_FS + USB On The Go FS global + interrupt + 67 + + + + FS_GOTGCTL + FS_GOTGCTL + OTG_FS control and status register + (OTG_FS_GOTGCTL) + 0x0 + 0x20 + 0x00000800 + + + SRQSCS + Session request success + 0 + 1 + read-only + + + SRQ + Session request + 1 + 1 + read-write + + + HNGSCS + Host negotiation success + 8 + 1 + read-only + + + HNPRQ + HNP request + 9 + 1 + read-write + + + HSHNPEN + Host set HNP enable + 10 + 1 + read-write + + + DHNPEN + Device HNP enabled + 11 + 1 + read-write + + + CIDSTS + Connector ID status + 16 + 1 + read-only + + + DBCT + Long/short debounce time + 17 + 1 + read-only + + + ASVLD + A-session valid + 18 + 1 + read-only + + + BSVLD + B-session valid + 19 + 1 + read-only + + + + + FS_GOTGINT + FS_GOTGINT + OTG_FS interrupt register + (OTG_FS_GOTGINT) + 0x4 + 0x20 + read-write + 0x00000000 + + + SEDET + Session end detected + 2 + 1 + + + SRSSCHG + Session request success status + change + 8 + 1 + + + HNSSCHG + Host negotiation success status + change + 9 + 1 + + + HNGDET + Host negotiation detected + 17 + 1 + + + ADTOCHG + A-device timeout change + 18 + 1 + + + DBCDNE + Debounce done + 19 + 1 + + + + + FS_GAHBCFG + FS_GAHBCFG + OTG_FS AHB configuration register + (OTG_FS_GAHBCFG) + 0x8 + 0x20 + read-write + 0x00000000 + + + GINT + Global interrupt mask + 0 + 1 + + + TXFELVL + TxFIFO empty level + 7 + 1 + + + PTXFELVL + Periodic TxFIFO empty + level + 8 + 1 + + + + + FS_GUSBCFG + FS_GUSBCFG + OTG_FS USB configuration register + (OTG_FS_GUSBCFG) + 0xC + 0x20 + 0x00000A00 + + + TOCAL + FS timeout calibration + 0 + 3 + read-write + + + PHYSEL + Full Speed serial transceiver + select + 6 + 1 + write-only + + + SRPCAP + SRP-capable + 8 + 1 + read-write + + + HNPCAP + HNP-capable + 9 + 1 + read-write + + + TRDT + USB turnaround time + 10 + 4 + read-write + + + FHMOD + Force host mode + 29 + 1 + read-write + + + FDMOD + Force device mode + 30 + 1 + read-write + + + CTXPKT + Corrupt Tx packet + 31 + 1 + read-write + + + + + FS_GRSTCTL + FS_GRSTCTL + OTG_FS reset register + (OTG_FS_GRSTCTL) + 0x10 + 0x20 + 0x20000000 + + + CSRST + Core soft reset + 0 + 1 + read-write + + + HSRST + HCLK soft reset + 1 + 1 + read-write + + + FCRST + Host frame counter reset + 2 + 1 + read-write + + + RXFFLSH + RxFIFO flush + 4 + 1 + read-write + + + TXFFLSH + TxFIFO flush + 5 + 1 + read-write + + + TXFNUM + TxFIFO number + 6 + 5 + read-write + + + AHBIDL + AHB master idle + 31 + 1 + read-only + + + + + FS_GINTSTS + FS_GINTSTS + OTG_FS core interrupt register + (OTG_FS_GINTSTS) + 0x14 + 0x20 + 0x04000020 + + + CMOD + Current mode of operation + 0 + 1 + read-only + + + MMIS + Mode mismatch interrupt + 1 + 1 + read-write + + + OTGINT + OTG interrupt + 2 + 1 + read-only + + + SOF + Start of frame + 3 + 1 + read-write + + + RXFLVL + RxFIFO non-empty + 4 + 1 + read-only + + + NPTXFE + Non-periodic TxFIFO empty + 5 + 1 + read-only + + + GINAKEFF + Global IN non-periodic NAK + effective + 6 + 1 + read-only + + + GOUTNAKEFF + Global OUT NAK effective + 7 + 1 + read-only + + + ESUSP + Early suspend + 10 + 1 + read-write + + + USBSUSP + USB suspend + 11 + 1 + read-write + + + USBRST + USB reset + 12 + 1 + read-write + + + ENUMDNE + Enumeration done + 13 + 1 + read-write + + + ISOODRP + Isochronous OUT packet dropped + interrupt + 14 + 1 + read-write + + + EOPF + End of periodic frame + interrupt + 15 + 1 + read-write + + + IEPINT + IN endpoint interrupt + 18 + 1 + read-only + + + OEPINT + OUT endpoint interrupt + 19 + 1 + read-only + + + IISOIXFR + Incomplete isochronous IN + transfer + 20 + 1 + read-write + + + IPXFR_INCOMPISOOUT + Incomplete periodic transfer(Host + mode)/Incomplete isochronous OUT transfer(Device + mode) + 21 + 1 + read-write + + + HPRTINT + Host port interrupt + 24 + 1 + read-only + + + HCINT + Host channels interrupt + 25 + 1 + read-only + + + PTXFE + Periodic TxFIFO empty + 26 + 1 + read-only + + + CIDSCHG + Connector ID status change + 28 + 1 + read-write + + + DISCINT + Disconnect detected + interrupt + 29 + 1 + read-write + + + SRQINT + Session request/new session detected + interrupt + 30 + 1 + read-write + + + WKUPINT + Resume/remote wakeup detected + interrupt + 31 + 1 + read-write + + + + + FS_GINTMSK + FS_GINTMSK + OTG_FS interrupt mask register + (OTG_FS_GINTMSK) + 0x18 + 0x20 + 0x00000000 + + + MMISM + Mode mismatch interrupt + mask + 1 + 1 + read-write + + + OTGINT + OTG interrupt mask + 2 + 1 + read-write + + + SOFM + Start of frame mask + 3 + 1 + read-write + + + RXFLVLM + Receive FIFO non-empty + mask + 4 + 1 + read-write + + + NPTXFEM + Non-periodic TxFIFO empty + mask + 5 + 1 + read-write + + + GINAKEFFM + Global non-periodic IN NAK effective + mask + 6 + 1 + read-write + + + GONAKEFFM + Global OUT NAK effective + mask + 7 + 1 + read-write + + + ESUSPM + Early suspend mask + 10 + 1 + read-write + + + USBSUSPM + USB suspend mask + 11 + 1 + read-write + + + USBRST + USB reset mask + 12 + 1 + read-write + + + ENUMDNEM + Enumeration done mask + 13 + 1 + read-write + + + ISOODRPM + Isochronous OUT packet dropped interrupt + mask + 14 + 1 + read-write + + + EOPFM + End of periodic frame interrupt + mask + 15 + 1 + read-write + + + EPMISM + Endpoint mismatch interrupt + mask + 17 + 1 + read-write + + + IEPINT + IN endpoints interrupt + mask + 18 + 1 + read-write + + + OEPINT + OUT endpoints interrupt + mask + 19 + 1 + read-write + + + IISOIXFRM + Incomplete isochronous IN transfer + mask + 20 + 1 + read-write + + + IPXFRM_IISOOXFRM + Incomplete periodic transfer mask(Host + mode)/Incomplete isochronous OUT transfer mask(Device + mode) + 21 + 1 + read-write + + + PRTIM + Host port interrupt mask + 24 + 1 + read-only + + + HCIM + Host channels interrupt + mask + 25 + 1 + read-write + + + PTXFEM + Periodic TxFIFO empty mask + 26 + 1 + read-write + + + CIDSCHGM + Connector ID status change + mask + 28 + 1 + read-write + + + DISCINT + Disconnect detected interrupt + mask + 29 + 1 + read-write + + + SRQIM + Session request/new session detected + interrupt mask + 30 + 1 + read-write + + + WUIM + Resume/remote wakeup detected interrupt + mask + 31 + 1 + read-write + + + + + FS_GRXSTSR_Device + FS_GRXSTSR_Device + OTG_FS Receive status debug read(Device + mode) + 0x1C + 0x20 + read-only + 0x00000000 + + + EPNUM + Endpoint number + 0 + 4 + + + BCNT + Byte count + 4 + 11 + + + DPID + Data PID + 15 + 2 + + + PKTSTS + Packet status + 17 + 4 + + + FRMNUM + Frame number + 21 + 4 + + + + + FS_GRXSTSR_Host + FS_GRXSTSR_Host + OTG_FS Receive status debug read(Host + mode) + FS_GRXSTSR_Device + 0x1C + 0x20 + read-only + 0x00000000 + + + EPNUM + Endpoint number + 0 + 4 + + + BCNT + Byte count + 4 + 11 + + + DPID + Data PID + 15 + 2 + + + PKTSTS + Packet status + 17 + 4 + + + FRMNUM + Frame number + 21 + 4 + + + + + FS_GRXFSIZ + FS_GRXFSIZ + OTG_FS Receive FIFO size register + (OTG_FS_GRXFSIZ) + 0x24 + 0x20 + read-write + 0x00000200 + + + RXFD + RxFIFO depth + 0 + 16 + + + + + FS_GNPTXFSIZ_Device + FS_GNPTXFSIZ_Device + OTG_FS non-periodic transmit FIFO size + register (Device mode) + 0x28 + 0x20 + read-write + 0x00000200 + + + TX0FSA + Endpoint 0 transmit RAM start + address + 0 + 16 + + + TX0FD + Endpoint 0 TxFIFO depth + 16 + 16 + + + + + FS_GNPTXFSIZ_Host + FS_GNPTXFSIZ_Host + OTG_FS non-periodic transmit FIFO size + register (Host mode) + FS_GNPTXFSIZ_Device + 0x28 + 0x20 + read-write + 0x00000200 + + + NPTXFSA + Non-periodic transmit RAM start + address + 0 + 16 + + + NPTXFD + Non-periodic TxFIFO depth + 16 + 16 + + + + + FS_GNPTXSTS + FS_GNPTXSTS + OTG_FS non-periodic transmit FIFO/queue + status register (OTG_FS_GNPTXSTS) + 0x2C + 0x20 + read-only + 0x00080200 + + + NPTXFSAV + Non-periodic TxFIFO space + available + 0 + 16 + + + NPTQXSAV + Non-periodic transmit request queue + space available + 16 + 8 + + + NPTXQTOP + Top of the non-periodic transmit request + queue + 24 + 7 + + + + + FS_GCCFG + FS_GCCFG + OTG_FS general core configuration register + (OTG_FS_GCCFG) + 0x38 + 0x20 + read-write + 0x00000000 + + + PWRDWN + Power down + 16 + 1 + + + VBUSASEN + Enable the VBUS sensing + device + 18 + 1 + + + VBUSBSEN + Enable the VBUS sensing + device + 19 + 1 + + + SOFOUTEN + SOF output enable + 20 + 1 + + + + + FS_CID + FS_CID + core ID register + 0x3C + 0x20 + read-write + 0x00001000 + + + PRODUCT_ID + Product ID field + 0 + 32 + + + + + FS_HPTXFSIZ + FS_HPTXFSIZ + OTG_FS Host periodic transmit FIFO size + register (OTG_FS_HPTXFSIZ) + 0x100 + 0x20 + read-write + 0x02000600 + + + PTXSA + Host periodic TxFIFO start + address + 0 + 16 + + + PTXFSIZ + Host periodic TxFIFO depth + 16 + 16 + + + + + FS_DIEPTXF1 + FS_DIEPTXF1 + OTG_FS device IN endpoint transmit FIFO size + register (OTG_FS_DIEPTXF2) + 0x104 + 0x20 + read-write + 0x02000400 + + + INEPTXSA + IN endpoint FIFO2 transmit RAM start + address + 0 + 16 + + + INEPTXFD + IN endpoint TxFIFO depth + 16 + 16 + + + + + FS_DIEPTXF2 + FS_DIEPTXF2 + OTG_FS device IN endpoint transmit FIFO size + register (OTG_FS_DIEPTXF3) + 0x108 + 0x20 + read-write + 0x02000400 + + + INEPTXSA + IN endpoint FIFO3 transmit RAM start + address + 0 + 16 + + + INEPTXFD + IN endpoint TxFIFO depth + 16 + 16 + + + + + FS_DIEPTXF3 + FS_DIEPTXF3 + OTG_FS device IN endpoint transmit FIFO size + register (OTG_FS_DIEPTXF4) + 0x10C + 0x20 + read-write + 0x02000400 + + + INEPTXSA + IN endpoint FIFO4 transmit RAM start + address + 0 + 16 + + + INEPTXFD + IN endpoint TxFIFO depth + 16 + 16 + + + + + + + OTG_FS_HOST + USB on the go full speed + USB_OTG_FS + 0x50000400 + + 0x0 + 0x400 + registers + + + + FS_HCFG + FS_HCFG + OTG_FS host configuration register + (OTG_FS_HCFG) + 0x0 + 0x20 + 0x00000000 + + + FSLSPCS + FS/LS PHY clock select + 0 + 2 + read-write + + + FSLSS + FS- and LS-only support + 2 + 1 + read-only + + + + + HFIR + HFIR + OTG_FS Host frame interval + register + 0x4 + 0x20 + read-write + 0x0000EA60 + + + FRIVL + Frame interval + 0 + 16 + + + + + FS_HFNUM + FS_HFNUM + OTG_FS host frame number/frame time + remaining register (OTG_FS_HFNUM) + 0x8 + 0x20 + read-only + 0x00003FFF + + + FRNUM + Frame number + 0 + 16 + + + FTREM + Frame time remaining + 16 + 16 + + + + + FS_HPTXSTS + FS_HPTXSTS + OTG_FS_Host periodic transmit FIFO/queue + status register (OTG_FS_HPTXSTS) + 0x10 + 0x20 + 0x00080100 + + + PTXFSAVL + Periodic transmit data FIFO space + available + 0 + 16 + read-write + + + PTXQSAV + Periodic transmit request queue space + available + 16 + 8 + read-only + + + PTXQTOP + Top of the periodic transmit request + queue + 24 + 8 + read-only + + + + + HAINT + HAINT + OTG_FS Host all channels interrupt + register + 0x14 + 0x20 + read-only + 0x00000000 + + + HAINT + Channel interrupts + 0 + 16 + + + + + HAINTMSK + HAINTMSK + OTG_FS host all channels interrupt mask + register + 0x18 + 0x20 + read-write + 0x00000000 + + + HAINTM + Channel interrupt mask + 0 + 16 + + + + + FS_HPRT + FS_HPRT + OTG_FS host port control and status register + (OTG_FS_HPRT) + 0x40 + 0x20 + 0x00000000 + + + PCSTS + Port connect status + 0 + 1 + read-only + + + PCDET + Port connect detected + 1 + 1 + read-write + + + PENA + Port enable + 2 + 1 + read-write + + + PENCHNG + Port enable/disable change + 3 + 1 + read-write + + + POCA + Port overcurrent active + 4 + 1 + read-only + + + POCCHNG + Port overcurrent change + 5 + 1 + read-write + + + PRES + Port resume + 6 + 1 + read-write + + + PSUSP + Port suspend + 7 + 1 + read-write + + + PRST + Port reset + 8 + 1 + read-write + + + PLSTS + Port line status + 10 + 2 + read-only + + + PPWR + Port power + 12 + 1 + read-write + + + PTCTL + Port test control + 13 + 4 + read-write + + + PSPD + Port speed + 17 + 2 + read-only + + + + + FS_HCCHAR0 + FS_HCCHAR0 + OTG_FS host channel-0 characteristics + register (OTG_FS_HCCHAR0) + 0x100 + 0x20 + read-write + 0x00000000 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MCNT + Multicount + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + FS_HCCHAR1 + FS_HCCHAR1 + OTG_FS host channel-1 characteristics + register (OTG_FS_HCCHAR1) + 0x120 + 0x20 + read-write + 0x00000000 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MCNT + Multicount + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + FS_HCCHAR2 + FS_HCCHAR2 + OTG_FS host channel-2 characteristics + register (OTG_FS_HCCHAR2) + 0x140 + 0x20 + read-write + 0x00000000 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MCNT + Multicount + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + FS_HCCHAR3 + FS_HCCHAR3 + OTG_FS host channel-3 characteristics + register (OTG_FS_HCCHAR3) + 0x160 + 0x20 + read-write + 0x00000000 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MCNT + Multicount + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + FS_HCCHAR4 + FS_HCCHAR4 + OTG_FS host channel-4 characteristics + register (OTG_FS_HCCHAR4) + 0x180 + 0x20 + read-write + 0x00000000 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MCNT + Multicount + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + FS_HCCHAR5 + FS_HCCHAR5 + OTG_FS host channel-5 characteristics + register (OTG_FS_HCCHAR5) + 0x1A0 + 0x20 + read-write + 0x00000000 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MCNT + Multicount + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + FS_HCCHAR6 + FS_HCCHAR6 + OTG_FS host channel-6 characteristics + register (OTG_FS_HCCHAR6) + 0x1C0 + 0x20 + read-write + 0x00000000 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MCNT + Multicount + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + FS_HCCHAR7 + FS_HCCHAR7 + OTG_FS host channel-7 characteristics + register (OTG_FS_HCCHAR7) + 0x1E0 + 0x20 + read-write + 0x00000000 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MCNT + Multicount + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + FS_HCINT0 + FS_HCINT0 + OTG_FS host channel-0 interrupt register + (OTG_FS_HCINT0) + 0x108 + 0x20 + read-write + 0x00000000 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + FS_HCINT1 + FS_HCINT1 + OTG_FS host channel-1 interrupt register + (OTG_FS_HCINT1) + 0x128 + 0x20 + read-write + 0x00000000 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + FS_HCINT2 + FS_HCINT2 + OTG_FS host channel-2 interrupt register + (OTG_FS_HCINT2) + 0x148 + 0x20 + read-write + 0x00000000 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + FS_HCINT3 + FS_HCINT3 + OTG_FS host channel-3 interrupt register + (OTG_FS_HCINT3) + 0x168 + 0x20 + read-write + 0x00000000 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + FS_HCINT4 + FS_HCINT4 + OTG_FS host channel-4 interrupt register + (OTG_FS_HCINT4) + 0x188 + 0x20 + read-write + 0x00000000 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + FS_HCINT5 + FS_HCINT5 + OTG_FS host channel-5 interrupt register + (OTG_FS_HCINT5) + 0x1A8 + 0x20 + read-write + 0x00000000 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + FS_HCINT6 + FS_HCINT6 + OTG_FS host channel-6 interrupt register + (OTG_FS_HCINT6) + 0x1C8 + 0x20 + read-write + 0x00000000 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + FS_HCINT7 + FS_HCINT7 + OTG_FS host channel-7 interrupt register + (OTG_FS_HCINT7) + 0x1E8 + 0x20 + read-write + 0x00000000 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + FS_HCINTMSK0 + FS_HCINTMSK0 + OTG_FS host channel-0 mask register + (OTG_FS_HCINTMSK0) + 0x10C + 0x20 + read-write + 0x00000000 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + FS_HCINTMSK1 + FS_HCINTMSK1 + OTG_FS host channel-1 mask register + (OTG_FS_HCINTMSK1) + 0x12C + 0x20 + read-write + 0x00000000 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + FS_HCINTMSK2 + FS_HCINTMSK2 + OTG_FS host channel-2 mask register + (OTG_FS_HCINTMSK2) + 0x14C + 0x20 + read-write + 0x00000000 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + FS_HCINTMSK3 + FS_HCINTMSK3 + OTG_FS host channel-3 mask register + (OTG_FS_HCINTMSK3) + 0x16C + 0x20 + read-write + 0x00000000 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + FS_HCINTMSK4 + FS_HCINTMSK4 + OTG_FS host channel-4 mask register + (OTG_FS_HCINTMSK4) + 0x18C + 0x20 + read-write + 0x00000000 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + FS_HCINTMSK5 + FS_HCINTMSK5 + OTG_FS host channel-5 mask register + (OTG_FS_HCINTMSK5) + 0x1AC + 0x20 + read-write + 0x00000000 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + FS_HCINTMSK6 + FS_HCINTMSK6 + OTG_FS host channel-6 mask register + (OTG_FS_HCINTMSK6) + 0x1CC + 0x20 + read-write + 0x00000000 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + FS_HCINTMSK7 + FS_HCINTMSK7 + OTG_FS host channel-7 mask register + (OTG_FS_HCINTMSK7) + 0x1EC + 0x20 + read-write + 0x00000000 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + FS_HCTSIZ0 + FS_HCTSIZ0 + OTG_FS host channel-0 transfer size + register + 0x110 + 0x20 + read-write + 0x00000000 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + FS_HCTSIZ1 + FS_HCTSIZ1 + OTG_FS host channel-1 transfer size + register + 0x130 + 0x20 + read-write + 0x00000000 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + FS_HCTSIZ2 + FS_HCTSIZ2 + OTG_FS host channel-2 transfer size + register + 0x150 + 0x20 + read-write + 0x00000000 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + FS_HCTSIZ3 + FS_HCTSIZ3 + OTG_FS host channel-3 transfer size + register + 0x170 + 0x20 + read-write + 0x00000000 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + FS_HCTSIZ4 + FS_HCTSIZ4 + OTG_FS host channel-x transfer size + register + 0x190 + 0x20 + read-write + 0x00000000 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + FS_HCTSIZ5 + FS_HCTSIZ5 + OTG_FS host channel-5 transfer size + register + 0x1B0 + 0x20 + read-write + 0x00000000 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + FS_HCTSIZ6 + FS_HCTSIZ6 + OTG_FS host channel-6 transfer size + register + 0x1D0 + 0x20 + read-write + 0x00000000 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + FS_HCTSIZ7 + FS_HCTSIZ7 + OTG_FS host channel-7 transfer size + register + 0x1F0 + 0x20 + read-write + 0x00000000 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + + + OTG_FS_DEVICE + USB on the go full speed + USB_OTG_FS + 0x50000800 + + 0x0 + 0x400 + registers + + + + FS_DCFG + FS_DCFG + OTG_FS device configuration register + (OTG_FS_DCFG) + 0x0 + 0x20 + read-write + 0x02200000 + + + DSPD + Device speed + 0 + 2 + + + NZLSOHSK + Non-zero-length status OUT + handshake + 2 + 1 + + + DAD + Device address + 4 + 7 + + + PFIVL + Periodic frame interval + 11 + 2 + + + + + FS_DCTL + FS_DCTL + OTG_FS device control register + (OTG_FS_DCTL) + 0x4 + 0x20 + 0x00000000 + + + RWUSIG + Remote wakeup signaling + 0 + 1 + read-write + + + SDIS + Soft disconnect + 1 + 1 + read-write + + + GINSTS + Global IN NAK status + 2 + 1 + read-only + + + GONSTS + Global OUT NAK status + 3 + 1 + read-only + + + TCTL + Test control + 4 + 3 + read-write + + + SGINAK + Set global IN NAK + 7 + 1 + read-write + + + CGINAK + Clear global IN NAK + 8 + 1 + read-write + + + SGONAK + Set global OUT NAK + 9 + 1 + read-write + + + CGONAK + Clear global OUT NAK + 10 + 1 + read-write + + + POPRGDNE + Power-on programming done + 11 + 1 + read-write + + + + + FS_DSTS + FS_DSTS + OTG_FS device status register + (OTG_FS_DSTS) + 0x8 + 0x20 + read-only + 0x00000010 + + + SUSPSTS + Suspend status + 0 + 1 + + + ENUMSPD + Enumerated speed + 1 + 2 + + + EERR + Erratic error + 3 + 1 + + + FNSOF + Frame number of the received + SOF + 8 + 14 + + + + + FS_DIEPMSK + FS_DIEPMSK + OTG_FS device IN endpoint common interrupt + mask register (OTG_FS_DIEPMSK) + 0x10 + 0x20 + read-write + 0x00000000 + + + XFRCM + Transfer completed interrupt + mask + 0 + 1 + + + EPDM + Endpoint disabled interrupt + mask + 1 + 1 + + + TOM + Timeout condition mask (Non-isochronous + endpoints) + 3 + 1 + + + ITTXFEMSK + IN token received when TxFIFO empty + mask + 4 + 1 + + + INEPNMM + IN token received with EP mismatch + mask + 5 + 1 + + + INEPNEM + IN endpoint NAK effective + mask + 6 + 1 + + + + + FS_DOEPMSK + FS_DOEPMSK + OTG_FS device OUT endpoint common interrupt + mask register (OTG_FS_DOEPMSK) + 0x14 + 0x20 + read-write + 0x00000000 + + + XFRCM + Transfer completed interrupt + mask + 0 + 1 + + + EPDM + Endpoint disabled interrupt + mask + 1 + 1 + + + STUPM + SETUP phase done mask + 3 + 1 + + + OTEPDM + OUT token received when endpoint + disabled mask + 4 + 1 + + + + + FS_DAINT + FS_DAINT + OTG_FS device all endpoints interrupt + register (OTG_FS_DAINT) + 0x18 + 0x20 + read-only + 0x00000000 + + + IEPINT + IN endpoint interrupt bits + 0 + 16 + + + OEPINT + OUT endpoint interrupt + bits + 16 + 16 + + + + + FS_DAINTMSK + FS_DAINTMSK + OTG_FS all endpoints interrupt mask register + (OTG_FS_DAINTMSK) + 0x1C + 0x20 + read-write + 0x00000000 + + + IEPM + IN EP interrupt mask bits + 0 + 16 + + + OEPINT + OUT endpoint interrupt + bits + 16 + 16 + + + + + DVBUSDIS + DVBUSDIS + OTG_FS device VBUS discharge time + register + 0x28 + 0x20 + read-write + 0x000017D7 + + + VBUSDT + Device VBUS discharge time + 0 + 16 + + + + + DVBUSPULSE + DVBUSPULSE + OTG_FS device VBUS pulsing time + register + 0x2C + 0x20 + read-write + 0x000005B8 + + + DVBUSP + Device VBUS pulsing time + 0 + 12 + + + + + DIEPEMPMSK + DIEPEMPMSK + OTG_FS device IN endpoint FIFO empty + interrupt mask register + 0x34 + 0x20 + read-write + 0x00000000 + + + INEPTXFEM + IN EP Tx FIFO empty interrupt mask + bits + 0 + 16 + + + + + FS_DIEPCTL0 + FS_DIEPCTL0 + OTG_FS device control IN endpoint 0 control + register (OTG_FS_DIEPCTL0) + 0x100 + 0x20 + 0x00000000 + + + MPSIZ + Maximum packet size + 0 + 2 + read-write + + + USBAEP + USB active endpoint + 15 + 1 + read-only + + + NAKSTS + NAK status + 17 + 1 + read-only + + + EPTYP + Endpoint type + 18 + 2 + read-only + + + STALL + STALL handshake + 21 + 1 + read-write + + + TXFNUM + TxFIFO number + 22 + 4 + read-write + + + CNAK + Clear NAK + 26 + 1 + write-only + + + SNAK + Set NAK + 27 + 1 + write-only + + + EPDIS + Endpoint disable + 30 + 1 + read-only + + + EPENA + Endpoint enable + 31 + 1 + read-only + + + + + DIEPCTL1 + DIEPCTL1 + OTG device endpoint-1 control + register + 0x120 + 0x20 + 0x00000000 + + + EPENA + EPENA + 31 + 1 + read-write + + + EPDIS + EPDIS + 30 + 1 + read-write + + + SODDFRM_SD1PID + SODDFRM/SD1PID + 29 + 1 + write-only + + + SD0PID_SEVNFRM + SD0PID/SEVNFRM + 28 + 1 + write-only + + + SNAK + SNAK + 27 + 1 + write-only + + + CNAK + CNAK + 26 + 1 + write-only + + + TXFNUM + TXFNUM + 22 + 4 + read-write + + + Stall + Stall + 21 + 1 + read-write + + + EPTYP + EPTYP + 18 + 2 + read-write + + + NAKSTS + NAKSTS + 17 + 1 + read-only + + + EONUM_DPID + EONUM/DPID + 16 + 1 + read-only + + + USBAEP + USBAEP + 15 + 1 + read-write + + + MPSIZ + MPSIZ + 0 + 11 + read-write + + + + + DIEPCTL2 + DIEPCTL2 + OTG device endpoint-2 control + register + 0x140 + 0x20 + 0x00000000 + + + EPENA + EPENA + 31 + 1 + read-write + + + EPDIS + EPDIS + 30 + 1 + read-write + + + SODDFRM + SODDFRM + 29 + 1 + write-only + + + SD0PID_SEVNFRM + SD0PID/SEVNFRM + 28 + 1 + write-only + + + SNAK + SNAK + 27 + 1 + write-only + + + CNAK + CNAK + 26 + 1 + write-only + + + TXFNUM + TXFNUM + 22 + 4 + read-write + + + Stall + Stall + 21 + 1 + read-write + + + EPTYP + EPTYP + 18 + 2 + read-write + + + NAKSTS + NAKSTS + 17 + 1 + read-only + + + EONUM_DPID + EONUM/DPID + 16 + 1 + read-only + + + USBAEP + USBAEP + 15 + 1 + read-write + + + MPSIZ + MPSIZ + 0 + 11 + read-write + + + + + DIEPCTL3 + DIEPCTL3 + OTG device endpoint-3 control + register + 0x160 + 0x20 + 0x00000000 + + + EPENA + EPENA + 31 + 1 + read-write + + + EPDIS + EPDIS + 30 + 1 + read-write + + + SODDFRM + SODDFRM + 29 + 1 + write-only + + + SD0PID_SEVNFRM + SD0PID/SEVNFRM + 28 + 1 + write-only + + + SNAK + SNAK + 27 + 1 + write-only + + + CNAK + CNAK + 26 + 1 + write-only + + + TXFNUM + TXFNUM + 22 + 4 + read-write + + + Stall + Stall + 21 + 1 + read-write + + + EPTYP + EPTYP + 18 + 2 + read-write + + + NAKSTS + NAKSTS + 17 + 1 + read-only + + + EONUM_DPID + EONUM/DPID + 16 + 1 + read-only + + + USBAEP + USBAEP + 15 + 1 + read-write + + + MPSIZ + MPSIZ + 0 + 11 + read-write + + + + + DOEPCTL0 + DOEPCTL0 + device endpoint-0 control + register + 0x300 + 0x20 + 0x00008000 + + + EPENA + EPENA + 31 + 1 + write-only + + + EPDIS + EPDIS + 30 + 1 + read-only + + + SNAK + SNAK + 27 + 1 + write-only + + + CNAK + CNAK + 26 + 1 + write-only + + + Stall + Stall + 21 + 1 + read-write + + + SNPM + SNPM + 20 + 1 + read-write + + + EPTYP + EPTYP + 18 + 2 + read-only + + + NAKSTS + NAKSTS + 17 + 1 + read-only + + + USBAEP + USBAEP + 15 + 1 + read-only + + + MPSIZ + MPSIZ + 0 + 2 + read-only + + + + + DOEPCTL1 + DOEPCTL1 + device endpoint-1 control + register + 0x320 + 0x20 + 0x00000000 + + + EPENA + EPENA + 31 + 1 + read-write + + + EPDIS + EPDIS + 30 + 1 + read-write + + + SODDFRM + SODDFRM + 29 + 1 + write-only + + + SD0PID_SEVNFRM + SD0PID/SEVNFRM + 28 + 1 + write-only + + + SNAK + SNAK + 27 + 1 + write-only + + + CNAK + CNAK + 26 + 1 + write-only + + + Stall + Stall + 21 + 1 + read-write + + + SNPM + SNPM + 20 + 1 + read-write + + + EPTYP + EPTYP + 18 + 2 + read-write + + + NAKSTS + NAKSTS + 17 + 1 + read-only + + + EONUM_DPID + EONUM/DPID + 16 + 1 + read-only + + + USBAEP + USBAEP + 15 + 1 + read-write + + + MPSIZ + MPSIZ + 0 + 11 + read-write + + + + + DOEPCTL2 + DOEPCTL2 + device endpoint-2 control + register + 0x340 + 0x20 + 0x00000000 + + + EPENA + EPENA + 31 + 1 + read-write + + + EPDIS + EPDIS + 30 + 1 + read-write + + + SODDFRM + SODDFRM + 29 + 1 + write-only + + + SD0PID_SEVNFRM + SD0PID/SEVNFRM + 28 + 1 + write-only + + + SNAK + SNAK + 27 + 1 + write-only + + + CNAK + CNAK + 26 + 1 + write-only + + + Stall + Stall + 21 + 1 + read-write + + + SNPM + SNPM + 20 + 1 + read-write + + + EPTYP + EPTYP + 18 + 2 + read-write + + + NAKSTS + NAKSTS + 17 + 1 + read-only + + + EONUM_DPID + EONUM/DPID + 16 + 1 + read-only + + + USBAEP + USBAEP + 15 + 1 + read-write + + + MPSIZ + MPSIZ + 0 + 11 + read-write + + + + + DOEPCTL3 + DOEPCTL3 + device endpoint-3 control + register + 0x360 + 0x20 + 0x00000000 + + + EPENA + EPENA + 31 + 1 + read-write + + + EPDIS + EPDIS + 30 + 1 + read-write + + + SODDFRM + SODDFRM + 29 + 1 + write-only + + + SD0PID_SEVNFRM + SD0PID/SEVNFRM + 28 + 1 + write-only + + + SNAK + SNAK + 27 + 1 + write-only + + + CNAK + CNAK + 26 + 1 + write-only + + + Stall + Stall + 21 + 1 + read-write + + + SNPM + SNPM + 20 + 1 + read-write + + + EPTYP + EPTYP + 18 + 2 + read-write + + + NAKSTS + NAKSTS + 17 + 1 + read-only + + + EONUM_DPID + EONUM/DPID + 16 + 1 + read-only + + + USBAEP + USBAEP + 15 + 1 + read-write + + + MPSIZ + MPSIZ + 0 + 11 + read-write + + + + + DIEPINT0 + DIEPINT0 + device endpoint-x interrupt + register + 0x108 + 0x20 + 0x00000080 + + + TXFE + TXFE + 7 + 1 + read-only + + + INEPNE + INEPNE + 6 + 1 + read-write + + + ITTXFE + ITTXFE + 4 + 1 + read-write + + + TOC + TOC + 3 + 1 + read-write + + + EPDISD + EPDISD + 1 + 1 + read-write + + + XFRC + XFRC + 0 + 1 + read-write + + + + + DIEPINT1 + DIEPINT1 + device endpoint-1 interrupt + register + 0x128 + 0x20 + 0x00000080 + + + TXFE + TXFE + 7 + 1 + read-only + + + INEPNE + INEPNE + 6 + 1 + read-write + + + ITTXFE + ITTXFE + 4 + 1 + read-write + + + TOC + TOC + 3 + 1 + read-write + + + EPDISD + EPDISD + 1 + 1 + read-write + + + XFRC + XFRC + 0 + 1 + read-write + + + + + DIEPINT2 + DIEPINT2 + device endpoint-2 interrupt + register + 0x148 + 0x20 + 0x00000080 + + + TXFE + TXFE + 7 + 1 + read-only + + + INEPNE + INEPNE + 6 + 1 + read-write + + + ITTXFE + ITTXFE + 4 + 1 + read-write + + + TOC + TOC + 3 + 1 + read-write + + + EPDISD + EPDISD + 1 + 1 + read-write + + + XFRC + XFRC + 0 + 1 + read-write + + + + + DIEPINT3 + DIEPINT3 + device endpoint-3 interrupt + register + 0x168 + 0x20 + 0x00000080 + + + TXFE + TXFE + 7 + 1 + read-only + + + INEPNE + INEPNE + 6 + 1 + read-write + + + ITTXFE + ITTXFE + 4 + 1 + read-write + + + TOC + TOC + 3 + 1 + read-write + + + EPDISD + EPDISD + 1 + 1 + read-write + + + XFRC + XFRC + 0 + 1 + read-write + + + + + DOEPINT0 + DOEPINT0 + device endpoint-0 interrupt + register + 0x308 + 0x20 + read-write + 0x00000080 + + + B2BSTUP + B2BSTUP + 6 + 1 + + + OTEPDIS + OTEPDIS + 4 + 1 + + + STUP + STUP + 3 + 1 + + + EPDISD + EPDISD + 1 + 1 + + + XFRC + XFRC + 0 + 1 + + + + + DOEPINT1 + DOEPINT1 + device endpoint-1 interrupt + register + 0x328 + 0x20 + read-write + 0x00000080 + + + B2BSTUP + B2BSTUP + 6 + 1 + + + OTEPDIS + OTEPDIS + 4 + 1 + + + STUP + STUP + 3 + 1 + + + EPDISD + EPDISD + 1 + 1 + + + XFRC + XFRC + 0 + 1 + + + + + DOEPINT2 + DOEPINT2 + device endpoint-2 interrupt + register + 0x348 + 0x20 + read-write + 0x00000080 + + + B2BSTUP + B2BSTUP + 6 + 1 + + + OTEPDIS + OTEPDIS + 4 + 1 + + + STUP + STUP + 3 + 1 + + + EPDISD + EPDISD + 1 + 1 + + + XFRC + XFRC + 0 + 1 + + + + + DOEPINT3 + DOEPINT3 + device endpoint-3 interrupt + register + 0x368 + 0x20 + read-write + 0x00000080 + + + B2BSTUP + B2BSTUP + 6 + 1 + + + OTEPDIS + OTEPDIS + 4 + 1 + + + STUP + STUP + 3 + 1 + + + EPDISD + EPDISD + 1 + 1 + + + XFRC + XFRC + 0 + 1 + + + + + DIEPTSIZ0 + DIEPTSIZ0 + device endpoint-0 transfer size + register + 0x110 + 0x20 + read-write + 0x00000000 + + + PKTCNT + Packet count + 19 + 2 + + + XFRSIZ + Transfer size + 0 + 7 + + + + + DOEPTSIZ0 + DOEPTSIZ0 + device OUT endpoint-0 transfer size + register + 0x310 + 0x20 + read-write + 0x00000000 + + + STUPCNT + SETUP packet count + 29 + 2 + + + PKTCNT + Packet count + 19 + 1 + + + XFRSIZ + Transfer size + 0 + 7 + + + + + DIEPTSIZ1 + DIEPTSIZ1 + device endpoint-1 transfer size + register + 0x130 + 0x20 + read-write + 0x00000000 + + + MCNT + Multi count + 29 + 2 + + + PKTCNT + Packet count + 19 + 10 + + + XFRSIZ + Transfer size + 0 + 19 + + + + + DIEPTSIZ2 + DIEPTSIZ2 + device endpoint-2 transfer size + register + 0x150 + 0x20 + read-write + 0x00000000 + + + MCNT + Multi count + 29 + 2 + + + PKTCNT + Packet count + 19 + 10 + + + XFRSIZ + Transfer size + 0 + 19 + + + + + DIEPTSIZ3 + DIEPTSIZ3 + device endpoint-3 transfer size + register + 0x170 + 0x20 + read-write + 0x00000000 + + + MCNT + Multi count + 29 + 2 + + + PKTCNT + Packet count + 19 + 10 + + + XFRSIZ + Transfer size + 0 + 19 + + + + + DTXFSTS0 + DTXFSTS0 + OTG_FS device IN endpoint transmit FIFO + status register + 0x118 + 0x20 + read-only + 0x00000000 + + + INEPTFSAV + IN endpoint TxFIFO space + available + 0 + 16 + + + + + DTXFSTS1 + DTXFSTS1 + OTG_FS device IN endpoint transmit FIFO + status register + 0x138 + 0x20 + read-only + 0x00000000 + + + INEPTFSAV + IN endpoint TxFIFO space + available + 0 + 16 + + + + + DTXFSTS2 + DTXFSTS2 + OTG_FS device IN endpoint transmit FIFO + status register + 0x158 + 0x20 + read-only + 0x00000000 + + + INEPTFSAV + IN endpoint TxFIFO space + available + 0 + 16 + + + + + DTXFSTS3 + DTXFSTS3 + OTG_FS device IN endpoint transmit FIFO + status register + 0x178 + 0x20 + read-only + 0x00000000 + + + INEPTFSAV + IN endpoint TxFIFO space + available + 0 + 16 + + + + + DOEPTSIZ1 + DOEPTSIZ1 + device OUT endpoint-1 transfer size + register + 0x330 + 0x20 + read-write + 0x00000000 + + + RXDPID_STUPCNT + Received data PID/SETUP packet + count + 29 + 2 + + + PKTCNT + Packet count + 19 + 10 + + + XFRSIZ + Transfer size + 0 + 19 + + + + + DOEPTSIZ2 + DOEPTSIZ2 + device OUT endpoint-2 transfer size + register + 0x350 + 0x20 + read-write + 0x00000000 + + + RXDPID_STUPCNT + Received data PID/SETUP packet + count + 29 + 2 + + + PKTCNT + Packet count + 19 + 10 + + + XFRSIZ + Transfer size + 0 + 19 + + + + + DOEPTSIZ3 + DOEPTSIZ3 + device OUT endpoint-3 transfer size + register + 0x370 + 0x20 + read-write + 0x00000000 + + + RXDPID_STUPCNT + Received data PID/SETUP packet + count + 29 + 2 + + + PKTCNT + Packet count + 19 + 10 + + + XFRSIZ + Transfer size + 0 + 19 + + + + + + + OTG_FS_PWRCLK + USB on the go full speed + USB_OTG_FS + 0x50000E00 + + 0x0 + 0x400 + registers + + + + FS_PCGCCTL + FS_PCGCCTL + OTG_FS power and clock gating control + register + 0x0 + 0x20 + read-write + 0x00000000 + + + STPPCLK + Stop PHY clock + 0 + 1 + + + GATEHCLK + Gate HCLK + 1 + 1 + + + PHYSUSP + PHY Suspended + 4 + 1 + + + + + + + CAN1 + Controller area network + CAN + 0x40006400 + + 0x0 + 0x400 + registers + + + CAN1_TX + CAN1 TX interrupts + 19 + + + CAN1_RX0 + CAN1 RX0 interrupts + 20 + + + CAN1_RX1 + CAN1 RX1 interrupts + 21 + + + CAN1_SCE + CAN1 SCE interrupt + 22 + + + + MCR + MCR + master control register + 0x0 + 0x20 + read-write + 0x00010002 + + + DBF + DBF + 16 + 1 + + + RESET + RESET + 15 + 1 + + + TTCM + TTCM + 7 + 1 + + + ABOM + ABOM + 6 + 1 + + + AWUM + AWUM + 5 + 1 + + + NART + NART + 4 + 1 + + + RFLM + RFLM + 3 + 1 + + + TXFP + TXFP + 2 + 1 + + + SLEEP + SLEEP + 1 + 1 + + + INRQ + INRQ + 0 + 1 + + + + + MSR + MSR + master status register + 0x4 + 0x20 + 0x00000C02 + + + RX + RX + 11 + 1 + read-only + + + SAMP + SAMP + 10 + 1 + read-only + + + RXM + RXM + 9 + 1 + read-only + + + TXM + TXM + 8 + 1 + read-only + + + SLAKI + SLAKI + 4 + 1 + read-write + + + WKUI + WKUI + 3 + 1 + read-write + + + ERRI + ERRI + 2 + 1 + read-write + + + SLAK + SLAK + 1 + 1 + read-only + + + INAK + INAK + 0 + 1 + read-only + + + + + TSR + TSR + transmit status register + 0x8 + 0x20 + 0x1C000000 + + + LOW2 + Lowest priority flag for mailbox + 2 + 31 + 1 + read-only + + + LOW1 + Lowest priority flag for mailbox + 1 + 30 + 1 + read-only + + + LOW0 + Lowest priority flag for mailbox + 0 + 29 + 1 + read-only + + + TME2 + Lowest priority flag for mailbox + 2 + 28 + 1 + read-only + + + TME1 + Lowest priority flag for mailbox + 1 + 27 + 1 + read-only + + + TME0 + Lowest priority flag for mailbox + 0 + 26 + 1 + read-only + + + CODE + CODE + 24 + 2 + read-only + + + ABRQ2 + ABRQ2 + 23 + 1 + read-write + + + TERR2 + TERR2 + 19 + 1 + read-write + + + ALST2 + ALST2 + 18 + 1 + read-write + + + TXOK2 + TXOK2 + 17 + 1 + read-write + + + RQCP2 + RQCP2 + 16 + 1 + read-write + + + ABRQ1 + ABRQ1 + 15 + 1 + read-write + + + TERR1 + TERR1 + 11 + 1 + read-write + + + ALST1 + ALST1 + 10 + 1 + read-write + + + TXOK1 + TXOK1 + 9 + 1 + read-write + + + RQCP1 + RQCP1 + 8 + 1 + read-write + + + ABRQ0 + ABRQ0 + 7 + 1 + read-write + + + TERR0 + TERR0 + 3 + 1 + read-write + + + ALST0 + ALST0 + 2 + 1 + read-write + + + TXOK0 + TXOK0 + 1 + 1 + read-write + + + RQCP0 + RQCP0 + 0 + 1 + read-write + + + + + RF0R + RF0R + receive FIFO 0 register + 0xC + 0x20 + 0x00000000 + + + RFOM0 + RFOM0 + 5 + 1 + read-write + + + FOVR0 + FOVR0 + 4 + 1 + read-write + + + FULL0 + FULL0 + 3 + 1 + read-write + + + FMP0 + FMP0 + 0 + 2 + read-only + + + + + RF1R + RF1R + receive FIFO 1 register + 0x10 + 0x20 + 0x00000000 + + + RFOM1 + RFOM1 + 5 + 1 + read-write + + + FOVR1 + FOVR1 + 4 + 1 + read-write + + + FULL1 + FULL1 + 3 + 1 + read-write + + + FMP1 + FMP1 + 0 + 2 + read-only + + + + + IER + IER + interrupt enable register + 0x14 + 0x20 + read-write + 0x00000000 + + + SLKIE + SLKIE + 17 + 1 + + + WKUIE + WKUIE + 16 + 1 + + + ERRIE + ERRIE + 15 + 1 + + + LECIE + LECIE + 11 + 1 + + + BOFIE + BOFIE + 10 + 1 + + + EPVIE + EPVIE + 9 + 1 + + + EWGIE + EWGIE + 8 + 1 + + + FOVIE1 + FOVIE1 + 6 + 1 + + + FFIE1 + FFIE1 + 5 + 1 + + + FMPIE1 + FMPIE1 + 4 + 1 + + + FOVIE0 + FOVIE0 + 3 + 1 + + + FFIE0 + FFIE0 + 2 + 1 + + + FMPIE0 + FMPIE0 + 1 + 1 + + + TMEIE + TMEIE + 0 + 1 + + + + + ESR + ESR + interrupt enable register + 0x18 + 0x20 + 0x00000000 + + + REC + REC + 24 + 8 + read-only + + + TEC + TEC + 16 + 8 + read-only + + + LEC + LEC + 4 + 3 + read-write + + + BOFF + BOFF + 2 + 1 + read-only + + + EPVF + EPVF + 1 + 1 + read-only + + + EWGF + EWGF + 0 + 1 + read-only + + + + + BTR + BTR + bit timing register + 0x1C + 0x20 + read-write + 0x00000000 + + + SILM + SILM + 31 + 1 + + + LBKM + LBKM + 30 + 1 + + + SJW + SJW + 24 + 2 + + + TS2 + TS2 + 20 + 3 + + + TS1 + TS1 + 16 + 4 + + + BRP + BRP + 0 + 10 + + + + + TI0R + TI0R + TX mailbox identifier register + 0x180 + 0x20 + read-write + 0x00000000 + + + STID + STID + 21 + 11 + + + EXID + EXID + 3 + 18 + + + IDE + IDE + 2 + 1 + + + RTR + RTR + 1 + 1 + + + TXRQ + TXRQ + 0 + 1 + + + + + TDT0R + TDT0R + mailbox data length control and time stamp + register + 0x184 + 0x20 + read-write + 0x00000000 + + + TIME + TIME + 16 + 16 + + + TGT + TGT + 8 + 1 + + + DLC + DLC + 0 + 4 + + + + + TDL0R + TDL0R + mailbox data low register + 0x188 + 0x20 + read-write + 0x00000000 + + + DATA3 + DATA3 + 24 + 8 + + + DATA2 + DATA2 + 16 + 8 + + + DATA1 + DATA1 + 8 + 8 + + + DATA0 + DATA0 + 0 + 8 + + + + + TDH0R + TDH0R + mailbox data high register + 0x18C + 0x20 + read-write + 0x00000000 + + + DATA7 + DATA7 + 24 + 8 + + + DATA6 + DATA6 + 16 + 8 + + + DATA5 + DATA5 + 8 + 8 + + + DATA4 + DATA4 + 0 + 8 + + + + + TI1R + TI1R + mailbox identifier register + 0x190 + 0x20 + read-write + 0x00000000 + + + STID + STID + 21 + 11 + + + EXID + EXID + 3 + 18 + + + IDE + IDE + 2 + 1 + + + RTR + RTR + 1 + 1 + + + TXRQ + TXRQ + 0 + 1 + + + + + TDT1R + TDT1R + mailbox data length control and time stamp + register + 0x194 + 0x20 + read-write + 0x00000000 + + + TIME + TIME + 16 + 16 + + + TGT + TGT + 8 + 1 + + + DLC + DLC + 0 + 4 + + + + + TDL1R + TDL1R + mailbox data low register + 0x198 + 0x20 + read-write + 0x00000000 + + + DATA3 + DATA3 + 24 + 8 + + + DATA2 + DATA2 + 16 + 8 + + + DATA1 + DATA1 + 8 + 8 + + + DATA0 + DATA0 + 0 + 8 + + + + + TDH1R + TDH1R + mailbox data high register + 0x19C + 0x20 + read-write + 0x00000000 + + + DATA7 + DATA7 + 24 + 8 + + + DATA6 + DATA6 + 16 + 8 + + + DATA5 + DATA5 + 8 + 8 + + + DATA4 + DATA4 + 0 + 8 + + + + + TI2R + TI2R + mailbox identifier register + 0x1A0 + 0x20 + read-write + 0x00000000 + + + STID + STID + 21 + 11 + + + EXID + EXID + 3 + 18 + + + IDE + IDE + 2 + 1 + + + RTR + RTR + 1 + 1 + + + TXRQ + TXRQ + 0 + 1 + + + + + TDT2R + TDT2R + mailbox data length control and time stamp + register + 0x1A4 + 0x20 + read-write + 0x00000000 + + + TIME + TIME + 16 + 16 + + + TGT + TGT + 8 + 1 + + + DLC + DLC + 0 + 4 + + + + + TDL2R + TDL2R + mailbox data low register + 0x1A8 + 0x20 + read-write + 0x00000000 + + + DATA3 + DATA3 + 24 + 8 + + + DATA2 + DATA2 + 16 + 8 + + + DATA1 + DATA1 + 8 + 8 + + + DATA0 + DATA0 + 0 + 8 + + + + + TDH2R + TDH2R + mailbox data high register + 0x1AC + 0x20 + read-write + 0x00000000 + + + DATA7 + DATA7 + 24 + 8 + + + DATA6 + DATA6 + 16 + 8 + + + DATA5 + DATA5 + 8 + 8 + + + DATA4 + DATA4 + 0 + 8 + + + + + RI0R + RI0R + receive FIFO mailbox identifier + register + 0x1B0 + 0x20 + read-only + 0x00000000 + + + STID + STID + 21 + 11 + + + EXID + EXID + 3 + 18 + + + IDE + IDE + 2 + 1 + + + RTR + RTR + 1 + 1 + + + + + RDT0R + RDT0R + mailbox data high register + 0x1B4 + 0x20 + read-only + 0x00000000 + + + TIME + TIME + 16 + 16 + + + FMI + FMI + 8 + 8 + + + DLC + DLC + 0 + 4 + + + + + RDL0R + RDL0R + mailbox data high register + 0x1B8 + 0x20 + read-only + 0x00000000 + + + DATA3 + DATA3 + 24 + 8 + + + DATA2 + DATA2 + 16 + 8 + + + DATA1 + DATA1 + 8 + 8 + + + DATA0 + DATA0 + 0 + 8 + + + + + RDH0R + RDH0R + receive FIFO mailbox data high + register + 0x1BC + 0x20 + read-only + 0x00000000 + + + DATA7 + DATA7 + 24 + 8 + + + DATA6 + DATA6 + 16 + 8 + + + DATA5 + DATA5 + 8 + 8 + + + DATA4 + DATA4 + 0 + 8 + + + + + RI1R + RI1R + mailbox data high register + 0x1C0 + 0x20 + read-only + 0x00000000 + + + STID + STID + 21 + 11 + + + EXID + EXID + 3 + 18 + + + IDE + IDE + 2 + 1 + + + RTR + RTR + 1 + 1 + + + + + RDT1R + RDT1R + mailbox data high register + 0x1C4 + 0x20 + read-only + 0x00000000 + + + TIME + TIME + 16 + 16 + + + FMI + FMI + 8 + 8 + + + DLC + DLC + 0 + 4 + + + + + RDL1R + RDL1R + mailbox data high register + 0x1C8 + 0x20 + read-only + 0x00000000 + + + DATA3 + DATA3 + 24 + 8 + + + DATA2 + DATA2 + 16 + 8 + + + DATA1 + DATA1 + 8 + 8 + + + DATA0 + DATA0 + 0 + 8 + + + + + RDH1R + RDH1R + mailbox data high register + 0x1CC + 0x20 + read-only + 0x00000000 + + + DATA7 + DATA7 + 24 + 8 + + + DATA6 + DATA6 + 16 + 8 + + + DATA5 + DATA5 + 8 + 8 + + + DATA4 + DATA4 + 0 + 8 + + + + + FMR + FMR + filter master register + 0x200 + 0x20 + read-write + 0x2A1C0E01 + + + CAN2SB + CAN2SB + 8 + 6 + + + FINIT + FINIT + 0 + 1 + + + + + FM1R + FM1R + filter mode register + 0x204 + 0x20 + read-write + 0x00000000 + + + FBM0 + Filter mode + 0 + 1 + + + FBM1 + Filter mode + 1 + 1 + + + FBM2 + Filter mode + 2 + 1 + + + FBM3 + Filter mode + 3 + 1 + + + FBM4 + Filter mode + 4 + 1 + + + FBM5 + Filter mode + 5 + 1 + + + FBM6 + Filter mode + 6 + 1 + + + FBM7 + Filter mode + 7 + 1 + + + FBM8 + Filter mode + 8 + 1 + + + FBM9 + Filter mode + 9 + 1 + + + FBM10 + Filter mode + 10 + 1 + + + FBM11 + Filter mode + 11 + 1 + + + FBM12 + Filter mode + 12 + 1 + + + FBM13 + Filter mode + 13 + 1 + + + FBM14 + Filter mode + 14 + 1 + + + FBM15 + Filter mode + 15 + 1 + + + FBM16 + Filter mode + 16 + 1 + + + FBM17 + Filter mode + 17 + 1 + + + FBM18 + Filter mode + 18 + 1 + + + FBM19 + Filter mode + 19 + 1 + + + FBM20 + Filter mode + 20 + 1 + + + FBM21 + Filter mode + 21 + 1 + + + FBM22 + Filter mode + 22 + 1 + + + FBM23 + Filter mode + 23 + 1 + + + FBM24 + Filter mode + 24 + 1 + + + FBM25 + Filter mode + 25 + 1 + + + FBM26 + Filter mode + 26 + 1 + + + FBM27 + Filter mode + 27 + 1 + + + + + FS1R + FS1R + filter scale register + 0x20C + 0x20 + read-write + 0x00000000 + + + FSC0 + Filter scale configuration + 0 + 1 + + + FSC1 + Filter scale configuration + 1 + 1 + + + FSC2 + Filter scale configuration + 2 + 1 + + + FSC3 + Filter scale configuration + 3 + 1 + + + FSC4 + Filter scale configuration + 4 + 1 + + + FSC5 + Filter scale configuration + 5 + 1 + + + FSC6 + Filter scale configuration + 6 + 1 + + + FSC7 + Filter scale configuration + 7 + 1 + + + FSC8 + Filter scale configuration + 8 + 1 + + + FSC9 + Filter scale configuration + 9 + 1 + + + FSC10 + Filter scale configuration + 10 + 1 + + + FSC11 + Filter scale configuration + 11 + 1 + + + FSC12 + Filter scale configuration + 12 + 1 + + + FSC13 + Filter scale configuration + 13 + 1 + + + FSC14 + Filter scale configuration + 14 + 1 + + + FSC15 + Filter scale configuration + 15 + 1 + + + FSC16 + Filter scale configuration + 16 + 1 + + + FSC17 + Filter scale configuration + 17 + 1 + + + FSC18 + Filter scale configuration + 18 + 1 + + + FSC19 + Filter scale configuration + 19 + 1 + + + FSC20 + Filter scale configuration + 20 + 1 + + + FSC21 + Filter scale configuration + 21 + 1 + + + FSC22 + Filter scale configuration + 22 + 1 + + + FSC23 + Filter scale configuration + 23 + 1 + + + FSC24 + Filter scale configuration + 24 + 1 + + + FSC25 + Filter scale configuration + 25 + 1 + + + FSC26 + Filter scale configuration + 26 + 1 + + + FSC27 + Filter scale configuration + 27 + 1 + + + + + FFA1R + FFA1R + filter FIFO assignment + register + 0x214 + 0x20 + read-write + 0x00000000 + + + FFA0 + Filter FIFO assignment for filter + 0 + 0 + 1 + + + FFA1 + Filter FIFO assignment for filter + 1 + 1 + 1 + + + FFA2 + Filter FIFO assignment for filter + 2 + 2 + 1 + + + FFA3 + Filter FIFO assignment for filter + 3 + 3 + 1 + + + FFA4 + Filter FIFO assignment for filter + 4 + 4 + 1 + + + FFA5 + Filter FIFO assignment for filter + 5 + 5 + 1 + + + FFA6 + Filter FIFO assignment for filter + 6 + 6 + 1 + + + FFA7 + Filter FIFO assignment for filter + 7 + 7 + 1 + + + FFA8 + Filter FIFO assignment for filter + 8 + 8 + 1 + + + FFA9 + Filter FIFO assignment for filter + 9 + 9 + 1 + + + FFA10 + Filter FIFO assignment for filter + 10 + 10 + 1 + + + FFA11 + Filter FIFO assignment for filter + 11 + 11 + 1 + + + FFA12 + Filter FIFO assignment for filter + 12 + 12 + 1 + + + FFA13 + Filter FIFO assignment for filter + 13 + 13 + 1 + + + FFA14 + Filter FIFO assignment for filter + 14 + 14 + 1 + + + FFA15 + Filter FIFO assignment for filter + 15 + 15 + 1 + + + FFA16 + Filter FIFO assignment for filter + 16 + 16 + 1 + + + FFA17 + Filter FIFO assignment for filter + 17 + 17 + 1 + + + FFA18 + Filter FIFO assignment for filter + 18 + 18 + 1 + + + FFA19 + Filter FIFO assignment for filter + 19 + 19 + 1 + + + FFA20 + Filter FIFO assignment for filter + 20 + 20 + 1 + + + FFA21 + Filter FIFO assignment for filter + 21 + 21 + 1 + + + FFA22 + Filter FIFO assignment for filter + 22 + 22 + 1 + + + FFA23 + Filter FIFO assignment for filter + 23 + 23 + 1 + + + FFA24 + Filter FIFO assignment for filter + 24 + 24 + 1 + + + FFA25 + Filter FIFO assignment for filter + 25 + 25 + 1 + + + FFA26 + Filter FIFO assignment for filter + 26 + 26 + 1 + + + FFA27 + Filter FIFO assignment for filter + 27 + 27 + 1 + + + + + FA1R + FA1R + filter activation register + 0x21C + 0x20 + read-write + 0x00000000 + + + FACT0 + Filter active + 0 + 1 + + + FACT1 + Filter active + 1 + 1 + + + FACT2 + Filter active + 2 + 1 + + + FACT3 + Filter active + 3 + 1 + + + FACT4 + Filter active + 4 + 1 + + + FACT5 + Filter active + 5 + 1 + + + FACT6 + Filter active + 6 + 1 + + + FACT7 + Filter active + 7 + 1 + + + FACT8 + Filter active + 8 + 1 + + + FACT9 + Filter active + 9 + 1 + + + FACT10 + Filter active + 10 + 1 + + + FACT11 + Filter active + 11 + 1 + + + FACT12 + Filter active + 12 + 1 + + + FACT13 + Filter active + 13 + 1 + + + FACT14 + Filter active + 14 + 1 + + + FACT15 + Filter active + 15 + 1 + + + FACT16 + Filter active + 16 + 1 + + + FACT17 + Filter active + 17 + 1 + + + FACT18 + Filter active + 18 + 1 + + + FACT19 + Filter active + 19 + 1 + + + FACT20 + Filter active + 20 + 1 + + + FACT21 + Filter active + 21 + 1 + + + FACT22 + Filter active + 22 + 1 + + + FACT23 + Filter active + 23 + 1 + + + FACT24 + Filter active + 24 + 1 + + + FACT25 + Filter active + 25 + 1 + + + FACT26 + Filter active + 26 + 1 + + + FACT27 + Filter active + 27 + 1 + + + + + F0R1 + F0R1 + Filter bank 0 register 1 + 0x240 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F0R2 + F0R2 + Filter bank 0 register 2 + 0x244 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F1R1 + F1R1 + Filter bank 1 register 1 + 0x248 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F1R2 + F1R2 + Filter bank 1 register 2 + 0x24C + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F2R1 + F2R1 + Filter bank 2 register 1 + 0x250 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F2R2 + F2R2 + Filter bank 2 register 2 + 0x254 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F3R1 + F3R1 + Filter bank 3 register 1 + 0x258 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F3R2 + F3R2 + Filter bank 3 register 2 + 0x25C + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F4R1 + F4R1 + Filter bank 4 register 1 + 0x260 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F4R2 + F4R2 + Filter bank 4 register 2 + 0x264 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F5R1 + F5R1 + Filter bank 5 register 1 + 0x268 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F5R2 + F5R2 + Filter bank 5 register 2 + 0x26C + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F6R1 + F6R1 + Filter bank 6 register 1 + 0x270 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F6R2 + F6R2 + Filter bank 6 register 2 + 0x274 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + + F7R1 + F7R1 + Filter bank 7 register 1 + 0x278 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F7R2 + F7R2 + Filter bank 7 register 2 + 0x27C + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F8R1 + F8R1 + Filter bank 8 register 1 + 0x280 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F8R2 + F8R2 + Filter bank 8 register 2 + 0x284 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F9R1 + F9R1 + Filter bank 9 register 1 + 0x288 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F9R2 + F9R2 + Filter bank 9 register 2 + 0x28C + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F10R1 + F10R1 + Filter bank 10 register 1 + 0x290 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F10R2 + F10R2 + Filter bank 10 register 2 + 0x294 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F11R1 + F11R1 + Filter bank 11 register 1 + 0x298 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F11R2 + F11R2 + Filter bank 11 register 2 + 0x29C + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F12R1 + F12R1 + Filter bank 4 register 1 + 0x2A0 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F12R2 + F12R2 + Filter bank 12 register 2 + 0x2A4 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F13R1 + F13R1 + Filter bank 13 register 1 + 0x2A8 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F13R2 + F13R2 + Filter bank 13 register 2 + 0x2AC + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F14R1 + F14R1 + Filter bank 14 register 1 + 0x2B0 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F14R2 + F14R2 + Filter bank 14 register 2 + 0x2B4 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F15R1 + F15R1 + Filter bank 15 register 1 + 0x2B8 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F15R2 + F15R2 + Filter bank 15 register 2 + 0x2BC + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F16R1 + F16R1 + Filter bank 16 register 1 + 0x2C0 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F16R2 + F16R2 + Filter bank 16 register 2 + 0x2C4 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F17R1 + F17R1 + Filter bank 17 register 1 + 0x2C8 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F17R2 + F17R2 + Filter bank 17 register 2 + 0x2CC + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F18R1 + F18R1 + Filter bank 18 register 1 + 0x2D0 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F18R2 + F18R2 + Filter bank 18 register 2 + 0x2D4 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F19R1 + F19R1 + Filter bank 19 register 1 + 0x2D8 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F19R2 + F19R2 + Filter bank 19 register 2 + 0x2DC + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F20R1 + F20R1 + Filter bank 20 register 1 + 0x2E0 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F20R2 + F20R2 + Filter bank 20 register 2 + 0x2E4 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F21R1 + F21R1 + Filter bank 21 register 1 + 0x2E8 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F21R2 + F21R2 + Filter bank 21 register 2 + 0x2EC + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F22R1 + F22R1 + Filter bank 22 register 1 + 0x2F0 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F22R2 + F22R2 + Filter bank 22 register 2 + 0x2F4 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F23R1 + F23R1 + Filter bank 23 register 1 + 0x2F8 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F23R2 + F23R2 + Filter bank 23 register 2 + 0x2FC + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F24R1 + F24R1 + Filter bank 24 register 1 + 0x300 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F24R2 + F24R2 + Filter bank 24 register 2 + 0x304 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F25R1 + F25R1 + Filter bank 25 register 1 + 0x308 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F25R2 + F25R2 + Filter bank 25 register 2 + 0x30C + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F26R1 + F26R1 + Filter bank 26 register 1 + 0x310 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F26R2 + F26R2 + Filter bank 26 register 2 + 0x314 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F27R1 + F27R1 + Filter bank 27 register 1 + 0x318 + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + F27R2 + F27R2 + Filter bank 27 register 2 + 0x31C + 0x20 + read-write + 0x00000000 + + + FB0 + Filter bits + 0 + 1 + + + FB1 + Filter bits + 1 + 1 + + + FB2 + Filter bits + 2 + 1 + + + FB3 + Filter bits + 3 + 1 + + + FB4 + Filter bits + 4 + 1 + + + FB5 + Filter bits + 5 + 1 + + + FB6 + Filter bits + 6 + 1 + + + FB7 + Filter bits + 7 + 1 + + + FB8 + Filter bits + 8 + 1 + + + FB9 + Filter bits + 9 + 1 + + + FB10 + Filter bits + 10 + 1 + + + FB11 + Filter bits + 11 + 1 + + + FB12 + Filter bits + 12 + 1 + + + FB13 + Filter bits + 13 + 1 + + + FB14 + Filter bits + 14 + 1 + + + FB15 + Filter bits + 15 + 1 + + + FB16 + Filter bits + 16 + 1 + + + FB17 + Filter bits + 17 + 1 + + + FB18 + Filter bits + 18 + 1 + + + FB19 + Filter bits + 19 + 1 + + + FB20 + Filter bits + 20 + 1 + + + FB21 + Filter bits + 21 + 1 + + + FB22 + Filter bits + 22 + 1 + + + FB23 + Filter bits + 23 + 1 + + + FB24 + Filter bits + 24 + 1 + + + FB25 + Filter bits + 25 + 1 + + + FB26 + Filter bits + 26 + 1 + + + FB27 + Filter bits + 27 + 1 + + + FB28 + Filter bits + 28 + 1 + + + FB29 + Filter bits + 29 + 1 + + + FB30 + Filter bits + 30 + 1 + + + FB31 + Filter bits + 31 + 1 + + + + + + + CAN2 + 0x40006800 + + CAN2_TX + CAN2 TX interrupts + 63 + + + CAN2_RX0 + CAN2 RX0 interrupts + 64 + + + CAN2_RX1 + CAN2 RX1 interrupts + 65 + + + CAN2_SCE + CAN2 SCE interrupt + 66 + + + + FLASH + FLASH + FLASH + 0x40023C00 + + 0x0 + 0x400 + registers + + + + ACR + ACR + Flash access control register + 0x0 + 0x20 + 0x00000000 + + + LATENCY + Latency + 0 + 3 + read-write + + + PRFTEN + Prefetch enable + 8 + 1 + read-write + + + ICEN + Instruction cache enable + 9 + 1 + read-write + + + DCEN + Data cache enable + 10 + 1 + read-write + + + ICRST + Instruction cache reset + 11 + 1 + write-only + + + DCRST + Data cache reset + 12 + 1 + read-write + + + + + KEYR + KEYR + Flash key register + 0x4 + 0x20 + write-only + 0x00000000 + + + KEY + FPEC key + 0 + 32 + + + + + OPTKEYR + OPTKEYR + Flash option key register + 0x8 + 0x20 + write-only + 0x00000000 + + + OPTKEY + Option byte key + 0 + 32 + + + + + SR + SR + Status register + 0xC + 0x20 + 0x00000000 + + + EOP + End of operation + 0 + 1 + read-write + + + OPERR + Operation error + 1 + 1 + read-write + + + WRPERR + Write protection error + 4 + 1 + read-write + + + PGAERR + Programming alignment + error + 5 + 1 + read-write + + + PGPERR + Programming parallelism + error + 6 + 1 + read-write + + + PGSERR + Programming sequence error + 7 + 1 + read-write + + + BSY + Busy + 16 + 1 + read-only + + + + + CR + CR + Control register + 0x10 + 0x20 + read-write + 0x80000000 + + + PG + Programming + 0 + 1 + + + SER + Sector Erase + 1 + 1 + + + MER + Mass Erase + 2 + 1 + + + SNB + Sector number + 3 + 4 + + + PSIZE + Program size + 8 + 2 + + + STRT + Start + 16 + 1 + + + EOPIE + End of operation interrupt + enable + 24 + 1 + + + ERRIE + Error interrupt enable + 25 + 1 + + + LOCK + Lock + 31 + 1 + + + + + OPTCR + OPTCR + Flash option control register + 0x14 + 0x20 + read-write + 0x00000014 + + + OPTLOCK + Option lock + 0 + 1 + + + OPTSTRT + Option start + 1 + 1 + + + BOR_LEV + BOR reset Level + 2 + 2 + + + WDG_SW + WDG_SW User option bytes + 5 + 1 + + + nRST_STOP + nRST_STOP User option + bytes + 6 + 1 + + + nRST_STDBY + nRST_STDBY User option + bytes + 7 + 1 + + + RDP + Read protect + 8 + 8 + + + nWRP + Not write protect + 16 + 12 + + + + + + + EXTI + External interrupt/event + controller + EXTI + 0x40013C00 + + 0x0 + 0x400 + registers + + + TAMP_STAMP + Tamper and TimeStamp interrupts through the + EXTI line + 2 + + + EXTI0 + EXTI Line0 interrupt + 6 + + + EXTI1 + EXTI Line1 interrupt + 7 + + + EXTI2 + EXTI Line2 interrupt + 8 + + + EXTI3 + EXTI Line3 interrupt + 9 + + + EXTI4 + EXTI Line4 interrupt + 10 + + + EXTI9_5 + EXTI Line[9:5] interrupts + 23 + + + EXTI9_5 + EXTI Line[9:5] interrupts + 23 + + + EXTI15_10 + EXTI Line[15:10] interrupts + 40 + + + + IMR + IMR + Interrupt mask register + (EXTI_IMR) + 0x0 + 0x20 + read-write + 0x00000000 + + + MR0 + Interrupt Mask on line 0 + 0 + 1 + + + MR1 + Interrupt Mask on line 1 + 1 + 1 + + + MR2 + Interrupt Mask on line 2 + 2 + 1 + + + MR3 + Interrupt Mask on line 3 + 3 + 1 + + + MR4 + Interrupt Mask on line 4 + 4 + 1 + + + MR5 + Interrupt Mask on line 5 + 5 + 1 + + + MR6 + Interrupt Mask on line 6 + 6 + 1 + + + MR7 + Interrupt Mask on line 7 + 7 + 1 + + + MR8 + Interrupt Mask on line 8 + 8 + 1 + + + MR9 + Interrupt Mask on line 9 + 9 + 1 + + + MR10 + Interrupt Mask on line 10 + 10 + 1 + + + MR11 + Interrupt Mask on line 11 + 11 + 1 + + + MR12 + Interrupt Mask on line 12 + 12 + 1 + + + MR13 + Interrupt Mask on line 13 + 13 + 1 + + + MR14 + Interrupt Mask on line 14 + 14 + 1 + + + MR15 + Interrupt Mask on line 15 + 15 + 1 + + + MR16 + Interrupt Mask on line 16 + 16 + 1 + + + MR17 + Interrupt Mask on line 17 + 17 + 1 + + + MR18 + Interrupt Mask on line 18 + 18 + 1 + + + MR19 + Interrupt Mask on line 19 + 19 + 1 + + + MR20 + Interrupt Mask on line 20 + 20 + 1 + + + MR21 + Interrupt Mask on line 21 + 21 + 1 + + + MR22 + Interrupt Mask on line 22 + 22 + 1 + + + + + EMR + EMR + Event mask register (EXTI_EMR) + 0x4 + 0x20 + read-write + 0x00000000 + + + MR0 + Event Mask on line 0 + 0 + 1 + + + MR1 + Event Mask on line 1 + 1 + 1 + + + MR2 + Event Mask on line 2 + 2 + 1 + + + MR3 + Event Mask on line 3 + 3 + 1 + + + MR4 + Event Mask on line 4 + 4 + 1 + + + MR5 + Event Mask on line 5 + 5 + 1 + + + MR6 + Event Mask on line 6 + 6 + 1 + + + MR7 + Event Mask on line 7 + 7 + 1 + + + MR8 + Event Mask on line 8 + 8 + 1 + + + MR9 + Event Mask on line 9 + 9 + 1 + + + MR10 + Event Mask on line 10 + 10 + 1 + + + MR11 + Event Mask on line 11 + 11 + 1 + + + MR12 + Event Mask on line 12 + 12 + 1 + + + MR13 + Event Mask on line 13 + 13 + 1 + + + MR14 + Event Mask on line 14 + 14 + 1 + + + MR15 + Event Mask on line 15 + 15 + 1 + + + MR16 + Event Mask on line 16 + 16 + 1 + + + MR17 + Event Mask on line 17 + 17 + 1 + + + MR18 + Event Mask on line 18 + 18 + 1 + + + MR19 + Event Mask on line 19 + 19 + 1 + + + MR20 + Event Mask on line 20 + 20 + 1 + + + MR21 + Event Mask on line 21 + 21 + 1 + + + MR22 + Event Mask on line 22 + 22 + 1 + + + + + RTSR + RTSR + Rising Trigger selection register + (EXTI_RTSR) + 0x8 + 0x20 + read-write + 0x00000000 + + + TR0 + Rising trigger event configuration of + line 0 + 0 + 1 + + + TR1 + Rising trigger event configuration of + line 1 + 1 + 1 + + + TR2 + Rising trigger event configuration of + line 2 + 2 + 1 + + + TR3 + Rising trigger event configuration of + line 3 + 3 + 1 + + + TR4 + Rising trigger event configuration of + line 4 + 4 + 1 + + + TR5 + Rising trigger event configuration of + line 5 + 5 + 1 + + + TR6 + Rising trigger event configuration of + line 6 + 6 + 1 + + + TR7 + Rising trigger event configuration of + line 7 + 7 + 1 + + + TR8 + Rising trigger event configuration of + line 8 + 8 + 1 + + + TR9 + Rising trigger event configuration of + line 9 + 9 + 1 + + + TR10 + Rising trigger event configuration of + line 10 + 10 + 1 + + + TR11 + Rising trigger event configuration of + line 11 + 11 + 1 + + + TR12 + Rising trigger event configuration of + line 12 + 12 + 1 + + + TR13 + Rising trigger event configuration of + line 13 + 13 + 1 + + + TR14 + Rising trigger event configuration of + line 14 + 14 + 1 + + + TR15 + Rising trigger event configuration of + line 15 + 15 + 1 + + + TR16 + Rising trigger event configuration of + line 16 + 16 + 1 + + + TR17 + Rising trigger event configuration of + line 17 + 17 + 1 + + + TR18 + Rising trigger event configuration of + line 18 + 18 + 1 + + + TR19 + Rising trigger event configuration of + line 19 + 19 + 1 + + + TR20 + Rising trigger event configuration of + line 20 + 20 + 1 + + + TR21 + Rising trigger event configuration of + line 21 + 21 + 1 + + + TR22 + Rising trigger event configuration of + line 22 + 22 + 1 + + + + + FTSR + FTSR + Falling Trigger selection register + (EXTI_FTSR) + 0xC + 0x20 + read-write + 0x00000000 + + + TR0 + Falling trigger event configuration of + line 0 + 0 + 1 + + + TR1 + Falling trigger event configuration of + line 1 + 1 + 1 + + + TR2 + Falling trigger event configuration of + line 2 + 2 + 1 + + + TR3 + Falling trigger event configuration of + line 3 + 3 + 1 + + + TR4 + Falling trigger event configuration of + line 4 + 4 + 1 + + + TR5 + Falling trigger event configuration of + line 5 + 5 + 1 + + + TR6 + Falling trigger event configuration of + line 6 + 6 + 1 + + + TR7 + Falling trigger event configuration of + line 7 + 7 + 1 + + + TR8 + Falling trigger event configuration of + line 8 + 8 + 1 + + + TR9 + Falling trigger event configuration of + line 9 + 9 + 1 + + + TR10 + Falling trigger event configuration of + line 10 + 10 + 1 + + + TR11 + Falling trigger event configuration of + line 11 + 11 + 1 + + + TR12 + Falling trigger event configuration of + line 12 + 12 + 1 + + + TR13 + Falling trigger event configuration of + line 13 + 13 + 1 + + + TR14 + Falling trigger event configuration of + line 14 + 14 + 1 + + + TR15 + Falling trigger event configuration of + line 15 + 15 + 1 + + + TR16 + Falling trigger event configuration of + line 16 + 16 + 1 + + + TR17 + Falling trigger event configuration of + line 17 + 17 + 1 + + + TR18 + Falling trigger event configuration of + line 18 + 18 + 1 + + + TR19 + Falling trigger event configuration of + line 19 + 19 + 1 + + + TR20 + Falling trigger event configuration of + line 20 + 20 + 1 + + + TR21 + Falling trigger event configuration of + line 21 + 21 + 1 + + + TR22 + Falling trigger event configuration of + line 22 + 22 + 1 + + + + + SWIER + SWIER + Software interrupt event register + (EXTI_SWIER) + 0x10 + 0x20 + read-write + 0x00000000 + + + SWIER0 + Software Interrupt on line + 0 + 0 + 1 + + + SWIER1 + Software Interrupt on line + 1 + 1 + 1 + + + SWIER2 + Software Interrupt on line + 2 + 2 + 1 + + + SWIER3 + Software Interrupt on line + 3 + 3 + 1 + + + SWIER4 + Software Interrupt on line + 4 + 4 + 1 + + + SWIER5 + Software Interrupt on line + 5 + 5 + 1 + + + SWIER6 + Software Interrupt on line + 6 + 6 + 1 + + + SWIER7 + Software Interrupt on line + 7 + 7 + 1 + + + SWIER8 + Software Interrupt on line + 8 + 8 + 1 + + + SWIER9 + Software Interrupt on line + 9 + 9 + 1 + + + SWIER10 + Software Interrupt on line + 10 + 10 + 1 + + + SWIER11 + Software Interrupt on line + 11 + 11 + 1 + + + SWIER12 + Software Interrupt on line + 12 + 12 + 1 + + + SWIER13 + Software Interrupt on line + 13 + 13 + 1 + + + SWIER14 + Software Interrupt on line + 14 + 14 + 1 + + + SWIER15 + Software Interrupt on line + 15 + 15 + 1 + + + SWIER16 + Software Interrupt on line + 16 + 16 + 1 + + + SWIER17 + Software Interrupt on line + 17 + 17 + 1 + + + SWIER18 + Software Interrupt on line + 18 + 18 + 1 + + + SWIER19 + Software Interrupt on line + 19 + 19 + 1 + + + SWIER20 + Software Interrupt on line + 20 + 20 + 1 + + + SWIER21 + Software Interrupt on line + 21 + 21 + 1 + + + SWIER22 + Software Interrupt on line + 22 + 22 + 1 + + + + + PR + PR + Pending register (EXTI_PR) + 0x14 + 0x20 + read-write + 0x00000000 + + + PR0 + Pending bit 0 + 0 + 1 + + + PR1 + Pending bit 1 + 1 + 1 + + + PR2 + Pending bit 2 + 2 + 1 + + + PR3 + Pending bit 3 + 3 + 1 + + + PR4 + Pending bit 4 + 4 + 1 + + + PR5 + Pending bit 5 + 5 + 1 + + + PR6 + Pending bit 6 + 6 + 1 + + + PR7 + Pending bit 7 + 7 + 1 + + + PR8 + Pending bit 8 + 8 + 1 + + + PR9 + Pending bit 9 + 9 + 1 + + + PR10 + Pending bit 10 + 10 + 1 + + + PR11 + Pending bit 11 + 11 + 1 + + + PR12 + Pending bit 12 + 12 + 1 + + + PR13 + Pending bit 13 + 13 + 1 + + + PR14 + Pending bit 14 + 14 + 1 + + + PR15 + Pending bit 15 + 15 + 1 + + + PR16 + Pending bit 16 + 16 + 1 + + + PR17 + Pending bit 17 + 17 + 1 + + + PR18 + Pending bit 18 + 18 + 1 + + + PR19 + Pending bit 19 + 19 + 1 + + + PR20 + Pending bit 20 + 20 + 1 + + + PR21 + Pending bit 21 + 21 + 1 + + + PR22 + Pending bit 22 + 22 + 1 + + + + + + + OTG_HS_GLOBAL + USB on the go high speed + USB_OTG_HS + 0x40040000 + + 0x0 + 0xFFFC0400 + registers + + + 0xFFFC0400 + 0x40000 + reserved + + + OTG_HS_EP1_OUT + USB On The Go HS End Point 1 Out global + interrupt + 74 + + + OTG_HS_EP1_IN + USB On The Go HS End Point 1 In global + interrupt + 75 + + + OTG_HS_WKUP + USB On The Go HS Wakeup through EXTI + interrupt + 76 + + + OTG_HS + USB On The Go HS global + interrupt + 77 + + + + OTG_HS_GOTGCTL + OTG_HS_GOTGCTL + OTG_HS control and status + register + 0x0 + 32 + 0x00000800 + + + SRQSCS + Session request success + 0 + 1 + read-only + + + SRQ + Session request + 1 + 1 + read-write + + + HNGSCS + Host negotiation success + 8 + 1 + read-only + + + HNPRQ + HNP request + 9 + 1 + read-write + + + HSHNPEN + Host set HNP enable + 10 + 1 + read-write + + + DHNPEN + Device HNP enabled + 11 + 1 + read-write + + + CIDSTS + Connector ID status + 16 + 1 + read-only + + + DBCT + Long/short debounce time + 17 + 1 + read-only + + + ASVLD + A-session valid + 18 + 1 + read-only + + + BSVLD + B-session valid + 19 + 1 + read-only + + + + + OTG_HS_GOTGINT + OTG_HS_GOTGINT + OTG_HS interrupt register + 0x4 + 32 + read-write + 0x0 + + + SEDET + Session end detected + 2 + 1 + + + SRSSCHG + Session request success status + change + 8 + 1 + + + HNSSCHG + Host negotiation success status + change + 9 + 1 + + + HNGDET + Host negotiation detected + 17 + 1 + + + ADTOCHG + A-device timeout change + 18 + 1 + + + DBCDNE + Debounce done + 19 + 1 + + + + + OTG_HS_GAHBCFG + OTG_HS_GAHBCFG + OTG_HS AHB configuration + register + 0x8 + 32 + read-write + 0x0 + + + GINT + Global interrupt mask + 0 + 1 + + + HBSTLEN + Burst length/type + 1 + 4 + + + DMAEN + DMA enable + 5 + 1 + + + TXFELVL + TxFIFO empty level + 7 + 1 + + + PTXFELVL + Periodic TxFIFO empty + level + 8 + 1 + + + + + OTG_HS_GUSBCFG + OTG_HS_GUSBCFG + OTG_HS USB configuration + register + 0xC + 32 + 0x00000A00 + + + TOCAL + FS timeout calibration + 0 + 3 + read-write + + + PHYSEL + USB 2.0 high-speed ULPI PHY or USB 1.1 + full-speed serial transceiver select + 6 + 1 + write-only + + + SRPCAP + SRP-capable + 8 + 1 + read-write + + + HNPCAP + HNP-capable + 9 + 1 + read-write + + + TRDT + USB turnaround time + 10 + 4 + read-write + + + PHYLPCS + PHY Low-power clock select + 15 + 1 + read-write + + + ULPIFSLS + ULPI FS/LS select + 17 + 1 + read-write + + + ULPIAR + ULPI Auto-resume + 18 + 1 + read-write + + + ULPICSM + ULPI Clock SuspendM + 19 + 1 + read-write + + + ULPIEVBUSD + ULPI External VBUS Drive + 20 + 1 + read-write + + + ULPIEVBUSI + ULPI external VBUS + indicator + 21 + 1 + read-write + + + TSDPS + TermSel DLine pulsing + selection + 22 + 1 + read-write + + + PCCI + Indicator complement + 23 + 1 + read-write + + + PTCI + Indicator pass through + 24 + 1 + read-write + + + ULPIIPD + ULPI interface protect + disable + 25 + 1 + read-write + + + FHMOD + Forced host mode + 29 + 1 + read-write + + + FDMOD + Forced peripheral mode + 30 + 1 + read-write + + + CTXPKT + Corrupt Tx packet + 31 + 1 + read-write + + + + + OTG_HS_GRSTCTL + OTG_HS_GRSTCTL + OTG_HS reset register + 0x10 + 32 + 0x20000000 + + + CSRST + Core soft reset + 0 + 1 + read-write + + + HSRST + HCLK soft reset + 1 + 1 + read-write + + + FCRST + Host frame counter reset + 2 + 1 + read-write + + + RXFFLSH + RxFIFO flush + 4 + 1 + read-write + + + TXFFLSH + TxFIFO flush + 5 + 1 + read-write + + + TXFNUM + TxFIFO number + 6 + 5 + read-write + + + DMAREQ + DMA request signal + 30 + 1 + read-only + + + AHBIDL + AHB master idle + 31 + 1 + read-only + + + + + OTG_HS_GINTSTS + OTG_HS_GINTSTS + OTG_HS core interrupt register + 0x14 + 32 + 0x04000020 + + + CMOD + Current mode of operation + 0 + 1 + read-only + + + MMIS + Mode mismatch interrupt + 1 + 1 + read-write + + + OTGINT + OTG interrupt + 2 + 1 + read-only + + + SOF + Start of frame + 3 + 1 + read-write + + + RXFLVL + RxFIFO nonempty + 4 + 1 + read-only + + + NPTXFE + Nonperiodic TxFIFO empty + 5 + 1 + read-only + + + GINAKEFF + Global IN nonperiodic NAK + effective + 6 + 1 + read-only + + + BOUTNAKEFF + Global OUT NAK effective + 7 + 1 + read-only + + + ESUSP + Early suspend + 10 + 1 + read-write + + + USBSUSP + USB suspend + 11 + 1 + read-write + + + USBRST + USB reset + 12 + 1 + read-write + + + ENUMDNE + Enumeration done + 13 + 1 + read-write + + + ISOODRP + Isochronous OUT packet dropped + interrupt + 14 + 1 + read-write + + + EOPF + End of periodic frame + interrupt + 15 + 1 + read-write + + + IEPINT + IN endpoint interrupt + 18 + 1 + read-only + + + OEPINT + OUT endpoint interrupt + 19 + 1 + read-only + + + IISOIXFR + Incomplete isochronous IN + transfer + 20 + 1 + read-write + + + PXFR_INCOMPISOOUT + Incomplete periodic + transfer + 21 + 1 + read-write + + + DATAFSUSP + Data fetch suspended + 22 + 1 + read-write + + + HPRTINT + Host port interrupt + 24 + 1 + read-only + + + HCINT + Host channels interrupt + 25 + 1 + read-only + + + PTXFE + Periodic TxFIFO empty + 26 + 1 + read-only + + + CIDSCHG + Connector ID status change + 28 + 1 + read-write + + + DISCINT + Disconnect detected + interrupt + 29 + 1 + read-write + + + SRQINT + Session request/new session detected + interrupt + 30 + 1 + read-write + + + WKUINT + Resume/remote wakeup detected + interrupt + 31 + 1 + read-write + + + + + OTG_HS_GINTMSK + OTG_HS_GINTMSK + OTG_HS interrupt mask register + 0x18 + 32 + 0x0 + + + MMISM + Mode mismatch interrupt + mask + 1 + 1 + read-write + + + OTGINT + OTG interrupt mask + 2 + 1 + read-write + + + SOFM + Start of frame mask + 3 + 1 + read-write + + + RXFLVLM + Receive FIFO nonempty mask + 4 + 1 + read-write + + + NPTXFEM + Nonperiodic TxFIFO empty + mask + 5 + 1 + read-write + + + GINAKEFFM + Global nonperiodic IN NAK effective + mask + 6 + 1 + read-write + + + GONAKEFFM + Global OUT NAK effective + mask + 7 + 1 + read-write + + + ESUSPM + Early suspend mask + 10 + 1 + read-write + + + USBSUSPM + USB suspend mask + 11 + 1 + read-write + + + USBRST + USB reset mask + 12 + 1 + read-write + + + ENUMDNEM + Enumeration done mask + 13 + 1 + read-write + + + ISOODRPM + Isochronous OUT packet dropped interrupt + mask + 14 + 1 + read-write + + + EOPFM + End of periodic frame interrupt + mask + 15 + 1 + read-write + + + EPMISM + Endpoint mismatch interrupt + mask + 17 + 1 + read-write + + + IEPINT + IN endpoints interrupt + mask + 18 + 1 + read-write + + + OEPINT + OUT endpoints interrupt + mask + 19 + 1 + read-write + + + IISOIXFRM + Incomplete isochronous IN transfer + mask + 20 + 1 + read-write + + + PXFRM_IISOOXFRM + Incomplete periodic transfer + mask + 21 + 1 + read-write + + + FSUSPM + Data fetch suspended mask + 22 + 1 + read-write + + + PRTIM + Host port interrupt mask + 24 + 1 + read-only + + + HCIM + Host channels interrupt + mask + 25 + 1 + read-write + + + PTXFEM + Periodic TxFIFO empty mask + 26 + 1 + read-write + + + CIDSCHGM + Connector ID status change + mask + 28 + 1 + read-write + + + DISCINT + Disconnect detected interrupt + mask + 29 + 1 + read-write + + + SRQIM + Session request/new session detected + interrupt mask + 30 + 1 + read-write + + + WUIM + Resume/remote wakeup detected interrupt + mask + 31 + 1 + read-write + + + + + OTG_HS_GRXSTSR_Host + OTG_HS_GRXSTSR_Host + OTG_HS Receive status debug read register + (host mode) + 0x1C + 32 + read-only + 0x0 + + + CHNUM + Channel number + 0 + 4 + + + BCNT + Byte count + 4 + 11 + + + DPID + Data PID + 15 + 2 + + + PKTSTS + Packet status + 17 + 4 + + + + + OTG_HS_GRXSTSP_Host + OTG_HS_GRXSTSP_Host + OTG_HS status read and pop register (host + mode) + 0x20 + 32 + read-only + 0x0 + + + CHNUM + Channel number + 0 + 4 + + + BCNT + Byte count + 4 + 11 + + + DPID + Data PID + 15 + 2 + + + PKTSTS + Packet status + 17 + 4 + + + + + OTG_HS_GRXFSIZ + OTG_HS_GRXFSIZ + OTG_HS Receive FIFO size + register + 0x24 + 32 + read-write + 0x00000200 + + + RXFD + RxFIFO depth + 0 + 16 + + + + + OTG_HS_GNPTXFSIZ_Host + OTG_HS_GNPTXFSIZ_Host + OTG_HS nonperiodic transmit FIFO size + register (host mode) + 0x28 + 32 + read-write + 0x00000200 + + + NPTXFSA + Nonperiodic transmit RAM start + address + 0 + 16 + + + NPTXFD + Nonperiodic TxFIFO depth + 16 + 16 + + + + + OTG_HS_TX0FSIZ_Peripheral + OTG_HS_TX0FSIZ_Peripheral + Endpoint 0 transmit FIFO size (peripheral + mode) + OTG_HS_GNPTXFSIZ_Host + 0x28 + 32 + read-write + 0x00000200 + + + TX0FSA + Endpoint 0 transmit RAM start + address + 0 + 16 + + + TX0FD + Endpoint 0 TxFIFO depth + 16 + 16 + + + + + OTG_HS_GNPTXSTS + OTG_HS_GNPTXSTS + OTG_HS nonperiodic transmit FIFO/queue + status register + 0x2C + 32 + read-only + 0x00080200 + + + NPTXFSAV + Nonperiodic TxFIFO space + available + 0 + 16 + + + NPTQXSAV + Nonperiodic transmit request queue space + available + 16 + 8 + + + NPTXQTOP + Top of the nonperiodic transmit request + queue + 24 + 7 + + + + + OTG_HS_GCCFG + OTG_HS_GCCFG + OTG_HS general core configuration + register + 0x38 + 32 + read-write + 0x0 + + + PWRDWN + Power down + 16 + 1 + + + I2CPADEN + Enable I2C bus connection for the + external I2C PHY interface + 17 + 1 + + + VBUSASEN + Enable the VBUS sensing + device + 18 + 1 + + + VBUSBSEN + Enable the VBUS sensing device + 19 + 1 + + + SOFOUTEN + SOF output enable + 20 + 1 + + + NOVBUSSENS + VBUS sensing disable + option + 21 + 1 + + + + + OTG_HS_CID + OTG_HS_CID + OTG_HS core ID register + 0x3C + 32 + read-write + 0x00001200 + + + PRODUCT_ID + Product ID field + 0 + 32 + + + + + OTG_HS_HPTXFSIZ + OTG_HS_HPTXFSIZ + OTG_HS Host periodic transmit FIFO size + register + 0x100 + 32 + read-write + 0x02000600 + + + PTXSA + Host periodic TxFIFO start + address + 0 + 16 + + + PTXFD + Host periodic TxFIFO depth + 16 + 16 + + + + + OTG_HS_DIEPTXF1 + OTG_HS_DIEPTXF1 + OTG_HS device IN endpoint transmit FIFO size + register + 0x104 + 32 + read-write + 0x02000400 + + + INEPTXSA + IN endpoint FIFOx transmit RAM start + address + 0 + 16 + + + INEPTXFD + IN endpoint TxFIFO depth + 16 + 16 + + + + + OTG_HS_DIEPTXF2 + OTG_HS_DIEPTXF2 + OTG_HS device IN endpoint transmit FIFO size + register + 0x108 + 32 + read-write + 0x02000400 + + + INEPTXSA + IN endpoint FIFOx transmit RAM start + address + 0 + 16 + + + INEPTXFD + IN endpoint TxFIFO depth + 16 + 16 + + + + + OTG_HS_DIEPTXF3 + OTG_HS_DIEPTXF3 + OTG_HS device IN endpoint transmit FIFO size + register + 0x11C + 32 + read-write + 0x02000400 + + + INEPTXSA + IN endpoint FIFOx transmit RAM start + address + 0 + 16 + + + INEPTXFD + IN endpoint TxFIFO depth + 16 + 16 + + + + + OTG_HS_DIEPTXF4 + OTG_HS_DIEPTXF4 + OTG_HS device IN endpoint transmit FIFO size + register + 0x120 + 32 + read-write + 0x02000400 + + + INEPTXSA + IN endpoint FIFOx transmit RAM start + address + 0 + 16 + + + INEPTXFD + IN endpoint TxFIFO depth + 16 + 16 + + + + + OTG_HS_DIEPTXF5 + OTG_HS_DIEPTXF5 + OTG_HS device IN endpoint transmit FIFO size + register + 0x124 + 32 + read-write + 0x02000400 + + + INEPTXSA + IN endpoint FIFOx transmit RAM start + address + 0 + 16 + + + INEPTXFD + IN endpoint TxFIFO depth + 16 + 16 + + + + + OTG_HS_DIEPTXF6 + OTG_HS_DIEPTXF6 + OTG_HS device IN endpoint transmit FIFO size + register + 0x128 + 32 + read-write + 0x02000400 + + + INEPTXSA + IN endpoint FIFOx transmit RAM start + address + 0 + 16 + + + INEPTXFD + IN endpoint TxFIFO depth + 16 + 16 + + + + + OTG_HS_DIEPTXF7 + OTG_HS_DIEPTXF7 + OTG_HS device IN endpoint transmit FIFO size + register + 0x12C + 32 + read-write + 0x02000400 + + + INEPTXSA + IN endpoint FIFOx transmit RAM start + address + 0 + 16 + + + INEPTXFD + IN endpoint TxFIFO depth + 16 + 16 + + + + + OTG_HS_GRXSTSR_Peripheral + OTG_HS_GRXSTSR_Peripheral + OTG_HS Receive status debug read register + (peripheral mode mode) + OTG_HS_GRXSTSR_Host + 0x1C + 32 + read-only + 0x0 + + + EPNUM + Endpoint number + 0 + 4 + + + BCNT + Byte count + 4 + 11 + + + DPID + Data PID + 15 + 2 + + + PKTSTS + Packet status + 17 + 4 + + + FRMNUM + Frame number + 21 + 4 + + + + + OTG_HS_GRXSTSP_Peripheral + OTG_HS_GRXSTSP_Peripheral + OTG_HS status read and pop register + (peripheral mode) + OTG_HS_GRXSTSP_Host + 0x20 + 32 + read-only + 0x0 + + + EPNUM + Endpoint number + 0 + 4 + + + BCNT + Byte count + 4 + 11 + + + DPID + Data PID + 15 + 2 + + + PKTSTS + Packet status + 17 + 4 + + + FRMNUM + Frame number + 21 + 4 + + + + + + + OTG_HS_HOST + USB on the go high speed + USB_OTG_HS + 0x40040400 + + 0x0 + 0x400 + registers + + + + OTG_HS_HCFG + OTG_HS_HCFG + OTG_HS host configuration + register + 0x0 + 32 + 0x0 + + + FSLSPCS + FS/LS PHY clock select + 0 + 2 + read-write + + + FSLSS + FS- and LS-only support + 2 + 1 + read-only + + + + + OTG_HS_HFIR + OTG_HS_HFIR + OTG_HS Host frame interval + register + 0x4 + 32 + read-write + 0x0000EA60 + + + FRIVL + Frame interval + 0 + 16 + + + + + OTG_HS_HFNUM + OTG_HS_HFNUM + OTG_HS host frame number/frame time + remaining register + 0x8 + 32 + read-only + 0x00003FFF + + + FRNUM + Frame number + 0 + 16 + + + FTREM + Frame time remaining + 16 + 16 + + + + + OTG_HS_HPTXSTS + OTG_HS_HPTXSTS + OTG_HS_Host periodic transmit FIFO/queue + status register + 0x10 + 32 + 0x00080100 + + + PTXFSAVL + Periodic transmit data FIFO space + available + 0 + 16 + read-write + + + PTXQSAV + Periodic transmit request queue space + available + 16 + 8 + read-only + + + PTXQTOP + Top of the periodic transmit request + queue + 24 + 8 + read-only + + + + + OTG_HS_HAINT + OTG_HS_HAINT + OTG_HS Host all channels interrupt + register + 0x14 + 32 + read-only + 0x0 + + + HAINT + Channel interrupts + 0 + 16 + + + + + OTG_HS_HAINTMSK + OTG_HS_HAINTMSK + OTG_HS host all channels interrupt mask + register + 0x18 + 32 + read-write + 0x0 + + + HAINTM + Channel interrupt mask + 0 + 16 + + + + + OTG_HS_HPRT + OTG_HS_HPRT + OTG_HS host port control and status + register + 0x40 + 32 + 0x0 + + + PCSTS + Port connect status + 0 + 1 + read-only + + + PCDET + Port connect detected + 1 + 1 + read-write + + + PENA + Port enable + 2 + 1 + read-write + + + PENCHNG + Port enable/disable change + 3 + 1 + read-write + + + POCA + Port overcurrent active + 4 + 1 + read-only + + + POCCHNG + Port overcurrent change + 5 + 1 + read-write + + + PRES + Port resume + 6 + 1 + read-write + + + PSUSP + Port suspend + 7 + 1 + read-write + + + PRST + Port reset + 8 + 1 + read-write + + + PLSTS + Port line status + 10 + 2 + read-only + + + PPWR + Port power + 12 + 1 + read-write + + + PTCTL + Port test control + 13 + 4 + read-write + + + PSPD + Port speed + 17 + 2 + read-only + + + + + OTG_HS_HCCHAR0 + OTG_HS_HCCHAR0 + OTG_HS host channel-0 characteristics + register + 0x100 + 32 + read-write + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MC + Multi Count (MC) / Error Count + (EC) + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + OTG_HS_HCCHAR1 + OTG_HS_HCCHAR1 + OTG_HS host channel-1 characteristics + register + 0x120 + 32 + read-write + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MC + Multi Count (MC) / Error Count + (EC) + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + OTG_HS_HCCHAR2 + OTG_HS_HCCHAR2 + OTG_HS host channel-2 characteristics + register + 0x140 + 32 + read-write + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MC + Multi Count (MC) / Error Count + (EC) + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + OTG_HS_HCCHAR3 + OTG_HS_HCCHAR3 + OTG_HS host channel-3 characteristics + register + 0x160 + 32 + read-write + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MC + Multi Count (MC) / Error Count + (EC) + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + OTG_HS_HCCHAR4 + OTG_HS_HCCHAR4 + OTG_HS host channel-4 characteristics + register + 0x180 + 32 + read-write + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MC + Multi Count (MC) / Error Count + (EC) + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + OTG_HS_HCCHAR5 + OTG_HS_HCCHAR5 + OTG_HS host channel-5 characteristics + register + 0x1A0 + 32 + read-write + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MC + Multi Count (MC) / Error Count + (EC) + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + OTG_HS_HCCHAR6 + OTG_HS_HCCHAR6 + OTG_HS host channel-6 characteristics + register + 0x1C0 + 32 + read-write + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MC + Multi Count (MC) / Error Count + (EC) + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + OTG_HS_HCCHAR7 + OTG_HS_HCCHAR7 + OTG_HS host channel-7 characteristics + register + 0x1E0 + 32 + read-write + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MC + Multi Count (MC) / Error Count + (EC) + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + OTG_HS_HCCHAR8 + OTG_HS_HCCHAR8 + OTG_HS host channel-8 characteristics + register + 0x200 + 32 + read-write + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MC + Multi Count (MC) / Error Count + (EC) + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + OTG_HS_HCCHAR9 + OTG_HS_HCCHAR9 + OTG_HS host channel-9 characteristics + register + 0x220 + 32 + read-write + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MC + Multi Count (MC) / Error Count + (EC) + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + OTG_HS_HCCHAR10 + OTG_HS_HCCHAR10 + OTG_HS host channel-10 characteristics + register + 0x240 + 32 + read-write + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MC + Multi Count (MC) / Error Count + (EC) + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + OTG_HS_HCCHAR11 + OTG_HS_HCCHAR11 + OTG_HS host channel-11 characteristics + register + 0x260 + 32 + read-write + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + + + EPNUM + Endpoint number + 11 + 4 + + + EPDIR + Endpoint direction + 15 + 1 + + + LSDEV + Low-speed device + 17 + 1 + + + EPTYP + Endpoint type + 18 + 2 + + + MC + Multi Count (MC) / Error Count + (EC) + 20 + 2 + + + DAD + Device address + 22 + 7 + + + ODDFRM + Odd frame + 29 + 1 + + + CHDIS + Channel disable + 30 + 1 + + + CHENA + Channel enable + 31 + 1 + + + + + OTG_HS_HCSPLT0 + OTG_HS_HCSPLT0 + OTG_HS host channel-0 split control + register + 0x104 + 32 + read-write + 0x0 + + + PRTADDR + Port address + 0 + 7 + + + HUBADDR + Hub address + 7 + 7 + + + XACTPOS + XACTPOS + 14 + 2 + + + COMPLSPLT + Do complete split + 16 + 1 + + + SPLITEN + Split enable + 31 + 1 + + + + + OTG_HS_HCSPLT1 + OTG_HS_HCSPLT1 + OTG_HS host channel-1 split control + register + 0x124 + 32 + read-write + 0x0 + + + PRTADDR + Port address + 0 + 7 + + + HUBADDR + Hub address + 7 + 7 + + + XACTPOS + XACTPOS + 14 + 2 + + + COMPLSPLT + Do complete split + 16 + 1 + + + SPLITEN + Split enable + 31 + 1 + + + + + OTG_HS_HCSPLT2 + OTG_HS_HCSPLT2 + OTG_HS host channel-2 split control + register + 0x144 + 32 + read-write + 0x0 + + + PRTADDR + Port address + 0 + 7 + + + HUBADDR + Hub address + 7 + 7 + + + XACTPOS + XACTPOS + 14 + 2 + + + COMPLSPLT + Do complete split + 16 + 1 + + + SPLITEN + Split enable + 31 + 1 + + + + + OTG_HS_HCSPLT3 + OTG_HS_HCSPLT3 + OTG_HS host channel-3 split control + register + 0x164 + 32 + read-write + 0x0 + + + PRTADDR + Port address + 0 + 7 + + + HUBADDR + Hub address + 7 + 7 + + + XACTPOS + XACTPOS + 14 + 2 + + + COMPLSPLT + Do complete split + 16 + 1 + + + SPLITEN + Split enable + 31 + 1 + + + + + OTG_HS_HCSPLT4 + OTG_HS_HCSPLT4 + OTG_HS host channel-4 split control + register + 0x184 + 32 + read-write + 0x0 + + + PRTADDR + Port address + 0 + 7 + + + HUBADDR + Hub address + 7 + 7 + + + XACTPOS + XACTPOS + 14 + 2 + + + COMPLSPLT + Do complete split + 16 + 1 + + + SPLITEN + Split enable + 31 + 1 + + + + + OTG_HS_HCSPLT5 + OTG_HS_HCSPLT5 + OTG_HS host channel-5 split control + register + 0x1A4 + 32 + read-write + 0x0 + + + PRTADDR + Port address + 0 + 7 + + + HUBADDR + Hub address + 7 + 7 + + + XACTPOS + XACTPOS + 14 + 2 + + + COMPLSPLT + Do complete split + 16 + 1 + + + SPLITEN + Split enable + 31 + 1 + + + + + OTG_HS_HCSPLT6 + OTG_HS_HCSPLT6 + OTG_HS host channel-6 split control + register + 0x1C4 + 32 + read-write + 0x0 + + + PRTADDR + Port address + 0 + 7 + + + HUBADDR + Hub address + 7 + 7 + + + XACTPOS + XACTPOS + 14 + 2 + + + COMPLSPLT + Do complete split + 16 + 1 + + + SPLITEN + Split enable + 31 + 1 + + + + + OTG_HS_HCSPLT7 + OTG_HS_HCSPLT7 + OTG_HS host channel-7 split control + register + 0x1E4 + 32 + read-write + 0x0 + + + PRTADDR + Port address + 0 + 7 + + + HUBADDR + Hub address + 7 + 7 + + + XACTPOS + XACTPOS + 14 + 2 + + + COMPLSPLT + Do complete split + 16 + 1 + + + SPLITEN + Split enable + 31 + 1 + + + + + OTG_HS_HCSPLT8 + OTG_HS_HCSPLT8 + OTG_HS host channel-8 split control + register + 0x204 + 32 + read-write + 0x0 + + + PRTADDR + Port address + 0 + 7 + + + HUBADDR + Hub address + 7 + 7 + + + XACTPOS + XACTPOS + 14 + 2 + + + COMPLSPLT + Do complete split + 16 + 1 + + + SPLITEN + Split enable + 31 + 1 + + + + + OTG_HS_HCSPLT9 + OTG_HS_HCSPLT9 + OTG_HS host channel-9 split control + register + 0x224 + 32 + read-write + 0x0 + + + PRTADDR + Port address + 0 + 7 + + + HUBADDR + Hub address + 7 + 7 + + + XACTPOS + XACTPOS + 14 + 2 + + + COMPLSPLT + Do complete split + 16 + 1 + + + SPLITEN + Split enable + 31 + 1 + + + + + OTG_HS_HCSPLT10 + OTG_HS_HCSPLT10 + OTG_HS host channel-10 split control + register + 0x244 + 32 + read-write + 0x0 + + + PRTADDR + Port address + 0 + 7 + + + HUBADDR + Hub address + 7 + 7 + + + XACTPOS + XACTPOS + 14 + 2 + + + COMPLSPLT + Do complete split + 16 + 1 + + + SPLITEN + Split enable + 31 + 1 + + + + + OTG_HS_HCSPLT11 + OTG_HS_HCSPLT11 + OTG_HS host channel-11 split control + register + 0x264 + 32 + read-write + 0x0 + + + PRTADDR + Port address + 0 + 7 + + + HUBADDR + Hub address + 7 + 7 + + + XACTPOS + XACTPOS + 14 + 2 + + + COMPLSPLT + Do complete split + 16 + 1 + + + SPLITEN + Split enable + 31 + 1 + + + + + OTG_HS_HCINT0 + OTG_HS_HCINT0 + OTG_HS host channel-11 interrupt + register + 0x108 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + NYET + Response received + interrupt + 6 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + OTG_HS_HCINT1 + OTG_HS_HCINT1 + OTG_HS host channel-1 interrupt + register + 0x128 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + NYET + Response received + interrupt + 6 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + OTG_HS_HCINT2 + OTG_HS_HCINT2 + OTG_HS host channel-2 interrupt + register + 0x148 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + NYET + Response received + interrupt + 6 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + OTG_HS_HCINT3 + OTG_HS_HCINT3 + OTG_HS host channel-3 interrupt + register + 0x168 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + NYET + Response received + interrupt + 6 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + OTG_HS_HCINT4 + OTG_HS_HCINT4 + OTG_HS host channel-4 interrupt + register + 0x188 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + NYET + Response received + interrupt + 6 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + OTG_HS_HCINT5 + OTG_HS_HCINT5 + OTG_HS host channel-5 interrupt + register + 0x1A8 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + NYET + Response received + interrupt + 6 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + OTG_HS_HCINT6 + OTG_HS_HCINT6 + OTG_HS host channel-6 interrupt + register + 0x1C8 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + NYET + Response received + interrupt + 6 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + OTG_HS_HCINT7 + OTG_HS_HCINT7 + OTG_HS host channel-7 interrupt + register + 0x1E8 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + NYET + Response received + interrupt + 6 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + OTG_HS_HCINT8 + OTG_HS_HCINT8 + OTG_HS host channel-8 interrupt + register + 0x208 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + NYET + Response received + interrupt + 6 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + OTG_HS_HCINT9 + OTG_HS_HCINT9 + OTG_HS host channel-9 interrupt + register + 0x228 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + NYET + Response received + interrupt + 6 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + OTG_HS_HCINT10 + OTG_HS_HCINT10 + OTG_HS host channel-10 interrupt + register + 0x248 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + NYET + Response received + interrupt + 6 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + OTG_HS_HCINT11 + OTG_HS_HCINT11 + OTG_HS host channel-11 interrupt + register + 0x268 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + 0 + 1 + + + CHH + Channel halted + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALL + STALL response received + interrupt + 3 + 1 + + + NAK + NAK response received + interrupt + 4 + 1 + + + ACK + ACK response received/transmitted + interrupt + 5 + 1 + + + NYET + Response received + interrupt + 6 + 1 + + + TXERR + Transaction error + 7 + 1 + + + BBERR + Babble error + 8 + 1 + + + FRMOR + Frame overrun + 9 + 1 + + + DTERR + Data toggle error + 10 + 1 + + + + + OTG_HS_HCINTMSK0 + OTG_HS_HCINTMSK0 + OTG_HS host channel-11 interrupt mask + register + 0x10C + 32 + read-write + 0x0 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + OTG_HS_HCINTMSK1 + OTG_HS_HCINTMSK1 + OTG_HS host channel-1 interrupt mask + register + 0x12C + 32 + read-write + 0x0 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + OTG_HS_HCINTMSK2 + OTG_HS_HCINTMSK2 + OTG_HS host channel-2 interrupt mask + register + 0x14C + 32 + read-write + 0x0 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + OTG_HS_HCINTMSK3 + OTG_HS_HCINTMSK3 + OTG_HS host channel-3 interrupt mask + register + 0x16C + 32 + read-write + 0x0 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + OTG_HS_HCINTMSK4 + OTG_HS_HCINTMSK4 + OTG_HS host channel-4 interrupt mask + register + 0x18C + 32 + read-write + 0x0 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + OTG_HS_HCINTMSK5 + OTG_HS_HCINTMSK5 + OTG_HS host channel-5 interrupt mask + register + 0x1AC + 32 + read-write + 0x0 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + OTG_HS_HCINTMSK6 + OTG_HS_HCINTMSK6 + OTG_HS host channel-6 interrupt mask + register + 0x1CC + 32 + read-write + 0x0 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + OTG_HS_HCINTMSK7 + OTG_HS_HCINTMSK7 + OTG_HS host channel-7 interrupt mask + register + 0x1EC + 32 + read-write + 0x0 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + OTG_HS_HCINTMSK8 + OTG_HS_HCINTMSK8 + OTG_HS host channel-8 interrupt mask + register + 0x20C + 32 + read-write + 0x0 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + OTG_HS_HCINTMSK9 + OTG_HS_HCINTMSK9 + OTG_HS host channel-9 interrupt mask + register + 0x22C + 32 + read-write + 0x0 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + OTG_HS_HCINTMSK10 + OTG_HS_HCINTMSK10 + OTG_HS host channel-10 interrupt mask + register + 0x24C + 32 + read-write + 0x0 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + OTG_HS_HCINTMSK11 + OTG_HS_HCINTMSK11 + OTG_HS host channel-11 interrupt mask + register + 0x26C + 32 + read-write + 0x0 + + + XFRCM + Transfer completed mask + 0 + 1 + + + CHHM + Channel halted mask + 1 + 1 + + + AHBERR + AHB error + 2 + 1 + + + STALLM + STALL response received interrupt + mask + 3 + 1 + + + NAKM + NAK response received interrupt + mask + 4 + 1 + + + ACKM + ACK response received/transmitted + interrupt mask + 5 + 1 + + + NYET + response received interrupt + mask + 6 + 1 + + + TXERRM + Transaction error mask + 7 + 1 + + + BBERRM + Babble error mask + 8 + 1 + + + FRMORM + Frame overrun mask + 9 + 1 + + + DTERRM + Data toggle error mask + 10 + 1 + + + + + OTG_HS_HCTSIZ0 + OTG_HS_HCTSIZ0 + OTG_HS host channel-11 transfer size + register + 0x110 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + OTG_HS_HCTSIZ1 + OTG_HS_HCTSIZ1 + OTG_HS host channel-1 transfer size + register + 0x130 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + OTG_HS_HCTSIZ2 + OTG_HS_HCTSIZ2 + OTG_HS host channel-2 transfer size + register + 0x150 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + OTG_HS_HCTSIZ3 + OTG_HS_HCTSIZ3 + OTG_HS host channel-3 transfer size + register + 0x170 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + OTG_HS_HCTSIZ4 + OTG_HS_HCTSIZ4 + OTG_HS host channel-4 transfer size + register + 0x190 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + OTG_HS_HCTSIZ5 + OTG_HS_HCTSIZ5 + OTG_HS host channel-5 transfer size + register + 0x1B0 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + OTG_HS_HCTSIZ6 + OTG_HS_HCTSIZ6 + OTG_HS host channel-6 transfer size + register + 0x1D0 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + OTG_HS_HCTSIZ7 + OTG_HS_HCTSIZ7 + OTG_HS host channel-7 transfer size + register + 0x1F0 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + OTG_HS_HCTSIZ8 + OTG_HS_HCTSIZ8 + OTG_HS host channel-8 transfer size + register + 0x210 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + OTG_HS_HCTSIZ9 + OTG_HS_HCTSIZ9 + OTG_HS host channel-9 transfer size + register + 0x230 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + OTG_HS_HCTSIZ10 + OTG_HS_HCTSIZ10 + OTG_HS host channel-10 transfer size + register + 0x250 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + OTG_HS_HCTSIZ11 + OTG_HS_HCTSIZ11 + OTG_HS host channel-11 transfer size + register + 0x270 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + DPID + Data PID + 29 + 2 + + + + + OTG_HS_HCDMA0 + OTG_HS_HCDMA0 + OTG_HS host channel-0 DMA address + register + 0x114 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_HCDMA1 + OTG_HS_HCDMA1 + OTG_HS host channel-1 DMA address + register + 0x134 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_HCDMA2 + OTG_HS_HCDMA2 + OTG_HS host channel-2 DMA address + register + 0x154 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_HCDMA3 + OTG_HS_HCDMA3 + OTG_HS host channel-3 DMA address + register + 0x174 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_HCDMA4 + OTG_HS_HCDMA4 + OTG_HS host channel-4 DMA address + register + 0x194 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_HCDMA5 + OTG_HS_HCDMA5 + OTG_HS host channel-5 DMA address + register + 0x1B4 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_HCDMA6 + OTG_HS_HCDMA6 + OTG_HS host channel-6 DMA address + register + 0x1D4 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_HCDMA7 + OTG_HS_HCDMA7 + OTG_HS host channel-7 DMA address + register + 0x1F4 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_HCDMA8 + OTG_HS_HCDMA8 + OTG_HS host channel-8 DMA address + register + 0x214 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_HCDMA9 + OTG_HS_HCDMA9 + OTG_HS host channel-9 DMA address + register + 0x234 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_HCDMA10 + OTG_HS_HCDMA10 + OTG_HS host channel-10 DMA address + register + 0x254 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_HCDMA11 + OTG_HS_HCDMA11 + OTG_HS host channel-11 DMA address + register + 0x274 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + + + OTG_HS_DEVICE + USB on the go high speed + USB_OTG_HS + 0x40040800 + + 0x0 + 0x400 + registers + + + + OTG_HS_DCFG + OTG_HS_DCFG + OTG_HS device configuration + register + 0x0 + 32 + read-write + 0x02200000 + + + DSPD + Device speed + 0 + 2 + + + NZLSOHSK + Nonzero-length status OUT + handshake + 2 + 1 + + + DAD + Device address + 4 + 7 + + + PFIVL + Periodic (micro)frame + interval + 11 + 2 + + + PERSCHIVL + Periodic scheduling + interval + 24 + 2 + + + + + OTG_HS_DCTL + OTG_HS_DCTL + OTG_HS device control register + 0x4 + 32 + 0x0 + + + RWUSIG + Remote wakeup signaling + 0 + 1 + read-write + + + SDIS + Soft disconnect + 1 + 1 + read-write + + + GINSTS + Global IN NAK status + 2 + 1 + read-only + + + GONSTS + Global OUT NAK status + 3 + 1 + read-only + + + TCTL + Test control + 4 + 3 + read-write + + + SGINAK + Set global IN NAK + 7 + 1 + write-only + + + CGINAK + Clear global IN NAK + 8 + 1 + write-only + + + SGONAK + Set global OUT NAK + 9 + 1 + write-only + + + CGONAK + Clear global OUT NAK + 10 + 1 + write-only + + + POPRGDNE + Power-on programming done + 11 + 1 + read-write + + + + + OTG_HS_DSTS + OTG_HS_DSTS + OTG_HS device status register + 0x8 + 32 + read-only + 0x00000010 + + + SUSPSTS + Suspend status + 0 + 1 + + + ENUMSPD + Enumerated speed + 1 + 2 + + + EERR + Erratic error + 3 + 1 + + + FNSOF + Frame number of the received + SOF + 8 + 14 + + + + + OTG_HS_DIEPMSK + OTG_HS_DIEPMSK + OTG_HS device IN endpoint common interrupt + mask register + 0x10 + 32 + read-write + 0x0 + + + XFRCM + Transfer completed interrupt + mask + 0 + 1 + + + EPDM + Endpoint disabled interrupt + mask + 1 + 1 + + + TOM + Timeout condition mask (nonisochronous + endpoints) + 3 + 1 + + + ITTXFEMSK + IN token received when TxFIFO empty + mask + 4 + 1 + + + INEPNMM + IN token received with EP mismatch + mask + 5 + 1 + + + INEPNEM + IN endpoint NAK effective + mask + 6 + 1 + + + TXFURM + FIFO underrun mask + 8 + 1 + + + BIM + BNA interrupt mask + 9 + 1 + + + + + OTG_HS_DOEPMSK + OTG_HS_DOEPMSK + OTG_HS device OUT endpoint common interrupt + mask register + 0x14 + 32 + read-write + 0x0 + + + XFRCM + Transfer completed interrupt + mask + 0 + 1 + + + EPDM + Endpoint disabled interrupt + mask + 1 + 1 + + + STUPM + SETUP phase done mask + 3 + 1 + + + OTEPDM + OUT token received when endpoint + disabled mask + 4 + 1 + + + B2BSTUP + Back-to-back SETUP packets received + mask + 6 + 1 + + + OPEM + OUT packet error mask + 8 + 1 + + + BOIM + BNA interrupt mask + 9 + 1 + + + + + OTG_HS_DAINT + OTG_HS_DAINT + OTG_HS device all endpoints interrupt + register + 0x18 + 32 + read-only + 0x0 + + + IEPINT + IN endpoint interrupt bits + 0 + 16 + + + OEPINT + OUT endpoint interrupt + bits + 16 + 16 + + + + + OTG_HS_DAINTMSK + OTG_HS_DAINTMSK + OTG_HS all endpoints interrupt mask + register + 0x1C + 32 + read-write + 0x0 + + + IEPM + IN EP interrupt mask bits + 0 + 16 + + + OEPM + OUT EP interrupt mask bits + 16 + 16 + + + + + OTG_HS_DVBUSDIS + OTG_HS_DVBUSDIS + OTG_HS device VBUS discharge time + register + 0x28 + 32 + read-write + 0x000017D7 + + + VBUSDT + Device VBUS discharge time + 0 + 16 + + + + + OTG_HS_DVBUSPULSE + OTG_HS_DVBUSPULSE + OTG_HS device VBUS pulsing time + register + 0x2C + 32 + read-write + 0x000005B8 + + + DVBUSP + Device VBUS pulsing time + 0 + 12 + + + + + OTG_HS_DTHRCTL + OTG_HS_DTHRCTL + OTG_HS Device threshold control + register + 0x30 + 32 + read-write + 0x0 + + + NONISOTHREN + Nonisochronous IN endpoints threshold + enable + 0 + 1 + + + ISOTHREN + ISO IN endpoint threshold + enable + 1 + 1 + + + TXTHRLEN + Transmit threshold length + 2 + 9 + + + RXTHREN + Receive threshold enable + 16 + 1 + + + RXTHRLEN + Receive threshold length + 17 + 9 + + + ARPEN + Arbiter parking enable + 27 + 1 + + + + + OTG_HS_DIEPEMPMSK + OTG_HS_DIEPEMPMSK + OTG_HS device IN endpoint FIFO empty + interrupt mask register + 0x34 + 32 + read-write + 0x0 + + + INEPTXFEM + IN EP Tx FIFO empty interrupt mask + bits + 0 + 16 + + + + + OTG_HS_DEACHINT + OTG_HS_DEACHINT + OTG_HS device each endpoint interrupt + register + 0x38 + 32 + read-write + 0x0 + + + IEP1INT + IN endpoint 1interrupt bit + 1 + 1 + + + OEP1INT + OUT endpoint 1 interrupt + bit + 17 + 1 + + + + + OTG_HS_DEACHINTMSK + OTG_HS_DEACHINTMSK + OTG_HS device each endpoint interrupt + register mask + 0x3C + 32 + read-write + 0x0 + + + IEP1INTM + IN Endpoint 1 interrupt mask + bit + 1 + 1 + + + OEP1INTM + OUT Endpoint 1 interrupt mask + bit + 17 + 1 + + + + + OTG_HS_DIEPEACHMSK1 + OTG_HS_DIEPEACHMSK1 + OTG_HS device each in endpoint-1 interrupt + register + 0x40 + 32 + read-write + 0x0 + + + XFRCM + Transfer completed interrupt + mask + 0 + 1 + + + EPDM + Endpoint disabled interrupt + mask + 1 + 1 + + + TOM + Timeout condition mask (nonisochronous + endpoints) + 3 + 1 + + + ITTXFEMSK + IN token received when TxFIFO empty + mask + 4 + 1 + + + INEPNMM + IN token received with EP mismatch + mask + 5 + 1 + + + INEPNEM + IN endpoint NAK effective + mask + 6 + 1 + + + TXFURM + FIFO underrun mask + 8 + 1 + + + BIM + BNA interrupt mask + 9 + 1 + + + NAKM + NAK interrupt mask + 13 + 1 + + + + + OTG_HS_DOEPEACHMSK1 + OTG_HS_DOEPEACHMSK1 + OTG_HS device each OUT endpoint-1 interrupt + register + 0x80 + 32 + read-write + 0x0 + + + XFRCM + Transfer completed interrupt + mask + 0 + 1 + + + EPDM + Endpoint disabled interrupt + mask + 1 + 1 + + + TOM + Timeout condition mask + 3 + 1 + + + ITTXFEMSK + IN token received when TxFIFO empty + mask + 4 + 1 + + + INEPNMM + IN token received with EP mismatch + mask + 5 + 1 + + + INEPNEM + IN endpoint NAK effective + mask + 6 + 1 + + + TXFURM + OUT packet error mask + 8 + 1 + + + BIM + BNA interrupt mask + 9 + 1 + + + BERRM + Bubble error interrupt + mask + 12 + 1 + + + NAKM + NAK interrupt mask + 13 + 1 + + + NYETM + NYET interrupt mask + 14 + 1 + + + + + OTG_HS_DIEPCTL0 + OTG_HS_DIEPCTL0 + OTG device endpoint-0 control + register + 0x100 + 32 + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + read-write + + + USBAEP + USB active endpoint + 15 + 1 + read-write + + + EONUM_DPID + Even/odd frame + 16 + 1 + read-only + + + NAKSTS + NAK status + 17 + 1 + read-only + + + EPTYP + Endpoint type + 18 + 2 + read-write + + + Stall + STALL handshake + 21 + 1 + read-write + + + TXFNUM + TxFIFO number + 22 + 4 + read-write + + + CNAK + Clear NAK + 26 + 1 + write-only + + + SNAK + Set NAK + 27 + 1 + write-only + + + SD0PID_SEVNFRM + Set DATA0 PID + 28 + 1 + write-only + + + SODDFRM + Set odd frame + 29 + 1 + write-only + + + EPDIS + Endpoint disable + 30 + 1 + read-write + + + EPENA + Endpoint enable + 31 + 1 + read-write + + + + + OTG_HS_DIEPCTL1 + OTG_HS_DIEPCTL1 + OTG device endpoint-1 control + register + 0x120 + 32 + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + read-write + + + USBAEP + USB active endpoint + 15 + 1 + read-write + + + EONUM_DPID + Even/odd frame + 16 + 1 + read-only + + + NAKSTS + NAK status + 17 + 1 + read-only + + + EPTYP + Endpoint type + 18 + 2 + read-write + + + Stall + STALL handshake + 21 + 1 + read-write + + + TXFNUM + TxFIFO number + 22 + 4 + read-write + + + CNAK + Clear NAK + 26 + 1 + write-only + + + SNAK + Set NAK + 27 + 1 + write-only + + + SD0PID_SEVNFRM + Set DATA0 PID + 28 + 1 + write-only + + + SODDFRM + Set odd frame + 29 + 1 + write-only + + + EPDIS + Endpoint disable + 30 + 1 + read-write + + + EPENA + Endpoint enable + 31 + 1 + read-write + + + + + OTG_HS_DIEPCTL2 + OTG_HS_DIEPCTL2 + OTG device endpoint-2 control + register + 0x140 + 32 + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + read-write + + + USBAEP + USB active endpoint + 15 + 1 + read-write + + + EONUM_DPID + Even/odd frame + 16 + 1 + read-only + + + NAKSTS + NAK status + 17 + 1 + read-only + + + EPTYP + Endpoint type + 18 + 2 + read-write + + + Stall + STALL handshake + 21 + 1 + read-write + + + TXFNUM + TxFIFO number + 22 + 4 + read-write + + + CNAK + Clear NAK + 26 + 1 + write-only + + + SNAK + Set NAK + 27 + 1 + write-only + + + SD0PID_SEVNFRM + Set DATA0 PID + 28 + 1 + write-only + + + SODDFRM + Set odd frame + 29 + 1 + write-only + + + EPDIS + Endpoint disable + 30 + 1 + read-write + + + EPENA + Endpoint enable + 31 + 1 + read-write + + + + + OTG_HS_DIEPCTL3 + OTG_HS_DIEPCTL3 + OTG device endpoint-3 control + register + 0x160 + 32 + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + read-write + + + USBAEP + USB active endpoint + 15 + 1 + read-write + + + EONUM_DPID + Even/odd frame + 16 + 1 + read-only + + + NAKSTS + NAK status + 17 + 1 + read-only + + + EPTYP + Endpoint type + 18 + 2 + read-write + + + Stall + STALL handshake + 21 + 1 + read-write + + + TXFNUM + TxFIFO number + 22 + 4 + read-write + + + CNAK + Clear NAK + 26 + 1 + write-only + + + SNAK + Set NAK + 27 + 1 + write-only + + + SD0PID_SEVNFRM + Set DATA0 PID + 28 + 1 + write-only + + + SODDFRM + Set odd frame + 29 + 1 + write-only + + + EPDIS + Endpoint disable + 30 + 1 + read-write + + + EPENA + Endpoint enable + 31 + 1 + read-write + + + + + OTG_HS_DIEPCTL4 + OTG_HS_DIEPCTL4 + OTG device endpoint-4 control + register + 0x180 + 32 + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + read-write + + + USBAEP + USB active endpoint + 15 + 1 + read-write + + + EONUM_DPID + Even/odd frame + 16 + 1 + read-only + + + NAKSTS + NAK status + 17 + 1 + read-only + + + EPTYP + Endpoint type + 18 + 2 + read-write + + + Stall + STALL handshake + 21 + 1 + read-write + + + TXFNUM + TxFIFO number + 22 + 4 + read-write + + + CNAK + Clear NAK + 26 + 1 + write-only + + + SNAK + Set NAK + 27 + 1 + write-only + + + SD0PID_SEVNFRM + Set DATA0 PID + 28 + 1 + write-only + + + SODDFRM + Set odd frame + 29 + 1 + write-only + + + EPDIS + Endpoint disable + 30 + 1 + read-write + + + EPENA + Endpoint enable + 31 + 1 + read-write + + + + + OTG_HS_DIEPCTL5 + OTG_HS_DIEPCTL5 + OTG device endpoint-5 control + register + 0x1A0 + 32 + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + read-write + + + USBAEP + USB active endpoint + 15 + 1 + read-write + + + EONUM_DPID + Even/odd frame + 16 + 1 + read-only + + + NAKSTS + NAK status + 17 + 1 + read-only + + + EPTYP + Endpoint type + 18 + 2 + read-write + + + Stall + STALL handshake + 21 + 1 + read-write + + + TXFNUM + TxFIFO number + 22 + 4 + read-write + + + CNAK + Clear NAK + 26 + 1 + write-only + + + SNAK + Set NAK + 27 + 1 + write-only + + + SD0PID_SEVNFRM + Set DATA0 PID + 28 + 1 + write-only + + + SODDFRM + Set odd frame + 29 + 1 + write-only + + + EPDIS + Endpoint disable + 30 + 1 + read-write + + + EPENA + Endpoint enable + 31 + 1 + read-write + + + + + OTG_HS_DIEPCTL6 + OTG_HS_DIEPCTL6 + OTG device endpoint-6 control + register + 0x1C0 + 32 + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + read-write + + + USBAEP + USB active endpoint + 15 + 1 + read-write + + + EONUM_DPID + Even/odd frame + 16 + 1 + read-only + + + NAKSTS + NAK status + 17 + 1 + read-only + + + EPTYP + Endpoint type + 18 + 2 + read-write + + + Stall + STALL handshake + 21 + 1 + read-write + + + TXFNUM + TxFIFO number + 22 + 4 + read-write + + + CNAK + Clear NAK + 26 + 1 + write-only + + + SNAK + Set NAK + 27 + 1 + write-only + + + SD0PID_SEVNFRM + Set DATA0 PID + 28 + 1 + write-only + + + SODDFRM + Set odd frame + 29 + 1 + write-only + + + EPDIS + Endpoint disable + 30 + 1 + read-write + + + EPENA + Endpoint enable + 31 + 1 + read-write + + + + + OTG_HS_DIEPCTL7 + OTG_HS_DIEPCTL7 + OTG device endpoint-7 control + register + 0x1E0 + 32 + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + read-write + + + USBAEP + USB active endpoint + 15 + 1 + read-write + + + EONUM_DPID + Even/odd frame + 16 + 1 + read-only + + + NAKSTS + NAK status + 17 + 1 + read-only + + + EPTYP + Endpoint type + 18 + 2 + read-write + + + Stall + STALL handshake + 21 + 1 + read-write + + + TXFNUM + TxFIFO number + 22 + 4 + read-write + + + CNAK + Clear NAK + 26 + 1 + write-only + + + SNAK + Set NAK + 27 + 1 + write-only + + + SD0PID_SEVNFRM + Set DATA0 PID + 28 + 1 + write-only + + + SODDFRM + Set odd frame + 29 + 1 + write-only + + + EPDIS + Endpoint disable + 30 + 1 + read-write + + + EPENA + Endpoint enable + 31 + 1 + read-write + + + + + OTG_HS_DIEPINT0 + OTG_HS_DIEPINT0 + OTG device endpoint-0 interrupt + register + 0x108 + 32 + 0x00000080 + + + XFRC + Transfer completed + interrupt + 0 + 1 + read-write + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + read-write + + + TOC + Timeout condition + 3 + 1 + read-write + + + ITTXFE + IN token received when TxFIFO is + empty + 4 + 1 + read-write + + + INEPNE + IN endpoint NAK effective + 6 + 1 + read-write + + + TXFE + Transmit FIFO empty + 7 + 1 + read-only + + + TXFIFOUDRN + Transmit Fifo Underrun + 8 + 1 + read-write + + + BNA + Buffer not available + interrupt + 9 + 1 + read-write + + + PKTDRPSTS + Packet dropped status + 11 + 1 + read-write + + + BERR + Babble error interrupt + 12 + 1 + read-write + + + NAK + NAK interrupt + 13 + 1 + read-write + + + + + OTG_HS_DIEPINT1 + OTG_HS_DIEPINT1 + OTG device endpoint-1 interrupt + register + 0x128 + 32 + 0x0 + + + XFRC + Transfer completed + interrupt + 0 + 1 + read-write + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + read-write + + + TOC + Timeout condition + 3 + 1 + read-write + + + ITTXFE + IN token received when TxFIFO is + empty + 4 + 1 + read-write + + + INEPNE + IN endpoint NAK effective + 6 + 1 + read-write + + + TXFE + Transmit FIFO empty + 7 + 1 + read-only + + + TXFIFOUDRN + Transmit Fifo Underrun + 8 + 1 + read-write + + + BNA + Buffer not available + interrupt + 9 + 1 + read-write + + + PKTDRPSTS + Packet dropped status + 11 + 1 + read-write + + + BERR + Babble error interrupt + 12 + 1 + read-write + + + NAK + NAK interrupt + 13 + 1 + read-write + + + + + OTG_HS_DIEPINT2 + OTG_HS_DIEPINT2 + OTG device endpoint-2 interrupt + register + 0x148 + 32 + 0x0 + + + XFRC + Transfer completed + interrupt + 0 + 1 + read-write + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + read-write + + + TOC + Timeout condition + 3 + 1 + read-write + + + ITTXFE + IN token received when TxFIFO is + empty + 4 + 1 + read-write + + + INEPNE + IN endpoint NAK effective + 6 + 1 + read-write + + + TXFE + Transmit FIFO empty + 7 + 1 + read-only + + + TXFIFOUDRN + Transmit Fifo Underrun + 8 + 1 + read-write + + + BNA + Buffer not available + interrupt + 9 + 1 + read-write + + + PKTDRPSTS + Packet dropped status + 11 + 1 + read-write + + + BERR + Babble error interrupt + 12 + 1 + read-write + + + NAK + NAK interrupt + 13 + 1 + read-write + + + + + OTG_HS_DIEPINT3 + OTG_HS_DIEPINT3 + OTG device endpoint-3 interrupt + register + 0x168 + 32 + 0x0 + + + XFRC + Transfer completed + interrupt + 0 + 1 + read-write + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + read-write + + + TOC + Timeout condition + 3 + 1 + read-write + + + ITTXFE + IN token received when TxFIFO is + empty + 4 + 1 + read-write + + + INEPNE + IN endpoint NAK effective + 6 + 1 + read-write + + + TXFE + Transmit FIFO empty + 7 + 1 + read-only + + + TXFIFOUDRN + Transmit Fifo Underrun + 8 + 1 + read-write + + + BNA + Buffer not available + interrupt + 9 + 1 + read-write + + + PKTDRPSTS + Packet dropped status + 11 + 1 + read-write + + + BERR + Babble error interrupt + 12 + 1 + read-write + + + NAK + NAK interrupt + 13 + 1 + read-write + + + + + OTG_HS_DIEPINT4 + OTG_HS_DIEPINT4 + OTG device endpoint-4 interrupt + register + 0x188 + 32 + 0x0 + + + XFRC + Transfer completed + interrupt + 0 + 1 + read-write + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + read-write + + + TOC + Timeout condition + 3 + 1 + read-write + + + ITTXFE + IN token received when TxFIFO is + empty + 4 + 1 + read-write + + + INEPNE + IN endpoint NAK effective + 6 + 1 + read-write + + + TXFE + Transmit FIFO empty + 7 + 1 + read-only + + + TXFIFOUDRN + Transmit Fifo Underrun + 8 + 1 + read-write + + + BNA + Buffer not available + interrupt + 9 + 1 + read-write + + + PKTDRPSTS + Packet dropped status + 11 + 1 + read-write + + + BERR + Babble error interrupt + 12 + 1 + read-write + + + NAK + NAK interrupt + 13 + 1 + read-write + + + + + OTG_HS_DIEPINT5 + OTG_HS_DIEPINT5 + OTG device endpoint-5 interrupt + register + 0x1A8 + 32 + 0x0 + + + XFRC + Transfer completed + interrupt + 0 + 1 + read-write + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + read-write + + + TOC + Timeout condition + 3 + 1 + read-write + + + ITTXFE + IN token received when TxFIFO is + empty + 4 + 1 + read-write + + + INEPNE + IN endpoint NAK effective + 6 + 1 + read-write + + + TXFE + Transmit FIFO empty + 7 + 1 + read-only + + + TXFIFOUDRN + Transmit Fifo Underrun + 8 + 1 + read-write + + + BNA + Buffer not available + interrupt + 9 + 1 + read-write + + + PKTDRPSTS + Packet dropped status + 11 + 1 + read-write + + + BERR + Babble error interrupt + 12 + 1 + read-write + + + NAK + NAK interrupt + 13 + 1 + read-write + + + + + OTG_HS_DIEPINT6 + OTG_HS_DIEPINT6 + OTG device endpoint-6 interrupt + register + 0x1C8 + 32 + 0x0 + + + XFRC + Transfer completed + interrupt + 0 + 1 + read-write + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + read-write + + + TOC + Timeout condition + 3 + 1 + read-write + + + ITTXFE + IN token received when TxFIFO is + empty + 4 + 1 + read-write + + + INEPNE + IN endpoint NAK effective + 6 + 1 + read-write + + + TXFE + Transmit FIFO empty + 7 + 1 + read-only + + + TXFIFOUDRN + Transmit Fifo Underrun + 8 + 1 + read-write + + + BNA + Buffer not available + interrupt + 9 + 1 + read-write + + + PKTDRPSTS + Packet dropped status + 11 + 1 + read-write + + + BERR + Babble error interrupt + 12 + 1 + read-write + + + NAK + NAK interrupt + 13 + 1 + read-write + + + + + OTG_HS_DIEPINT7 + OTG_HS_DIEPINT7 + OTG device endpoint-7 interrupt + register + 0x1E8 + 32 + 0x0 + + + XFRC + Transfer completed + interrupt + 0 + 1 + read-write + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + read-write + + + TOC + Timeout condition + 3 + 1 + read-write + + + ITTXFE + IN token received when TxFIFO is + empty + 4 + 1 + read-write + + + INEPNE + IN endpoint NAK effective + 6 + 1 + read-write + + + TXFE + Transmit FIFO empty + 7 + 1 + read-only + + + TXFIFOUDRN + Transmit Fifo Underrun + 8 + 1 + read-write + + + BNA + Buffer not available + interrupt + 9 + 1 + read-write + + + PKTDRPSTS + Packet dropped status + 11 + 1 + read-write + + + BERR + Babble error interrupt + 12 + 1 + read-write + + + NAK + NAK interrupt + 13 + 1 + read-write + + + + + OTG_HS_DIEPTSIZ0 + OTG_HS_DIEPTSIZ0 + OTG_HS device IN endpoint 0 transfer size + register + 0x110 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 7 + + + PKTCNT + Packet count + 19 + 2 + + + + + OTG_HS_DIEPDMA1 + OTG_HS_DIEPDMA1 + OTG_HS device endpoint-1 DMA address + register + 0x114 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_DIEPDMA2 + OTG_HS_DIEPDMA2 + OTG_HS device endpoint-2 DMA address + register + 0x134 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_DIEPDMA3 + OTG_HS_DIEPDMA3 + OTG_HS device endpoint-3 DMA address + register + 0x154 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_DIEPDMA4 + OTG_HS_DIEPDMA4 + OTG_HS device endpoint-4 DMA address + register + 0x174 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_DIEPDMA5 + OTG_HS_DIEPDMA5 + OTG_HS device endpoint-5 DMA address + register + 0x194 + 32 + read-write + 0x0 + + + DMAADDR + DMA address + 0 + 32 + + + + + OTG_HS_DTXFSTS0 + OTG_HS_DTXFSTS0 + OTG_HS device IN endpoint transmit FIFO + status register + 0x118 + 32 + read-only + 0x0 + + + INEPTFSAV + IN endpoint TxFIFO space + avail + 0 + 16 + + + + + OTG_HS_DTXFSTS1 + OTG_HS_DTXFSTS1 + OTG_HS device IN endpoint transmit FIFO + status register + 0x138 + 32 + read-only + 0x0 + + + INEPTFSAV + IN endpoint TxFIFO space + avail + 0 + 16 + + + + + OTG_HS_DTXFSTS2 + OTG_HS_DTXFSTS2 + OTG_HS device IN endpoint transmit FIFO + status register + 0x158 + 32 + read-only + 0x0 + + + INEPTFSAV + IN endpoint TxFIFO space + avail + 0 + 16 + + + + + OTG_HS_DTXFSTS3 + OTG_HS_DTXFSTS3 + OTG_HS device IN endpoint transmit FIFO + status register + 0x178 + 32 + read-only + 0x0 + + + INEPTFSAV + IN endpoint TxFIFO space + avail + 0 + 16 + + + + + OTG_HS_DTXFSTS4 + OTG_HS_DTXFSTS4 + OTG_HS device IN endpoint transmit FIFO + status register + 0x198 + 32 + read-only + 0x0 + + + INEPTFSAV + IN endpoint TxFIFO space + avail + 0 + 16 + + + + + OTG_HS_DTXFSTS5 + OTG_HS_DTXFSTS5 + OTG_HS device IN endpoint transmit FIFO + status register + 0x1B8 + 32 + read-only + 0x0 + + + INEPTFSAV + IN endpoint TxFIFO space + avail + 0 + 16 + + + + + OTG_HS_DIEPTSIZ1 + OTG_HS_DIEPTSIZ1 + OTG_HS device endpoint transfer size + register + 0x130 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + MCNT + Multi count + 29 + 2 + + + + + OTG_HS_DIEPTSIZ2 + OTG_HS_DIEPTSIZ2 + OTG_HS device endpoint transfer size + register + 0x150 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + MCNT + Multi count + 29 + 2 + + + + + OTG_HS_DIEPTSIZ3 + OTG_HS_DIEPTSIZ3 + OTG_HS device endpoint transfer size + register + 0x170 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + MCNT + Multi count + 29 + 2 + + + + + OTG_HS_DIEPTSIZ4 + OTG_HS_DIEPTSIZ4 + OTG_HS device endpoint transfer size + register + 0x190 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + MCNT + Multi count + 29 + 2 + + + + + OTG_HS_DIEPTSIZ5 + OTG_HS_DIEPTSIZ5 + OTG_HS device endpoint transfer size + register + 0x1B0 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + MCNT + Multi count + 29 + 2 + + + + + OTG_HS_DOEPCTL0 + OTG_HS_DOEPCTL0 + OTG_HS device control OUT endpoint 0 control + register + 0x300 + 32 + 0x00008000 + + + MPSIZ + Maximum packet size + 0 + 2 + read-only + + + USBAEP + USB active endpoint + 15 + 1 + read-only + + + NAKSTS + NAK status + 17 + 1 + read-only + + + EPTYP + Endpoint type + 18 + 2 + read-only + + + SNPM + Snoop mode + 20 + 1 + read-write + + + Stall + STALL handshake + 21 + 1 + read-write + + + CNAK + Clear NAK + 26 + 1 + write-only + + + SNAK + Set NAK + 27 + 1 + write-only + + + EPDIS + Endpoint disable + 30 + 1 + read-only + + + EPENA + Endpoint enable + 31 + 1 + write-only + + + + + OTG_HS_DOEPCTL1 + OTG_HS_DOEPCTL1 + OTG device endpoint-1 control + register + 0x320 + 32 + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + read-write + + + USBAEP + USB active endpoint + 15 + 1 + read-write + + + EONUM_DPID + Even odd frame/Endpoint data + PID + 16 + 1 + read-only + + + NAKSTS + NAK status + 17 + 1 + read-only + + + EPTYP + Endpoint type + 18 + 2 + read-write + + + SNPM + Snoop mode + 20 + 1 + read-write + + + Stall + STALL handshake + 21 + 1 + read-write + + + CNAK + Clear NAK + 26 + 1 + write-only + + + SNAK + Set NAK + 27 + 1 + write-only + + + SD0PID_SEVNFRM + Set DATA0 PID/Set even + frame + 28 + 1 + write-only + + + SODDFRM + Set odd frame + 29 + 1 + write-only + + + EPDIS + Endpoint disable + 30 + 1 + read-write + + + EPENA + Endpoint enable + 31 + 1 + read-write + + + + + OTG_HS_DOEPCTL2 + OTG_HS_DOEPCTL2 + OTG device endpoint-2 control + register + 0x340 + 32 + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + read-write + + + USBAEP + USB active endpoint + 15 + 1 + read-write + + + EONUM_DPID + Even odd frame/Endpoint data + PID + 16 + 1 + read-only + + + NAKSTS + NAK status + 17 + 1 + read-only + + + EPTYP + Endpoint type + 18 + 2 + read-write + + + SNPM + Snoop mode + 20 + 1 + read-write + + + Stall + STALL handshake + 21 + 1 + read-write + + + CNAK + Clear NAK + 26 + 1 + write-only + + + SNAK + Set NAK + 27 + 1 + write-only + + + SD0PID_SEVNFRM + Set DATA0 PID/Set even + frame + 28 + 1 + write-only + + + SODDFRM + Set odd frame + 29 + 1 + write-only + + + EPDIS + Endpoint disable + 30 + 1 + read-write + + + EPENA + Endpoint enable + 31 + 1 + read-write + + + + + OTG_HS_DOEPCTL3 + OTG_HS_DOEPCTL3 + OTG device endpoint-3 control + register + 0x360 + 32 + 0x0 + + + MPSIZ + Maximum packet size + 0 + 11 + read-write + + + USBAEP + USB active endpoint + 15 + 1 + read-write + + + EONUM_DPID + Even odd frame/Endpoint data + PID + 16 + 1 + read-only + + + NAKSTS + NAK status + 17 + 1 + read-only + + + EPTYP + Endpoint type + 18 + 2 + read-write + + + SNPM + Snoop mode + 20 + 1 + read-write + + + Stall + STALL handshake + 21 + 1 + read-write + + + CNAK + Clear NAK + 26 + 1 + write-only + + + SNAK + Set NAK + 27 + 1 + write-only + + + SD0PID_SEVNFRM + Set DATA0 PID/Set even + frame + 28 + 1 + write-only + + + SODDFRM + Set odd frame + 29 + 1 + write-only + + + EPDIS + Endpoint disable + 30 + 1 + read-write + + + EPENA + Endpoint enable + 31 + 1 + read-write + + + + + OTG_HS_DOEPINT0 + OTG_HS_DOEPINT0 + OTG_HS device endpoint-0 interrupt + register + 0x308 + 32 + read-write + 0x00000080 + + + XFRC + Transfer completed + interrupt + 0 + 1 + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + + + STUP + SETUP phase done + 3 + 1 + + + OTEPDIS + OUT token received when endpoint + disabled + 4 + 1 + + + B2BSTUP + Back-to-back SETUP packets + received + 6 + 1 + + + NYET + NYET interrupt + 14 + 1 + + + + + OTG_HS_DOEPINT1 + OTG_HS_DOEPINT1 + OTG_HS device endpoint-1 interrupt + register + 0x328 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + interrupt + 0 + 1 + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + + + STUP + SETUP phase done + 3 + 1 + + + OTEPDIS + OUT token received when endpoint + disabled + 4 + 1 + + + B2BSTUP + Back-to-back SETUP packets + received + 6 + 1 + + + NYET + NYET interrupt + 14 + 1 + + + + + OTG_HS_DOEPINT2 + OTG_HS_DOEPINT2 + OTG_HS device endpoint-2 interrupt + register + 0x348 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + interrupt + 0 + 1 + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + + + STUP + SETUP phase done + 3 + 1 + + + OTEPDIS + OUT token received when endpoint + disabled + 4 + 1 + + + B2BSTUP + Back-to-back SETUP packets + received + 6 + 1 + + + NYET + NYET interrupt + 14 + 1 + + + + + OTG_HS_DOEPINT3 + OTG_HS_DOEPINT3 + OTG_HS device endpoint-3 interrupt + register + 0x368 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + interrupt + 0 + 1 + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + + + STUP + SETUP phase done + 3 + 1 + + + OTEPDIS + OUT token received when endpoint + disabled + 4 + 1 + + + B2BSTUP + Back-to-back SETUP packets + received + 6 + 1 + + + NYET + NYET interrupt + 14 + 1 + + + + + OTG_HS_DOEPINT4 + OTG_HS_DOEPINT4 + OTG_HS device endpoint-4 interrupt + register + 0x388 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + interrupt + 0 + 1 + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + + + STUP + SETUP phase done + 3 + 1 + + + OTEPDIS + OUT token received when endpoint + disabled + 4 + 1 + + + B2BSTUP + Back-to-back SETUP packets + received + 6 + 1 + + + NYET + NYET interrupt + 14 + 1 + + + + + OTG_HS_DOEPINT5 + OTG_HS_DOEPINT5 + OTG_HS device endpoint-5 interrupt + register + 0x3A8 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + interrupt + 0 + 1 + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + + + STUP + SETUP phase done + 3 + 1 + + + OTEPDIS + OUT token received when endpoint + disabled + 4 + 1 + + + B2BSTUP + Back-to-back SETUP packets + received + 6 + 1 + + + NYET + NYET interrupt + 14 + 1 + + + + + OTG_HS_DOEPINT6 + OTG_HS_DOEPINT6 + OTG_HS device endpoint-6 interrupt + register + 0x3C8 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + interrupt + 0 + 1 + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + + + STUP + SETUP phase done + 3 + 1 + + + OTEPDIS + OUT token received when endpoint + disabled + 4 + 1 + + + B2BSTUP + Back-to-back SETUP packets + received + 6 + 1 + + + NYET + NYET interrupt + 14 + 1 + + + + + OTG_HS_DOEPINT7 + OTG_HS_DOEPINT7 + OTG_HS device endpoint-7 interrupt + register + 0x3E8 + 32 + read-write + 0x0 + + + XFRC + Transfer completed + interrupt + 0 + 1 + + + EPDISD + Endpoint disabled + interrupt + 1 + 1 + + + STUP + SETUP phase done + 3 + 1 + + + OTEPDIS + OUT token received when endpoint + disabled + 4 + 1 + + + B2BSTUP + Back-to-back SETUP packets + received + 6 + 1 + + + NYET + NYET interrupt + 14 + 1 + + + + + OTG_HS_DOEPTSIZ0 + OTG_HS_DOEPTSIZ0 + OTG_HS device endpoint-1 transfer size + register + 0x310 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 7 + + + PKTCNT + Packet count + 19 + 1 + + + STUPCNT + SETUP packet count + 29 + 2 + + + + + OTG_HS_DOEPTSIZ1 + OTG_HS_DOEPTSIZ1 + OTG_HS device endpoint-2 transfer size + register + 0x330 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + RXDPID_STUPCNT + Received data PID/SETUP packet + count + 29 + 2 + + + + + OTG_HS_DOEPTSIZ2 + OTG_HS_DOEPTSIZ2 + OTG_HS device endpoint-3 transfer size + register + 0x350 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + RXDPID_STUPCNT + Received data PID/SETUP packet + count + 29 + 2 + + + + + OTG_HS_DOEPTSIZ3 + OTG_HS_DOEPTSIZ3 + OTG_HS device endpoint-4 transfer size + register + 0x370 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + RXDPID_STUPCNT + Received data PID/SETUP packet + count + 29 + 2 + + + + + OTG_HS_DOEPTSIZ4 + OTG_HS_DOEPTSIZ4 + OTG_HS device endpoint-5 transfer size + register + 0x390 + 32 + read-write + 0x0 + + + XFRSIZ + Transfer size + 0 + 19 + + + PKTCNT + Packet count + 19 + 10 + + + RXDPID_STUPCNT + Received data PID/SETUP packet + count + 29 + 2 + + + + + + + OTG_HS_PWRCLK + USB on the go high speed + USB_OTG_HS + 0x40040E00 + + 0x0 + 0x3F200 + registers + + + 0x3F200 + 0xFFFC1200 + reserved + + + + OTG_HS_PCGCR + OTG_HS_PCGCR + Power and clock gating control + register + 0x0 + 32 + read-write + 0x0 + + + STPPCLK + Stop PHY clock + 0 + 1 + + + GATEHCLK + Gate HCLK + 1 + 1 + + + PHYSUSP + PHY suspended + 4 + 1 + + + + + + + NVIC + Nested Vectored Interrupt + Controller + NVIC + 0xE000E000 + + 0x0 + 0x1001 + registers + + + 0x1001 + 0xFFFFF3FF + reserved + + + + ICTR + ICTR + Interrupt Controller Type + Register + 0x4 + 0x20 + read-only + 0x00000000 + + + INTLINESNUM + Total number of interrupt lines in + groups + 0 + 4 + + + + + STIR + STIR + Software Triggered Interrupt + Register + 0xF00 + 0x20 + write-only + 0x00000000 + + + INTID + interrupt to be triggered + 0 + 9 + + + + + ISER0 + ISER0 + Interrupt Set-Enable Register + 0x100 + 0x20 + read-write + 0x00000000 + + + SETENA + SETENA + 0 + 32 + + + + + ISER1 + ISER1 + Interrupt Set-Enable Register + 0x104 + 0x20 + read-write + 0x00000000 + + + SETENA + SETENA + 0 + 32 + + + + + ISER2 + ISER2 + Interrupt Set-Enable Register + 0x108 + 0x20 + read-write + 0x00000000 + + + SETENA + SETENA + 0 + 32 + + + + + ICER0 + ICER0 + Interrupt Clear-Enable + Register + 0x180 + 0x20 + read-write + 0x00000000 + + + CLRENA + CLRENA + 0 + 32 + + + + + ICER1 + ICER1 + Interrupt Clear-Enable + Register + 0x184 + 0x20 + read-write + 0x00000000 + + + CLRENA + CLRENA + 0 + 32 + + + + + ICER2 + ICER2 + Interrupt Clear-Enable + Register + 0x188 + 0x20 + read-write + 0x00000000 + + + CLRENA + CLRENA + 0 + 32 + + + + + ISPR0 + ISPR0 + Interrupt Set-Pending Register + 0x200 + 0x20 + read-write + 0x00000000 + + + SETPEND + SETPEND + 0 + 32 + + + + + ISPR1 + ISPR1 + Interrupt Set-Pending Register + 0x204 + 0x20 + read-write + 0x00000000 + + + SETPEND + SETPEND + 0 + 32 + + + + + ISPR2 + ISPR2 + Interrupt Set-Pending Register + 0x208 + 0x20 + read-write + 0x00000000 + + + SETPEND + SETPEND + 0 + 32 + + + + + ICPR0 + ICPR0 + Interrupt Clear-Pending + Register + 0x280 + 0x20 + read-write + 0x00000000 + + + CLRPEND + CLRPEND + 0 + 32 + + + + + ICPR1 + ICPR1 + Interrupt Clear-Pending + Register + 0x284 + 0x20 + read-write + 0x00000000 + + + CLRPEND + CLRPEND + 0 + 32 + + + + + ICPR2 + ICPR2 + Interrupt Clear-Pending + Register + 0x288 + 0x20 + read-write + 0x00000000 + + + CLRPEND + CLRPEND + 0 + 32 + + + + + IABR0 + IABR0 + Interrupt Active Bit Register + 0x300 + 0x20 + read-only + 0x00000000 + + + ACTIVE + ACTIVE + 0 + 32 + + + + + IABR1 + IABR1 + Interrupt Active Bit Register + 0x304 + 0x20 + read-only + 0x00000000 + + + ACTIVE + ACTIVE + 0 + 32 + + + + + IABR2 + IABR2 + Interrupt Active Bit Register + 0x308 + 0x20 + read-only + 0x00000000 + + + ACTIVE + ACTIVE + 0 + 32 + + + + + IPR0 + IPR0 + Interrupt Priority Register + 0x400 + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR1 + IPR1 + Interrupt Priority Register + 0x404 + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR2 + IPR2 + Interrupt Priority Register + 0x408 + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR3 + IPR3 + Interrupt Priority Register + 0x40C + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR4 + IPR4 + Interrupt Priority Register + 0x410 + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR5 + IPR5 + Interrupt Priority Register + 0x414 + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR6 + IPR6 + Interrupt Priority Register + 0x418 + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR7 + IPR7 + Interrupt Priority Register + 0x41C + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR8 + IPR8 + Interrupt Priority Register + 0x420 + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR9 + IPR9 + Interrupt Priority Register + 0x424 + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR10 + IPR10 + Interrupt Priority Register + 0x428 + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR11 + IPR11 + Interrupt Priority Register + 0x42C + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR12 + IPR12 + Interrupt Priority Register + 0x430 + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR13 + IPR13 + Interrupt Priority Register + 0x434 + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR14 + IPR14 + Interrupt Priority Register + 0x438 + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR15 + IPR15 + Interrupt Priority Register + 0x43C + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR16 + IPR16 + Interrupt Priority Register + 0x440 + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR17 + IPR17 + Interrupt Priority Register + 0x444 + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR18 + IPR18 + Interrupt Priority Register + 0x448 + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + IPR19 + IPR19 + Interrupt Priority Register + 0x44C + 0x20 + read-write + 0x00000000 + + + IPR_N0 + IPR_N0 + 0 + 8 + + + IPR_N1 + IPR_N1 + 8 + 8 + + + IPR_N2 + IPR_N2 + 16 + 8 + + + IPR_N3 + IPR_N3 + 24 + 8 + + + + + + + From 04a0a78c3d9cd392f2482375df532284e60ce02d Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 30 Sep 2018 01:54:02 -0400 Subject: [PATCH 036/549] Get semaphore-controlled bus reception working --- Firmware/communication/interface_can.cpp | 155 ++++++++++++++++------- Firmware/communication/interface_can.hpp | 12 +- ODrive_Workspace.code-workspace | 9 +- 3 files changed, 127 insertions(+), 49 deletions(-) diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 29a03a24..c570b28e 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -32,6 +32,7 @@ */ #include "interface_can.hpp" +#include #include "fibre/crc.hpp" #include "freertos_vars.h" #include "utils.h" @@ -40,28 +41,37 @@ #include #include +std::unordered_map ctxMap; + // Constructor is called by communication.cpp and the handle is assigned appropriately ODriveCAN::ODriveCAN(CAN_HandleTypeDef *handle, ODriveCAN::Config_t &config) : handle_{handle}, config_{config} { + ctxMap[handle_] = this; } void ODriveCAN::can_server_thread() { - static uint32_t counter = 0; + CAN_message_t heartbeat; + heartbeat.id = 0x700 + config_.node_id; + uint32_t lastHeartbeatTick = osKernelSysTick(); + for (;;) { - CAN_message_t txmsg; - txmsg.id = 0x100; + CAN_message_t rxmsg; + osSemaphoreWait(sem_can, 10); - txmsg.isExt = false; + while (available()) { + read(rxmsg); + write(rxmsg); + } - txmsg.buf[0] = counter >> 24; - txmsg.buf[1] = counter >> 16; - txmsg.buf[2] = counter >> 8; - txmsg.buf[3] = counter; - - counter++; - write(txmsg); - osDelay(10); + // Handle heartbeat message + uint32_t now = osKernelSysTick(); + if(now - lastHeartbeatTick >= 100){ + write(heartbeat); + lastHeartbeatTick = now; + } + + HAL_CAN_ActivateNotification(handle_, CAN_IT_RX_FIFO0_MSG_PENDING); } } @@ -78,19 +88,35 @@ bool ODriveCAN::start_can_server() { if (status != HAL_OK) return false; + CAN_FilterTypeDef filter; + filter.FilterActivation = ENABLE; + filter.FilterBank = 0; + filter.FilterFIFOAssignment = CAN_RX_FIFO0; + filter.FilterIdHigh = 0x0000; + filter.FilterIdLow = 0x0000; + filter.FilterMaskIdHigh = 0x0000; + filter.FilterMaskIdLow = 0x0000; + filter.FilterMode = CAN_FILTERMODE_IDMASK; + filter.FilterScale = CAN_FILTERSCALE_32BIT; + + status = HAL_CAN_ConfigFilter(handle_, &filter); + if (status != HAL_OK) + return false; + status = HAL_CAN_Start(handle_); if (status != HAL_OK) return false; status = HAL_CAN_ActivateNotification(handle_, - CAN_IT_TX_MAILBOX_EMPTY | - CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_RX_FIFO1_MSG_PENDING | /* we probably only want this */ - CAN_IT_RX_FIFO0_FULL | CAN_IT_RX_FIFO1_FULL | - CAN_IT_RX_FIFO0_OVERRUN | CAN_IT_RX_FIFO1_OVERRUN | - CAN_IT_WAKEUP | CAN_IT_SLEEP_ACK | - CAN_IT_ERROR_WARNING | CAN_IT_ERROR_PASSIVE | - CAN_IT_BUSOFF | CAN_IT_LAST_ERROR_CODE | - CAN_IT_ERROR); + // CAN_IT_TX_MAILBOX_EMPTY | + CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_RX_FIFO1_MSG_PENDING /* we probably only want this */ + // CAN_IT_RX_FIFO0_FULL | CAN_IT_RX_FIFO1_FULL + // CAN_IT_RX_FIFO0_OVERRUN | CAN_IT_RX_FIFO1_OVERRUN | + // CAN_IT_WAKEUP | CAN_IT_SLEEP_ACK | + // CAN_IT_ERROR_WARNING | CAN_IT_ERROR_PASSIVE | + // CAN_IT_BUSOFF | CAN_IT_LAST_ERROR_CODE | + // | CAN_IT_ERROR + ); if (status != HAL_OK) return false; @@ -101,33 +127,70 @@ bool ODriveCAN::start_can_server() { return true; } + + +// Send a CAN message on the bus +uint32_t ODriveCAN::write(CAN_message_t &txmsg) { + CAN_TxHeaderTypeDef header; + header.StdId = txmsg.id; + header.ExtId = txmsg.id; + header.IDE = txmsg.isExt ? CAN_ID_EXT : CAN_ID_STD; + header.RTR = CAN_RTR_DATA; + header.DLC = txmsg.len; + header.TransmitGlobalTime = FunctionalState::DISABLE; + + uint32_t retTxMailbox; + if (HAL_CAN_GetTxMailboxesFreeLevel(handle_) > 0) + HAL_CAN_AddTxMessage(handle_, &header, txmsg.buf, &retTxMailbox); + + return retTxMailbox; +} + +uint32_t ODriveCAN::available() { + return (HAL_CAN_GetRxFifoFillLevel(handle_, CAN_RX_FIFO0) + HAL_CAN_GetRxFifoFillLevel(handle_, CAN_RX_FIFO1)); +} + +bool ODriveCAN::read(CAN_message_t &rxmsg) { + CAN_RxHeaderTypeDef header; + bool validRead = false; + if (HAL_CAN_GetRxFifoFillLevel(handle_, CAN_RX_FIFO0) > 0) { + HAL_CAN_GetRxMessage(handle_, CAN_RX_FIFO0, &header, rxmsg.buf); + validRead = true; + } else if (HAL_CAN_GetRxFifoFillLevel(handle_, CAN_RX_FIFO1) > 0) { + HAL_CAN_GetRxMessage(handle_, CAN_RX_FIFO1, &header, rxmsg.buf); + validRead = true; + } + + rxmsg.isExt = header.IDE; + rxmsg.id = rxmsg.isExt ? header.ExtId : header.StdId; // If it's an extended message, pass the extended ID + rxmsg.len = header.DLC; + + return validRead; +} + + +// Set one of only a few common baud rates. CAN doesn't do arbitrary baud rates well due to the time-quanta issue. +// 21 TQ allows for easy sampling at exactly 80% (recommended by Vector Informatik GmbH for high reliability systems) +// Conveniently, the CAN peripheral's 42MHz clock lets us easily create 21TQs for all common baud rates void ODriveCAN::set_baud_rate(uint32_t baudRate) { switch (baudRate) { case CAN_BAUD_125K: handle_->Init.Prescaler = 16; // 21 TQ's - handle_->Init.TimeSeg1 = CAN_BS1_16TQ; - handle_->Init.TimeSeg2 = CAN_BS2_4TQ; config_.baud = baudRate; break; case CAN_BAUD_250K: handle_->Init.Prescaler = 8; // 21 TQ's - handle_->Init.TimeSeg1 = CAN_BS1_16TQ; - handle_->Init.TimeSeg2 = CAN_BS2_4TQ; config_.baud = baudRate; break; case CAN_BAUD_500K: handle_->Init.Prescaler = 4; // 21 TQ's - handle_->Init.TimeSeg1 = CAN_BS1_16TQ; - handle_->Init.TimeSeg2 = CAN_BS2_4TQ; config_.baud = baudRate; break; case CAN_BAUD_1000K: handle_->Init.Prescaler = 2; // 21 TQ's - handle_->Init.TimeSeg1 = CAN_BS1_16TQ; - handle_->Init.TimeSeg2 = CAN_BS2_4TQ; config_.baud = baudRate; break; @@ -141,19 +204,23 @@ void ODriveCAN::set_node_id(uint8_t nodeID) { config_.node_id = nodeID; } -// Send a CAN message on the bus -uint32_t ODriveCAN::write(CAN_message_t &txmsg) { - CAN_TxHeaderTypeDef header; - header.StdId = txmsg.id; - header.ExtId = txmsg.id; - header.IDE = txmsg.isExt ? CAN_ID_EXT : CAN_ID_STD; - header.RTR = CAN_RTR_DATA; - header.DLC = txmsg.len; - header.TransmitGlobalTime = FunctionalState::DISABLE; - - uint32_t retTxMailbox; - if(HAL_CAN_GetTxMailboxesFreeLevel(handle_) > 0) - HAL_CAN_AddTxMessage(handle_, &header, txmsg.buf, &retTxMailbox); - - return retTxMailbox; -} \ No newline at end of file +void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan) {} +void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef *hcan) {} +void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef *hcan) {} +void HAL_CAN_TxMailbox0AbortCallback(CAN_HandleTypeDef *hcan) {} +void HAL_CAN_TxMailbox1AbortCallback(CAN_HandleTypeDef *hcan) {} +void HAL_CAN_TxMailbox2AbortCallback(CAN_HandleTypeDef *hcan) {} +void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { + HAL_CAN_DeactivateNotification(hcan, CAN_IT_RX_FIFO0_MSG_PENDING); + osSemaphoreRelease(sem_can); +} +void HAL_CAN_RxFifo0FullCallback(CAN_HandleTypeDef *hcan) { + // osSemaphoreRelease(sem_can); +} +void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan) {} +void HAL_CAN_RxFifo1FullCallback(CAN_HandleTypeDef *hcan) {} +void HAL_CAN_SleepCallback(CAN_HandleTypeDef *hcan) {} +void HAL_CAN_WakeUpFromRxMsgCallback(CAN_HandleTypeDef *hcan) {} +void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) { + HAL_CAN_ResetError(hcan); +} diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index a309658b..546680cb 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -34,15 +34,18 @@ class ODriveCAN { ODriveCAN(CAN_HandleTypeDef *handle, ODriveCAN::Config_t &config); + // Thread Relevant Data + osThreadId thread_id_; + volatile bool thread_id_valid_ = false; bool start_can_server(); void can_server_thread(); + // I/O Functions + uint32_t available(); uint32_t write(CAN_message_t &txmsg); - int read(CAN_message_t &rxmsg); - - osThreadId thread_id_; - volatile bool thread_id_valid_ = false; + bool read(CAN_message_t &rxmsg); + // Communication Protocol Handling auto make_protocol_definitions() { return make_protocol_member_list( make_protocol_object("config", @@ -58,6 +61,7 @@ class ODriveCAN { void set_node_id(uint8_t nodeID); void set_baud_rate(uint32_t baudRate); + }; #endif // __INTERFACE_CAN_HPP diff --git a/ODrive_Workspace.code-workspace b/ODrive_Workspace.code-workspace index 250eb601..fd56d688 100644 --- a/ODrive_Workspace.code-workspace +++ b/ODrive_Workspace.code-workspace @@ -44,7 +44,14 @@ "algorithm": "cpp", "chrono": "cpp", "condition_variable": "cpp", - "future": "cpp" + "future": "cpp", + "cmath": "cpp", + "cstdarg": "cpp", + "unordered_map": "cpp", + "fstream": "cpp", + "numeric": "cpp", + "optional": "cpp", + "sstream": "cpp" } } } From c48251f3f598a0cd39f40740fc7974d82719d201 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 30 Sep 2018 02:04:37 -0400 Subject: [PATCH 037/549] Report axis errors and current state in heartbeat --- Firmware/communication/interface_can.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index c570b28e..80fe84d9 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -41,6 +41,8 @@ #include #include +#include + std::unordered_map ctxMap; // Constructor is called by communication.cpp and the handle is assigned appropriately @@ -65,6 +67,10 @@ void ODriveCAN::can_server_thread() { } // Handle heartbeat message + heartbeat.buf[0] = axes[0]->error_; + heartbeat.buf[1] = axes[0]->current_state_; + heartbeat.buf[2] = axes[1]->error_; + heartbeat.buf[3] = axes[2]->current_state_; uint32_t now = osKernelSysTick(); if(now - lastHeartbeatTick >= 100){ write(heartbeat); From 0589b12238d591324e3ad1627435a06b46cd21fb Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 30 Sep 2018 02:06:19 -0400 Subject: [PATCH 038/549] We don't have an axis 2... --- Firmware/communication/interface_can.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 80fe84d9..f53db443 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -70,7 +70,7 @@ void ODriveCAN::can_server_thread() { heartbeat.buf[0] = axes[0]->error_; heartbeat.buf[1] = axes[0]->current_state_; heartbeat.buf[2] = axes[1]->error_; - heartbeat.buf[3] = axes[2]->current_state_; + heartbeat.buf[3] = axes[1]->current_state_; uint32_t now = osKernelSysTick(); if(now - lastHeartbeatTick >= 100){ write(heartbeat); From 2431fd98c67c9890a6d9b9f687e4cedd9cdc79b8 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 4 Oct 2018 18:50:45 -0400 Subject: [PATCH 039/549] Start with CAN_SIMPLE protocol --- Firmware/Tupfile.lua | 1 + Firmware/communication/can_simple.cpp | 26 ++++++++++++++++++++++++ Firmware/communication/can_simple.hpp | 22 ++++++++++++++++++++ Firmware/communication/interface_can.cpp | 17 +++++++++++----- Firmware/communication/interface_can.hpp | 25 ++++++++++++++--------- 5 files changed, 76 insertions(+), 15 deletions(-) create mode 100644 Firmware/communication/can_simple.cpp create mode 100644 Firmware/communication/can_simple.hpp diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 0b3d43e3..9678a31a 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -157,6 +157,7 @@ build{ 'MotorControl/sensorless_estimator.cpp', 'MotorControl/trapTraj.cpp', 'MotorControl/main.cpp', + 'communication/can_simple.cpp', 'communication/communication.cpp', 'communication/ascii_protocol.cpp', 'communication/interface_uart.cpp', diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp new file mode 100644 index 00000000..dc4c8cba --- /dev/null +++ b/Firmware/communication/can_simple.cpp @@ -0,0 +1,26 @@ +#include "can_simple.hpp" +#include "odrive_main.h" + +void CANSimple::handle_can_message(CAN_message_t& msg) { + // This functional way of handling the messages is neat and is much cleaner from + // a data security point of view, but it will require some tweaking to fix the syntax. + // + // auto func = callback_map.find(msg.id); + // if(func != callback_map.end()){ + // func->second(msg); + // } + + // Frame + // nodeID | CMD + // 4 bits | 7 bits + auto nodeID = (msg.id >> 7 & 0x15); + for(int i = 0; i < AXIS_COUNT; i++){ + if(axes[i]->config_.nodeID) + } + switch (msg.id & 0x7F) { + case 0x010: move_to_pos_callback(); break; + } +} + +void move_to_pos_callback(Axis& axis, uint32_t pos){ +} \ No newline at end of file diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp new file mode 100644 index 00000000..f2ed42ed --- /dev/null +++ b/Firmware/communication/can_simple.hpp @@ -0,0 +1,22 @@ +#ifndef __CAN_SIMPLE_HPP_ +#define __CAN_SIMPLE_HPP_ + +#include "interface_can.hpp" + +class CANSimple { + public: + static void handle_can_message(CAN_message_t& msg); + + private: + // Controller + static void move_to_pos_callback(Axis& axis, uint32_t pos); + + // This functional way of handling the messages is neat and is much cleaner from + // a data security point of view, but it will require some tweaking + // + // const std::map> callback_map = { + // {0x000, std::bind(&CANSimple::heartbeat_callback, this, _1)} + // }; +}; + +#endif \ No newline at end of file diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index f53db443..21edf0ca 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -32,7 +32,7 @@ */ #include "interface_can.hpp" -#include + #include "fibre/crc.hpp" #include "freertos_vars.h" #include "utils.h" @@ -41,15 +41,20 @@ #include #include +// Specific CAN Protocols +#include "can_simple.hpp" + #include -std::unordered_map ctxMap; +// Safer context handling via maps instead of arrays +// #include +// std::unordered_map ctxMap; // Constructor is called by communication.cpp and the handle is assigned appropriately ODriveCAN::ODriveCAN(CAN_HandleTypeDef *handle, ODriveCAN::Config_t &config) : handle_{handle}, config_{config} { - ctxMap[handle_] = this; + // ctxMap[handle_] = this; } void ODriveCAN::can_server_thread() { @@ -60,10 +65,12 @@ void ODriveCAN::can_server_thread() { for (;;) { CAN_message_t rxmsg; - osSemaphoreWait(sem_can, 10); + osSemaphoreWait(sem_can, 10); // Poll every 10ms regardless of sempahore status while (available()) { read(rxmsg); - write(rxmsg); + switch(config_.protocol) { + case CAN_PROTOCOL_SIMPLE: CANSimple::handle_can_message(rxmsg); break; + } } // Handle heartbeat message diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 546680cb..356c6cde 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -16,20 +16,25 @@ typedef struct { } CAN_message_t; // Anonymous enum for defining the most common CAN baud rates - - enum { - CAN_BAUD_125K = 125000, - CAN_BAUD_250K = 250000, - CAN_BAUD_500K = 500000, - CAN_BAUD_1000K = 1000000, - CAN_BAUD_1M = 1000000 - }; + +enum { + CAN_BAUD_125K = 125000, + CAN_BAUD_250K = 250000, + CAN_BAUD_500K = 500000, + CAN_BAUD_1000K = 1000000, + CAN_BAUD_1M = 1000000 +}; + +enum CAN_Protocol_t { + CAN_PROTOCOL_SIMPLE +}; class ODriveCAN { public: struct Config_t { uint8_t node_id = 0; uint32_t baud = CAN_BAUD_250K; + CAN_Protocol_t protocol = CAN_PROTOCOL_SIMPLE; }; ODriveCAN(CAN_HandleTypeDef *handle, ODriveCAN::Config_t &config); @@ -39,7 +44,7 @@ class ODriveCAN { volatile bool thread_id_valid_ = false; bool start_can_server(); void can_server_thread(); - + // I/O Functions uint32_t available(); uint32_t write(CAN_message_t &txmsg); @@ -51,6 +56,7 @@ class ODriveCAN { make_protocol_object("config", make_protocol_ro_property("node_id", &config_.node_id), make_protocol_ro_property("baud_rate", &config_.baud)), + make_protocol_property("can_protocol", &config_.protocol), make_protocol_function("set_node_id", *this, &ODriveCAN::set_node_id, "nodeID"), make_protocol_function("set_baud_rate", *this, &ODriveCAN::set_baud_rate, "baudRate")); } @@ -61,7 +67,6 @@ class ODriveCAN { void set_node_id(uint8_t nodeID); void set_baud_rate(uint32_t baudRate); - }; #endif // __INTERFACE_CAN_HPP From 538236ab21fc4f6b13ff4402ae40d560bccad458 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 4 Oct 2018 18:50:56 -0400 Subject: [PATCH 040/549] Each axis needs a can nodeID --- Firmware/MotorControl/axis.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 6063ee3a..ec8fa8ef 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -54,6 +54,8 @@ public: float spin_up_current = 10.0f; // [A] float spin_up_acceleration = 400.0f; // [rad/s^2] float spin_up_target_vel = 400.0f; // [rad/s] + + uint8_t can_node_id = 0; // If both axes are 0, only the first one will get commands. }; enum thread_signals { From c88d3f7a012d849f2aac00f2e5137dbb7b8a94eb Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 4 Oct 2018 20:39:00 -0400 Subject: [PATCH 041/549] We're using gcc --- Firmware/.vscode/c_cpp_properties.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index 570c0f29..5176fa6c 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -31,7 +31,7 @@ "__packed=\"__attribute__((__packed__))\"", "__GNUC__" ], - "intelliSenseMode": "clang-x64", + "intelliSenseMode": "gcc-x64", "browse": { "path": [ "${workspaceRoot}", From 4fa09d6bbca4a591c54a45036279f2450604cd08 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 5 Oct 2018 22:20:04 -0400 Subject: [PATCH 042/549] Get CAN_SIMPLE to compile --- Firmware/MotorControl/axis.cpp | 43 +++++----- Firmware/MotorControl/axis.hpp | 10 ++- Firmware/communication/can_simple.cpp | 103 +++++++++++++++++++++-- Firmware/communication/can_simple.hpp | 8 +- Firmware/communication/interface_can.cpp | 77 +++++------------ Firmware/communication/interface_can.hpp | 2 + 6 files changed, 156 insertions(+), 87 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 4ff660fd..3aaf5ed4 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -3,8 +3,9 @@ #include #include "gpio.h" -#include "utils.h" #include "odrive_main.h" +#include "utils.h" +#include "communication/interface_can.hpp" Axis::Axis(const AxisHardwareConfig_t& hw_config, Config_t& config, @@ -19,8 +20,7 @@ Axis::Axis(const AxisHardwareConfig_t& hw_config, sensorless_estimator_(sensorless_estimator), controller_(controller), motor_(motor), - trap_(trap) -{ + trap_(trap) { encoder_.axis_ = this; sensorless_estimator_.axis_ = this; controller_.axis_ = this; @@ -46,7 +46,7 @@ static void run_state_machine_loop_wrapper(void* ctx) { // @brief Starts run_state_machine_loop in a new thread void Axis::start_thread() { - osThreadDef(thread_def, run_state_machine_loop_wrapper, hw_config_.thread_priority, 0, 4*512); + osThreadDef(thread_def, run_state_machine_loop_wrapper, hw_config_.thread_priority, 0, 4 * 512); thread_id_ = osThreadCreate(osThread(thread_def), this); thread_id_valid_ = true; } @@ -85,7 +85,7 @@ void Axis::set_step_dir_enabled(bool enable) { // Subscribe to rising edges of the step GPIO GPIO_subscribe(hw_config_.step_port, hw_config_.step_pin, GPIO_PULLDOWN, - step_cb_wrapper, this); + step_cb_wrapper, this); enable_step_dir_ = true; } else { @@ -136,7 +136,9 @@ bool Axis::do_updates() { // Sub-components should use set_error which will propegate to this error_ encoder_.update(); sensorless_estimator_.update(); - return check_for_errors(); + bool ret = check_for_errors(); + odCAN->send_heartbeat(this); + return ret; } float Axis::get_temp() { @@ -148,7 +150,7 @@ float Axis::get_temp() { bool Axis::run_sensorless_spin_up() { // Early Spin-up: spiral up current float x = 0.0f; - run_control_loop([&](){ + run_control_loop([&]() { float phase = wrap_pm_pi(config_.ramp_up_distance * x); float I_mag = config_.spin_up_current * x; x += current_meas_period / config_.ramp_up_time; @@ -158,11 +160,11 @@ bool Axis::run_sensorless_spin_up() { }); if (error_ != ERROR_NONE) return false; - + // Late Spin-up: accelerate float vel = config_.ramp_up_distance / config_.ramp_up_time; float phase = wrap_pm_pi(config_.ramp_up_distance); - run_control_loop([&](){ + run_control_loop([&]() { vel += config_.spin_up_acceleration * current_meas_period; phase = wrap_pm_pi(phase + vel * current_meas_period); float I_mag = config_.spin_up_current; @@ -181,7 +183,7 @@ bool Axis::run_sensorless_spin_up() { // Note run_sensorless_control_loop and run_closed_loop_control_loop are very similar and differ only in where we get the estimate from. bool Axis::run_sensorless_control_loop() { set_step_dir_enabled(config_.enable_step_dir); - run_control_loop([this](){ + run_control_loop([this]() { if (controller_.config_.control_mode >= Controller::CTRL_MODE_POSITION_CONTROL) return error_ |= ERROR_POS_CTRL_DURING_SENSORLESS, false; @@ -190,7 +192,7 @@ bool Axis::run_sensorless_control_loop() { if (!controller_.update(sensorless_estimator_.pll_pos_, sensorless_estimator_.vel_estimate_, ¤t_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; if (!motor_.update(current_setpoint, sensorless_estimator_.phase_)) - return false; // set_error should update axis.error_ + return false; // set_error should update axis.error_ return true; }); set_step_dir_enabled(false); @@ -199,13 +201,13 @@ bool Axis::run_sensorless_control_loop() { bool Axis::run_closed_loop_control_loop() { set_step_dir_enabled(config_.enable_step_dir); - run_control_loop([this](){ + run_control_loop([this]() { // Note that all estimators are updated in the loop prefix in run_control_loop float current_setpoint; if (!controller_.update(encoder_.pos_estimate_, encoder_.vel_estimate_, ¤t_setpoint)) - return error_ |= ERROR_CONTROLLER_FAILED, false; //TODO: Make controller.set_error + return error_ |= ERROR_CONTROLLER_FAILED, false; //TODO: Make controller.set_error if (!motor_.update(current_setpoint, encoder_.phase_)) - return false; // set_error should update axis.error_ + return false; // set_error should update axis.error_ return true; }); set_step_dir_enabled(false); @@ -216,7 +218,7 @@ bool Axis::run_idle_loop() { // run_control_loop ignores missed modulation timing updates // if and only if we're in AXIS_STATE_IDLE safety_critical_disarm_motor_pwm(motor_); - run_control_loop([this](){ + run_control_loop([this]() { return true; }); return check_for_errors(); @@ -224,7 +226,6 @@ bool Axis::run_idle_loop() { // Infinite loop that does calibration and enters main control loop as appropriate void Axis::run_state_machine_loop() { - // Allocate the map for anti-cogging algorithm and initialize all values to 0.0f // TODO: Move this somewhere else // TODO: respect changes of CPR @@ -238,7 +239,7 @@ void Axis::run_state_machine_loop() { // arm! motor_.arm(); - + for (;;) { // Load the task chain if a specific request is pending if (requested_state_ != AXIS_STATE_UNDEFINED) { @@ -265,7 +266,7 @@ void Axis::run_state_machine_loop() { task_chain_[pos++] = requested_state_; task_chain_[pos++] = AXIS_STATE_IDLE; } - task_chain_[pos++] = AXIS_STATE_UNDEFINED; // TODO: bounds checking + task_chain_[pos++] = AXIS_STATE_UNDEFINED; // TODO: bounds checking requested_state_ = AXIS_STATE_UNDEFINED; // Auto-clear any invalid state error error_ &= ~ERROR_INVALID_STATE; @@ -296,7 +297,7 @@ void Axis::run_state_machine_loop() { break; case AXIS_STATE_SENSORLESS_CONTROL: - status = run_sensorless_spin_up(); // TODO: restart if desired + status = run_sensorless_spin_up(); // TODO: restart if desired if (status) status = run_sensorless_control_loop(); break; @@ -307,12 +308,12 @@ void Axis::run_state_machine_loop() { case AXIS_STATE_IDLE: run_idle_loop(); - status = motor_.arm(); // done with idling - try to arm the motor + status = motor_.arm(); // done with idling - try to arm the motor break; default: error_ |= ERROR_INVALID_STATE; - status = false; // this will set the state to idle + status = false; // this will set the state to idle break; } diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index ec8fa8ef..3b46e347 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -20,6 +20,7 @@ public: ERROR_ENCODER_FAILED = 0x100, ERROR_CONTROLLER_FAILED = 0x200, ERROR_POS_CTRL_DURING_SENSORLESS = 0x400, + ERROR_ESTOP_REQUESTED = 0x800 }; // Warning: Do not reorder these enum values. @@ -55,7 +56,7 @@ public: float spin_up_acceleration = 400.0f; // [rad/s^2] float spin_up_target_vel = 400.0f; // [rad/s] - uint8_t can_node_id = 0; // If both axes are 0, only the first one will get commands. + uint8_t can_node_id = 0; // Both axes will have the same id to start }; enum thread_signals { @@ -113,10 +114,9 @@ public: // Update all estimators // Note: updates run even if checks fail bool updates_ok = do_updates(); - if (!checks_ok || !updates_ok) break; - + // Run main loop function, defer quitting for after wait // TODO: change arming logic to arm after waiting bool main_continue = update_handler(); @@ -165,6 +165,7 @@ public: State_t task_chain_[10] = { AXIS_STATE_UNDEFINED }; State_t& current_state_ = task_chain_[0]; uint32_t loop_counter_ = 0; + uint32_t last_heartbeat_ = 0; // Communication protocol definitions auto make_protocol_definitions() { @@ -186,7 +187,8 @@ public: make_protocol_property("ramp_up_distance", &config_.ramp_up_distance), make_protocol_property("spin_up_current", &config_.spin_up_current), make_protocol_property("spin_up_acceleration", &config_.spin_up_acceleration), - make_protocol_property("spin_up_target_vel", &config_.spin_up_target_vel) + make_protocol_property("spin_up_target_vel", &config_.spin_up_target_vel), + make_protocol_property("can_node_id", &config_.can_node_id) ), make_protocol_function("get_temp", *this, &Axis::get_temp), make_protocol_object("motor", motor_.make_protocol_definitions()), diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index dc4c8cba..92acf2a8 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -1,3 +1,4 @@ + #include "can_simple.hpp" #include "odrive_main.h" @@ -14,13 +15,105 @@ void CANSimple::handle_can_message(CAN_message_t& msg) { // nodeID | CMD // 4 bits | 7 bits auto nodeID = (msg.id >> 7 & 0x15); - for(int i = 0; i < AXIS_COUNT; i++){ - if(axes[i]->config_.nodeID) + Axis* axis = nullptr; + + for (uint8_t i = 0; i < AXIS_COUNT; i++) { + if (axes[i]->config_.can_node_id == nodeID) { + axis = axes[i]; + } } - switch (msg.id & 0x7F) { - case 0x010: move_to_pos_callback(); break; + if (axis != nullptr) { + switch (msg.id & 0x7F) { + case 0x010: + move_to_pos_callback(axis, msg); + break; + case 0x011: + set_pos_setpoint_callback(axis, msg); + break; + case 0x012: + set_vel_setpoint_callback(axis, msg); + break; + case 0x013: + set_current_setpoint_callback(axis, msg); + break; + } } } -void move_to_pos_callback(Axis& axis, uint32_t pos){ +void CANSimple::estop_callback(){ + for(Axis* axis : axes){ + axis->error_ |= Axis::ERROR_ESTOP_REQUESTED; + } +} + +void CANSimple::move_to_pos_callback(Axis* axis, CAN_message_t& msg) { + float pos = msg.buf[0]; + pos += msg.buf[1] << 8; + pos += msg.buf[2] << 16; + pos += msg.buf[3] << 24; + + axis->controller_.move_to_pos(pos); +} + +void CANSimple::set_pos_setpoint_callback(Axis* axis, CAN_message_t& msg) { + float pos = msg.buf[0]; + pos += msg.buf[1] << 8; + pos += msg.buf[2] << 16; + pos += msg.buf[3] << 24; + + float vel = msg.buf[4]; + vel += msg.buf[5] << 8; + vel *= 0.1f; // Factor of 10 + + float current = msg.buf[6]; + current += (msg.buf[7] << 8); + current *= 0.01f; // Factor of 100 + + axis->controller_.set_pos_setpoint(pos, vel, current); +} + +void CANSimple::set_vel_setpoint_callback(Axis* axis, CAN_message_t& msg) { + float vel = msg.buf[0]; + vel += msg.buf[1] << 8; + vel += msg.buf[2] << 16; + vel += msg.buf[3] << 24; + vel *= 0.01f; + + float current = msg.buf[4]; + current += msg.buf[5] << 8; + current += msg.buf[6] << 16; + current += msg.buf[7] << 24; + current *= 0.01f; + + axis->controller_.set_vel_setpoint(vel, current); +} + +void CANSimple::set_current_setpoint_callback(Axis* axis, CAN_message_t& msg) { + float current = msg.buf[0]; + current += msg.buf[1] << 8; + current += msg.buf[2] << 16; + current += msg.buf[3] << 24; + current *= 0.01f; + + axis->controller_.set_current_setpoint(current); +} + +void CANSimple::send_heartbeat(Axis* axis){ + CAN_message_t txmsg; + txmsg.id = axis->config_.can_node_id << 7; + txmsg.id += 0x1; + txmsg.isExt = false; + txmsg.len = 8; + + // Axis errors in 1st 32-bit value + txmsg.buf[0] = axis->error_; + txmsg.buf[1] = axis->error_ >> 8; + txmsg.buf[2] = axis->error_ >> 16; + txmsg.buf[3] = axis->error_ >> 24; + + // Current state of axis in 2nd 32-bit value + txmsg.buf[4] = axis->current_state_; + txmsg.buf[5] = axis->current_state_ >> 8; + txmsg.buf[6] = axis->current_state_ >> 16; + txmsg.buf[7] = axis->current_state_ >> 24; } \ No newline at end of file diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp index f2ed42ed..7d87f2da 100644 --- a/Firmware/communication/can_simple.hpp +++ b/Firmware/communication/can_simple.hpp @@ -6,10 +6,16 @@ class CANSimple { public: static void handle_can_message(CAN_message_t& msg); + static void send_heartbeat(Axis* axis); private: + static void estop_callback(); + // Controller - static void move_to_pos_callback(Axis& axis, uint32_t pos); + static void move_to_pos_callback(Axis* axis, CAN_message_t& msg); + static void set_pos_setpoint_callback(Axis* axis, CAN_message_t& msg); + static void set_vel_setpoint_callback(Axis* axis, CAN_message_t& msg); + static void set_current_setpoint_callback(Axis* axis, CAN_message_t& msg); // This functional way of handling the messages is neat and is much cleaner from // a data security point of view, but it will require some tweaking diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 21edf0ca..716821d2 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -1,36 +1,3 @@ -/* -* -* Zero-config node ID negotiation -* ------------------------------- -* -* A heartbeat message is a message with a 8 byte unique serial number as payload. -* A regular message is any message that is not a heartbeat message. -* -* All nodes MUST obey these four rules: -* -* a) At a given point in time, a node MUST consider a node ID taken (by others) -* if any of the following is true: -* - the node received a (not self-emitted) heartbeat message with that node ID -* within the last second -* - the node attempted and failed at sending a heartbeat message with that -* node ID within the last second (failed in the sense of not ACK'd) -* -* b) At a given point in time, a node MUST NOT consider a node ID self-assigned -* if, within the last second, it did not succeed in sending a heartbeat -* message with that node ID. -* -* c) At a given point in time, a node MUST NOT send any heartbeat message with -* a node ID that is taken. -* -* d) At a given point in time, a node MUST NOT send any regular message with -* a node ID that is not self-assigned. -* -* Hardware allocation -* ------------------- -* RX FIFO0: -* - filter bank 0: heartbeat messages -*/ - #include "interface_can.hpp" #include "fibre/crc.hpp" @@ -58,32 +25,18 @@ ODriveCAN::ODriveCAN(CAN_HandleTypeDef *handle, ODriveCAN::Config_t &config) } void ODriveCAN::can_server_thread() { - CAN_message_t heartbeat; - heartbeat.id = 0x700 + config_.node_id; - uint32_t lastHeartbeatTick = osKernelSysTick(); - for (;;) { CAN_message_t rxmsg; - osSemaphoreWait(sem_can, 10); // Poll every 10ms regardless of sempahore status + osSemaphoreWait(sem_can, 10); // Poll every 10ms regardless of sempahore status while (available()) { read(rxmsg); - switch(config_.protocol) { - case CAN_PROTOCOL_SIMPLE: CANSimple::handle_can_message(rxmsg); break; + switch (config_.protocol) { + case CAN_PROTOCOL_SIMPLE: + CANSimple::handle_can_message(rxmsg); + break; } } - - // Handle heartbeat message - heartbeat.buf[0] = axes[0]->error_; - heartbeat.buf[1] = axes[0]->current_state_; - heartbeat.buf[2] = axes[1]->error_; - heartbeat.buf[3] = axes[1]->current_state_; - uint32_t now = osKernelSysTick(); - if(now - lastHeartbeatTick >= 100){ - write(heartbeat); - lastHeartbeatTick = now; - } - HAL_CAN_ActivateNotification(handle_, CAN_IT_RX_FIFO0_MSG_PENDING); } } @@ -140,8 +93,6 @@ bool ODriveCAN::start_can_server() { return true; } - - // Send a CAN message on the bus uint32_t ODriveCAN::write(CAN_message_t &txmsg) { CAN_TxHeaderTypeDef header; @@ -181,8 +132,7 @@ bool ODriveCAN::read(CAN_message_t &rxmsg) { return validRead; } - -// Set one of only a few common baud rates. CAN doesn't do arbitrary baud rates well due to the time-quanta issue. +// Set one of only a few common baud rates. CAN doesn't do arbitrary baud rates well due to the time-quanta issue. // 21 TQ allows for easy sampling at exactly 80% (recommended by Vector Informatik GmbH for high reliability systems) // Conveniently, the CAN peripheral's 42MHz clock lets us easily create 21TQs for all common baud rates void ODriveCAN::set_baud_rate(uint32_t baudRate) { @@ -217,6 +167,21 @@ void ODriveCAN::set_node_id(uint8_t nodeID) { config_.node_id = nodeID; } +// This function is called by each axis. +// It provides an abstraction from the specific CAN protocol in use +void ODriveCAN::send_heartbeat(Axis *axis) { + // Handle heartbeat message + uint32_t now = osKernelSysTick(); + if (now - axis->last_heartbeat_ >= 100) { + switch (config_.protocol) { + case CAN_PROTOCOL_SIMPLE: + CANSimple::send_heartbeat(axis); + break; + } + axis->last_heartbeat_ = now; + } +} + void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan) {} void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef *hcan) {} void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef *hcan) {} diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 356c6cde..db44d3d0 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -4,6 +4,7 @@ #include #include #include "fibre/protocol.hpp" +#include "odrive_main.h" #define CAN_CLK_HZ (42000000) #define CAN_CLK_MHZ (42) @@ -44,6 +45,7 @@ class ODriveCAN { volatile bool thread_id_valid_ = false; bool start_can_server(); void can_server_thread(); + void send_heartbeat(Axis* axis); // I/O Functions uint32_t available(); From 8052762684f64d37cbf9c60697c671683329f167 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 6 Oct 2018 12:54:53 -0400 Subject: [PATCH 043/549] Remove nodeID from CAN interface, since it's moving to each axis --- Firmware/communication/interface_can.cpp | 5 ----- Firmware/communication/interface_can.hpp | 5 +---- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 716821d2..f7b7e078 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -162,11 +162,6 @@ void ODriveCAN::set_baud_rate(uint32_t baudRate) { } } -void ODriveCAN::set_node_id(uint8_t nodeID) { - // Allow for future nodeID validation by making this a set function - config_.node_id = nodeID; -} - // This function is called by each axis. // It provides an abstraction from the specific CAN protocol in use void ODriveCAN::send_heartbeat(Axis *axis) { diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index db44d3d0..1a8d36c0 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -33,8 +33,7 @@ enum CAN_Protocol_t { class ODriveCAN { public: struct Config_t { - uint8_t node_id = 0; - uint32_t baud = CAN_BAUD_250K; + uint32_t baud = CAN_BAUD_1M; CAN_Protocol_t protocol = CAN_PROTOCOL_SIMPLE; }; @@ -56,10 +55,8 @@ class ODriveCAN { auto make_protocol_definitions() { return make_protocol_member_list( make_protocol_object("config", - make_protocol_ro_property("node_id", &config_.node_id), make_protocol_ro_property("baud_rate", &config_.baud)), make_protocol_property("can_protocol", &config_.protocol), - make_protocol_function("set_node_id", *this, &ODriveCAN::set_node_id, "nodeID"), make_protocol_function("set_baud_rate", *this, &ODriveCAN::set_baud_rate, "baudRate")); } From 165838ccef2ac5ad6846b9e8ac6fabc830bd2b1b Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 6 Oct 2018 21:13:36 -0400 Subject: [PATCH 044/549] Change message IDs so they don't collide with test messages --- Firmware/communication/can_simple.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 92acf2a8..7808465b 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -24,16 +24,16 @@ void CANSimple::handle_can_message(CAN_message_t& msg) { } if (axis != nullptr) { switch (msg.id & 0x7F) { - case 0x010: + case 0x020: move_to_pos_callback(axis, msg); break; - case 0x011: + case 0x021: set_pos_setpoint_callback(axis, msg); break; - case 0x012: + case 0x022: set_vel_setpoint_callback(axis, msg); break; - case 0x013: + case 0x023: set_current_setpoint_callback(axis, msg); break; } From a4ebed742cbe695af865160c18cec0fd86150ba9 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 6 Oct 2018 21:14:26 -0400 Subject: [PATCH 045/549] Actually send the heartbeat message... --- Firmware/communication/can_simple.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 7808465b..14ca8af0 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -116,4 +116,5 @@ void CANSimple::send_heartbeat(Axis* axis){ txmsg.buf[5] = axis->current_state_ >> 8; txmsg.buf[6] = axis->current_state_ >> 16; txmsg.buf[7] = axis->current_state_ >> 24; + odCAN->write(txmsg); } \ No newline at end of file From a59e29b591f04e157f3ffad87f676455ebf38d8a Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 6 Oct 2018 21:15:02 -0400 Subject: [PATCH 046/549] Remove unnecessary includes --- Firmware/communication/can_simple.cpp | 1 - Firmware/communication/interface_can.cpp | 2 -- 2 files changed, 3 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 14ca8af0..38032469 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -1,6 +1,5 @@ #include "can_simple.hpp" -#include "odrive_main.h" void CANSimple::handle_can_message(CAN_message_t& msg) { // This functional way of handling the messages is neat and is much cleaner from diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index f7b7e078..2fd624f1 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -11,8 +11,6 @@ // Specific CAN Protocols #include "can_simple.hpp" -#include - // Safer context handling via maps instead of arrays // #include // std::unordered_map ctxMap; From 605b7d2fbf32ec166e1221fb4291baf0146a11f4 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 7 Oct 2018 00:05:53 -0400 Subject: [PATCH 047/549] Handle CAN bus initialization failures and baud rate reinitialization --- Firmware/communication/can_simple.cpp | 2 +- Firmware/communication/interface_can.cpp | 92 +++++++++++++----------- Firmware/communication/interface_can.hpp | 1 + 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 38032469..0a33f704 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -100,7 +100,7 @@ void CANSimple::set_current_setpoint_callback(Axis* axis, CAN_message_t& msg) { void CANSimple::send_heartbeat(Axis* axis){ CAN_message_t txmsg; txmsg.id = axis->config_.can_node_id << 7; - txmsg.id += 0x1; + txmsg.id += 0x001; // heartbeat ID txmsg.isExt = false; txmsg.len = 8; diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 2fd624f1..ca504fea 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -24,18 +24,28 @@ ODriveCAN::ODriveCAN(CAN_HandleTypeDef *handle, ODriveCAN::Config_t &config) void ODriveCAN::can_server_thread() { for (;;) { - CAN_message_t rxmsg; + uint32_t status = HAL_CAN_GetError(handle_); + if (status == HAL_CAN_ERROR_NONE) { + CAN_message_t rxmsg; - osSemaphoreWait(sem_can, 10); // Poll every 10ms regardless of sempahore status - while (available()) { - read(rxmsg); - switch (config_.protocol) { - case CAN_PROTOCOL_SIMPLE: - CANSimple::handle_can_message(rxmsg); - break; + osSemaphoreWait(sem_can, 10); // Poll every 10ms regardless of sempahore status + while (available()) { + read(rxmsg); + switch (config_.protocol) { + case CAN_PROTOCOL_SIMPLE: + CANSimple::handle_can_message(rxmsg); + break; + } + } + HAL_CAN_ActivateNotification(handle_, CAN_IT_RX_FIFO0_MSG_PENDING); + } else { + if (status == HAL_CAN_ERROR_TIMEOUT) { + HAL_CAN_ResetError(handle_); + status = HAL_CAN_Start(handle_); + if (status == HAL_OK) + status = HAL_CAN_ActivateNotification(handle_, CAN_IT_RX_FIFO0_MSG_PENDING); } } - HAL_CAN_ActivateNotification(handle_, CAN_IT_RX_FIFO0_MSG_PENDING); } } @@ -48,9 +58,8 @@ bool ODriveCAN::start_can_server() { HAL_StatusTypeDef status; set_baud_rate(config_.baud); + status = HAL_CAN_Init(handle_); - if (status != HAL_OK) - return false; CAN_FilterTypeDef filter; filter.FilterActivation = ENABLE; @@ -64,48 +73,37 @@ bool ODriveCAN::start_can_server() { filter.FilterScale = CAN_FILTERSCALE_32BIT; status = HAL_CAN_ConfigFilter(handle_, &filter); - if (status != HAL_OK) - return false; status = HAL_CAN_Start(handle_); - if (status != HAL_OK) - return false; - - status = HAL_CAN_ActivateNotification(handle_, - // CAN_IT_TX_MAILBOX_EMPTY | - CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_RX_FIFO1_MSG_PENDING /* we probably only want this */ - // CAN_IT_RX_FIFO0_FULL | CAN_IT_RX_FIFO1_FULL - // CAN_IT_RX_FIFO0_OVERRUN | CAN_IT_RX_FIFO1_OVERRUN | - // CAN_IT_WAKEUP | CAN_IT_SLEEP_ACK | - // CAN_IT_ERROR_WARNING | CAN_IT_ERROR_PASSIVE | - // CAN_IT_BUSOFF | CAN_IT_LAST_ERROR_CODE | - // | CAN_IT_ERROR - ); - if (status != HAL_OK) - return false; + if (status == HAL_OK) + status = HAL_CAN_ActivateNotification(handle_, CAN_IT_RX_FIFO0_MSG_PENDING); osThreadDef(can_server_thread_def, can_server_thread_wrapper, osPriorityNormal, 0, 512); thread_id_ = osThreadCreate(osThread(can_server_thread_def), this); thread_id_valid_ = true; - return true; + return status; } // Send a CAN message on the bus uint32_t ODriveCAN::write(CAN_message_t &txmsg) { - CAN_TxHeaderTypeDef header; - header.StdId = txmsg.id; - header.ExtId = txmsg.id; - header.IDE = txmsg.isExt ? CAN_ID_EXT : CAN_ID_STD; - header.RTR = CAN_RTR_DATA; - header.DLC = txmsg.len; - header.TransmitGlobalTime = FunctionalState::DISABLE; + if (HAL_CAN_GetError(handle_) == HAL_CAN_ERROR_NONE) { + CAN_TxHeaderTypeDef header; + header.StdId = txmsg.id; + header.ExtId = txmsg.id; + header.IDE = txmsg.isExt ? CAN_ID_EXT : CAN_ID_STD; + header.RTR = CAN_RTR_DATA; + header.DLC = txmsg.len; + header.TransmitGlobalTime = FunctionalState::DISABLE; - uint32_t retTxMailbox; - if (HAL_CAN_GetTxMailboxesFreeLevel(handle_) > 0) - HAL_CAN_AddTxMessage(handle_, &header, txmsg.buf, &retTxMailbox); + uint32_t retTxMailbox; + if (HAL_CAN_GetTxMailboxesFreeLevel(handle_) > 0) + HAL_CAN_AddTxMessage(handle_, &header, txmsg.buf, &retTxMailbox); - return retTxMailbox; + return retTxMailbox; + } else { + return -1; + } } uint32_t ODriveCAN::available() { @@ -138,21 +136,25 @@ void ODriveCAN::set_baud_rate(uint32_t baudRate) { case CAN_BAUD_125K: handle_->Init.Prescaler = 16; // 21 TQ's config_.baud = baudRate; + reinit_can(); break; case CAN_BAUD_250K: handle_->Init.Prescaler = 8; // 21 TQ's config_.baud = baudRate; + reinit_can(); break; case CAN_BAUD_500K: handle_->Init.Prescaler = 4; // 21 TQ's config_.baud = baudRate; + reinit_can(); break; case CAN_BAUD_1000K: handle_->Init.Prescaler = 2; // 21 TQ's config_.baud = baudRate; + reinit_can(); break; default: @@ -160,7 +162,15 @@ void ODriveCAN::set_baud_rate(uint32_t baudRate) { } } -// This function is called by each axis. +void ODriveCAN::reinit_can() { + HAL_CAN_Stop(handle_); + HAL_CAN_Init(handle_); + auto status = HAL_CAN_Start(handle_); + if (status == HAL_OK) + status = HAL_CAN_ActivateNotification(handle_, CAN_IT_RX_FIFO0_MSG_PENDING); +} + +// This function is called by each axis. // It provides an abstraction from the specific CAN protocol in use void ODriveCAN::send_heartbeat(Axis *axis) { // Handle heartbeat message diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 1a8d36c0..00ec5bfa 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -45,6 +45,7 @@ class ODriveCAN { bool start_can_server(); void can_server_thread(); void send_heartbeat(Axis* axis); + void reinit_can(); // I/O Functions uint32_t available(); From 1e47eb90ef9cd7440cda7c8fdef85b5b9c90e76f Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 7 Oct 2018 01:29:17 -0400 Subject: [PATCH 048/549] Create initial list of CAN commands --- Firmware/communication/can_simple.hpp | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp index 7d87f2da..21ad509e 100644 --- a/Firmware/communication/can_simple.hpp +++ b/Firmware/communication/can_simple.hpp @@ -5,6 +5,44 @@ class CANSimple { public: + enum { + MSG_NMT_CTRL = 0x000, // CANOpen NMT Message + MSG_SYNC_CTRL = 0x080, // CANOpen SYNC message + MSG_HEARTBEAT_CMD = 0x700, // CANOpen NMT Heartbeat + MSG_EMERGENCY = 0x080, // CANOpen Emergency Message + MSG_GET_MOTOR_ERROR = 0x001, // Errors + MSG_GET_ENCODER_ERROR, + MSG_GET_CONTROLLER_ERROR, + MSG_GET_SENSORLESS_ERROR, + MSG_GET_VBUS_VOLTAGE, // ODrive-level properties + MSG_GET_SERIAL_NUMBER, + MSG_GET_HW_VERSION, + MSG_GET_FW_VERSION, + MSG_REBOOT_ODRIVE, + MSG_SAVE_CONFIG, + MSG_ERASE_CONFIG, + MSG_SET_AXIS_NODE_ID, // Axis properties + MSG_SET_AXIS_REQUESTED_STATE, + MSG_SET_STARTUP_CONFIG, + MSG_SET_MOTOR_PRECALIBRATED, // Motor properties + MSG_GET_MOTOR_CURRENT_LIM, + MSG_SET_MOTOR_CURRENT_LIM, + MSG_SET_MOTOR_POLE_PAIRS, + MSG_GET_ENCODER_CPR, // Encoder Properties + MSG_SET_ENCODER_CPR, + MSG_GET_ENCODER_INDEX_FOUND, + MSG_GET_ENCODER_POS_ESTIMATE, + MSG_GET_ENCODER_VEL_ESTIMATE, + MSG_SET_ENCODER_USE_INDEX, + MSG_SET_ENCODER_PRECALIBRATED, + MSG_MOVE_TO_POS, // Controller properties + MSG_SET_POS_SETPOINT, + MSG_SET_VEL_SETPOINT, + MSG_SET_CUR_SETPOINT, + MSG_SET_VEL_LIMIT, + MSG_START_ANTICOGGING + }; + static void handle_can_message(CAN_message_t& msg); static void send_heartbeat(Axis* axis); From 3197220a1a5e7dfe063d34a5617048f710dc1741 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 7 Oct 2018 01:29:43 -0400 Subject: [PATCH 049/549] More graceful message handling --- Firmware/communication/can_simple.cpp | 27 +++++++++++++++++++-------- Firmware/communication/can_simple.hpp | 4 ++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 0a33f704..c7b42ca0 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -1,6 +1,9 @@ #include "can_simple.hpp" +static const uint8_t NUM_NODE_ID_BITS = 6; +static constexpr uint8_t NUM_CMD_ID_BITS = 11 - NUM_NODE_ID_BITS; + void CANSimple::handle_can_message(CAN_message_t& msg) { // This functional way of handling the messages is neat and is much cleaner from // a data security point of view, but it will require some tweaking to fix the syntax. @@ -13,7 +16,7 @@ void CANSimple::handle_can_message(CAN_message_t& msg) { // Frame // nodeID | CMD // 4 bits | 7 bits - auto nodeID = (msg.id >> 7 & 0x15); + auto nodeID = get_node_id(msg.id); Axis* axis = nullptr; for (uint8_t i = 0; i < AXIS_COUNT; i++) { @@ -22,23 +25,31 @@ void CANSimple::handle_can_message(CAN_message_t& msg) { } } if (axis != nullptr) { - switch (msg.id & 0x7F) { - case 0x020: + switch (get_cmd_id(msg.id)) { + case MSG_MOVE_TO_POS: move_to_pos_callback(axis, msg); break; - case 0x021: + case MSG_SET_POS_SETPOINT: set_pos_setpoint_callback(axis, msg); break; - case 0x022: + case MSG_SET_VEL_SETPOINT: set_vel_setpoint_callback(axis, msg); break; - case 0x023: + case MSG_SET_CUR_SETPOINT: set_current_setpoint_callback(axis, msg); break; } } } +uint8_t CANSimple::get_node_id(uint32_t msgID){ + return ((msgID >> NUM_NODE_ID_BITS) & 0x03F); // Upper 6 bits +} + +uint8_t CANSimple::get_cmd_id(uint32_t msgID){ + return (msgID & 0x01F); // Bottom 5 bits +} + void CANSimple::estop_callback(){ for(Axis* axis : axes){ axis->error_ |= Axis::ERROR_ESTOP_REQUESTED; @@ -99,8 +110,8 @@ void CANSimple::set_current_setpoint_callback(Axis* axis, CAN_message_t& msg) { void CANSimple::send_heartbeat(Axis* axis){ CAN_message_t txmsg; - txmsg.id = axis->config_.can_node_id << 7; - txmsg.id += 0x001; // heartbeat ID + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_HEARTBEAT_CMD; // heartbeat ID txmsg.isExt = false; txmsg.len = 8; diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp index 21ad509e..a20c1703 100644 --- a/Firmware/communication/can_simple.hpp +++ b/Firmware/communication/can_simple.hpp @@ -55,6 +55,10 @@ class CANSimple { static void set_vel_setpoint_callback(Axis* axis, CAN_message_t& msg); static void set_current_setpoint_callback(Axis* axis, CAN_message_t& msg); + // Utility functions + static uint8_t get_node_id(uint32_t msgID); + static uint8_t get_cmd_id(uint32_t msgID); + // This functional way of handling the messages is neat and is much cleaner from // a data security point of view, but it will require some tweaking // From 887a785e94e54f7c4e61144ea9c46fc67f7aea6e Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 7 Oct 2018 01:53:01 -0400 Subject: [PATCH 050/549] Make 250kbps the default CAN baud rate --- Firmware/communication/interface_can.cpp | 3 ++- Firmware/communication/interface_can.hpp | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index ca504fea..2940cdf1 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -158,7 +158,8 @@ void ODriveCAN::set_baud_rate(uint32_t baudRate) { break; default: - break; // baudRate is invalid, so do nothing + // baudRate is invalid, so don't accept it. + break; } } diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 00ec5bfa..850c240b 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -19,11 +19,11 @@ typedef struct { // Anonymous enum for defining the most common CAN baud rates enum { - CAN_BAUD_125K = 125000, - CAN_BAUD_250K = 250000, - CAN_BAUD_500K = 500000, - CAN_BAUD_1000K = 1000000, - CAN_BAUD_1M = 1000000 + CAN_BAUD_125K = 125000, + CAN_BAUD_250K = 250000, + CAN_BAUD_500K = 500000, + CAN_BAUD_1000K = 1000000, + CAN_BAUD_1M = 1000000 }; enum CAN_Protocol_t { @@ -33,7 +33,7 @@ enum CAN_Protocol_t { class ODriveCAN { public: struct Config_t { - uint32_t baud = CAN_BAUD_1M; + uint32_t baud = CAN_BAUD_250K; CAN_Protocol_t protocol = CAN_PROTOCOL_SIMPLE; }; @@ -44,7 +44,7 @@ class ODriveCAN { volatile bool thread_id_valid_ = false; bool start_can_server(); void can_server_thread(); - void send_heartbeat(Axis* axis); + void send_heartbeat(Axis *axis); void reinit_can(); // I/O Functions @@ -57,7 +57,7 @@ class ODriveCAN { return make_protocol_member_list( make_protocol_object("config", make_protocol_ro_property("baud_rate", &config_.baud)), - make_protocol_property("can_protocol", &config_.protocol), + make_protocol_property("can_protocol", &config_.protocol), make_protocol_function("set_baud_rate", *this, &ODriveCAN::set_baud_rate, "baudRate")); } From 8373771457c1393d834ba553a05d0ee252210328 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 7 Oct 2018 01:54:41 -0400 Subject: [PATCH 051/549] Remove nodeID function --- Firmware/communication/interface_can.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 850c240b..4105592b 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -65,7 +65,6 @@ class ODriveCAN { CAN_HandleTypeDef *handle_ = nullptr; ODriveCAN::Config_t &config_; - void set_node_id(uint8_t nodeID); void set_baud_rate(uint32_t baudRate); }; From 2f76d3c284742bf3a56cb1bffeb02242eb4d773b Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 7 Oct 2018 13:29:18 -0400 Subject: [PATCH 052/549] Implement can_simple --- Firmware/communication/can_simple.cpp | 235 ++++++++++++++++++-------- Firmware/communication/can_simple.hpp | 55 +++--- 2 files changed, 192 insertions(+), 98 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index c7b42ca0..8b2567c7 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -15,8 +15,10 @@ void CANSimple::handle_can_message(CAN_message_t& msg) { // Frame // nodeID | CMD - // 4 bits | 7 bits - auto nodeID = get_node_id(msg.id); + // 6 bits | 5 bits + uint32_t nodeID = get_node_id(msg.id); + uint32_t cmd = get_cmd_id(msg.id); + Axis* axis = nullptr; for (uint8_t i = 0; i < AXIS_COUNT; i++) { @@ -24,97 +26,177 @@ void CANSimple::handle_can_message(CAN_message_t& msg) { axis = axes[i]; } } - if (axis != nullptr) { - switch (get_cmd_id(msg.id)) { - case MSG_MOVE_TO_POS: - move_to_pos_callback(axis, msg); - break; - case MSG_SET_POS_SETPOINT: - set_pos_setpoint_callback(axis, msg); - break; - case MSG_SET_VEL_SETPOINT: - set_vel_setpoint_callback(axis, msg); - break; - case MSG_SET_CUR_SETPOINT: - set_current_setpoint_callback(axis, msg); - break; - } + switch (cmd) { + case MSG_CO_NMT_CTRL: + nmt_callback(axis, msg); + break; + case MSG_CO_SYNC_CTRL: + sync_callback(axis, msg); + break; + case MSG_CO_HEARTBEAT_CMD: + break; + case MSG_GET_ENCODER_ERROR: + get_encoder_error_callback(axis, msg); + break; + case MSG_GET_SENSORLESS_ERROR: + get_sensorless_error_callback(axis, msg); + break; + case MSG_SET_AXIS_NODE_ID: + set_axis_nodeid_callback(axis, msg); + break; + case MSG_SET_AXIS_REQUESTED_STATE: + set_axis_requested_state_callback(axis, msg); + break; + case MSG_SET_AXIS_STARTUP_CONFIG: + set_axis_startup_config_callback(axis, msg); + break; + case MSG_GET_ENCODER_ESTIMATES: + get_encoder_estimates_callback(axis, msg); + break; + case MSG_MOVE_TO_POS: + move_to_pos_callback(axis, msg); + break; + case MSG_SET_POS_SETPOINT: + set_pos_setpoint_callback(axis, msg); + break; + case MSG_SET_VEL_SETPOINT: + set_vel_setpoint_callback(axis, msg); + break; + case MSG_SET_CUR_SETPOINT: + set_current_setpoint_callback(axis, msg); + break; + case MSG_SET_VEL_LIMIT: + set_vel_limit_callback(axis, msg); + break; + case MSG_START_ANTICOGGING: + start_anticogging_callback(axis, msg); + break; + default: + break; } } -uint8_t CANSimple::get_node_id(uint32_t msgID){ - return ((msgID >> NUM_NODE_ID_BITS) & 0x03F); // Upper 6 bits +void CANSimple::nmt_callback(Axis* axis, CAN_message_t& msg) { + // Not implemented } -uint8_t CANSimple::get_cmd_id(uint32_t msgID){ - return (msgID & 0x01F); // Bottom 5 bits +void CANSimple::sync_callback(Axis* axis, CAN_message_t& msg) { + // Not implemented } -void CANSimple::estop_callback(){ - for(Axis* axis : axes){ - axis->error_ |= Axis::ERROR_ESTOP_REQUESTED; - } +void CANSimple::estop_callback(Axis* axis, CAN_message_t& msg) { + axis->error_ |= Axis::ERROR_ESTOP_REQUESTED; +} + +void CANSimple::get_motor_error_callback(Axis* axis, CAN_message_t& msg) { + CAN_message_t txmsg; + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_MOTOR_ERROR; // heartbeat ID + txmsg.isExt = false; + txmsg.len = 8; + + txmsg.buf[0] = axis->motor_.error_; + txmsg.buf[1] = axis->motor_.error_ >> 8; + txmsg.buf[2] = axis->motor_.error_ >> 16; + txmsg.buf[3] = axis->motor_.error_ >> 24; + + odCAN->write(txmsg); +} + +void CANSimple::get_encoder_error_callback(Axis* axis, CAN_message_t& msg) { + CAN_message_t txmsg; + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_ENCODER_ERROR; // heartbeat ID + txmsg.isExt = false; + txmsg.len = 8; + + txmsg.buf[0] = axis->encoder_.error_; + txmsg.buf[1] = axis->encoder_.error_ >> 8; + txmsg.buf[2] = axis->encoder_.error_ >> 16; + txmsg.buf[3] = axis->encoder_.error_ >> 24; + + odCAN->write(txmsg); +} + +void CANSimple::get_sensorless_error_callback(Axis* axis, CAN_message_t& msg) { + CAN_message_t txmsg; + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_SENSORLESS_ERROR; // heartbeat ID + txmsg.isExt = false; + txmsg.len = 8; + + txmsg.buf[0] = axis->sensorless_estimator_.error_; + txmsg.buf[1] = axis->sensorless_estimator_.error_ >> 8; + txmsg.buf[2] = axis->sensorless_estimator_.error_ >> 16; + txmsg.buf[3] = axis->sensorless_estimator_.error_ >> 24; + + odCAN->write(txmsg); +} + +void CANSimple::set_axis_nodeid_callback(Axis* axis, CAN_message_t& msg) { + axis->config_.can_node_id = msg.buf[0]; +} + +void CANSimple::set_axis_requested_state_callback(Axis* axis, CAN_message_t& msg) { + axis->requested_state_ = static_cast(get_16bit_val(msg, 0)); +} +void CANSimple::set_axis_startup_config_callback(Axis* axis, CAN_message_t& msg) { + // Not Implemented +} + +void CANSimple::get_encoder_estimates_callback(Axis* axis, CAN_message_t& msg) { + CAN_message_t txmsg; + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_ENCODER_ESTIMATES; // heartbeat ID + txmsg.isExt = false; + txmsg.len = 8; + + uint32_t floatBytes = *(reinterpret_cast(&(axis->encoder_.pos_estimate_))); + txmsg.buf[0] = floatBytes; + txmsg.buf[1] = floatBytes >> 8; + txmsg.buf[2] = floatBytes >> 16; + txmsg.buf[3] = floatBytes >> 24; + + floatBytes = *(reinterpret_cast(&(axis->encoder_.vel_estimate_))); + txmsg.buf[4] = floatBytes; + txmsg.buf[5] = floatBytes >> 8; + txmsg.buf[6] = floatBytes >> 16; + txmsg.buf[7] = floatBytes >> 24; + + odCAN->write(txmsg); } void CANSimple::move_to_pos_callback(Axis* axis, CAN_message_t& msg) { - float pos = msg.buf[0]; - pos += msg.buf[1] << 8; - pos += msg.buf[2] << 16; - pos += msg.buf[3] << 24; - - axis->controller_.move_to_pos(pos); + axis->controller_.move_to_pos(get_32bit_val(msg, 0)); } void CANSimple::set_pos_setpoint_callback(Axis* axis, CAN_message_t& msg) { - float pos = msg.buf[0]; - pos += msg.buf[1] << 8; - pos += msg.buf[2] << 16; - pos += msg.buf[3] << 24; - - float vel = msg.buf[4]; - vel += msg.buf[5] << 8; - vel *= 0.1f; // Factor of 10 - - float current = msg.buf[6]; - current += (msg.buf[7] << 8); - current *= 0.01f; // Factor of 100 - - axis->controller_.set_pos_setpoint(pos, vel, current); + axis->controller_.set_pos_setpoint(get_32bit_val(msg, 0), get_16bit_val(msg, 4) * 0.1f, get_16bit_val(msg, 6) * 0.01f); } void CANSimple::set_vel_setpoint_callback(Axis* axis, CAN_message_t& msg) { - float vel = msg.buf[0]; - vel += msg.buf[1] << 8; - vel += msg.buf[2] << 16; - vel += msg.buf[3] << 24; - vel *= 0.01f; - - float current = msg.buf[4]; - current += msg.buf[5] << 8; - current += msg.buf[6] << 16; - current += msg.buf[7] << 24; - current *= 0.01f; - - axis->controller_.set_vel_setpoint(vel, current); + axis->controller_.set_vel_setpoint(get_32bit_val(msg, 0) * 0.01f, get_32bit_val(msg, 4) * 0.01f); } void CANSimple::set_current_setpoint_callback(Axis* axis, CAN_message_t& msg) { - float current = msg.buf[0]; - current += msg.buf[1] << 8; - current += msg.buf[2] << 16; - current += msg.buf[3] << 24; - current *= 0.01f; - - axis->controller_.set_current_setpoint(current); + axis->controller_.set_current_setpoint(get_32bit_val(msg, 0) * 0.01f); } -void CANSimple::send_heartbeat(Axis* axis){ +void CANSimple::set_vel_limit_callback(Axis* axis, CAN_message_t& msg) { + axis->controller_.config_.vel_limit = get_32bit_val(msg, 0); +} + +void CANSimple::start_anticogging_callback(Axis* axis, CAN_message_t& msg) { + axis->controller_.start_anticogging_calibration(); +} + +void CANSimple::send_heartbeat(Axis* axis) { CAN_message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; - txmsg.id += MSG_HEARTBEAT_CMD; // heartbeat ID + txmsg.id += MSG_CO_HEARTBEAT_CMD; // heartbeat ID txmsg.isExt = false; txmsg.len = 8; - + // Axis errors in 1st 32-bit value txmsg.buf[0] = axis->error_; txmsg.buf[1] = axis->error_ >> 8; @@ -127,4 +209,21 @@ void CANSimple::send_heartbeat(Axis* axis){ txmsg.buf[6] = axis->current_state_ >> 16; txmsg.buf[7] = axis->current_state_ >> 24; odCAN->write(txmsg); +} + +uint8_t CANSimple::get_node_id(uint32_t msgID) { + return ((msgID >> NUM_CMD_ID_BITS) & 0x03F); // Upper 6 bits +} + +uint8_t CANSimple::get_cmd_id(uint32_t msgID) { + return (msgID & 0x01F); // Bottom 5 bits +} + +uint16_t CANSimple::get_16bit_val(CAN_message_t& msg, uint8_t start_byte) { + return msg.buf[start_byte] + (msg.buf[start_byte + 1] << 8); +} + +uint32_t CANSimple::get_32bit_val(CAN_message_t& msg, uint8_t start_byte) { + return get_16bit_val(msg, 0) + (get_16bit_val(msg, 2) << 16); + // return msg.buf[start_byte] + (msg.buf[start_byte+1] << 8) + (msg.buf[start_byte+1] << 16) + (msg.buf[start_byte+1] << 24); } \ No newline at end of file diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp index a20c1703..f1b63998 100644 --- a/Firmware/communication/can_simple.hpp +++ b/Firmware/communication/can_simple.hpp @@ -6,36 +6,18 @@ class CANSimple { public: enum { - MSG_NMT_CTRL = 0x000, // CANOpen NMT Message - MSG_SYNC_CTRL = 0x080, // CANOpen SYNC message - MSG_HEARTBEAT_CMD = 0x700, // CANOpen NMT Heartbeat - MSG_EMERGENCY = 0x080, // CANOpen Emergency Message - MSG_GET_MOTOR_ERROR = 0x001, // Errors + MSG_CO_NMT_CTRL = 0x000, // CANOpen NMT Message REC + MSG_CO_SYNC_CTRL = 0x080, // CANOpen SYNC message REC + MSG_CO_HEARTBEAT_CMD = 0x700, // CANOpen NMT Heartbeat SEND + MSG_CO_EMERGENCY = 0x080, // CANOpen Emergency Message SEND + MSG_GET_MOTOR_ERROR = 0x001, // Errors MSG_GET_ENCODER_ERROR, - MSG_GET_CONTROLLER_ERROR, MSG_GET_SENSORLESS_ERROR, - MSG_GET_VBUS_VOLTAGE, // ODrive-level properties - MSG_GET_SERIAL_NUMBER, - MSG_GET_HW_VERSION, - MSG_GET_FW_VERSION, - MSG_REBOOT_ODRIVE, - MSG_SAVE_CONFIG, - MSG_ERASE_CONFIG, - MSG_SET_AXIS_NODE_ID, // Axis properties + MSG_SET_AXIS_NODE_ID, MSG_SET_AXIS_REQUESTED_STATE, - MSG_SET_STARTUP_CONFIG, - MSG_SET_MOTOR_PRECALIBRATED, // Motor properties - MSG_GET_MOTOR_CURRENT_LIM, - MSG_SET_MOTOR_CURRENT_LIM, - MSG_SET_MOTOR_POLE_PAIRS, - MSG_GET_ENCODER_CPR, // Encoder Properties - MSG_SET_ENCODER_CPR, - MSG_GET_ENCODER_INDEX_FOUND, - MSG_GET_ENCODER_POS_ESTIMATE, - MSG_GET_ENCODER_VEL_ESTIMATE, - MSG_SET_ENCODER_USE_INDEX, - MSG_SET_ENCODER_PRECALIBRATED, - MSG_MOVE_TO_POS, // Controller properties + MSG_SET_AXIS_STARTUP_CONFIG, + MSG_GET_ENCODER_ESTIMATES, + MSG_MOVE_TO_POS, MSG_SET_POS_SETPOINT, MSG_SET_VEL_SETPOINT, MSG_SET_CUR_SETPOINT, @@ -47,18 +29,31 @@ class CANSimple { static void send_heartbeat(Axis* axis); private: - static void estop_callback(); - - // Controller + static void nmt_callback(Axis* axis, CAN_message_t& msg); + static void sync_callback(Axis* axis, CAN_message_t& msg); + static void estop_callback(Axis* axis, CAN_message_t& msg); + static void get_motor_error_callback(Axis* axis, CAN_message_t& msg); + static void get_encoder_error_callback(Axis* axis, CAN_message_t& msg); + static void get_controller_error_callback(Axis* axis, CAN_message_t& msg); + static void get_sensorless_error_callback(Axis* axis, CAN_message_t& msg); + static void set_axis_nodeid_callback(Axis* axis, CAN_message_t& msg); + static void set_axis_requested_state_callback(Axis* axis, CAN_message_t& msg); + static void set_axis_startup_config_callback(Axis* axis, CAN_message_t& msg); + static void get_encoder_estimates_callback(Axis* axis, CAN_message_t& msg); static void move_to_pos_callback(Axis* axis, CAN_message_t& msg); static void set_pos_setpoint_callback(Axis* axis, CAN_message_t& msg); static void set_vel_setpoint_callback(Axis* axis, CAN_message_t& msg); static void set_current_setpoint_callback(Axis* axis, CAN_message_t& msg); + static void set_vel_limit_callback(Axis* axis, CAN_message_t& msg); + static void start_anticogging_callback(Axis* axis, CAN_message_t& msg); // Utility functions static uint8_t get_node_id(uint32_t msgID); static uint8_t get_cmd_id(uint32_t msgID); + static uint16_t get_16bit_val(CAN_message_t& msg, uint8_t start_byte); + static uint32_t get_32bit_val(CAN_message_t& msg, uint8_t start_byte); + // This functional way of handling the messages is neat and is much cleaner from // a data security point of view, but it will require some tweaking // From 867fa5452a70b12d5278225ee6f0f6b3cfe5f7b8 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 7 Oct 2018 17:35:08 -0400 Subject: [PATCH 053/549] Add documentation for the CAN Protocol --- Firmware/communication/can_simple.cpp | 72 ++++++++++++++---- Firmware/communication/can_simple.hpp | 23 ++++-- docs/can-protocol.md | 105 ++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 22 deletions(-) create mode 100644 docs/can-protocol.md diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 8b2567c7..df19d269 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -28,13 +28,18 @@ void CANSimple::handle_can_message(CAN_message_t& msg) { } switch (cmd) { case MSG_CO_NMT_CTRL: - nmt_callback(axis, msg); - break; - case MSG_CO_SYNC_CTRL: - sync_callback(axis, msg); break; case MSG_CO_HEARTBEAT_CMD: break; + case MSG_ODRIVE_HEARTBEAT: + // We don't currently do anything to respond to ODrive heartbeat messages + break; + case MSG_ODRIVE_ESTOP: + estop_callback(axis, msg); + break; + case MSG_GET_MOTOR_ERROR: + get_motor_error_callback(axis, msg); + break; case MSG_GET_ENCODER_ERROR: get_encoder_error_callback(axis, msg); break; @@ -53,6 +58,9 @@ void CANSimple::handle_can_message(CAN_message_t& msg) { case MSG_GET_ENCODER_ESTIMATES: get_encoder_estimates_callback(axis, msg); break; + case MSG_GET_ENCODER_COUNT: + get_encoder_count_callback(axis, msg); + break; case MSG_MOVE_TO_POS: move_to_pos_callback(axis, msg); break; @@ -80,10 +88,6 @@ void CANSimple::nmt_callback(Axis* axis, CAN_message_t& msg) { // Not implemented } -void CANSimple::sync_callback(Axis* axis, CAN_message_t& msg) { - // Not implemented -} - void CANSimple::estop_callback(Axis* axis, CAN_message_t& msg) { axis->error_ |= Axis::ERROR_ESTOP_REQUESTED; } @@ -134,7 +138,7 @@ void CANSimple::get_sensorless_error_callback(Axis* axis, CAN_message_t& msg) { } void CANSimple::set_axis_nodeid_callback(Axis* axis, CAN_message_t& msg) { - axis->config_.can_node_id = msg.buf[0]; + axis->config_.can_node_id = msg.buf[0] & 0x3F; // Node ID bitmask } void CANSimple::set_axis_requested_state_callback(Axis* axis, CAN_message_t& msg) { @@ -166,6 +170,26 @@ void CANSimple::get_encoder_estimates_callback(Axis* axis, CAN_message_t& msg) { odCAN->write(txmsg); } +void CANSimple::get_encoder_count_callback(Axis* axis, CAN_message_t& msg){ + CAN_message_t txmsg; + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_ENCODER_COUNT; + txmsg.isExt = false; + txmsg.len = 8; + + txmsg.buf[0] = axis->encoder_.shadow_count_; + txmsg.buf[1] = axis->encoder_.shadow_count_ >> 8; + txmsg.buf[2] = axis->encoder_.shadow_count_ >> 16; + txmsg.buf[3] = axis->encoder_.shadow_count_ >> 24; + + txmsg.buf[4] = axis->encoder_.count_in_cpr_; + txmsg.buf[5] = axis->encoder_.count_in_cpr_ >> 8; + txmsg.buf[6] = axis->encoder_.count_in_cpr_ >> 16; + txmsg.buf[7] = axis->encoder_.count_in_cpr_ >> 24; + + odCAN->write(txmsg); +} + void CANSimple::move_to_pos_callback(Axis* axis, CAN_message_t& msg) { axis->controller_.move_to_pos(get_32bit_val(msg, 0)); } @@ -183,17 +207,31 @@ void CANSimple::set_current_setpoint_callback(Axis* axis, CAN_message_t& msg) { } void CANSimple::set_vel_limit_callback(Axis* axis, CAN_message_t& msg) { - axis->controller_.config_.vel_limit = get_32bit_val(msg, 0); + axis->controller_.config_.vel_limit = get_float(msg, 0); } void CANSimple::start_anticogging_callback(Axis* axis, CAN_message_t& msg) { axis->controller_.start_anticogging_calibration(); } +void CANSimple::set_traj_vel_limit_callback(Axis* axis, CAN_message_t& msg) { + axis->trap_.config_.vel_limit = get_float(msg, 0); +} + +void CANSimple::set_traj_accel_limits_callback(Axis* axis, CAN_message_t& msg) { + axis->trap_.config_.accel_limit = get_float(msg, 0); + axis->trap_.config_.decel_limit = get_float(msg, 4); +} + +void CANSimple::set_traj_A_per_css_callback(Axis* axis, CAN_message_t& msg) { + axis->trap_.config_.A_per_css = get_float(msg, 0); +} + + void CANSimple::send_heartbeat(Axis* axis) { CAN_message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; - txmsg.id += MSG_CO_HEARTBEAT_CMD; // heartbeat ID + txmsg.id += MSG_ODRIVE_HEARTBEAT; // heartbeat ID txmsg.isExt = false; txmsg.len = 8; @@ -219,11 +257,15 @@ uint8_t CANSimple::get_cmd_id(uint32_t msgID) { return (msgID & 0x01F); // Bottom 5 bits } -uint16_t CANSimple::get_16bit_val(CAN_message_t& msg, uint8_t start_byte) { +int16_t CANSimple::get_16bit_val(CAN_message_t& msg, uint8_t start_byte) { return msg.buf[start_byte] + (msg.buf[start_byte + 1] << 8); } -uint32_t CANSimple::get_32bit_val(CAN_message_t& msg, uint8_t start_byte) { - return get_16bit_val(msg, 0) + (get_16bit_val(msg, 2) << 16); - // return msg.buf[start_byte] + (msg.buf[start_byte+1] << 8) + (msg.buf[start_byte+1] << 16) + (msg.buf[start_byte+1] << 24); +int32_t CANSimple::get_32bit_val(CAN_message_t& msg, uint8_t start_byte) { + return get_16bit_val(msg, start_byte) + (get_16bit_val(msg, start_byte + 2) << 16); +} + +float CANSimple::get_float(CAN_message_t& msg, uint8_t start_byte){ + int32_t val = get_32bit_val(msg, start_byte); + return *(reinterpret_cast(val)); // Sexy int32_t -> float cast } \ No newline at end of file diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp index f1b63998..269fa956 100644 --- a/Firmware/communication/can_simple.hpp +++ b/Firmware/communication/can_simple.hpp @@ -7,22 +7,26 @@ class CANSimple { public: enum { MSG_CO_NMT_CTRL = 0x000, // CANOpen NMT Message REC - MSG_CO_SYNC_CTRL = 0x080, // CANOpen SYNC message REC MSG_CO_HEARTBEAT_CMD = 0x700, // CANOpen NMT Heartbeat SEND - MSG_CO_EMERGENCY = 0x080, // CANOpen Emergency Message SEND - MSG_GET_MOTOR_ERROR = 0x001, // Errors + MSG_ODRIVE_HEARTBEAT = 0x001, + MSG_ODRIVE_ESTOP, + MSG_GET_MOTOR_ERROR, // Errors MSG_GET_ENCODER_ERROR, MSG_GET_SENSORLESS_ERROR, MSG_SET_AXIS_NODE_ID, MSG_SET_AXIS_REQUESTED_STATE, MSG_SET_AXIS_STARTUP_CONFIG, MSG_GET_ENCODER_ESTIMATES, + MSG_GET_ENCODER_COUNT, MSG_MOVE_TO_POS, MSG_SET_POS_SETPOINT, MSG_SET_VEL_SETPOINT, MSG_SET_CUR_SETPOINT, MSG_SET_VEL_LIMIT, - MSG_START_ANTICOGGING + MSG_START_ANTICOGGING, + MSG_SET_TRAJ_VEL_LIMIT, + MSG_SET_TRAJ_ACCEL_LIMITS, + MSG_SET_TRAJ_A_PER_CSS }; static void handle_can_message(CAN_message_t& msg); @@ -30,7 +34,6 @@ class CANSimple { private: static void nmt_callback(Axis* axis, CAN_message_t& msg); - static void sync_callback(Axis* axis, CAN_message_t& msg); static void estop_callback(Axis* axis, CAN_message_t& msg); static void get_motor_error_callback(Axis* axis, CAN_message_t& msg); static void get_encoder_error_callback(Axis* axis, CAN_message_t& msg); @@ -40,19 +43,25 @@ class CANSimple { static void set_axis_requested_state_callback(Axis* axis, CAN_message_t& msg); static void set_axis_startup_config_callback(Axis* axis, CAN_message_t& msg); static void get_encoder_estimates_callback(Axis* axis, CAN_message_t& msg); + static void get_encoder_count_callback(Axis* axis, CAN_message_t& msg); static void move_to_pos_callback(Axis* axis, CAN_message_t& msg); static void set_pos_setpoint_callback(Axis* axis, CAN_message_t& msg); static void set_vel_setpoint_callback(Axis* axis, CAN_message_t& msg); static void set_current_setpoint_callback(Axis* axis, CAN_message_t& msg); static void set_vel_limit_callback(Axis* axis, CAN_message_t& msg); static void start_anticogging_callback(Axis* axis, CAN_message_t& msg); + static void set_traj_vel_limit_callback(Axis* axis, CAN_message_t& msg); + static void set_traj_accel_limits_callback(Axis* axis, CAN_message_t& msg); + static void set_traj_A_per_css_callback(Axis* axis, CAN_message_t& msg); + // Utility functions static uint8_t get_node_id(uint32_t msgID); static uint8_t get_cmd_id(uint32_t msgID); - static uint16_t get_16bit_val(CAN_message_t& msg, uint8_t start_byte); - static uint32_t get_32bit_val(CAN_message_t& msg, uint8_t start_byte); + static int16_t get_16bit_val(CAN_message_t& msg, uint8_t start_byte); + static int32_t get_32bit_val(CAN_message_t& msg, uint8_t start_byte); + static float get_float(CAN_message_t& msg, uint8_t start_byte); // This functional way of handling the messages is neat and is much cleaner from // a data security point of view, but it will require some tweaking diff --git a/docs/can-protocol.md b/docs/can-protocol.md new file mode 100644 index 00000000..212220d4 --- /dev/null +++ b/docs/can-protocol.md @@ -0,0 +1,105 @@ +# CAN Protocol + +## Hardware Setup +ODrive assumes the CAN PHY is a standard differential twisted pair in a linear bus configuration with 120 ohm termination resistance at each end. ODrive uses 3.3v as the high output, but conforms to the CAN PHY requirement of achieving a differential voltage > 1.5V to represent a "0". As such, it is compatible with standard 5V bus architectures. + +ODrive currently supports the following CAN baud rates: +* 125 kbps +* 250 kbps (default) +* 500 kbps +* 1000 kbps + +--- +## Transport Protocol +We've implemented a very basic CAN protocol that we call "CAN Simple" to get users going with ODrive. This protocol is sufficiently abstracted that it is straightforward to add other protocols such as CANOpen, J1939, or Fibre over ISO-TP in the future. Unfortunately, implementing those protocols is a lot of work, and we wanted to give users a way to control ODrive's basic functions via CAN sooner rather than later. + +### CAN Frame +At its most basic, the CAN Simple frame looks like this: + +* Upper 6 bits - Node ID - max 0x3F +* Lower 5 bits - Command ID - max 0x1F + +To understand how the Node ID and Command ID interact, let's look at an example + +`odrv0.axis0.can_node_id = 0x010` - Reserves messages 0x200 through 0x21F +`odrv0.axis1.can_node_id = 0x018` - Reserves messages 0x300 through 0x31F + +It may not be obvious, but this allows for some compatibility with CANOpen. Although the address space 0x200 and 0x300 correspond to receive PDO base addresses, we can guarantee they will not conflict if all CANopen node IDs are >= 32. E.g.: + +CANopen nodeID = 35 = 0x23 +Receive PDO 0x200 + nodeID = 0x223, which does not conflict with the range [0x200 : 0x21F] + +Be careful that you don't assign too many nodeIDs per PDO group. Four CAN Simple nodes (32*4) is all of the available address space of a single PDO. If the bus is strictly ODrive CAN Simple nodes, a simple sequential Node ID assignment will work fine. + +### Messages +CMD ID | Name | Sender | Signals | Start byte +--: | :-- | :-- | :-- | :-- +0x000 | CANOpen NMT Message\*\* | Master | - | - | - +0x700 | CANOpen Heartbeat Message\*\* | Slave | - | - | - +0x001 | ODrive Heartbeat Message | Axis | Axis Error
Axis Current State | 0
4 +0x002 | ODrive Estop Message | Master | - | - | - +0x003 | Get Motor Error\* | Axis | Motor Error | 0 +0x004 | Get Encoder Error\* | Axis | Encoder Error | 0 +0x005 | Get Sensorless Error\* | Axis | Sensorless Error | 0 +0x006 | Set Axis Node ID | Master | Axis CAN Node ID | 0 +0x007 | Set Axis Requested State | Master | Axis Requested State | 0 +0x008 | Set Axis Startup Config | Master | - Not yet implemented - | - +0x009 | Get Encoder Estimates\* | Master | Encoder Pos Estimate
Encoder Vel Estimate | 0
4 +0x010 | Get Encoder Count\* | Master | Encoder Shadow Count
Encoder Count in CPR | 0
4 +0x011 | Move To Pos | Master | Goal Position | 0 +0x012 | Set Pos Setpoint | Master | Pos Setpoint
Vel FF
Current FF | 0
4
6 +0x013 | Set Vel Setpoint | Master | Vel Setpoint
Current FF | 0
4 +0x014 | Set Current Setpoint | Master | Current Setpoint | 0 +0x015 | Set Velocity Limit | Master | Velocity Limit | 0 +0x016 | Start Anticogging | Master | - | - +0x017 | Set Traj Vel Limit | Master | Traj Vel Limit | 0 +0x018 | Set Traj Accel Limits | Master | Traj Accel Limit
Traj Decel Limit | 0
4 +0x019 | Set Traj A per Count / s^2 | Master | Traj A per CSS | 0 + +\* Note: These messages are call & response. The Master node sends a message with no payload, and the axis responds with the same ID and specified payload. +\*\* Note: These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple. + +--- +### Signals +Name | Type | Bits | Factor | Offset | Byte Order +:-- | :-- | :--: | --: | :--: | :--: +Axis Error | Unsigned Int | 32 | 1 | 0 | Intel +Axis Current State | Unsigned Int | 32 | 1 | 0 | Intel +Motor Error | Unsigned Int | 32 | 1 | 0 | Intel +Encoder Error | Unsigned Int | 32 | 1 | 0 | Intel +Sensorless Error | Unsigned Int | 32 | 1 | 0 | Intel +Axis CAN Node ID | Unsigned Int | 16 | 1 | 0 | Intel +Axis Requested State | Unsigned Int | 32 | 1 | 0 | Intel +Encoder Pos Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel +Encoder Vel Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel +Encoder Shadow Count | Signed Int | 32 | 1 | 0 | Intel +Encoder Count In CPR | Signed Int | 32 | 1 | 0 | Intel +Goal Position | Signed Int | 32 | 1 | 0 | Intel +Pos Setpoint | Signed Int | 32 | 1 | 0 | Intel +Vel FF | Signed Int | 16 | 0.1 | 0 | Intel +Current FF | Signed Int | 16 | 0.01 | 0 | Intel +Vel Setpoint | Signed Int | 32 | 0.01 | 0 | Intel +Current Setpoint | Signed Int | 32 | 0.01 | 0 | Intel +Velocity Limit | IEEE 754 Float | 32 | 1 | 0 | Intel +Traj Vel Limit | IEEE 754 Float | 32 | 1 | 0 | Intel +Traj Accel Limit | IEEE 754 Float | 32 | 1 | 0 | Intel +Traj Decel Limit | IEEE 754 Float | 32 | 1 | 0 | Intel +Traj A per CSS | IEEE 754 Float | 32 | 1 | 0 | Intel + +--- +## Configuring ODrive for CAN +Configuration of the CAN parameters should be done via USB before putting the device on the bus. + +To set the desired baud rate, use `.can.set_baud_rate()`. The baud rate can be done without rebooting the device. If you'd like to keep the baud rate, simply call `.save_configuration()` before rebooting. + +Each axis looks like a separate node on the bus. Thus, they've inherited a new configuration property: `can_node_id`. This ID can be from 0 to 63 (0x3F) inclusive. + +### Example Configuration + +``` +odrv0.axis0.config.can_node_id = 3 +odrv0.axis1.config.can_node_id = 1 +odrv0.can.set_baud_rate(500000) +odrv0.save_configuration() +odrv0.reboot() +``` From 87f3a1b011d0bf59e66618d51761931ff4f8c72c Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 28 Oct 2018 18:45:15 -0400 Subject: [PATCH 054/549] Bump comms thread stack space to 6000 --- Firmware/communication/communication.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 843f46b3..dfeabfef 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -84,7 +84,7 @@ void init_communication(void) { printf("hi!\r\n"); // Start command handling thread - osThreadDef(task_cmd_parse, communication_task, osPriorityNormal, 0, 5000 /* in 32-bit words */); // TODO: fix stack issues + osThreadDef(task_cmd_parse, communication_task, osPriorityNormal, 0, 6000 /* in 32-bit words */); // TODO: fix stack issues comm_thread = osThreadCreate(osThread(task_cmd_parse), NULL); while (!endpoint_list_valid) From 3a74ea1791848417b573fc7f573814fae3ea69b6 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 18 Nov 2018 01:05:04 -0500 Subject: [PATCH 055/549] Remove undefined behaviour from float->int casts --- Firmware/communication/can_simple.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index df19d269..07a19001 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -1,6 +1,8 @@ #include "can_simple.hpp" +#include + static const uint8_t NUM_NODE_ID_BITS = 6; static constexpr uint8_t NUM_CMD_ID_BITS = 11 - NUM_NODE_ID_BITS; @@ -155,13 +157,20 @@ void CANSimple::get_encoder_estimates_callback(Axis* axis, CAN_message_t& msg) { txmsg.isExt = false; txmsg.len = 8; - uint32_t floatBytes = *(reinterpret_cast(&(axis->encoder_.pos_estimate_))); + // Undefined behaviour! + // uint32_t floatBytes = *(reinterpret_cast(&(axis->encoder_.pos_estimate_))); + + uint32_t floatBytes; + static_assert(sizeof axis->encoder_.pos_estimate_ == sizeof floatBytes); + std::memcpy(&floatBytes, &axis->encoder_.pos_estimate_, sizeof floatBytes); + txmsg.buf[0] = floatBytes; txmsg.buf[1] = floatBytes >> 8; txmsg.buf[2] = floatBytes >> 16; txmsg.buf[3] = floatBytes >> 24; - floatBytes = *(reinterpret_cast(&(axis->encoder_.vel_estimate_))); + static_assert(sizeof floatBytes == sizeof axis->encoder_.vel_estimate_); + std::memcpy(&floatBytes, &axis->encoder_.vel_estimate_, sizeof floatBytes); txmsg.buf[4] = floatBytes; txmsg.buf[5] = floatBytes >> 8; txmsg.buf[6] = floatBytes >> 16; @@ -267,5 +276,9 @@ int32_t CANSimple::get_32bit_val(CAN_message_t& msg, uint8_t start_byte) { float CANSimple::get_float(CAN_message_t& msg, uint8_t start_byte){ int32_t val = get_32bit_val(msg, start_byte); - return *(reinterpret_cast(val)); // Sexy int32_t -> float cast + float retVal; + + static_assert(sizeof retVal == sizeof val); + std::memcpy(&retVal, &val, sizeof val); // Sexier int32_t -> float cast that isn't UB + return retVal; } \ No newline at end of file From b45e58858d03a42b0637e65da21ba2019251bf44 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 21 Nov 2018 21:44:36 -0500 Subject: [PATCH 056/549] Fix HEX counting mistake -_- --- docs/can-protocol.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/can-protocol.md b/docs/can-protocol.md index 212220d4..d3da2473 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -45,16 +45,16 @@ CMD ID | Name | Sender | Signals | Start byte 0x007 | Set Axis Requested State | Master | Axis Requested State | 0 0x008 | Set Axis Startup Config | Master | - Not yet implemented - | - 0x009 | Get Encoder Estimates\* | Master | Encoder Pos Estimate
Encoder Vel Estimate | 0
4 -0x010 | Get Encoder Count\* | Master | Encoder Shadow Count
Encoder Count in CPR | 0
4 -0x011 | Move To Pos | Master | Goal Position | 0 -0x012 | Set Pos Setpoint | Master | Pos Setpoint
Vel FF
Current FF | 0
4
6 -0x013 | Set Vel Setpoint | Master | Vel Setpoint
Current FF | 0
4 -0x014 | Set Current Setpoint | Master | Current Setpoint | 0 -0x015 | Set Velocity Limit | Master | Velocity Limit | 0 -0x016 | Start Anticogging | Master | - | - -0x017 | Set Traj Vel Limit | Master | Traj Vel Limit | 0 -0x018 | Set Traj Accel Limits | Master | Traj Accel Limit
Traj Decel Limit | 0
4 -0x019 | Set Traj A per Count / s^2 | Master | Traj A per CSS | 0 +0x00A | Get Encoder Count\* | Master | Encoder Shadow Count
Encoder Count in CPR | 0
4 +0x00B | Move To Pos | Master | Goal Position | 0 +0x00C | Set Pos Setpoint | Master | Pos Setpoint
Vel FF
Current FF | 0
4
6 +0x00D | Set Vel Setpoint | Master | Vel Setpoint
Current FF | 0
4 +0x00E | Set Current Setpoint | Master | Current Setpoint | 0 +0x00F | Set Velocity Limit | Master | Velocity Limit | 0 +0x010 | Start Anticogging | Master | - | - +0x011 | Set Traj Vel Limit | Master | Traj Vel Limit | 0 +0x012 | Set Traj Accel Limits | Master | Traj Accel Limit
Traj Decel Limit | 0
4 +0x013 | Set Traj A per Count / s^2 | Master | Traj A per CSS | 0 \* Note: These messages are call & response. The Master node sends a message with no payload, and the axis responds with the same ID and specified payload. \*\* Note: These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple. From 1df442fd9dafaaecad1a2438ba0599259a6cd24d Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 2 Dec 2018 18:24:38 -0800 Subject: [PATCH 057/549] move inertia to controller --- CHANGELOG.md | 3 +++ Firmware/MotorControl/controller.cpp | 2 +- Firmware/MotorControl/controller.hpp | 4 +++- Firmware/MotorControl/trapTraj.hpp | 4 +--- docs/getting-started.md | 4 ++-- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d77a003e..7a4324fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Unreleased Features Please add a note of your changes below this heading if you make a Pull Request. +### Changed +* Moved `traptraj.A_per_css` to `controller.inertia` + # Releases ## [0.4.7] - 2018-11-28 ### Added diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 16aef47a..1e2ad7cd 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -113,7 +113,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s TrapezoidalTrajectory::Step_t traj_step = axis_->trap_.eval(t); pos_setpoint_ = traj_step.Y; vel_setpoint_ = traj_step.Yd; - current_setpoint_ = traj_step.Ydd * axis_->trap_.config_.A_per_css; + current_setpoint_ = traj_step.Ydd * config_.inertia; } anticogging_pos = pos_setpoint_; // FF the position setpoint instead of the pos_estimate } diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index b39c2890..a12172a2 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -32,6 +32,7 @@ public: float vel_limit_tolerance = 1.2f; // ratio to vel_lim. 0.0f to disable float vel_ramp_rate = 10000.0f; // [(counts/s) / s] bool setpoints_in_cpr = false; + float inertia = 0.0f; // [A/(count/s^2)] }; Controller(Config_t& config); @@ -107,7 +108,8 @@ public: make_protocol_property("vel_limit", &config_.vel_limit), make_protocol_property("vel_limit_tolerance", &config_.vel_limit_tolerance), make_protocol_property("vel_ramp_rate", &config_.vel_ramp_rate), - make_protocol_property("setpoints_in_cpr", &config_.setpoints_in_cpr) + make_protocol_property("setpoints_in_cpr", &config_.setpoints_in_cpr), + make_protocol_property("inertia", &config_.inertia) ), make_protocol_function("set_pos_setpoint", *this, &Controller::set_pos_setpoint, "pos_setpoint", "vel_feed_forward", "current_feed_forward"), diff --git a/Firmware/MotorControl/trapTraj.hpp b/Firmware/MotorControl/trapTraj.hpp index 42dac0ef..dc548d56 100644 --- a/Firmware/MotorControl/trapTraj.hpp +++ b/Firmware/MotorControl/trapTraj.hpp @@ -7,7 +7,6 @@ public: float vel_limit = 20000.0f; // [count/s] float accel_limit = 5000.0f; // [count/s^2] float decel_limit = 5000.0f; // [count/s^2] - float A_per_css = 0.0f; // [A/(count/s^2)] }; struct Step_t { float Y; @@ -25,8 +24,7 @@ public: make_protocol_object("config", make_protocol_property("vel_limit", &config_.vel_limit), make_protocol_property("accel_limit", &config_.accel_limit), - make_protocol_property("decel_limit", &config_.decel_limit), - make_protocol_property("A_per_css", &config_.A_per_css) + make_protocol_property("decel_limit", &config_.decel_limit) ) ); } diff --git a/docs/getting-started.md b/docs/getting-started.md index d9cc3dd6..41fcfd7c 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -265,13 +265,13 @@ In the above image blue is position and orange is velocity. ..trap_traj.config.vel_limit = ..trap_traj.config.accel_limit = ..trap_traj.config.decel_limit = -..trap_traj.config.A_per_css = +..controller.config.inertia = ``` `vel_limit` is the maximum planned trajectory speed. This sets your coasting speed.
`accel_limit` is the maximum acceleration in counts / sec^2
`decel_limit` is the maximum deceleration in counts / sec^2
-`A_per_css` is a value which correlates acceleration (in counts / sec^2) and motor current. It is 0 by default. It is optional, but can improve response of your system if correctly tuned. Keep in mind this will need to change with the load / mass of your system. +`controller.config.inertia` is a value which correlates acceleration (in counts / sec^2) and motor current. It is 0 by default. It is optional, but can improve response of your system if correctly tuned. Keep in mind this will need to change with the load / mass of your system. All values should be strictly positive (>= 0). From 192c3260f8f7dfb10d362683d1c80b6d10657488 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 2 Dec 2018 19:08:12 -0800 Subject: [PATCH 058/549] refactor vel ramp mode to use new controller input handling --- CHANGELOG.md | 1 + Firmware/MotorControl/controller.cpp | 48 ++++++++++++++++++++-------- Firmware/MotorControl/controller.hpp | 24 +++++++++++--- docs/getting-started.md | 18 ++--------- tools/odrive/enums.py | 6 ++++ 5 files changed, 64 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a4324fc..0f8e8ba0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Please add a note of your changes below this heading if you make a Pull Request. ### Changed * Moved `traptraj.A_per_css` to `controller.inertia` +* Refactored velocity ramp mode into the new general input filtering structure # Releases ## [0.4.7] - 2018-11-28 diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 1e2ad7cd..fea22d5d 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -93,11 +93,46 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) return false; } + + bool Controller::update(float pos_estimate, float vel_estimate, float* current_setpoint_output) { // Only runs if anticogging_.calib_anticogging is true; non-blocking anticogging_calibration(pos_estimate, vel_estimate); float anticogging_pos = pos_estimate; + // Update inputs + switch (config_.input_mode) { + case INPUT_MODE_INACTIVE: { + // do nothing + } break; + case INPUT_MODE_PASSTHROUGH: { + pos_setpoint_ = input_pos_; + vel_setpoint_ = input_vel_; + current_setpoint_ = input_current_; + } break; + case INPUT_MODE_VEL_RAMP: { + float max_step_size = current_meas_period * config_.vel_ramp_rate; + float full_step = input_vel_ - vel_setpoint_; + float step; + if (fabsf(full_step) > max_step_size) { + step = std::copysignf(max_step_size, full_step); + } else { + step = full_step; + } + vel_setpoint_ += step; + } break; + case INPUT_MODE_POS_FILTER: { + + } break; + // case INPUT_MODE_MIX_CHANNELS: { + // // NOT YET IMPLEMENTED + // } break; + default: { + set_error(ERROR_INVALID_INPUT_MODE); + return false; + } + } + // Trajectory control if (config_.control_mode == CTRL_MODE_TRAJECTORY_CONTROL) { // Note: uint32_t loop count delta is OK across overflow @@ -118,19 +153,6 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s anticogging_pos = pos_setpoint_; // FF the position setpoint instead of the pos_estimate } - // Ramp rate limited velocity setpoint - if (config_.control_mode == CTRL_MODE_VELOCITY_CONTROL && vel_ramp_enable_) { - float max_step_size = current_meas_period * config_.vel_ramp_rate; - float full_step = vel_ramp_target_ - vel_setpoint_; - float step; - if (fabsf(full_step) > max_step_size) { - step = std::copysignf(max_step_size, full_step); - } else { - step = full_step; - } - vel_setpoint_ += step; - } - // Position control // TODO Decide if we want to use encoder or pll position here float vel_des = vel_setpoint_; diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index a12172a2..b2786bc4 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -10,6 +10,7 @@ public: enum Error_t { ERROR_NONE = 0, ERROR_OVERSPEED = 0x01, + ERROR_INVALID_INPUT_MODE = 0x02, }; // Note: these should be sorted from lowest level of control to @@ -22,8 +23,17 @@ public: CTRL_MODE_TRAJECTORY_CONTROL = 4 }; + enum InputMode_t{ + INPUT_MODE_INACTIVE, + INPUT_MODE_PASSTHROUGH, + INPUT_MODE_VEL_RAMP, + INPUT_MODE_POS_FILTER, + INPUT_MODE_MIX_CHANNELS, + }; + struct Config_t { - ControlMode_t control_mode = CTRL_MODE_POSITION_CONTROL; //see: Motor_control_mode_t + ControlMode_t control_mode = CTRL_MODE_POSITION_CONTROL; //see: ControlMode_t + InputMode_t input_mode = INPUT_MODE_INACTIVE; //see: InputMode_t float pos_gain = 20.0f; // [(counts/s) / counts] float vel_gain = 5.0f / 10000.0f; // [A/(counts/s)] // float vel_gain = 5.0f / 200.0f, // [A/(rad/s)] @@ -85,8 +95,10 @@ public: // float vel_setpoint = 800.0f; float vel_integrator_current_ = 0.0f; // [A] float current_setpoint_ = 0.0f; // [A] - float vel_ramp_target_ = 0.0f; - bool vel_ramp_enable_ = false; + + float input_pos_ = 0.0f; + float input_vel_ = 0.0f; + float input_current_ = 0.0f; uint32_t traj_start_loop_count_ = 0; @@ -94,14 +106,16 @@ public: auto make_protocol_definitions() { return make_protocol_member_list( make_protocol_property("error", &error_), + make_protocol_property("input_pos", &input_pos_), + make_protocol_property("input_vel", &input_vel_), + make_protocol_property("input_current", &input_current_), make_protocol_property("pos_setpoint", &pos_setpoint_), make_protocol_property("vel_setpoint", &vel_setpoint_), make_protocol_property("vel_integrator_current", &vel_integrator_current_), make_protocol_property("current_setpoint", ¤t_setpoint_), - make_protocol_property("vel_ramp_target", &vel_ramp_target_), - make_protocol_property("vel_ramp_enable", &vel_ramp_enable_), make_protocol_object("config", make_protocol_property("control_mode", &config_.control_mode), + make_protocol_property("input_mode", &config_.input_mode), make_protocol_property("pos_gain", &config_.pos_gain), make_protocol_property("vel_gain", &config_.vel_gain), make_protocol_property("vel_integrator_gain", &config_.vel_integrator_gain), diff --git a/docs/getting-started.md b/docs/getting-started.md index 41fcfd7c..45be4328 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -7,19 +7,7 @@ permalink: / # Getting Started ### Table of contents - - -- [Hardware Requirements](#hardware-requirements) -- [Wiring up the ODrive](#wiring-up-the-odrive) -- [Downloading and Installing Tools](#downloading-and-installing-tools) -- [Firmware](#firmware) -- [Start `odrivetool`](#start-odrivetool) -- [Configure M0](#configure-m0) -- [Position control of M0](#position-control-of-m0) -- [Other control modes](#other-control-modes) -- [What's next?](#whats-next) - - +autoauto- [Hardware Requirements](#hardware-requirements)auto- [Wiring up the ODrive](#wiring-up-the-odrive)auto- [Downloading and Installing Tools](#downloading-and-installing-tools)auto- [Firmware](#firmware)auto- [Start `odrivetool`](#start-odrivetool)auto- [Configure M0](#configure-m0)auto- [Position control of M0](#position-control-of-m0)auto- [Other control modes](#other-control-modes)auto- [What's next?](#whats-next)autoauto ## Hardware Requirements @@ -303,8 +291,8 @@ You can now control the velocity with `axis.controller.vel_setpoint = 5000` [cou ### Ramped velocity control Set `axis.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL`.
Set the velocity ramp rate (acceleration): `axis.controller.config.vel_ramp_rate = 2000` [counts/s^2]
-Activate the ramped velocity mode: `axis.controller.vel_ramp_enable = True`.
-You can now control the velocity with `axis.controller.vel_ramp_target = 5000` [count/s]. +Activate the ramped velocity mode: `axis.controller.config.input_mode = INPUT_MODE_VEL_RAMP`.
+You can now control the velocity with `axis.controller.input_vel = 5000` [count/s]. ### Current control Set `axis.controller.config.control_mode = CTRL_MODE_CURRENT_CONTROL`.
diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 19572c90..038b77cc 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -32,5 +32,11 @@ CTRL_MODE_CURRENT_CONTROL = 1 CTRL_MODE_VELOCITY_CONTROL = 2 CTRL_MODE_POSITION_CONTROL = 3 +INPUT_MODE_INACTIVE = 0 +INPUT_MODE_PASSTHROUGH = 1 +INPUT_MODE_VEL_RAMP = 2 +INPUT_MODE_POS_FILTER = 3 +INPUT_MODE_MIX_CHANNELS = 4 + ENCODER_MODE_INCREMENTAL = 0 ENCODER_MODE_HALL = 1 From e9082f5621fa145a633a223a7bc7016700ea7410 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 2 Dec 2018 20:49:17 -0800 Subject: [PATCH 059/549] implemented pos filter, but something is wrong with overshoot and vel matching --- Firmware/MotorControl/controller.cpp | 21 +++++++++++++++++++-- Firmware/MotorControl/controller.hpp | 9 ++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index fea22d5d..5d20a2d9 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -4,7 +4,9 @@ Controller::Controller(Config_t& config) : config_(config) -{} +{ + update_filter_gains(); +} void Controller::reset() { pos_setpoint_ = 0.0f; @@ -93,7 +95,15 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) return false; } +void Controller::update_filter_gains() { + input_filter_kp_ = 2.0f * config_.input_filter_bandwidth; // basic conversion to discrete time + input_filter_ki_ = 0.25f * (input_filter_kp_ * input_filter_kp_); // Critically damped + // Check that we don't get problems with discrete time approximation + if (!(current_meas_period * input_filter_kp_ < 1.0f)) { + set_error(ERROR_UNSTABLE_GAIN); + } +} bool Controller::update(float pos_estimate, float vel_estimate, float* current_setpoint_output) { // Only runs if anticogging_.calib_anticogging is true; non-blocking @@ -120,9 +130,16 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s step = full_step; } vel_setpoint_ += step; + current_setpoint_ = step / current_meas_period * config_.inertia; } break; case INPUT_MODE_POS_FILTER: { - + // 2nd order pos tracking filter + pos_setpoint_ += current_meas_period * vel_setpoint_; // Integrate vel + float delta_pos = input_pos_ - pos_setpoint_; // Pos error + pos_setpoint_ += current_meas_period * input_filter_kp_ * delta_pos; // Kp + float accel = input_filter_ki_ * delta_pos; // Ki + vel_setpoint_ += current_meas_period * accel; // delta vel + current_setpoint_ = accel * config_.inertia; // Accel } break; // case INPUT_MODE_MIX_CHANNELS: { // // NOT YET IMPLEMENTED diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index b2786bc4..10dc9a90 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -11,6 +11,7 @@ public: ERROR_NONE = 0, ERROR_OVERSPEED = 0x01, ERROR_INVALID_INPUT_MODE = 0x02, + ERROR_UNSTABLE_GAIN = 0x04, }; // Note: these should be sorted from lowest level of control to @@ -43,6 +44,7 @@ public: float vel_ramp_rate = 10000.0f; // [(counts/s) / s] bool setpoints_in_cpr = false; float inertia = 0.0f; // [A/(count/s^2)] + float input_filter_bandwidth = 2.0f; // [1/s] }; Controller(Config_t& config); @@ -60,6 +62,7 @@ public: void start_anticogging_calibration(); bool anticogging_calibration(float pos_estimate, float vel_estimate); + void update_filter_gains(); bool update(float pos_estimate, float vel_estimate, float* current_setpoint); Config_t& config_; @@ -99,6 +102,8 @@ public: float input_pos_ = 0.0f; float input_vel_ = 0.0f; float input_current_ = 0.0f; + float input_filter_kp_ = 0.0f; + float input_filter_ki_ = 0.0f; uint32_t traj_start_loop_count_ = 0; @@ -123,7 +128,9 @@ public: make_protocol_property("vel_limit_tolerance", &config_.vel_limit_tolerance), make_protocol_property("vel_ramp_rate", &config_.vel_ramp_rate), make_protocol_property("setpoints_in_cpr", &config_.setpoints_in_cpr), - make_protocol_property("inertia", &config_.inertia) + make_protocol_property("inertia", &config_.inertia), + make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth, + [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this) ), make_protocol_function("set_pos_setpoint", *this, &Controller::set_pos_setpoint, "pos_setpoint", "vel_feed_forward", "current_feed_forward"), From ace1b5de2decaaa274f1c2e872024266a928bcb3 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 2 Dec 2018 21:44:10 -0800 Subject: [PATCH 060/549] add filter poles analysis script --- analysis/filterpoles.py | 75 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 analysis/filterpoles.py diff --git a/analysis/filterpoles.py b/analysis/filterpoles.py new file mode 100644 index 00000000..abdaee2c --- /dev/null +++ b/analysis/filterpoles.py @@ -0,0 +1,75 @@ + +import numpy as np +import sympy as sp +from scipy.integrate import solve_ivp +import matplotlib.pyplot as plt + +do_mass_spring = True +do_PLL = True + +bandwidth = 1 + +pos_ref = 0 +vel_ref = 0 +init_pos = 1000 +init_vel = 0 + +if do_mass_spring: + # 2nd order system response with manipulation of velocity only + # This is similar to a mass/spring/damper system + # pos_dot = vel + # vel_dot = Kp * delta_pos + Ki * delta_vel + + Ki = 2.0 * bandwidth + Kp = 0.25 * Ki**2 + + def get_Xdot(t, X): + pos = X[0] + vel = X[1] + + pos_err = pos_ref - pos + vel_err = vel_ref - vel + + pos_dot = vel + vel_dot = Kp * pos_err + Ki * vel_err + + Xdot = [pos_dot, vel_dot] + return Xdot + + sol = solve_ivp(get_Xdot, (0.0, 10.0), [init_pos, init_vel], t_eval=np.linspace(0, 10, 100)) + + plt.plot(np.transpose(sol.t), np.transpose(sol.y[0,:]), label='physical mass pos') + plt.plot(np.transpose(sol.t), np.transpose(sol.y[1,:]), label='physical mass vel') + + +if do_PLL: + # 2nd order system response with a "slipping displacement" term directly on position + # This formulation is given in the sensorless PLL paper + # pos_dot = vel + Kp * delta_pos + # vel_dot = Ki * delta_pos + + Kp = 2.0 * bandwidth + Ki = 0.25 * Kp**2 + + def get_Xdot(t, X): + pos = X[0] + vel = X[1] + + pos_err = pos_ref - pos + vel_err = vel_ref - vel + + pos_dot = vel + Kp * pos_err + vel_dot = Ki * pos_err + + Xdot = [pos_dot, vel_dot] + return Xdot + + sol = solve_ivp(get_Xdot, (0.0, 10.0), [init_pos, init_vel], t_eval=np.linspace(0, 10, 100)) + + plt.plot(np.transpose(sol.t), np.transpose(sol.y[0,:]), label='PLL pos') + plt.plot(np.transpose(sol.t), np.transpose(sol.y[1,:]), label='PLL vel') + + + +plt.legend() +plt.show(block=False) \ No newline at end of file From 2df1aa9257a97a24be7c6ffe41f7a24c6f2b6d96 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 2 Dec 2018 22:07:59 -0800 Subject: [PATCH 061/549] mass simulation style 2nd order filter works great --- Firmware/MotorControl/controller.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 5d20a2d9..a5a19256 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -96,13 +96,8 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) } void Controller::update_filter_gains() { - input_filter_kp_ = 2.0f * config_.input_filter_bandwidth; // basic conversion to discrete time - input_filter_ki_ = 0.25f * (input_filter_kp_ * input_filter_kp_); // Critically damped - - // Check that we don't get problems with discrete time approximation - if (!(current_meas_period * input_filter_kp_ < 1.0f)) { - set_error(ERROR_UNSTABLE_GAIN); - } + input_filter_ki_ = 2.0f * config_.input_filter_bandwidth; // basic conversion to discrete time + input_filter_kp_ = 0.25f * (input_filter_ki_ * input_filter_ki_); // Critically damped } bool Controller::update(float pos_estimate, float vel_estimate, float* current_setpoint_output) { @@ -134,10 +129,10 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s } break; case INPUT_MODE_POS_FILTER: { // 2nd order pos tracking filter - pos_setpoint_ += current_meas_period * vel_setpoint_; // Integrate vel + pos_setpoint_ += current_meas_period * vel_setpoint_; // Delta pos float delta_pos = input_pos_ - pos_setpoint_; // Pos error - pos_setpoint_ += current_meas_period * input_filter_kp_ * delta_pos; // Kp - float accel = input_filter_ki_ * delta_pos; // Ki + float delta_vel = input_vel_ - vel_setpoint_; // Vel error + float accel = input_filter_kp_*delta_pos + input_filter_ki_*delta_vel; // Feedback vel_setpoint_ += current_meas_period * accel; // delta vel current_setpoint_ = accel * config_.inertia; // Accel } break; From 69d1364ce0457729e8ab3965d5399759100d425a Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 2 Dec 2018 22:30:48 -0800 Subject: [PATCH 062/549] Update docs --- CHANGELOG.md | 3 +++ docs/getting-started.md | 12 ++++++++++++ docs/secondOrderResponse.PNG | Bin 0 -> 19045 bytes 3 files changed, 15 insertions(+) create mode 100644 docs/secondOrderResponse.PNG diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f8e8ba0..33e84795 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Unreleased Features Please add a note of your changes below this heading if you make a Pull Request. +### Added +* Second order setpoint input filter. + ### Changed * Moved `traptraj.A_per_css` to `controller.inertia` * Refactored velocity ramp mode into the new general input filtering structure diff --git a/docs/getting-started.md b/docs/getting-started.md index 45be4328..ac274f6a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -235,6 +235,7 @@ The default control mode is unfiltered position control in the absolute encoder You may also wish to control velocity (directly or with a ramping filter). You can also directly control the current of the motor, which is proportional to torque. +- [Filtered position control](#filtered-position-control) - [Trajectory control](#trajectory-control) - [Circular position control](#circular-position-control) - [Velocity control](#velocity-control) @@ -242,6 +243,17 @@ You can also directly control the current of the motor, which is proportional to - [Current control](#current-control) +### Filtered position control +Asking the ODrive controller to go as hard as it can to raw setpoints may result in jerky movement. Even if you are using a planned trajectory generated from an external source, if that is sent at a modest frequency, the ODrive may chase each stair in the incoming staircase in a jerky way. In this case, a good starting point for tuning the filter bandwidth is to set it to one half of your setpoint command rate. + +You can use the second order position filter in these cases. +Set the filter bandwidth: `axis.controller.config.input_filter_bandwidth = 2.0` [1/s]
+Activate the setpoint filter: `axis.controller.config.input_mode = INPUT_MODE_POS_FILTER`.
+You can now control the velocity with `axis.controller.input_pos = 1000` [counts]. + +![secondOrderResponse](secondOrderResponse.PNG)
+Step response of a 1000 to 0 position input with a filter bandwidth of 1.0 [/sec]. + ### Trajectory control This mode lets you smoothly accelerate, coast, and decelerate the axis from one position to another. With raw position control, the controller simply tries to go to the setpoint as quickly as possible. Using a trajectory lets you tune the feedback gains more aggressively to reject disturbance, while keeping smooth motion. diff --git a/docs/secondOrderResponse.PNG b/docs/secondOrderResponse.PNG new file mode 100644 index 0000000000000000000000000000000000000000..7199b37ffa6ea5b41911226d42f7b5fe1bff4b80 GIT binary patch literal 19045 zcmbWfXIN8F(=HrPY>1!;2nragh=5W=x`6biAib${ks{U5MT&w*=+aS|^db;i08t_I z5_$pbNgOPcjIR(BMT{cw*|B$(AE6Kr%yRR*Q4<~J8HDqD1vPjB_j^Wwttb+UK$ESnHo*x|6*8#bG0*y|KUYm-7oYeyjeWc{EbCm{(#k*8rWnt(Gvb{N^)|A!AZp2(XiDu(>{@k80ir#?jz z5v`cnXDiIq<&-qf&~+!thQRwoK27@0@utkNJn2frJW69z-H+l0277YF*TqE*Bg_;5 z-Q?(i`*yQSXqT=Ou9be>*JXl)W|T`iA;$uPZRiI{o(__P!KO>j zATB_X|26aSs^#G1>~pcxoO|@&FS|#-+RQb*K2eU?v>u}v8!9s9cev*=?tn^^c}2jt?J$O4 z5Df_lN!sL8i;qnbv2SmQxVri^zPkFJ-N2Wq3Fq%rIZLy&+?pdcd{^(jKr)Fu|5);< z>V@G_?L@C&bG|| zLBi$L13FapLoP5sXKCD>Po8%z?=Q(S_a?4Qdi3lHU;a%yZoHM z!k}ixt-cL?cM-lL^pPA!Q+Nh}m=%Q!u(367{cLF&lZwyLNLF1PtL&QPH?39PCCqir z7FGz{y45LJtXmMjn^L%C|9g07X_B(fn`}ENrRcUiPVF$hZL%oG#$GEZSZ$MaxSfz% zc!L~vk{c-DuPFvwd(RNPzgDN6tI51+Aa53Yj;?dIaINv!Pl&TX7%yT!)G4_$*6823 zJ0@P~4R5&8W?Uz#c}ifCkFQ0sT}X-{jo~kjvBAjWT6aC`MOeU-=^nTiLxkVonb4di zb&I9`>@Kg9>wFfq0gr8%o$CK6*6jT*=rVew?-CitoS>GxE@a(V z+GD*S3hx=lR*KR6h;hxk6s>Lbv)H2FnsBNHwSAs;Yb%pQD2@_#hQ214fl4J;r$4OT z^tt;5D#WmLN&G~BGYR~as%kD+y!ujpz zZ3pw?marEky6DBdm$E6+-5;#yruzxD>K5q5?W~R`w=&G;BNIM;ys<|4Me*Im0}w4`~!o{E|B+wa<1Xwii`%`^wkQ+JNyyq5V~&2&om19~r_FLA1V z3~XTQrTsILFhay4fs^{#J<@f|#PqA?$;L!+d(0`Ap=d~hu*%aphVN{^KN)5EqK zs_$f6kLbC8TL_$J4sVFGpT7CqBX1Wj126H~{@q_@p|r1TIo04xI3!MWs0pAseugt7 zNc--M*p$2KVm7%Q&mqjde!ifbEnt(FL445@T)wc#vwt22d;OiKzB<*j_HF?Zi44~q z*8F(;+gh8ZjJ^$@t30NITQo$Hu2RN~g_=@Sw!|w`%vziQ7I6R9I@q3OK6^{K*65~> zcP#K@_9arNhUuRS=EA(z3H+wd>CF}$Yz-OA76T)IT}}S^x$$VPKyqiqg7CYbB*|6m zB7x9W1lMAgPUN84GZNXk=_jV6w=U+GP6lf_-u1@pPrsqgLTt`kYc#F(->|9{IqCcUeM(RrDxjjt(T0BUq2Qn)N;9UtbxU`-wA@@9|N z@N+RT!IIhEG*6_RLXV;8lP^lcUV@pT2}G6skEq@lHMJgO$O-+A8M%~#nkkxPJ-`P#2G!StyJH$dC9|`2 z(|$`&okWYCDvEWZe+GoPe>+sNizS9zt12=DtPl^4T-un=bk^fNm-w?@Q=Jy@Q?RDP zGl-yiIl3EgEsYNoPI5RYI2PEJ2~ja{jql$zWw@5_4%Uly%We7VY;Ls-HEi1Kh>39O z{FUog9r1DR-=D8*B{WkCD9^S;bj{}JHh-UsdB^W>!eDE$_OJSgMX@>xQ~WcBguj1gT?TdaRbIWSo@Bqo zz+kgC-h3gVR*Lc zB;nryuVVm+W|N4_rM&63U3jWqGgW*P{fyr1GOwPxoV>ghSit%9dyW(LAb84nir&2bQfuSaQa-Y6i4Ia7h7SBsHj(6Rl{t6O1c*NeS~E+}ajE3EKcDQmTyZhF0A z2q_SpzSt!G9$z%(B$}vHBtl#UpvxZE-mr6EuLr{Mc~p1)xR@_9axH2YqNdg;U`?LD zwF4`a`{XR*_xQ#t7habtmGAVgo)I)ad8huJtjp?f0X`{trY%$JK(owmyops5u)ml8 z=bF~4Wb_TXmC4(R#Uu7$(H?I7cT7rzDoPxSg#6PbS{q&5DOkoAa3~-n@$CvhP_Vn5 zcS`BD10`&O$LZA{$L-*Wr8OpSTFA$!1B@$8AzX{fq`o7l{^qK6$G2A@?PM?o0ZvYZowZ4O z#eCZuQL0z(-o4uqif4xR$leaE-+KR*@EJX}WBy%F2omC~@3D%8MwdHBczo>gnY096 zc{3xUq|xt1CN(bF<(4Wn3u)e2@}U>37l+i$-6n||sPldH&xZ=_b^$n{{w#i!enB|g2GcL`P8;>46B7hy9*pz@oJ~4-Dqa021w6nu*nKw)b z``jQ-p<)&vkZ#v}^I7^Lj-Hm5b_=fM5GI9ibQ(8&bb1w>s%h&aa}x%FTNXbD z$AjYPU*~xA3P0{%p{KV_r+fGI?d>VI7cUAYE$qfB@&<}c$0gVLv%_Nr%yE+4s{7@B zMTJ`|GKaNd#-*W>gBCmVw1lGpMB0yZ6_VV>K<)@;3B9%I*$bO50g4fH!7KBa)>fQn zm7Yi;hSURq_R*rmbIMo$_duQni#?{`;io`E${+v&PKxJTzboT{IkR&*gv|MbWd$sM zoq|qub=*9|aIhrRE8qQ{%E()(;_lG^q9?Tmi9B@Ta^zUo7Jd%6}@JbC6wDWF$mWc4AAzqwbM-VATPta4Qyvp-cr zpVTJ>bfZ~(9^r)Ej-T+`UtLiBqMjtIai@=PU-R{h z2lI7#KDi`na%Hz$n9e6=3_A?blLU|8l}|fWNzZ45G4-6ZI>$Way^hYgSN>rLfe42$ zzcqVzi8D(vf{AOz%KhN)OT=u1sJfJt!`{bdm;|TpmN#7Z$#Jic7w;ob(4I)H7R1+3 zz}K5`td&D2(R;q}szzS(njFBLGn1l1hMYR{$5g2ffw&CLI>>%b)01L38w)*!WTgI5 zhhh2pUptauWKj6mW2o3-xZ4^q$F(TJQrG@J3l0%i`+(oS>{h(X^ zGC{WpI5kjBIm+R_GcTx4qN7nGbaoXQ6vzy=Q# z7~`^&9h?vzz?zot!g{WPvE@i(rxO*8jMGFG>(u*&zNDUp1qjd(t^sJY(A&n5*WW!x zc(GVM31O2Ps-!s&_Fw)qqb1x#rE>0TrIZ1+-hTv#0_M4NN5oD$@gh(KV^tC~@r`Gs zJ37A3ItNZ1{{&G;&LKTyasxi!hvu&HLC-r1k^Bb7ky}3S$FOr(NsmC_%+2mFU*+pL zl6XxT=_YBUz%pyC`FNX-5yE*`0Gli{2JbzGTm$1YsY&10DJoApg#J53$@F5pO=PH{ zahiK#*(jvT;9zK7^h=vXS%I%gLoSuWxkyS5CM}r$hBOncU7*<{PK`rE>N|7?MFi=N zjSpO0YTFlF)q9sw&Y>hJNh?|6d{BS4C8(a@VBKEK{*I(0Ws;oDpNvyPnCkmaN=@#m zqwa&`1VuP$s8`Q^X?C1&0FYc%@>wKlsB_Q*mpDflG_(fQx2Kh&{+ss&5)P)Jec9$AJuX14fef^Vijf|6QTNaR|u-9Jn}O#zFiFg)G{&Zgx=o znGGV3a*tJ`{X$)PK4Ia`BNoRF#;cQ)$z6lYx=#H z&C=d4gX%vIa{JVsMbxsl%$$U#t-c0q1XDL;ni2b}C@XOK%WZmC&Qen+q)K>>>A!Vb z9?~doJn~As_ww@t1%)>I60|kBxtv^}Z?H1(HvnGUEce-U!EU`g+}mFL%AkbdHL6W; zKufEEXY>5QM4IG0n54+4GCDWSt6f?=y3Z7E^GZqR)Puag_$C3mX-=!C?4M$mG|9Y*PoVmgG}b zL+PWx7KnpCtz8yQ7zyX;0I?k};-qIb0C@9dA@%3zZhmYvob*3F><77;{TmXdm`~=S zHUL&inK~FBQ5Gl@dh8)2HLVRC)TH~cDWO|peMBk#=x|?cwA`v|wmGTTilfJLkFO%f zfhYPrtSK9N@T(R%T>A9;CI}9tyl{O||B?mC7ID$h(aX)0rm4%>;kPRygBgxwa~;qM z(O0Pre^&5;oeW@y)AsK{?Lvb)LUXkitrr-jyedpwyF>(^58T*4R}d5yQVXy`;-#BR z=UsV8VgCQPkzY0**EErY9bA>nKZF+${?ZDggU!(pa$j{LQv|!Gyr@`LJU^4zyYx2A zOb38;+8t7W&hw>kd#hr}A`HHt{!PvEKPG>Fkq5Hh_g*?+Tu;cQNmznFec0dNX&{s( zO0+WpZsI+3z!q45MT`PyE;%FDeGB9!!J32L*V*#WQ6Y$xY4At;vyk;hUj6s|qVLBp z#o{}%C3+pxzVAwYcQ#wGsk`@%T3kmadBE zf9Ly2^qqpwkVqN-#u0ter%6k26MdhmbP4*JWTstZCaE%K?%RvLha9FVQ{B`5{{Cp@ ziBI(7_1mo{(WQyW&<%kk``zQ;876KkXI2A@tmXbipuB)-l0cgRMcjr~x)lb5nII0* z*CYqGDNx&GnCe6i^hR9Dwt1*W0i&rWNpxh>I`iObzup_O^o>=>!hQ=SX*!)09h0p1 zbK*x*kHzCsYF0-ZLyGQhgOxeMMN-3Y)ozmt z&y;n}S+7I-wNjwwGZ+3{$n_rcTJH%Tc{u>==k*QJq8v;?3FEKnL&+IL=hhl)PXLty zNiaa*w(VhkEjr<222s1wYQ+iU+0H@Z)Hjt8M|kosiI1N|gRp%J1Su!^NoM^?VjXE6 zbBZG&)OUuIGOlmJ`VIj&2>I4=YECGoImtr$`Lv2!&+YxO=e-4^7O#J#YgPj9N@GCM z(CZ~=tBkrgob`%0RTArVj`IYJoQ8xnoE_drB&eEq3(5+-Xt9Qr)I{=`lrOM;;UaLY zaXjV_(&=lG17!H?)mcUV?6Iwo&fOnB#-V^p=HKKUn0ve$Ky$L|e@dOjqLDA4oJ}K1 zCxPGFvK}LH7UXERj)t5PN8!NN`71$tkCLVC1BYtm4se_M&RZqcLDb;N{2r14pyY;c z0MLy{v9-0e5&D!^+cZQcIfhMKBkDJrDKF8|TBKJ#P*PF@ro9V+UYm`SiI0uFy#E4Z zp#wX@Ok43s0@gs&+bKnm@JDTf|4c=(iC7*0rd7z{dW37u;m)`ft{!oTk&&lIa`XM; z5;N0&Y?0mFLxXHpI&8_pc5S&;N79p5JZBK!7IH=Q)iKze&9Rlnd*Co@nOr3RF|vJj z30xV2g@&6ZnDEHR1jv+DzntwX3yX+|n>t+c6!hMC;VKDI?XHD~W)`g<;y|*`q~AOi z$&gZSoWAr0*KXPh9ma!jdwDYuIA?=g$o$J8q^SRq&n8vt&!34F0?e!S!^NqHi7E$U z*OWN3l62IQZ{&3gzkpaO>#g4Y14gu4`JNxyy+5RtgGHZ~0e;ABVD;N+*4V5p4%ZYB zd<2V>QE~0>o16Cvj4Ge2e6{ZCoCW#rq}i0M&6f9!7ObgS>)TlF=J73Q896iOe!42E zJ$@lsEn>A)lCm8gEWp3-;8#8p=&j&e@F(XIGOe*vj{KI5%Gr;o<3m3e{P70R*+APk=m(onOtwHJuE&030s+`Sa)U zN+nj`$54dI;>}?8o>c~SbUq41KKR-VxqYChNjGRo$B424ia`MI3hX}&w7CTd>}KuT9+VOt!XcIhUjWTPt-j~mjXlA2tn zhrv*r9U$i+C|nxHZ)#NBu=iYN%V2%3Lp2;eT%g~RGQ7OymHGjFIY`n{#6!0NSYX9l zQx?DiPiy*JB=`g)LsPtZ#vMr}Pmm}2SsRdq4MN3mZZvDjWb6%)8r$;#0SQS(BqSO- zVOxgRYAq3xEjeHivRc~7BwZsxwNOfM;E@2C*FA$$afOq~=qzx48Wf?E^(F>i;btfN zDjQ@w>Yma9hQgSKXnP%#x;_S_@3RHLG*EQ>P_aBxIhcOZjguO{+=Bsq0oB* zR(%4pFx0!BWJ?_5Gf>;t!B|it1vn#2$*f_Z8NkbU)JIxq_OMf=-zLbf_u2P)CZ6@8 zu1e-B@L)xO{rvDR>7HtncWrirG3pMD-h#h#>W1~JZ$TC|pqb<}l3My5zqR%;y8 zh~_{wYsU~o2=pX@C^Y0viH64A6zHLtyUJdtphwssiF4!DclI6S8XDe3w8XQB6crVc zsJH)JQx_7wh6I=&WNm@5Vo=>Fe=Hs@4s=7)6Ol`T<>q$mgF4(J&=NP3fhG~#zd z_NdjiKjpz z*FnY^EX_u?L+B-ZB8b#8a=mdMjF}z4wP|2$plg*eDtS2u|Nm5p~H5Y|ugF*q*?8;m9L<1Hs21p6s}0=y)P!7lkP5? zU7{V~0u48eQI3^8M$s`bbD5FS(aDarD#bPPOo*9a1`aN+7HRu;A3ntIZ7(NpdQG;n z`rqQ=;n^7tXUBhIU0h84@Jsi=1%LwRcnREUhXrITv?KLFsgLpQi;piRsvixNmk?%lSCyQ!<*m^QrJ9?xeV_|)toVOqAzf7A66)XXmnn3;fzu+0ZukX1TZ^K6WP3M{iAF`Q(v zN@SAoD2%=Fh^@!pP>$t51LVQBlIk?p9HaoalkrHgSKM~fGJ)N9{u7Qw0UW)lp{Ssc zIJ>*KkUR?t%&sfCph9N%e{n&rjBx5BRbuU+E(-Su?HN)sVm*=tXf$DP!>~{KrtmkTw#AElr?Fv zbR6Y`@)kn;b3AJo#hj<_7gGvUOsO$EFWn&jwsQY~arenU$%5iG%c`VSPT_b@^Lf~r z`yiNGZ#-^*Qk4MTaGa)QksR($5wjp=y&g3H7pTKFH)@mKRT=J`ba z_7=|L!4a__c%c~wYE=AzDA@dbs;V`){@*9l!fO&HSMlKByn}z zCcV=3j%86PowDa|D=Lm7$KXJoLytfZqH7{TB4 z@o1b3k-vZ7`c#WmEA0IpUN`!!DxV#G4H8PkBFUsnS3DG1A|{$xvG()#`td@iBF+$h|sZTkn@tP zDVt=Q?t5@+=3<6jQ7Un_*u9OMoCey_(Xr15znuK9Bf^*-BtF)$fKtjpiR`KK?WnYd zY8{>(T^P)qS7>hbPSmBc%^TZ0ungqCUv%VF?kJYLmv*{|vW}$a_n*-e^AT)o zxCDEM{P(cm#%O2-;IyI`kSI!}^d<(WC$z}xC3^35XFUv|fSn^{jHqv117$KxP;Wrw zO_jgM^fr32+ekZfz-+3wHnxDU~?^n+_ta)8CMxN#oM( z(gOgv@}jL8O&t7J?&%+GV!aYkOEkuLWC0-F1$KYhn+ZSfMV@-9A%)$iwys6u`|!uX zy3@0U*_8h=^f1r9yfKi??5eVh3=OuS^cnlTW5x!2LKI!y2NhK&Z0&zyNor>SzMT3y zcg=tYG6gY)VHNY;3SCDDH5^N=YRIigJ`gEA^kKZ;OTq(WN3$RtQa=z;GOozJqfLGDtT_ZBglu_u^isD4%1gU$?m}t2fXc$K0t~8$VT5}b3IWk z>c~gD#3UvkkOW?=559)e%WNg5J=hbN;GsmZ#DC|GscxhW;QhCarsI&Y&v$wU`x-#S zLnPCyCRo~m+B1dM>AVpS$QeZcvEON{_2uq7s)tRhk+v_)@Mo%O6z6!Pa$~Rr{(E0z z`&TK3b}5D{f3P|Ge~#=&o-YYne0^`s%xAxIP1O7ZER39_r>36(0f`p4wibuo-CsU( z?LGI^MHJP$;6UyEkzHWhm=`$fo<;DT)@o0QIBnG% zZQAdl8Y|3LW%QhAbPL%0IQzwpq9r++$yWW2?>^EqqPx#`^cc&0B0DFX?+>j>@q0(6 zMnC9D|C#^fdMQc8uRZs8@GQ>YR3f~Nul&B0d z?-tg^xK@N=qlTq%9<(8KCY<*%;^Y6UM-}m`ir{wr){h5aSX+3+H)`4>aJQnDl4R$Te3n9cC zxAe-NLErm$GrJyd{c=#^5ESp8Pq8O;gt`>i* ztjFXUocex7e{VK%PxP2R>3Z z1zTyT$->T|qHQ<#?I&8jLs43M!RY!>~*pN1Jwq(y|**AU@buF60 zm^%4-L0#>wDMV-Ne&0rehf@l|*KOeO_ae>1nzY#39)4JuS<+^=WKH)fiTnDpZ(0z(nYI)T6|sx! z395<0cF~g-fNP)ymFRXJd4PP+{JV{+j~_oaQzJUO!q5Af;d{C2PQaY+FwwXLZ-9IP z?Oov@+uMDmpS{o7XY>VRzE;wwx4+WAPwSrja$zaCv)zhHhfnfb{%)kb#z=qBlH0}b zkUPxp2A7+^3@ne1fF3xzB+wJO6?aUz ztNA;0D~dtht3wTrR^Rtl*4kCV=o5^VSqNUsYlNO5)9gpfw-I0684v~THi$ZRZoB^4 zH}$i28pffjLR#J>?hnD6#5 z97i4d{Yms$vxA!-7&q8%I_(lVw6Z4?_&5i!;uQ{XW~n0AajmTITz3cJJ2&b>Wv^F8 zT3Y+HhoSuiho}3;u?-H#4O>ea4L%EYz7oO-erZFEv)+PDSii*)^;2GB8Jc=m`}6Y7 zuWl9Rio`uli~KIp$j&ac+S;w5el@#Djh1n8&Rnns+u}7=&F|Flm){97Hcp6Hm<=jz z$vIrHqy3JHRq;rEf#NZ}iWo~KgfC=P<(W<)?Q#yWJ}G1S7W?LIGrb3wkZ^&!cZUvs zvazwPuz=!)X_j2@Ij{>L0PpHjB}au|Hzyo?6Mb8_=;R5a^573wvN^*!5Pmu7;x{if zn&baEMzI?!TyUfrZPc?|cZo6$e^Av!J1Qt|Icpg;<#*}bHO_L6kC^<|E*=PT6>~yF zR_8jGimQ0rlHsT5dqtHgDF>`#e_ScwT|It%jTNi@;qY(OHRhw7Ys`e&Ym8fA*LG)| zT=4sQE?MTLVgyq@F~X{l%T{vkoNWpBoIy`3endYK^M{36#RQdqX{xgPN2qRW5I<4n z*@BBpKGxJFXLdDuVCJsMnsY%`)$yCGN&!B`PohTaaN))m`{N0yDO3@ILx( z-B;sklV?!&**7*-OOX)oXMrBEFhfbet}AEdBFOdYm78UwEF_9DDxKmm-4uFFVSA&r z#eTxyRO5Yl%S)3n^$(*Giyq`u(IjrAVg9zTWt!76{`rhSinGZKTwWqpV^GxZd=3MY ziQWp$YcxxLLpO0x-sD#jgT7r5MWlr$D~M0t?9VP-K-B1DEt|4Uwj1_f_0yXAu$xDJ z-L0a`sJ)EnqMY)>wu;vf(#JRY7cNGvA93^(@ASAH1SubLD>NL3V*+xeib57@j?JT9 z_q317_Oy+DtJLVYqCJdFOW69ft*&-eZwkw#{ClGjF{YN2ecsif%%4qOk+Y?u$1>gSIBM|^ZpVC7q8r=d&+pxWY9I25q`u=Y{CB9_ z`?=@7jQoGLt;~ODw2$`vE00e6riLHBJ`HosW`D|)D=Bl0+91>Xj{O6VAmdC^;K&u9 zJ)({hGY?a@=MgN+TVNOoVf${rQNJr=5qyG*n(3zS3;cODg-PKp+mR&?6rHp1fbG4N zeH*P;i1w58e}XPEnoT9S_uUpNj=}`_8BvNDLd%5jK&+hj_uQoM!LQ4ky<057&FBRVn&a4WAaY9;V3@B#B(g{p4&Q74@j(?R$SL?Um$r>W4p= zeq#G1skE6s3*RqdcBJ5@(ipc6UopCI3EjNMY4knnROa6+yyurNPoH0!IV}s%kWWJe zwL{$SvvKi!#z~iMdK10;s^?^*f+VvWmwr>%a+;4b!;nf$dYa6%V)Z|nD8}x!CE-kZ z6)eYchum^PI8)8`^8-b^+|4kxKXP>Z#k+FZ&Bd)mtE=c*{Jf0u36VvQiYT74UG#l0 zbiQ)U4!fj}9@bFG-SQrcx^dW`(_`?B_Wo5$M4F5omahjW48gKf6jS>+mYAoZnZEb*# zB)|UzyYHm6@>S2}IkNxJ!P0RR{gb7wFPg@`FI#s%nICYo)Npz~?2U2D*H!v6?^%^2 zw0$Iy8ZT9f+jHG+5D#Rcfl{1dxA05U<19o&nJ>g!UnR)?@}Y?+1IwbDO*b;$7@Mb?dWm^U-#*J>&xS4B+dy<<8ExaG(}43S$@GIEK^Q3;1xT<3 zm0O{;xK{F}LpNbfr^PAh;o7-pE&hde1JcwBMJAzUnc?TR{KTjNW|)iUJ{sE?u{+i( z2mB<1@smafk>o4SllkkOPZ8j4Yj+OKy*eDFXGfE&J$T)-%zMV|vZj4_#6hwyRwLKa zeavm@dBF+=*@gRX5J27NM|%3`&tH9m6F8A!YZq|`t>oB6Q?Qmeq=+4}6Z>vFG_*u- zKNF~#-R)o>M;Jkm7~TG)Qw5ur2Z50S6Je`C60NT^b+4lOMnLnYnPtpf`^OGu-cfxe z)<;uy53#AVZrgPsGN$hN?jsdzOqnlXCZOP(p)8-)PoUTf`K*g2Gm%C+X`yYJEOAjMI1F1N)KBjEwDl%UG9<7ZhIVO&NZg79f zjh8>wW!0}|_o-&`8f6i$K_#Zd(KlPcca)pyJ`Kpe+#rB0`!TQK&Qi`GOfXgaW;r4s zQ#dqa>*=`v<+cQG@Zf&OVWjV{=e#E zQdt)(d`%7Uxu^&6#7fneQj7Dj6p-AWjzOaA!nWtLtr!EIe5SlsK~s7Zn0b8UdP>~E z_ju*3FJFO{GQrdh#GKUZhXlJ-jyjyid#KdhO$#hr`>NQ7(?)#1GTwe58pzXp(kva zWv>y9oZd<>jHCvHXLA{bb8(5t_%rEIQZMHm)s_?t1)hXmJ`2L%^kB*Dj@F^8S@Dsu z3FSAbLq*BG53)K|N8gx6?mx)!xlwKvqTFt`PYcr9?~n^kxp1d_$>lCW!MV$>!QP9H zS9WTK4nK>%G3`+uQZ+zN-dD;bgGqx-_%s%o5dX}fHOnc@$eMhE&Q$Uuy5DGftjJ{N zN$%CXWjQ$CgH8c<*fd0%lBPoZUhpgw?H6-cxsY*&dCFn16kE<^_$F2Jr{-~|(LLGH z6R=^BA=^M;HrM?6>XJl~$S+sL>aay+J_s4;Vp$QNvexKZOX&$SfNju1BT^&wLdKWq zmo@b4Bglef{a=p&c=nrHZ0##Ar{FII`%M2s)EU?YL{S63eZB(fFB24|VDhNG&v1@N!9%3^(OVhmE(hA z%o^u}&1o1}AkY*YTuao7=s4l$7CL#>gw3?*aQs!2tbf;ekFkdR7);o@LidkR29Q9d zfcL!Sl}qd2|GY(ouoI{|fyw;wedye1NxRJ{iNoxfTvXvB3HmMA{U)FQXAO+8)QFcW z%iqFDGLs*#w}+mOeDKGd_cSu_e&iVyB)_8o8X=&KmV``j?%m`vZTHHYw|-^v7N>@C z_<$+(O8lmotl8)I@rlMLIV=!VyVXZqM ztFYdh^2a<%69s;^&Qe(gpY@Y~-G`_SAnPqLZ|Jo0)H5tArAp%BhUSBCb^Ao$>=kMe zg}aAxGtXd66gB+#)zPm?Ny2t)P--EXQ>`5&=6Rv!m)O`9cDnyIrP+h>1~0R?8}h5z zbHBrb%^vL<7pt1z`30S=fU7fVLuhe_Yfbew<~>KZEiY1$kN=ruYxIdVJa0VSdrUi8 z6R_t23kY{H#UzW^$Jc`jzMxI_wb4rZgabl{0ATK-FgEGF*5*%rv4GHCP!BrVF456( zg2uR5h=1(0vtnlshiG9npzzFj>y|piwU(v_#C3?@W&ZLdcvw~99n5wUhiUs!OB(PqEg>H^57??-KZ;k2FI+3a-jX6q`2iSFYC8SIwqq*9g+T&jXGSsgB~YHo+r0 z;r!-2lQ3rOVjk#l69y$2>GB^fElG`UHL>{+kpUELx-0~whW0o4IfR7=d?Hf1K;3@9 zhxbg-Bs|)seJyCJPu!ET_N^OFGUHmvK-o?#;C=}Lpi}oSpnYtp(9pu%p>Nvb_>kBk zv0iUMH2Bx`CSATV)i~u7LC0H|$47;*XnB7p5)Gn%PC#=jjqF|!Wk=M)GCf6jFE47I z-Vn%BVe8m`=hnzAG(q*{X;GTBECnnhz!o9{#N;A9w-z6Lg*E4Kaw2dCFPJuNKK}9~ z$6ClG_T7!brLmNAzuI_&)ujAIsZO6ruaubfVq9WdR`foCOMhfs+mpOi{!(z}0_@>5 zKs%=AnB#cO8|n=?qSDq77M-#TW)rVwBSzcv0o8D|KA53L=C~fQl1+K%z;7lv0oOW* zmOY+6RYzN#t+R4?9L>1qrFY3t!h}Prs{s(Z0_SRx3#p$0t-wZOMB>%Pbj>_O^jG~N z{9>dx_F>lBSOhNuTJhg;ZrmO4SJcIEw@)t-o72#t@oWuYF9wVr~< zdC_^fo;)V31P{LQK=}nhws)bcn$^v1C)i?SHtMCb3x}1JOl^YSrbs(N`)>vuELYm$ zP)VvvFRiqYGgLGiQl_i*rO9;lXi&TP<>iy$3kYZmpq|sT4c9_+wf;hk1f;&@iGE8*BS4V;4IUBF_f1DKq zTno6^U`q5luVcyNJGtDRO3y#xRs5*&@SB2aWT5^GISSAXI`EdEDC5rDiMkWPTw;$n z>Mi0OFse|jaMK;RH}Dx^LZ%Qkh?ckX6P(s*ihhK`Z~2>R{(Gq+RR zYZWa0Z0y4Y0j20N>9>+}=zdy*6}xdhp%qQbn0U5JeM%fF7nTwIcN@wu?`)w>b-)10 z7%C}}8$oB%a8tl#uX^&V&|&^J;&`aw_sM|VqE*0FIYR@j0xLng=dBO%3*9p=;WCLg z6R)Qpgbeqb_hbrS_pdm|d;&&uAHbjkX?)(t%0yz)QhwhPcS3+J`A>=9BID>Tdx7Yb zC(!PA0*Wzb-Xe2_J8ess{7NwId_8QCy+3Iw>%!dlEu>D5aUG~}IF>;B?KQwY*PqCx zt#$VvtME=QZms57?7V};r*V~A+wS;USK?9~wNAlESoX|EWbQAE2Mxt9f|)1Zh=}z` zUwq>BD0q!$d?5-fad1Q?m-hMv7|mlKB>7Ez9>^ZmPC8JeM+q2mJ|KTr{9&+9+^Kq5 zPt#JteofDSQxHl z_h%aMDe<*x_!)t9n}{sBIH8vSe+~WF^kW@s$h&!|FO&}h_WMa7w=5l@zN)+PR#IV6 zt1nwe17H2GHf{?AJ*vx$M{@IF<_^q`ml&rEEEfWsZfugn?q38E+L?9`g}f?tJewn- z@Jc>`o<40S57W|?fZ|KitaMDXj$k_4XiPMp8sC#zI~#{qxD+{(4zxr7#^}1Oe44&j zzA`?NZA#kB?+O;}Y!hto^dW=&N{T@FvA%uwa+JeR;DuFlvHKkLnV7$#3JreK2PL#d zs+P`t_7zCoav1FL6`;WkYJ{0rBW}o$P00A&mN#2MhR0crj0k-_E36Ey;a&v5q`{s}tS}e`AUMEk z0cS0)K;8^=D*?LUq|Y1Bo&44Nzb*Sv_h0sl(enOJ@~6d;EvSdm{f`VGMmdy7*u7`h z&#|vg>a!1MbaBV*gQPircOhLy0KX*ay3nsT+E~PO1KkfjfCnLqZM{o1iZxm_((hdF^^FH z!oRNk1?DdA?Lqx)$I06HGJVh*q<*`T_uqRI0HGoD0(11ZQ==(x`0U<0D`En8G-%52 z2aS^Z7(lMHD;`ICqr@6EyCt$gfB1Y^0?QQOhVS(K1oaXacofhoA9auU_3}Voj951x zp2QNl>+qb_@R@~0m%F$|K^TJof2LC8-AFVb-1XGaFf$GSuHv(2&!D#&Gzr-G@kApT zn3$d)UC+&7_XN$>MNs!*bXuCD^tv3p#PF;2PL$O#IIOjFym}uk{!hr^`Ds0hv^E-1 z!S!IKBnlc$J{}$wGoL>SyOZmFHuM^=lKtN7l|k!|&77d$*VkwL zvk=vlDAeln#^{2Ad_sRPgDCD*W>!|#M~lHCV-&=0Z=Wc1WBM2jSmM{ORP*EKV#nPa z0Ck7wQ-A4GdD@eeN0U^d6Ud?<)dvrP7!R=NLu@MmecltG7E*CE2_gTnHX>AC-`2{i(A1wl(2_TNoM2Mj!JKUeDm|KDEj zxBq#)hItZ7_3eD(!MPFSg0BmB;Rf^yje|-=K@480DWo2>Uj z9FernR1zU^wIm$+^S%oHZ~Um9KgoP+**>kUB!CK`AZvcoH)@-GB6zqZ^_A(wyWyYc zR}K?x=4K3XIIC%ybnic7)wJOLKeg2i;P%~)WjRdU`GW;l0kf-~zuaGPn=uf7Rlel* zN0^>LB*+pwsHmt!SZDa_@2>q={-s&ZzxLC9R`ZBZ3%zdwVlh(#1;L=5^nZL<7u;GN z)@qOA<%YV5Ls|U{z}pI*rN0&k3Jz`qjIW!}dkrQ)n>gWDxIjD9)$!Gt(l@nY>wY*r z-wMm$e$clt7a@v6_Ghbg4Cn;A;bi`<{1adq?P|Jy~GP`OD)u=(@r zr#5I~gqnamdy3j(xW{d7fs-gw@M62ZK@Yt4AvWTw=$MUfBR znx|R@t&wqyi?;jcE?0`X|HeQ_&%bngc~}j!KH`h()}nQCHAnTEhV@F!9B|d(MG7F= zd<|Gy5G!hK6*UzPUVzY+Cgsf`DQN;wzMVOl`Qk*)*9kYQhM@1B+h&qFXy!j4jMaHM zfjQzJhFFu-%U?fC(w?-@?Re*%6>}sGl zf`fzOhjd^GvzW_|gWQ3sAJg8T3l(ZSNfdQT+jU4lE_nY>S52VtqQDo8B;m5iTLO&- z<-*SgFw%hn`11(yF#Q~LM%u0V3P&^00czP4K;{wY1e$5#|M%M9MIVRO56ts2*+(7} zQ}z?pWfd<*`z5*XHS)lib=iY}uH>WfH5RW$IO9F%3}5;Ns7pntxz=I)u4|zXEo#Cg z{5_*k%eC*KE+O?$Gfo2rqg>Yt*{RLVZYuDQG*_?Mn~l7Edj@=)t;UcCnkIcun)gqY zn!TE#P~dgwnY8HJzX3sZ)^7^bpN z+Hs^*R`J6%JNg!=2eg5_!TA%?bGnHP^wh7_ttWZ^m<&KBP^%Bujsw!PrQiKE9D021 zwZIa!!lCS!W1D)7NOw0m*4*6OasAIZNcsj$JyhFmh) zWB*0BTK=FKBFW{X;urL(`%?HJDPXhlJh}%C1 literal 0 HcmV?d00001 From 01b557122ed5b4f8ace81c7ba29a16b882727400 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 6 Dec 2018 23:37:36 -0500 Subject: [PATCH 063/549] Fix missing comma --- Firmware/MotorControl/controller.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index ca1012c3..d8e71b7c 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -110,7 +110,7 @@ public: make_protocol_property("vel_limit", &config_.vel_limit), make_protocol_property("vel_limit_tolerance", &config_.vel_limit_tolerance), make_protocol_property("vel_ramp_rate", &config_.vel_ramp_rate), - make_protocol_property("setpoints_in_cpr", &config_.setpoints_in_cpr) + make_protocol_property("setpoints_in_cpr", &config_.setpoints_in_cpr), make_protocol_property("homing_speed", &config_.homing_speed) ), make_protocol_function("set_pos_setpoint", *this, &Controller::set_pos_setpoint, From 1fc6bc90aaaafe8272001131e211479f36cf1f1b Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 2 Jan 2019 13:09:28 -0500 Subject: [PATCH 064/549] Move trajectory planner handling into the input filter --- Firmware/MotorControl/controller.cpp | 9 ++++++++- Firmware/MotorControl/controller.hpp | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index a5a19256..b6f5a156 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -139,6 +139,13 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // case INPUT_MODE_MIX_CHANNELS: { // // NOT YET IMPLEMENTED // } break; + case INPUT_MODE_TRAP_TRAJ: { + static auto last_pos = input_pos_; + if(last_pos != input_pos_){ + last_pos = input_pos_; + move_to_pos(input_pos_); // We should really move the *setpoint* handling here, but this will work for now + } + } break; default: { set_error(ERROR_INVALID_INPUT_MODE); return false; @@ -153,7 +160,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s if (t > axis_->trap_.Tf_) { // Drop into position control mode when done to avoid problems on loop counter delta overflow config_.control_mode = CTRL_MODE_POSITION_CONTROL; - // pos_setpoint already set by trajectory + pos_setpoint_ = input_pos_; vel_setpoint_ = 0.0f; current_setpoint_ = 0.0f; } else { diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 10dc9a90..9529f82f 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -30,6 +30,7 @@ public: INPUT_MODE_VEL_RAMP, INPUT_MODE_POS_FILTER, INPUT_MODE_MIX_CHANNELS, + INPUT_MODE_TRAP_TRAJ, }; struct Config_t { From f888fd171e6996f8c2aec5966d43647a65a1dfba Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 2 Jan 2019 13:10:02 -0500 Subject: [PATCH 065/549] Make xxx_setpoint properties read-only --- Firmware/MotorControl/controller.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 9529f82f..bd9dab63 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -115,10 +115,10 @@ public: make_protocol_property("input_pos", &input_pos_), make_protocol_property("input_vel", &input_vel_), make_protocol_property("input_current", &input_current_), - make_protocol_property("pos_setpoint", &pos_setpoint_), - make_protocol_property("vel_setpoint", &vel_setpoint_), - make_protocol_property("vel_integrator_current", &vel_integrator_current_), - make_protocol_property("current_setpoint", ¤t_setpoint_), + make_protocol_ro_property("pos_setpoint", &pos_setpoint_), + make_protocol_ro_property("vel_setpoint", &vel_setpoint_), + make_protocol_ro_property("vel_integrator_current", &vel_integrator_current_), + make_protocol_ro_property("current_setpoint", ¤t_setpoint_), make_protocol_object("config", make_protocol_property("control_mode", &config_.control_mode), make_protocol_property("input_mode", &config_.input_mode), From 33aeb1d5a8662932974abd5cff614861a0c98ee5 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 2 Jan 2019 13:10:23 -0500 Subject: [PATCH 066/549] remove set_xxx_setpoint and move_to_pos functions --- Firmware/MotorControl/controller.hpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index bd9dab63..62e0972e 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -133,13 +133,6 @@ public: make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth, [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this) ), - make_protocol_function("set_pos_setpoint", *this, &Controller::set_pos_setpoint, - "pos_setpoint", "vel_feed_forward", "current_feed_forward"), - make_protocol_function("set_vel_setpoint", *this, &Controller::set_vel_setpoint, - "vel_setpoint", "current_feed_forward"), - make_protocol_function("set_current_setpoint", *this, &Controller::set_current_setpoint, - "current_setpoint"), - make_protocol_function("move_to_pos", *this, &Controller::move_to_pos, "goal_point"), make_protocol_function("start_anticogging_calibration", *this, &Controller::start_anticogging_calibration) ); } From d90793b788e707372d4cc4749739f201f229450c Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 8 Jan 2019 23:22:12 -0500 Subject: [PATCH 067/549] Remove interrupt-based Endstop switching --- Firmware/MotorControl/endstop.cpp | 22 +++++----------------- Firmware/MotorControl/endstop.hpp | 1 - 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index 4443fdd6..d01c361b 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -5,14 +5,14 @@ Endstop::Endstop(Endstop::Config_t &config) set_endstop_enabled(config_.enabled); } -static void endstop_cb_wrapper(void* ctx){ - reinterpret_cast(ctx)->endstop_cb(); -} - void Endstop::update() { uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); + auto last_pin_state = pin_state_; pin_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); + if(pin_state_ != last_pin_state){ + debounce_timer_ = axis_->loop_counter_ * current_meas_period; + } if (config_.enabled) { float now = axis_->loop_counter_ * current_meas_period; if ((now - debounce_timer_) >= (config_.debounce_ms * 0.001f)) { // Debounce timer expired, take the new pin state @@ -30,10 +30,6 @@ bool Endstop::getEndstopState() { return endstop_state_; } -void Endstop::endstop_cb() { - debounce_timer_ = axis_->loop_counter_ * current_meas_period; -} - void Endstop::set_endstop_enabled(bool enable){ uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); @@ -42,15 +38,7 @@ void Endstop::set_endstop_enabled(bool enable){ GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = gpio_pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Pull = config_.is_active_high ? GPIO_PULLDOWN : GPIO_PULLUP;; HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); - - uint32_t pull_up_down = config_.is_active_high ? GPIO_PULLDOWN : GPIO_PULLUP; - uint32_t interrupt_mode = GPIO_MODE_IT_RISING_FALLING; - GPIO_subscribe(gpio_port, gpio_pin, pull_up_down, interrupt_mode, - endstop_cb_wrapper, this); - } - else { - GPIO_unsubscribe(gpio_port, gpio_pin); } } \ No newline at end of file diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index 92ff10d7..9ff3f079 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -17,7 +17,6 @@ class Endstop { Axis* axis_ = nullptr; void set_endstop_enabled(bool enable); - void endstop_cb(); void update(); bool getEndstopState(); From f2b66ab881d7d2468945f0cb0b81a77801589ac0 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Tue, 29 Jan 2019 19:45:50 +0100 Subject: [PATCH 068/549] Add Get IQ Message --- Firmware/communication/can_simple.cpp | 39 +++++++++++++++++++++++++++ Firmware/communication/can_simple.hpp | 4 ++- docs/can-protocol.md | 3 +++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 07a19001..78021b5b 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -1,5 +1,6 @@ #include "can_simple.hpp" +#include #include @@ -81,6 +82,18 @@ void CANSimple::handle_can_message(CAN_message_t& msg) { case MSG_START_ANTICOGGING: start_anticogging_callback(axis, msg); break; + case MSG_SET_TRAJ_A_PER_CSS: + set_traj_A_per_css_callback(axis, msg); + break; + case MSG_SET_TRAJ_ACCEL_LIMITS: + set_traj_accel_limits_callback(axis, msg); + break; + case MSG_SET_TRAJ_VEL_LIMIT: + set_traj_vel_limit_callback(axis, msg); + break; + case MSG_GET_IQ: + get_iq_callback(axis, msg); + break; default: break; } @@ -236,6 +249,32 @@ void CANSimple::set_traj_A_per_css_callback(Axis* axis, CAN_message_t& msg) { axis->trap_.config_.A_per_css = get_float(msg, 0); } +void CANSimple::get_iq_callback(Axis* axis, CAN_message_t& msg){ + CAN_message_t txmsg; + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_IQ; + txmsg.isExt = false; + txmsg.len = 8; + + uint32_t floatBytes; + static_assert(sizeof axis->motor_.current_control_.Iq_setpoint == sizeof floatBytes); + std::memcpy(&floatBytes, &axis->motor_.current_control_.Iq_setpoint, sizeof floatBytes); + + txmsg.buf[0] = floatBytes; + txmsg.buf[1] = floatBytes >> 8; + txmsg.buf[2] = floatBytes >> 16; + txmsg.buf[3] = floatBytes >> 24; + + static_assert(sizeof floatBytes == sizeof axis->motor_.current_control_.Iq_measured); + std::memcpy(&floatBytes, &axis->motor_.current_control_.Iq_measured, sizeof floatBytes); + txmsg.buf[4] = floatBytes; + txmsg.buf[5] = floatBytes >> 8; + txmsg.buf[6] = floatBytes >> 16; + txmsg.buf[7] = floatBytes >> 24; + + odCAN->write(txmsg); +} + void CANSimple::send_heartbeat(Axis* axis) { CAN_message_t txmsg; diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp index 269fa956..56bb209f 100644 --- a/Firmware/communication/can_simple.hpp +++ b/Firmware/communication/can_simple.hpp @@ -26,7 +26,8 @@ class CANSimple { MSG_START_ANTICOGGING, MSG_SET_TRAJ_VEL_LIMIT, MSG_SET_TRAJ_ACCEL_LIMITS, - MSG_SET_TRAJ_A_PER_CSS + MSG_SET_TRAJ_A_PER_CSS, + MSG_GET_IQ, }; static void handle_can_message(CAN_message_t& msg); @@ -53,6 +54,7 @@ class CANSimple { static void set_traj_vel_limit_callback(Axis* axis, CAN_message_t& msg); static void set_traj_accel_limits_callback(Axis* axis, CAN_message_t& msg); static void set_traj_A_per_css_callback(Axis* axis, CAN_message_t& msg); + static void get_iq_callback(Axis* axis, CAN_message_t& msg); // Utility functions diff --git a/docs/can-protocol.md b/docs/can-protocol.md index d3da2473..17f2975f 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -55,6 +55,7 @@ CMD ID | Name | Sender | Signals | Start byte 0x011 | Set Traj Vel Limit | Master | Traj Vel Limit | 0 0x012 | Set Traj Accel Limits | Master | Traj Accel Limit
Traj Decel Limit | 0
4 0x013 | Set Traj A per Count / s^2 | Master | Traj A per CSS | 0 +0x014 | Get IQ\* | Axis | Iq Setpoint | Iq Measured \* Note: These messages are call & response. The Master node sends a message with no payload, and the axis responds with the same ID and specified payload. \*\* Note: These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple. @@ -85,6 +86,8 @@ Traj Vel Limit | IEEE 754 Float | 32 | 1 | 0 | Intel Traj Accel Limit | IEEE 754 Float | 32 | 1 | 0 | Intel Traj Decel Limit | IEEE 754 Float | 32 | 1 | 0 | Intel Traj A per CSS | IEEE 754 Float | 32 | 1 | 0 | Intel +Iq Setpoint | IEEE 754 Float | 32 | 1 | 0 | Intel +Iq Measured | IEEE 754 Float | 32 | 1 | 0 | Intel --- ## Configuring ODrive for CAN From 3ecbdc64af9001e3ae1ce1965c2c127e9db44d75 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Tue, 29 Jan 2019 19:47:07 +0100 Subject: [PATCH 069/549] Add missing start bytes --- docs/can-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/can-protocol.md b/docs/can-protocol.md index 17f2975f..56a27055 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -55,7 +55,7 @@ CMD ID | Name | Sender | Signals | Start byte 0x011 | Set Traj Vel Limit | Master | Traj Vel Limit | 0 0x012 | Set Traj Accel Limits | Master | Traj Accel Limit
Traj Decel Limit | 0
4 0x013 | Set Traj A per Count / s^2 | Master | Traj A per CSS | 0 -0x014 | Get IQ\* | Axis | Iq Setpoint | Iq Measured +0x014 | Get IQ\* | Axis | Iq Setpoint | Iq Measured | 0
4 \* Note: These messages are call & response. The Master node sends a message with no payload, and the axis responds with the same ID and specified payload. \*\* Note: These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple. From ec1618f430b3f3880a6b2b36af4c3e1eb809fd45 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Tue, 29 Jan 2019 19:50:46 +0100 Subject: [PATCH 070/549] Fix line break in Iq table --- docs/can-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/can-protocol.md b/docs/can-protocol.md index 56a27055..5e67f567 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -55,7 +55,7 @@ CMD ID | Name | Sender | Signals | Start byte 0x011 | Set Traj Vel Limit | Master | Traj Vel Limit | 0 0x012 | Set Traj Accel Limits | Master | Traj Accel Limit
Traj Decel Limit | 0
4 0x013 | Set Traj A per Count / s^2 | Master | Traj A per CSS | 0 -0x014 | Get IQ\* | Axis | Iq Setpoint | Iq Measured | 0
4 +0x014 | Get IQ\* | Axis | Iq Setpoint
Iq Measured | 0
4 \* Note: These messages are call & response. The Master node sends a message with no payload, and the axis responds with the same ID and specified payload. \*\* Note: These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple. From bf242f13fe3c895d634f7674d6cfadb75f53fa9e Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Thu, 31 Jan 2019 20:11:57 +0100 Subject: [PATCH 071/549] Add "Get Sensorless Estimates" CAN message --- Firmware/communication/can_simple.cpp | 32 +++++++++++++++++++++++++++ Firmware/communication/can_simple.hpp | 2 ++ docs/can-protocol.md | 3 +++ 3 files changed, 37 insertions(+) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 78021b5b..bfe35858 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -94,6 +94,9 @@ void CANSimple::handle_can_message(CAN_message_t& msg) { case MSG_GET_IQ: get_iq_callback(axis, msg); break; + case MSG_GET_SENSORLESS_ESTIMATES: + get_sensorless_estimates_callback(axis, msg); + break; default: break; } @@ -192,6 +195,35 @@ void CANSimple::get_encoder_estimates_callback(Axis* axis, CAN_message_t& msg) { odCAN->write(txmsg); } +void CANSimple::get_sensorless_estimates_callback(Axis* axis, CAN_message_t& msg) { + CAN_message_t txmsg; + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_SENSORLESS_ESTIMATES; // heartbeat ID + txmsg.isExt = false; + txmsg.len = 8; + + // Undefined behaviour! + // uint32_t floatBytes = *(reinterpret_cast(&(axis->encoder_.pos_estimate_))); + + uint32_t floatBytes; + static_assert(sizeof axis->sensorless_estimator_.pll_pos_ == sizeof floatBytes); + std::memcpy(&floatBytes, &axis->sensorless_estimator_.pll_pos_, sizeof floatBytes); + + txmsg.buf[0] = floatBytes; + txmsg.buf[1] = floatBytes >> 8; + txmsg.buf[2] = floatBytes >> 16; + txmsg.buf[3] = floatBytes >> 24; + + static_assert(sizeof floatBytes == sizeof axis->sensorless_estimator_.vel_estimate_); + std::memcpy(&floatBytes, &axis->sensorless_estimator_.vel_estimate_, sizeof floatBytes); + txmsg.buf[4] = floatBytes; + txmsg.buf[5] = floatBytes >> 8; + txmsg.buf[6] = floatBytes >> 16; + txmsg.buf[7] = floatBytes >> 24; + + odCAN->write(txmsg); +} + void CANSimple::get_encoder_count_callback(Axis* axis, CAN_message_t& msg){ CAN_message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp index 56bb209f..4ed630af 100644 --- a/Firmware/communication/can_simple.hpp +++ b/Firmware/communication/can_simple.hpp @@ -28,6 +28,7 @@ class CANSimple { MSG_SET_TRAJ_ACCEL_LIMITS, MSG_SET_TRAJ_A_PER_CSS, MSG_GET_IQ, + MSG_GET_SENSORLESS_ESTIMATES, }; static void handle_can_message(CAN_message_t& msg); @@ -55,6 +56,7 @@ class CANSimple { static void set_traj_accel_limits_callback(Axis* axis, CAN_message_t& msg); static void set_traj_A_per_css_callback(Axis* axis, CAN_message_t& msg); static void get_iq_callback(Axis* axis, CAN_message_t& msg); + static void get_sensorless_estimates_callback(Axis* axis, CAN_message_t& ms); // Utility functions diff --git a/docs/can-protocol.md b/docs/can-protocol.md index 5e67f567..e704439c 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -56,6 +56,7 @@ CMD ID | Name | Sender | Signals | Start byte 0x012 | Set Traj Accel Limits | Master | Traj Accel Limit
Traj Decel Limit | 0
4 0x013 | Set Traj A per Count / s^2 | Master | Traj A per CSS | 0 0x014 | Get IQ\* | Axis | Iq Setpoint
Iq Measured | 0
4 +0x015 | Get Sensorless Estimates\* | Master | Sensorless Pos Estimate
Sensorless Vel Estimate | 0
4 \* Note: These messages are call & response. The Master node sends a message with no payload, and the axis responds with the same ID and specified payload. \*\* Note: These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple. @@ -88,6 +89,8 @@ Traj Decel Limit | IEEE 754 Float | 32 | 1 | 0 | Intel Traj A per CSS | IEEE 754 Float | 32 | 1 | 0 | Intel Iq Setpoint | IEEE 754 Float | 32 | 1 | 0 | Intel Iq Measured | IEEE 754 Float | 32 | 1 | 0 | Intel +Sensorless Pos Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel +Sensorless Vel Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel --- ## Configuring ODrive for CAN From f9765f4d73060fd167f008305e8eda28e115f2fc Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 2 Mar 2019 00:18:29 +0100 Subject: [PATCH 072/549] Add ODrive Reboot message and Get Vbus Voltage message --- Firmware/communication/can_simple.cpp | 31 +++++++++++++++++++++++++++ Firmware/communication/can_simple.hpp | 5 ++++- docs/can-protocol.md | 4 ++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index bfe35858..607495d4 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -97,6 +97,12 @@ void CANSimple::handle_can_message(CAN_message_t& msg) { case MSG_GET_SENSORLESS_ESTIMATES: get_sensorless_estimates_callback(axis, msg); break; + case MSG_RESET_ODRIVE: + NVIC_SystemReset(); + break; + case MSG_GET_VBUS_VOLTAGE: + get_vbus_voltage_callback(axis, msg); + break; default: break; } @@ -307,6 +313,31 @@ void CANSimple::get_iq_callback(Axis* axis, CAN_message_t& msg){ odCAN->write(txmsg); } +void CANSimple::get_vbus_voltage_callback(Axis* axis, CAN_message_t& msg) { + CAN_message_t txmsg; + + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_VBUS_VOLTAGE; + txmsg.isExt = false; + txmsg.len = 8; + + uint32_t floatBytes; + static_assert(sizeof vbus_voltage == sizeof floatBytes); + std::memcpy(&floatBytes, &vbus_voltage, sizeof floatBytes); + + // This also works in principle, but I don't have hardware to verify endianness + // std::memcpy(&txmsg.buf[0], &vbus_voltage, sizeof vbus_voltage); + + txmsg.buf[0] = floatBytes; + txmsg.buf[1] = floatBytes >> 8; + txmsg.buf[2] = floatBytes >> 16; + txmsg.buf[3] = floatBytes >> 24; + + txmsg.buf[4] = 0; + txmsg.buf[5] = 0; + txmsg.buf[6] = 0; + txmsg.buf[7] = 0; +} void CANSimple::send_heartbeat(Axis* axis) { CAN_message_t txmsg; diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp index 4ed630af..fd078c5a 100644 --- a/Firmware/communication/can_simple.hpp +++ b/Firmware/communication/can_simple.hpp @@ -29,6 +29,8 @@ class CANSimple { MSG_SET_TRAJ_A_PER_CSS, MSG_GET_IQ, MSG_GET_SENSORLESS_ESTIMATES, + MSG_RESET_ODRIVE, + MSG_GET_VBUS_VOLTAGE, }; static void handle_can_message(CAN_message_t& msg); @@ -56,7 +58,8 @@ class CANSimple { static void set_traj_accel_limits_callback(Axis* axis, CAN_message_t& msg); static void set_traj_A_per_css_callback(Axis* axis, CAN_message_t& msg); static void get_iq_callback(Axis* axis, CAN_message_t& msg); - static void get_sensorless_estimates_callback(Axis* axis, CAN_message_t& ms); + static void get_sensorless_estimates_callback(Axis* axis, CAN_message_t& msg); + static void get_vbus_voltage_callback(Axis* axis, CAN_message_t& msg); // Utility functions diff --git a/docs/can-protocol.md b/docs/can-protocol.md index e704439c..c66a5d7b 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -57,9 +57,12 @@ CMD ID | Name | Sender | Signals | Start byte 0x013 | Set Traj A per Count / s^2 | Master | Traj A per CSS | 0 0x014 | Get IQ\* | Axis | Iq Setpoint
Iq Measured | 0
4 0x015 | Get Sensorless Estimates\* | Master | Sensorless Pos Estimate
Sensorless Vel Estimate | 0
4 +0x016 | Reboot ODrive | Master\*\*\* | | +0x017 | Get Vbus Voltage | Master\*\*\* | Vbus Voltage | 0 \* Note: These messages are call & response. The Master node sends a message with no payload, and the axis responds with the same ID and specified payload. \*\* Note: These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple. +\*\*\* Note: These messages can be sent to either address on a given ODrive board. --- ### Signals @@ -91,6 +94,7 @@ Iq Setpoint | IEEE 754 Float | 32 | 1 | 0 | Intel Iq Measured | IEEE 754 Float | 32 | 1 | 0 | Intel Sensorless Pos Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel Sensorless Vel Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel +Vbus Voltage | IEEE 754 Float | 32 | 1 | 0 | Intel --- ## Configuring ODrive for CAN From c38abbcc131749b04bdb551e95c82dd473c44eab Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 2 Mar 2019 00:18:45 +0100 Subject: [PATCH 073/549] Check for valid axis ID before handling message... --- Firmware/communication/can_simple.cpp | 163 +++++++++++++------------- 1 file changed, 84 insertions(+), 79 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 607495d4..68bc1ae7 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -24,87 +24,92 @@ void CANSimple::handle_can_message(CAN_message_t& msg) { Axis* axis = nullptr; + bool validAxis = false; for (uint8_t i = 0; i < AXIS_COUNT; i++) { if (axes[i]->config_.can_node_id == nodeID) { axis = axes[i]; + validAxis = true; } } - switch (cmd) { - case MSG_CO_NMT_CTRL: - break; - case MSG_CO_HEARTBEAT_CMD: - break; - case MSG_ODRIVE_HEARTBEAT: - // We don't currently do anything to respond to ODrive heartbeat messages - break; - case MSG_ODRIVE_ESTOP: - estop_callback(axis, msg); - break; - case MSG_GET_MOTOR_ERROR: - get_motor_error_callback(axis, msg); - break; - case MSG_GET_ENCODER_ERROR: - get_encoder_error_callback(axis, msg); - break; - case MSG_GET_SENSORLESS_ERROR: - get_sensorless_error_callback(axis, msg); - break; - case MSG_SET_AXIS_NODE_ID: - set_axis_nodeid_callback(axis, msg); - break; - case MSG_SET_AXIS_REQUESTED_STATE: - set_axis_requested_state_callback(axis, msg); - break; - case MSG_SET_AXIS_STARTUP_CONFIG: - set_axis_startup_config_callback(axis, msg); - break; - case MSG_GET_ENCODER_ESTIMATES: - get_encoder_estimates_callback(axis, msg); - break; - case MSG_GET_ENCODER_COUNT: - get_encoder_count_callback(axis, msg); - break; - case MSG_MOVE_TO_POS: - move_to_pos_callback(axis, msg); - break; - case MSG_SET_POS_SETPOINT: - set_pos_setpoint_callback(axis, msg); - break; - case MSG_SET_VEL_SETPOINT: - set_vel_setpoint_callback(axis, msg); - break; - case MSG_SET_CUR_SETPOINT: - set_current_setpoint_callback(axis, msg); - break; - case MSG_SET_VEL_LIMIT: - set_vel_limit_callback(axis, msg); - break; - case MSG_START_ANTICOGGING: - start_anticogging_callback(axis, msg); - break; - case MSG_SET_TRAJ_A_PER_CSS: - set_traj_A_per_css_callback(axis, msg); - break; - case MSG_SET_TRAJ_ACCEL_LIMITS: - set_traj_accel_limits_callback(axis, msg); - break; - case MSG_SET_TRAJ_VEL_LIMIT: - set_traj_vel_limit_callback(axis, msg); - break; - case MSG_GET_IQ: - get_iq_callback(axis, msg); - break; - case MSG_GET_SENSORLESS_ESTIMATES: - get_sensorless_estimates_callback(axis, msg); - break; + + if (validAxis) { + switch (cmd) { + case MSG_CO_NMT_CTRL: + break; + case MSG_CO_HEARTBEAT_CMD: + break; + case MSG_ODRIVE_HEARTBEAT: + // We don't currently do anything to respond to ODrive heartbeat messages + break; + case MSG_ODRIVE_ESTOP: + estop_callback(axis, msg); + break; + case MSG_GET_MOTOR_ERROR: + get_motor_error_callback(axis, msg); + break; + case MSG_GET_ENCODER_ERROR: + get_encoder_error_callback(axis, msg); + break; + case MSG_GET_SENSORLESS_ERROR: + get_sensorless_error_callback(axis, msg); + break; + case MSG_SET_AXIS_NODE_ID: + set_axis_nodeid_callback(axis, msg); + break; + case MSG_SET_AXIS_REQUESTED_STATE: + set_axis_requested_state_callback(axis, msg); + break; + case MSG_SET_AXIS_STARTUP_CONFIG: + set_axis_startup_config_callback(axis, msg); + break; + case MSG_GET_ENCODER_ESTIMATES: + get_encoder_estimates_callback(axis, msg); + break; + case MSG_GET_ENCODER_COUNT: + get_encoder_count_callback(axis, msg); + break; + case MSG_MOVE_TO_POS: + move_to_pos_callback(axis, msg); + break; + case MSG_SET_POS_SETPOINT: + set_pos_setpoint_callback(axis, msg); + break; + case MSG_SET_VEL_SETPOINT: + set_vel_setpoint_callback(axis, msg); + break; + case MSG_SET_CUR_SETPOINT: + set_current_setpoint_callback(axis, msg); + break; + case MSG_SET_VEL_LIMIT: + set_vel_limit_callback(axis, msg); + break; + case MSG_START_ANTICOGGING: + start_anticogging_callback(axis, msg); + break; + case MSG_SET_TRAJ_A_PER_CSS: + set_traj_A_per_css_callback(axis, msg); + break; + case MSG_SET_TRAJ_ACCEL_LIMITS: + set_traj_accel_limits_callback(axis, msg); + break; + case MSG_SET_TRAJ_VEL_LIMIT: + set_traj_vel_limit_callback(axis, msg); + break; + case MSG_GET_IQ: + get_iq_callback(axis, msg); + break; + case MSG_GET_SENSORLESS_ESTIMATES: + get_sensorless_estimates_callback(axis, msg); + break; case MSG_RESET_ODRIVE: NVIC_SystemReset(); break; case MSG_GET_VBUS_VOLTAGE: get_vbus_voltage_callback(axis, msg); break; - default: - break; + default: + break; + } } } @@ -162,7 +167,7 @@ void CANSimple::get_sensorless_error_callback(Axis* axis, CAN_message_t& msg) { } void CANSimple::set_axis_nodeid_callback(Axis* axis, CAN_message_t& msg) { - axis->config_.can_node_id = msg.buf[0] & 0x3F; // Node ID bitmask + axis->config_.can_node_id = msg.buf[0] & 0x3F; // Node ID bitmask } void CANSimple::set_axis_requested_state_callback(Axis* axis, CAN_message_t& msg) { @@ -185,7 +190,7 @@ void CANSimple::get_encoder_estimates_callback(Axis* axis, CAN_message_t& msg) { uint32_t floatBytes; static_assert(sizeof axis->encoder_.pos_estimate_ == sizeof floatBytes); std::memcpy(&floatBytes, &axis->encoder_.pos_estimate_, sizeof floatBytes); - + txmsg.buf[0] = floatBytes; txmsg.buf[1] = floatBytes >> 8; txmsg.buf[2] = floatBytes >> 16; @@ -214,7 +219,7 @@ void CANSimple::get_sensorless_estimates_callback(Axis* axis, CAN_message_t& msg uint32_t floatBytes; static_assert(sizeof axis->sensorless_estimator_.pll_pos_ == sizeof floatBytes); std::memcpy(&floatBytes, &axis->sensorless_estimator_.pll_pos_, sizeof floatBytes); - + txmsg.buf[0] = floatBytes; txmsg.buf[1] = floatBytes >> 8; txmsg.buf[2] = floatBytes >> 16; @@ -230,7 +235,7 @@ void CANSimple::get_sensorless_estimates_callback(Axis* axis, CAN_message_t& msg odCAN->write(txmsg); } -void CANSimple::get_encoder_count_callback(Axis* axis, CAN_message_t& msg){ +void CANSimple::get_encoder_count_callback(Axis* axis, CAN_message_t& msg) { CAN_message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_ENCODER_COUNT; @@ -287,7 +292,7 @@ void CANSimple::set_traj_A_per_css_callback(Axis* axis, CAN_message_t& msg) { axis->trap_.config_.A_per_css = get_float(msg, 0); } -void CANSimple::get_iq_callback(Axis* axis, CAN_message_t& msg){ +void CANSimple::get_iq_callback(Axis* axis, CAN_message_t& msg) { CAN_message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_IQ; @@ -297,7 +302,7 @@ void CANSimple::get_iq_callback(Axis* axis, CAN_message_t& msg){ uint32_t floatBytes; static_assert(sizeof axis->motor_.current_control_.Iq_setpoint == sizeof floatBytes); std::memcpy(&floatBytes, &axis->motor_.current_control_.Iq_setpoint, sizeof floatBytes); - + txmsg.buf[0] = floatBytes; txmsg.buf[1] = floatBytes >> 8; txmsg.buf[2] = floatBytes >> 16; @@ -376,11 +381,11 @@ int32_t CANSimple::get_32bit_val(CAN_message_t& msg, uint8_t start_byte) { return get_16bit_val(msg, start_byte) + (get_16bit_val(msg, start_byte + 2) << 16); } -float CANSimple::get_float(CAN_message_t& msg, uint8_t start_byte){ +float CANSimple::get_float(CAN_message_t& msg, uint8_t start_byte) { int32_t val = get_32bit_val(msg, start_byte); float retVal; - + static_assert(sizeof retVal == sizeof val); - std::memcpy(&retVal, &val, sizeof val); // Sexier int32_t -> float cast that isn't UB + std::memcpy(&retVal, &val, sizeof val); // Sexier int32_t -> float cast that isn't UB return retVal; } \ No newline at end of file From 9355444f077e2d35a614663901ebe5ca2b154b66 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Fri, 8 Mar 2019 19:40:21 +0100 Subject: [PATCH 074/549] Fix get_32bit_val call --- Firmware/communication/can_simple.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 68bc1ae7..f716cc72 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -374,11 +374,17 @@ uint8_t CANSimple::get_cmd_id(uint32_t msgID) { } int16_t CANSimple::get_16bit_val(CAN_message_t& msg, uint8_t start_byte) { - return msg.buf[start_byte] + (msg.buf[start_byte + 1] << 8); + int16_t retVal = 0; + if(msg.len - start_byte >= sizeof(retVal)) + retVal = std::memcpy(&retVal, &(msg.buf[start_byte]), sizeof(retVal)); + return retVal; } int32_t CANSimple::get_32bit_val(CAN_message_t& msg, uint8_t start_byte) { - return get_16bit_val(msg, start_byte) + (get_16bit_val(msg, start_byte + 2) << 16); + int32_t retVal = 0; + if(msg.len - start_byte >= sizeof(retVal)) + std::memcpy(&retVal, &(msg.buf[start_byte]), sizeof(retVal)); + return retVal; } float CANSimple::get_float(CAN_message_t& msg, uint8_t start_byte) { From e8c83dc72fa8a70734e86a190298d2ea2d097a7a Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Fri, 8 Mar 2019 20:27:59 +0100 Subject: [PATCH 075/549] Fix uninitialized mailbox error --- Firmware/communication/interface_can.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 2940cdf1..af1af08a 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -96,7 +96,7 @@ uint32_t ODriveCAN::write(CAN_message_t &txmsg) { header.DLC = txmsg.len; header.TransmitGlobalTime = FunctionalState::DISABLE; - uint32_t retTxMailbox; + uint32_t retTxMailbox = 0; if (HAL_CAN_GetTxMailboxesFreeLevel(handle_) > 0) HAL_CAN_AddTxMessage(handle_, &header, txmsg.buf, &retTxMailbox); From 1a58918eaaa595fbc2445e301bb8f2eefad30590 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Fri, 8 Mar 2019 21:52:27 +0100 Subject: [PATCH 076/549] Fix compilation error in get_16bit_val --- Firmware/communication/can_simple.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index f716cc72..093cff0c 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -375,14 +375,14 @@ uint8_t CANSimple::get_cmd_id(uint32_t msgID) { int16_t CANSimple::get_16bit_val(CAN_message_t& msg, uint8_t start_byte) { int16_t retVal = 0; - if(msg.len - start_byte >= sizeof(retVal)) - retVal = std::memcpy(&retVal, &(msg.buf[start_byte]), sizeof(retVal)); + if(msg.len >= start_byte && size_t(msg.len - start_byte) >= sizeof(retVal)) + std::memcpy(&retVal, &(msg.buf[start_byte]), sizeof(retVal)); return retVal; } int32_t CANSimple::get_32bit_val(CAN_message_t& msg, uint8_t start_byte) { int32_t retVal = 0; - if(msg.len - start_byte >= sizeof(retVal)) + if(msg.len >= start_byte && size_t(msg.len - start_byte) >= sizeof(retVal)) std::memcpy(&retVal, &(msg.buf[start_byte]), sizeof(retVal)); return retVal; } From 1296537582d3daa091ae1f15bfa9e796fc87f837 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Fri, 8 Mar 2019 21:53:02 +0100 Subject: [PATCH 077/549] Assign different can_node_ids to each axis by default --- Firmware/MotorControl/axis.cpp | 4 ++++ Firmware/MotorControl/axis.hpp | 1 + Firmware/MotorControl/main.cpp | 1 + 3 files changed, 6 insertions(+) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index d3ecaac3..299fca54 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -81,6 +81,10 @@ void Axis::load_default_step_dir_pin_config( config->dir_gpio_pin = hw_config.dir_gpio_pin; } +void Axis::load_default_can_id(const int& id, Config_t& config){ + config.can_node_id = id; +} + void Axis::decode_step_dir_pins() { step_port_ = get_gpio_port_by_pin(config_.step_gpio_pin); step_pin_ = get_gpio_pin_by_pin(config_.step_gpio_pin); diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 63e23c23..d567a26c 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -97,6 +97,7 @@ public: void decode_step_dir_pins(); static void load_default_step_dir_pin_config( const AxisHardwareConfig_t& hw_config, Config_t* config); + static void load_default_can_id(const int& id, Config_t& config); bool check_DRV_fault(); bool check_PSU_brownout(); diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index b7a7275c..1dc8c404 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -74,6 +74,7 @@ void load_configuration(void) { axis_configs[i] = Axis::Config_t(); // Default step/dir pins are different, so we need to explicitly load them Axis::load_default_step_dir_pin_config(hw_configs[i].axis_config, &axis_configs[i]); + Axis::load_default_can_id(i, axis_configs[i]); } } else { user_config_loaded_ = true; From 603e0b1bf2e9952121f127336bc73637fc627b7c Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Thu, 14 Mar 2019 21:28:58 +0100 Subject: [PATCH 078/549] Throw a CAN error if we have duplicate node IDs --- Firmware/communication/can_simple.cpp | 9 ++++++++- Firmware/communication/interface_can.cpp | 3 +++ Firmware/communication/interface_can.hpp | 12 ++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 093cff0c..538e8d9e 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -28,7 +28,14 @@ void CANSimple::handle_can_message(CAN_message_t& msg) { for (uint8_t i = 0; i < AXIS_COUNT; i++) { if (axes[i]->config_.can_node_id == nodeID) { axis = axes[i]; - validAxis = true; + if (!validAxis) { + validAxis = true; + } else { + // Duplicate can IDs, don't assign to any axis + odCAN->set_error(ODriveCAN::ERROR_DUPLICATE_CAN_IDS); + validAxis = false; + break; + } } } diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index af1af08a..cc8a3745 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -171,6 +171,9 @@ void ODriveCAN::reinit_can() { status = HAL_CAN_ActivateNotification(handle_, CAN_IT_RX_FIFO0_MSG_PENDING); } +void ODriveCAN::set_error(Error_t error){ + error_ |= error; +} // This function is called by each axis. // It provides an abstraction from the specific CAN protocol in use void ODriveCAN::send_heartbeat(Axis *axis) { diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 4105592b..2690559b 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -37,16 +37,25 @@ class ODriveCAN { CAN_Protocol_t protocol = CAN_PROTOCOL_SIMPLE; }; + enum Error_t { + ERROR_NONE = 0x00, + ERROR_DUPLICATE_CAN_IDS = 0x01 + }; + ODriveCAN(CAN_HandleTypeDef *handle, ODriveCAN::Config_t &config); // Thread Relevant Data osThreadId thread_id_; + Error_t error_ = ERROR_NONE; + volatile bool thread_id_valid_ = false; bool start_can_server(); void can_server_thread(); void send_heartbeat(Axis *axis); void reinit_can(); + void set_error(Error_t error); + // I/O Functions uint32_t available(); uint32_t write(CAN_message_t &txmsg); @@ -55,6 +64,7 @@ class ODriveCAN { // Communication Protocol Handling auto make_protocol_definitions() { return make_protocol_member_list( + make_protocol_property("error", &error_), make_protocol_object("config", make_protocol_ro_property("baud_rate", &config_.baud)), make_protocol_property("can_protocol", &config_.protocol), @@ -68,4 +78,6 @@ class ODriveCAN { void set_baud_rate(uint32_t baudRate); }; +DEFINE_ENUM_FLAG_OPERATORS(ODriveCAN::Error_t) + #endif // __INTERFACE_CAN_HPP From dc1f0b71b43b9faafb788f846a424bf754f4d1e4 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Thu, 14 Mar 2019 23:14:57 +0100 Subject: [PATCH 079/549] Add missing include path --- Firmware/.vscode/c_cpp_properties.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index 5176fa6c..54854572 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -7,6 +7,7 @@ "${workspaceRoot}/fibre/cpp/include/**", "${workspaceRoot}/MotorControl", "${workspaceRoot}/Drivers/DRV8301", + "${workspaceRoot}/communication", "${workspaceRoot}/Board/v3/Inc", "${workspaceRoot}/Board/v3/Drivers/CMSIS/Include", "${workspaceRoot}/Board/v3/Drivers/CMSIS/Device/ST/STM32F4xx/Include", @@ -39,7 +40,7 @@ ], "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" }, From 13e8138364b97c1752e0fbc4aa1b8a45277e9839 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Thu, 14 Mar 2019 23:15:25 +0100 Subject: [PATCH 080/549] Formatting --- Firmware/communication/can_simple.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 538e8d9e..412a88c6 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -382,14 +382,14 @@ uint8_t CANSimple::get_cmd_id(uint32_t msgID) { int16_t CANSimple::get_16bit_val(CAN_message_t& msg, uint8_t start_byte) { int16_t retVal = 0; - if(msg.len >= start_byte && size_t(msg.len - start_byte) >= sizeof(retVal)) + if (msg.len >= start_byte && size_t(msg.len - start_byte) >= sizeof(retVal)) std::memcpy(&retVal, &(msg.buf[start_byte]), sizeof(retVal)); return retVal; } int32_t CANSimple::get_32bit_val(CAN_message_t& msg, uint8_t start_byte) { int32_t retVal = 0; - if(msg.len >= start_byte && size_t(msg.len - start_byte) >= sizeof(retVal)) + if (msg.len >= start_byte && size_t(msg.len - start_byte) >= sizeof(retVal)) std::memcpy(&retVal, &(msg.buf[start_byte]), sizeof(retVal)); return retVal; } From 230426f8c8cf177520b2e00e308e0638913a0c6d Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Thu, 14 Mar 2019 23:15:58 +0100 Subject: [PATCH 081/549] Declare single-argument constructors explicit --- Firmware/MotorControl/controller.hpp | 2 +- Firmware/MotorControl/sensorless_estimator.hpp | 2 +- Firmware/MotorControl/trapTraj.hpp | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) 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/sensorless_estimator.hpp b/Firmware/MotorControl/sensorless_estimator.hpp index 6d15820f..719a3227 100644 --- a/Firmware/MotorControl/sensorless_estimator.hpp +++ b/Firmware/MotorControl/sensorless_estimator.hpp @@ -14,7 +14,7 @@ public: float pm_flux_linkage = 1.58e-3f; // [V / (rad/s)] { 5.51328895422 / ( * ) } }; - 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); From 06cc3d8b0e044b6597b443a78f0b36c85c370fdc Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Thu, 14 Mar 2019 23:16:22 +0100 Subject: [PATCH 082/549] Implement CppCheck linting via flylint extension --- ODrive_Workspace.code-workspace | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/ODrive_Workspace.code-workspace b/ODrive_Workspace.code-workspace index d858dcc3..a245793e 100644 --- a/ODrive_Workspace.code-workspace +++ b/ODrive_Workspace.code-workspace @@ -11,6 +11,26 @@ } ], "settings": { + "c-cpp-flylint.cppcheck.includePaths": [ + "${workspaceRoot}", + "${workspaceRoot}/fibre/cpp/include/fibre", + "${workspaceRoot}/MotorControl", + "${workspaceRoot}/Drivers/DRV8301", + "${workspaceRoot}/communication" + ], + "c-cpp-flylint.cppcheck.language": "c++", + "c-cpp-flylint.cppcheck.standard": ["c99","c++14"], + "c-cpp-flylint.cppcheck.defines": [ + "STM32F405xx", + "USE_HAL_DRIVER", + "HW_VERSION_MAJOR=3", + "HW_VERSION_MINOR=5", + "HW_VERSION_VOLTAGE=24", + "USB_PROTOCOL_NATIVE", + "__weak=\"__attribute__((weak))\"", + "__packed=\"__attribute__((__packed__))\"", + "__GNUC__" + ], "files.associations": { "memory": "cpp", "utility": "cpp", From fa22c7a2ba3f8a542b709bedda9193e9eff8d59d Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Fri, 15 Mar 2019 23:01:21 +0100 Subject: [PATCH 083/549] Add configurable can_heartbeat_rate in ms --- Firmware/MotorControl/axis.hpp | 1 + Firmware/communication/interface_can.cpp | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index d567a26c..4eb605a5 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -66,6 +66,7 @@ public: LockinConfig_t lockin; uint8_t can_node_id = 0; // Both axes will have the same id to start + uint32_t can_heartbeat_rate_ms = 100; }; enum thread_signals { diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index cc8a3745..173698c3 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -159,7 +159,7 @@ void ODriveCAN::set_baud_rate(uint32_t baudRate) { default: // baudRate is invalid, so don't accept it. - break; + break; } } @@ -171,21 +171,23 @@ void ODriveCAN::reinit_can() { status = HAL_CAN_ActivateNotification(handle_, CAN_IT_RX_FIFO0_MSG_PENDING); } -void ODriveCAN::set_error(Error_t error){ +void ODriveCAN::set_error(Error_t error) { error_ |= error; } // This function is called by each axis. // It provides an abstraction from the specific CAN protocol in use void ODriveCAN::send_heartbeat(Axis *axis) { // Handle heartbeat message - uint32_t now = osKernelSysTick(); - if (now - axis->last_heartbeat_ >= 100) { - switch (config_.protocol) { - case CAN_PROTOCOL_SIMPLE: - CANSimple::send_heartbeat(axis); - break; + if (axis->config_.can_heartbeat_rate_ms > 0) { + uint32_t now = osKernelSysTick(); + if ((now - axis->last_heartbeat_) >= axis->config_.can_heartbeat_rate_ms) { + switch (config_.protocol) { + case CAN_PROTOCOL_SIMPLE: + CANSimple::send_heartbeat(axis); + break; + } + axis->last_heartbeat_ = now; } - axis->last_heartbeat_ = now; } } From 632fb4f7d17396dd826482067bda023e07d44fc9 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Fri, 15 Mar 2019 23:02:41 +0100 Subject: [PATCH 084/549] Add heartbeat_rate to config protocol --- Firmware/MotorControl/axis.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 4eb605a5..6aeb00f5 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -236,7 +236,8 @@ public: make_protocol_property("finish_on_distance", &config_.lockin.finish_on_distance), make_protocol_property("finish_on_enc_idx", &config_.lockin.finish_on_enc_idx) ), - make_protocol_property("can_node_id", &config_.can_node_id) + make_protocol_property("can_node_id", &config_.can_node_id), + make_protocol_property("can_heartbeat_rate_ms", &config_.can_heartbeat_rate_ms) ), make_protocol_object("motor", motor_.make_protocol_definitions()), make_protocol_object("controller", controller_.make_protocol_definitions()), From 97eccc8ff59b9d77e3712a41a9af9a641f0bc4b4 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 17 Mar 2019 18:30:58 +0100 Subject: [PATCH 085/549] Add rtr bit to can_Message_t --- Firmware/communication/interface_can.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 2690559b..68479a61 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -12,6 +12,7 @@ typedef struct { uint32_t id = 0x000; // 11-bit max is 0x7ff, 29-bit max is 0x1FFFFFFF bool isExt = false; + bool rtr = false; uint8_t len = 8; uint8_t buf[8] = {0, 0, 0, 0, 0, 0, 0, 0}; } CAN_message_t; From 4f333511425a13e747dc64c0d22472e1cca04a82 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 17 Mar 2019 18:31:18 +0100 Subject: [PATCH 086/549] Parse RTR from header when reading a CAN message --- Firmware/communication/interface_can.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 173698c3..1ee2e7cc 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -124,6 +124,7 @@ bool ODriveCAN::read(CAN_message_t &rxmsg) { rxmsg.isExt = header.IDE; rxmsg.id = rxmsg.isExt ? header.ExtId : header.StdId; // If it's an extended message, pass the extended ID rxmsg.len = header.DLC; + rxmsg.rtr = (header.RTR == CAN_RTR_REMOTE) ? true : false; return validRead; } From 1ff263a2b5804243c50c12348641589fb7c99085 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 17 Mar 2019 18:32:14 +0100 Subject: [PATCH 087/549] Fix missing call to Write in get_vbus_voltage --- Firmware/communication/can_simple.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 412a88c6..05148453 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -349,6 +349,7 @@ void CANSimple::get_vbus_voltage_callback(Axis* axis, CAN_message_t& msg) { txmsg.buf[5] = 0; txmsg.buf[6] = 0; txmsg.buf[7] = 0; + odCAN->write(txmsg); } void CANSimple::send_heartbeat(Axis* axis) { From 9e5704d7e99512b31a5bd9fc81e55df1f26c7926 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 17 Mar 2019 18:32:45 +0100 Subject: [PATCH 088/549] CAN "get" functions should look for RTR --- Firmware/communication/can_simple.cpp | 263 ++++++++++++++------------ 1 file changed, 140 insertions(+), 123 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 05148453..74d23c21 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -129,48 +129,54 @@ void CANSimple::estop_callback(Axis* axis, CAN_message_t& msg) { } void CANSimple::get_motor_error_callback(Axis* axis, CAN_message_t& msg) { - CAN_message_t txmsg; - txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; - txmsg.id += MSG_GET_MOTOR_ERROR; // heartbeat ID - txmsg.isExt = false; - txmsg.len = 8; + if (msg.rtr) { + CAN_message_t txmsg; + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_MOTOR_ERROR; // heartbeat ID + txmsg.isExt = false; + txmsg.len = 8; - txmsg.buf[0] = axis->motor_.error_; - txmsg.buf[1] = axis->motor_.error_ >> 8; - txmsg.buf[2] = axis->motor_.error_ >> 16; - txmsg.buf[3] = axis->motor_.error_ >> 24; + txmsg.buf[0] = axis->motor_.error_; + txmsg.buf[1] = axis->motor_.error_ >> 8; + txmsg.buf[2] = axis->motor_.error_ >> 16; + txmsg.buf[3] = axis->motor_.error_ >> 24; - odCAN->write(txmsg); + odCAN->write(txmsg); + } } void CANSimple::get_encoder_error_callback(Axis* axis, CAN_message_t& msg) { - CAN_message_t txmsg; - txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; - txmsg.id += MSG_GET_ENCODER_ERROR; // heartbeat ID - txmsg.isExt = false; - txmsg.len = 8; + if (msg.rtr) { + CAN_message_t txmsg; + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_ENCODER_ERROR; // heartbeat ID + txmsg.isExt = false; + txmsg.len = 8; - txmsg.buf[0] = axis->encoder_.error_; - txmsg.buf[1] = axis->encoder_.error_ >> 8; - txmsg.buf[2] = axis->encoder_.error_ >> 16; - txmsg.buf[3] = axis->encoder_.error_ >> 24; + txmsg.buf[0] = axis->encoder_.error_; + txmsg.buf[1] = axis->encoder_.error_ >> 8; + txmsg.buf[2] = axis->encoder_.error_ >> 16; + txmsg.buf[3] = axis->encoder_.error_ >> 24; - odCAN->write(txmsg); + odCAN->write(txmsg); + } } void CANSimple::get_sensorless_error_callback(Axis* axis, CAN_message_t& msg) { - CAN_message_t txmsg; - txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; - txmsg.id += MSG_GET_SENSORLESS_ERROR; // heartbeat ID - txmsg.isExt = false; - txmsg.len = 8; + if (msg.rtr) { + CAN_message_t txmsg; + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_SENSORLESS_ERROR; // heartbeat ID + txmsg.isExt = false; + txmsg.len = 8; - txmsg.buf[0] = axis->sensorless_estimator_.error_; - txmsg.buf[1] = axis->sensorless_estimator_.error_ >> 8; - txmsg.buf[2] = axis->sensorless_estimator_.error_ >> 16; - txmsg.buf[3] = axis->sensorless_estimator_.error_ >> 24; + txmsg.buf[0] = axis->sensorless_estimator_.error_; + txmsg.buf[1] = axis->sensorless_estimator_.error_ >> 8; + txmsg.buf[2] = axis->sensorless_estimator_.error_ >> 16; + txmsg.buf[3] = axis->sensorless_estimator_.error_ >> 24; - odCAN->write(txmsg); + odCAN->write(txmsg); + } } void CANSimple::set_axis_nodeid_callback(Axis* axis, CAN_message_t& msg) { @@ -185,81 +191,87 @@ void CANSimple::set_axis_startup_config_callback(Axis* axis, CAN_message_t& msg) } void CANSimple::get_encoder_estimates_callback(Axis* axis, CAN_message_t& msg) { - CAN_message_t txmsg; - txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; - txmsg.id += MSG_GET_ENCODER_ESTIMATES; // heartbeat ID - txmsg.isExt = false; - txmsg.len = 8; + if (msg.rtr) { + CAN_message_t txmsg; + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_ENCODER_ESTIMATES; // heartbeat ID + txmsg.isExt = false; + txmsg.len = 8; - // Undefined behaviour! - // uint32_t floatBytes = *(reinterpret_cast(&(axis->encoder_.pos_estimate_))); + // Undefined behaviour! + // uint32_t floatBytes = *(reinterpret_cast(&(axis->encoder_.pos_estimate_))); - uint32_t floatBytes; - static_assert(sizeof axis->encoder_.pos_estimate_ == sizeof floatBytes); - std::memcpy(&floatBytes, &axis->encoder_.pos_estimate_, sizeof floatBytes); + uint32_t floatBytes; + static_assert(sizeof axis->encoder_.pos_estimate_ == sizeof floatBytes); + std::memcpy(&floatBytes, &axis->encoder_.pos_estimate_, sizeof floatBytes); - txmsg.buf[0] = floatBytes; - txmsg.buf[1] = floatBytes >> 8; - txmsg.buf[2] = floatBytes >> 16; - txmsg.buf[3] = floatBytes >> 24; + txmsg.buf[0] = floatBytes; + txmsg.buf[1] = floatBytes >> 8; + txmsg.buf[2] = floatBytes >> 16; + txmsg.buf[3] = floatBytes >> 24; - static_assert(sizeof floatBytes == sizeof axis->encoder_.vel_estimate_); - std::memcpy(&floatBytes, &axis->encoder_.vel_estimate_, sizeof floatBytes); - txmsg.buf[4] = floatBytes; - txmsg.buf[5] = floatBytes >> 8; - txmsg.buf[6] = floatBytes >> 16; - txmsg.buf[7] = floatBytes >> 24; + static_assert(sizeof floatBytes == sizeof axis->encoder_.vel_estimate_); + std::memcpy(&floatBytes, &axis->encoder_.vel_estimate_, sizeof floatBytes); + txmsg.buf[4] = floatBytes; + txmsg.buf[5] = floatBytes >> 8; + txmsg.buf[6] = floatBytes >> 16; + txmsg.buf[7] = floatBytes >> 24; - odCAN->write(txmsg); + odCAN->write(txmsg); + } } void CANSimple::get_sensorless_estimates_callback(Axis* axis, CAN_message_t& msg) { - CAN_message_t txmsg; - txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; - txmsg.id += MSG_GET_SENSORLESS_ESTIMATES; // heartbeat ID - txmsg.isExt = false; - txmsg.len = 8; + if (msg.rtr) { + CAN_message_t txmsg; + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_SENSORLESS_ESTIMATES; // heartbeat ID + txmsg.isExt = false; + txmsg.len = 8; - // Undefined behaviour! - // uint32_t floatBytes = *(reinterpret_cast(&(axis->encoder_.pos_estimate_))); + // Undefined behaviour! + // uint32_t floatBytes = *(reinterpret_cast(&(axis->encoder_.pos_estimate_))); - uint32_t floatBytes; - static_assert(sizeof axis->sensorless_estimator_.pll_pos_ == sizeof floatBytes); - std::memcpy(&floatBytes, &axis->sensorless_estimator_.pll_pos_, sizeof floatBytes); + uint32_t floatBytes; + static_assert(sizeof axis->sensorless_estimator_.pll_pos_ == sizeof floatBytes); + std::memcpy(&floatBytes, &axis->sensorless_estimator_.pll_pos_, sizeof floatBytes); - txmsg.buf[0] = floatBytes; - txmsg.buf[1] = floatBytes >> 8; - txmsg.buf[2] = floatBytes >> 16; - txmsg.buf[3] = floatBytes >> 24; + txmsg.buf[0] = floatBytes; + txmsg.buf[1] = floatBytes >> 8; + txmsg.buf[2] = floatBytes >> 16; + txmsg.buf[3] = floatBytes >> 24; - static_assert(sizeof floatBytes == sizeof axis->sensorless_estimator_.vel_estimate_); - std::memcpy(&floatBytes, &axis->sensorless_estimator_.vel_estimate_, sizeof floatBytes); - txmsg.buf[4] = floatBytes; - txmsg.buf[5] = floatBytes >> 8; - txmsg.buf[6] = floatBytes >> 16; - txmsg.buf[7] = floatBytes >> 24; + static_assert(sizeof floatBytes == sizeof axis->sensorless_estimator_.vel_estimate_); + std::memcpy(&floatBytes, &axis->sensorless_estimator_.vel_estimate_, sizeof floatBytes); + txmsg.buf[4] = floatBytes; + txmsg.buf[5] = floatBytes >> 8; + txmsg.buf[6] = floatBytes >> 16; + txmsg.buf[7] = floatBytes >> 24; - odCAN->write(txmsg); + odCAN->write(txmsg); + } } void CANSimple::get_encoder_count_callback(Axis* axis, CAN_message_t& msg) { - CAN_message_t txmsg; - txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; - txmsg.id += MSG_GET_ENCODER_COUNT; - txmsg.isExt = false; - txmsg.len = 8; + if (msg.rtr) { + CAN_message_t txmsg; + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_ENCODER_COUNT; + txmsg.isExt = false; + txmsg.len = 8; - txmsg.buf[0] = axis->encoder_.shadow_count_; - txmsg.buf[1] = axis->encoder_.shadow_count_ >> 8; - txmsg.buf[2] = axis->encoder_.shadow_count_ >> 16; - txmsg.buf[3] = axis->encoder_.shadow_count_ >> 24; + txmsg.buf[0] = axis->encoder_.shadow_count_; + txmsg.buf[1] = axis->encoder_.shadow_count_ >> 8; + txmsg.buf[2] = axis->encoder_.shadow_count_ >> 16; + txmsg.buf[3] = axis->encoder_.shadow_count_ >> 24; - txmsg.buf[4] = axis->encoder_.count_in_cpr_; - txmsg.buf[5] = axis->encoder_.count_in_cpr_ >> 8; - txmsg.buf[6] = axis->encoder_.count_in_cpr_ >> 16; - txmsg.buf[7] = axis->encoder_.count_in_cpr_ >> 24; + txmsg.buf[4] = axis->encoder_.count_in_cpr_; + txmsg.buf[5] = axis->encoder_.count_in_cpr_ >> 8; + txmsg.buf[6] = axis->encoder_.count_in_cpr_ >> 16; + txmsg.buf[7] = axis->encoder_.count_in_cpr_ >> 24; - odCAN->write(txmsg); + odCAN->write(txmsg); + } } void CANSimple::move_to_pos_callback(Axis* axis, CAN_message_t& msg) { @@ -300,56 +312,61 @@ void CANSimple::set_traj_A_per_css_callback(Axis* axis, CAN_message_t& msg) { } void CANSimple::get_iq_callback(Axis* axis, CAN_message_t& msg) { - CAN_message_t txmsg; - txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; - txmsg.id += MSG_GET_IQ; - txmsg.isExt = false; - txmsg.len = 8; + if (msg.rtr) { + CAN_message_t txmsg; + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_IQ; + txmsg.isExt = false; + txmsg.len = 8; - uint32_t floatBytes; - static_assert(sizeof axis->motor_.current_control_.Iq_setpoint == sizeof floatBytes); - std::memcpy(&floatBytes, &axis->motor_.current_control_.Iq_setpoint, sizeof floatBytes); + uint32_t floatBytes; + static_assert(sizeof axis->motor_.current_control_.Iq_setpoint == sizeof floatBytes); + std::memcpy(&floatBytes, &axis->motor_.current_control_.Iq_setpoint, sizeof floatBytes); - txmsg.buf[0] = floatBytes; - txmsg.buf[1] = floatBytes >> 8; - txmsg.buf[2] = floatBytes >> 16; - txmsg.buf[3] = floatBytes >> 24; + txmsg.buf[0] = floatBytes; + txmsg.buf[1] = floatBytes >> 8; + txmsg.buf[2] = floatBytes >> 16; + txmsg.buf[3] = floatBytes >> 24; - static_assert(sizeof floatBytes == sizeof axis->motor_.current_control_.Iq_measured); - std::memcpy(&floatBytes, &axis->motor_.current_control_.Iq_measured, sizeof floatBytes); - txmsg.buf[4] = floatBytes; - txmsg.buf[5] = floatBytes >> 8; - txmsg.buf[6] = floatBytes >> 16; - txmsg.buf[7] = floatBytes >> 24; + static_assert(sizeof floatBytes == sizeof axis->motor_.current_control_.Iq_measured); + std::memcpy(&floatBytes, &axis->motor_.current_control_.Iq_measured, sizeof floatBytes); + txmsg.buf[4] = floatBytes; + txmsg.buf[5] = floatBytes >> 8; + txmsg.buf[6] = floatBytes >> 16; + txmsg.buf[7] = floatBytes >> 24; - odCAN->write(txmsg); + odCAN->write(txmsg); + } } void CANSimple::get_vbus_voltage_callback(Axis* axis, CAN_message_t& msg) { - CAN_message_t txmsg; + if (msg.rtr) { + CAN_message_t txmsg; - txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; - txmsg.id += MSG_GET_VBUS_VOLTAGE; - txmsg.isExt = false; - txmsg.len = 8; + txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; + txmsg.id += MSG_GET_VBUS_VOLTAGE; + txmsg.isExt = false; + txmsg.len = 8; - uint32_t floatBytes; - static_assert(sizeof vbus_voltage == sizeof floatBytes); - std::memcpy(&floatBytes, &vbus_voltage, sizeof floatBytes); + uint32_t floatBytes; + static_assert(sizeof vbus_voltage == sizeof floatBytes); + std::memcpy(&floatBytes, &vbus_voltage, sizeof floatBytes); - // This also works in principle, but I don't have hardware to verify endianness - // std::memcpy(&txmsg.buf[0], &vbus_voltage, sizeof vbus_voltage); + // This also works in principle, but I don't have hardware to verify endianness + // std::memcpy(&txmsg.buf[0], &vbus_voltage, sizeof vbus_voltage); - txmsg.buf[0] = floatBytes; - txmsg.buf[1] = floatBytes >> 8; - txmsg.buf[2] = floatBytes >> 16; - txmsg.buf[3] = floatBytes >> 24; + txmsg.buf[0] = floatBytes; + txmsg.buf[1] = floatBytes >> 8; + txmsg.buf[2] = floatBytes >> 16; + txmsg.buf[3] = floatBytes >> 24; + + txmsg.buf[4] = 0; + txmsg.buf[5] = 0; + txmsg.buf[6] = 0; + txmsg.buf[7] = 0; - txmsg.buf[4] = 0; - txmsg.buf[5] = 0; - txmsg.buf[6] = 0; - txmsg.buf[7] = 0; odCAN->write(txmsg); + } } void CANSimple::send_heartbeat(Axis* axis) { From 0a24ce3bf91dbe4672a400c0c263728ed88c3a04 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 17 Mar 2019 18:35:13 +0100 Subject: [PATCH 089/549] Fix minor CppCheck warning --- Firmware/MotorControl/axis.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 299fca54..bd4b65cc 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -13,8 +13,8 @@ Axis::Axis(const AxisHardwareConfig_t& hw_config, SensorlessEstimator& sensorless_estimator, Controller& controller, Motor& motor, - TrapezoidalTrajectory& trap) - : hw_config_(hw_config), + TrapezoidalTrajectory& trap) : + hw_config_(hw_config), config_(config), encoder_(encoder), sensorless_estimator_(sensorless_estimator), From 499d33d7886945b5c740e2856e46790a3d0850e9 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Tue, 19 Mar 2019 19:47:52 -0700 Subject: [PATCH 090/549] change to explicit pos-writing callback for trajectory updates --- Firmware/MotorControl/controller.cpp | 76 +++++++---------------- Firmware/MotorControl/controller.hpp | 24 +++---- Firmware/communication/ascii_protocol.cpp | 28 +++++---- tools/odrive/enums.py | 2 +- 4 files changed, 50 insertions(+), 80 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 35181049..741b3fd0 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -24,31 +24,10 @@ void Controller::set_error(Error_t error) { // Command Handling //-------------------------------- -void Controller::set_pos_setpoint(float pos_setpoint, float vel_feed_forward, float current_feed_forward) { - pos_setpoint_ = pos_setpoint; - vel_setpoint_ = vel_feed_forward; - current_setpoint_ = current_feed_forward; - config_.control_mode = CTRL_MODE_POSITION_CONTROL; -#ifdef DEBUG_PRINT - printf("POSITION_CONTROL %6.0f %3.3f %3.3f\n", pos_setpoint, vel_setpoint_, current_setpoint_); -#endif -} - -void Controller::set_vel_setpoint(float vel_setpoint, float current_feed_forward) { - vel_setpoint_ = vel_setpoint; - current_setpoint_ = current_feed_forward; - config_.control_mode = CTRL_MODE_VELOCITY_CONTROL; -#ifdef DEBUG_PRINT - printf("VELOCITY_CONTROL %3.3f %3.3f\n", vel_setpoint_, motor->current_setpoint_); -#endif -} - -void Controller::set_current_setpoint(float current_setpoint) { - current_setpoint_ = current_setpoint; - config_.control_mode = CTRL_MODE_CURRENT_CONTROL; -#ifdef DEBUG_PRINT - printf("CURRENT_CONTROL %3.3f\n", current_setpoint_); -#endif +void Controller::input_pos_updated() { + if (config_.input_mode == INPUT_MODE_TRAP_TRAJ) { + move_to_pos(input_pos_); + } } void Controller::move_to_pos(float goal_point) { @@ -57,7 +36,7 @@ void Controller::move_to_pos(float goal_point) { axis_->trap_.config_.accel_limit, axis_->trap_.config_.decel_limit); traj_start_loop_count_ = axis_->loop_counter_; - config_.control_mode = CTRL_MODE_TRAJECTORY_CONTROL; + trajectory_done_ = false; goal_point_ = goal_point; } @@ -91,11 +70,10 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) anticogging_.cogging_map[anticogging_.index++] = vel_integrator_current_; } if (anticogging_.index < axis_->encoder_.config_.cpr) { // TODO: remove the dependency on encoder CPR - set_pos_setpoint(anticogging_.index, 0.0f, 0.0f); + pos_setpoint_ = anticogging_.index; return false; } else { anticogging_.index = 0; - set_pos_setpoint(0.0f, 0.0f, 0.0f); // Send the motor home anticogging_.use_anticogging = true; // We're good to go, enable anti-cogging anticogging_.calib_anticogging = false; return true; @@ -149,11 +127,25 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // // NOT YET IMPLEMENTED // } break; case INPUT_MODE_TRAP_TRAJ: { - static auto last_pos = input_pos_; - if(last_pos != input_pos_){ - last_pos = input_pos_; - move_to_pos(input_pos_); // We should really move the *setpoint* handling here, but this will work for now + // Avoid updating uninitialized trajectory + if (trajectory_done_) + break; + // Note: uint32_t loop count delta is OK across overflow + // Beware of negative deltas, as they will not be well behaved due to uint! + float t = (axis_->loop_counter_ - traj_start_loop_count_) * current_meas_period; + if (t > axis_->trap_.Tf_) { + // Drop into position control mode when done to avoid problems on loop counter delta overflow + config_.control_mode = CTRL_MODE_POSITION_CONTROL; + pos_setpoint_ = input_pos_; + vel_setpoint_ = 0.0f; + current_setpoint_ = 0.0f; + } else { + TrapezoidalTrajectory::Step_t traj_step = axis_->trap_.eval(t); + pos_setpoint_ = traj_step.Y; + vel_setpoint_ = traj_step.Yd; + current_setpoint_ = traj_step.Ydd * config_.inertia; } + anticogging_pos = pos_setpoint_; // FF the position setpoint instead of the pos_estimate } break; default: { set_error(ERROR_INVALID_INPUT_MODE); @@ -161,26 +153,6 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s } } - // Trajectory control - if (config_.control_mode == CTRL_MODE_TRAJECTORY_CONTROL) { - // Note: uint32_t loop count delta is OK across overflow - // Beware of negative deltas, as they will not be well behaved due to uint! - float t = (axis_->loop_counter_ - traj_start_loop_count_) * current_meas_period; - if (t > axis_->trap_.Tf_) { - // Drop into position control mode when done to avoid problems on loop counter delta overflow - config_.control_mode = CTRL_MODE_POSITION_CONTROL; - pos_setpoint_ = input_pos_; - vel_setpoint_ = 0.0f; - current_setpoint_ = 0.0f; - } else { - TrapezoidalTrajectory::Step_t traj_step = axis_->trap_.eval(t); - pos_setpoint_ = traj_step.Y; - vel_setpoint_ = traj_step.Yd; - current_setpoint_ = traj_step.Ydd * config_.inertia; - } - anticogging_pos = pos_setpoint_; // FF the position setpoint instead of the pos_estimate - } - // Position control // TODO Decide if we want to use encoder or pll position here float vel_des = vel_setpoint_; diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 74021c0b..4bc3bae5 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -20,8 +20,7 @@ public: CTRL_MODE_VOLTAGE_CONTROL = 0, CTRL_MODE_CURRENT_CONTROL = 1, CTRL_MODE_VELOCITY_CONTROL = 2, - CTRL_MODE_POSITION_CONTROL = 3, - CTRL_MODE_TRAJECTORY_CONTROL = 4 + CTRL_MODE_POSITION_CONTROL = 3 }; enum InputMode_t{ @@ -35,7 +34,7 @@ public: struct Config_t { ControlMode_t control_mode = CTRL_MODE_POSITION_CONTROL; //see: ControlMode_t - InputMode_t input_mode = INPUT_MODE_INACTIVE; //see: InputMode_t + InputMode_t input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t float pos_gain = 20.0f; // [(counts/s) / counts] float vel_gain = 5.0f / 10000.0f; // [A/(counts/s)] // float vel_gain = 5.0f / 200.0f, // [A/(rad/s)] @@ -52,9 +51,7 @@ public: void reset(); void set_error(Error_t error); - void set_pos_setpoint(float pos_setpoint, float vel_feed_forward, float current_feed_forward); - void set_vel_setpoint(float vel_setpoint, float current_feed_forward); - void set_current_setpoint(float current_setpoint); + void input_pos_updated(); // Trajectory-Planned control void move_to_pos(float goal_point); @@ -94,7 +91,7 @@ public: }; Error_t error_ = ERROR_NONE; - // variables exposed on protocol + float pos_setpoint_ = 0.0f; float vel_setpoint_ = 0.0f; // float vel_setpoint = 800.0f; @@ -108,19 +105,21 @@ public: float input_filter_ki_ = 0.0f; uint32_t traj_start_loop_count_ = 0; - float goal_point_ = 0.0f; + bool trajectory_done_ = true; // Communication protocol definitions auto make_protocol_definitions() { return make_protocol_member_list( make_protocol_property("error", &error_), - make_protocol_property("input_pos", &input_pos_), + make_protocol_property("input_pos", &input_pos_, + [](void* ctx) { static_cast(ctx)->input_pos_updated(); }, this), make_protocol_property("input_vel", &input_vel_), make_protocol_property("input_current", &input_current_), make_protocol_ro_property("pos_setpoint", &pos_setpoint_), make_protocol_ro_property("vel_setpoint", &vel_setpoint_), make_protocol_ro_property("current_setpoint", ¤t_setpoint_), + make_protocol_ro_property("trajectory_done", &trajectory_done_), make_protocol_property("vel_integrator_current", &vel_integrator_current_), make_protocol_object("config", make_protocol_property("control_mode", &config_.control_mode), @@ -136,13 +135,6 @@ public: make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth, [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this) ), - make_protocol_function("set_pos_setpoint", *this, &Controller::set_pos_setpoint, - "pos_setpoint", "vel_feed_forward", "current_feed_forward"), - make_protocol_function("set_vel_setpoint", *this, &Controller::set_vel_setpoint, - "vel_setpoint", "current_feed_forward"), - make_protocol_function("set_current_setpoint", *this, &Controller::set_current_setpoint, - "current_setpoint"), - make_protocol_function("move_to_pos", *this, &Controller::move_to_pos, "pos_setpoint"), make_protocol_function("move_incremental", *this, &Controller::move_incremental, "displacement", "from_goal_point"), make_protocol_function("start_anticogging_calibration", *this, &Controller::start_anticogging_calibration) ); diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 1e1c9ba4..f16d5dc2 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -95,12 +95,14 @@ 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 { - if (numscan < 3) - vel_feed_forward = 0.0f; - if (numscan < 4) - current_feed_forward = 0.0f; Axis* axis = axes[motor_number]; - axis->controller_.set_pos_setpoint(pos_setpoint, vel_feed_forward, current_feed_forward); + axis->controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; + axis->controller_.input_pos_ = pos_setpoint; + if (numscan >= 3) + axis->controller_.input_vel_ = vel_feed_forward; + if (numscan >= 4) + axis->controller_.input_current_ = current_feed_forward; + axis->controller_.input_pos_updated(); axis->watchdog_feed(); } @@ -114,12 +116,13 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "invalid motor %u", motor_number); } else { Axis* axis = axes[motor_number]; - axis->controller_.pos_setpoint_ = pos_setpoint; + axis->controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; + axis->controller_.input_pos_ = pos_setpoint; if (numscan >= 3) axis->controller_.config_.vel_limit = vel_limit; if (numscan >= 4) axis->motor_.config_.current_lim = current_lim; - + axis->controller_.input_pos_updated(); axis->watchdog_feed(); } @@ -132,10 +135,11 @@ 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 { - if (numscan < 3) - current_feed_forward = 0.0f; Axis* axis = axes[motor_number]; - axis->controller_.set_vel_setpoint(vel_setpoint, current_feed_forward); + axis->controller_.config_.control_mode = Controller::CTRL_MODE_VELOCITY_CONTROL; + axis->controller_.input_vel_ = vel_setpoint; + if (numscan >= 3) + axis->controller_.input_current_ = current_feed_forward; axis->watchdog_feed(); } @@ -149,7 +153,8 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "invalid motor %u", motor_number); } else { Axis* axis = axes[motor_number]; - axis->controller_.set_current_setpoint(current_setpoint); + axis->controller_.config_.control_mode = Controller::CTRL_MODE_CURRENT_CONTROL; + axis->controller_.input_current_ = current_setpoint; axis->watchdog_feed(); } @@ -163,6 +168,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "invalid motor %u", motor_number); } else { Axis* axis = axes[motor_number]; + axis->controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ; axis->controller_.move_to_pos(goal_point); axis->watchdog_feed(); } diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 329d1396..6b7113d6 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -64,13 +64,13 @@ CTRL_MODE_VOLTAGE_CONTROL = 0 CTRL_MODE_CURRENT_CONTROL = 1 CTRL_MODE_VELOCITY_CONTROL = 2 CTRL_MODE_POSITION_CONTROL = 3 -CTRL_MODE_TRAJECTORY_CONTROL = 4 INPUT_MODE_INACTIVE = 0 INPUT_MODE_PASSTHROUGH = 1 INPUT_MODE_VEL_RAMP = 2 INPUT_MODE_POS_FILTER = 3 INPUT_MODE_MIX_CHANNELS = 4 +INPUT_MODE_TRAP_TRAJ = 5 ENCODER_MODE_INCREMENTAL = 0 ENCODER_MODE_HALL = 1 From c80358bcd575b1cbe54e799da0eb6fe856890908 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Tue, 19 Mar 2019 19:56:26 -0700 Subject: [PATCH 091/549] update docs --- docs/getting-started.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index f5997982..cff70a77 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -261,7 +261,7 @@ You can now control the velocity with `axis.controller.input_pos = 1000` [counts Step response of a 1000 to 0 position input with a filter bandwidth of 1.0 [/sec]. ### Trajectory control -While in position control mode, use the `move_to_pos` or `move_incremental` functions. See the **Usage** section for details
+See the **Usage** section for usage details.
This mode lets you smoothly accelerate, coast, and decelerate the axis from one position to another. With raw position control, the controller simply tries to go to the setpoint as quickly as possible. Using a trajectory lets you tune the feedback gains more aggressively to reject disturbance, while keeping smooth motion. ![Taptraj](TrapTrajPosVel.PNG)
@@ -282,16 +282,21 @@ In the above image blue is position and orange is velocity. All values should be strictly positive (>= 0). -Keep in mind that you must still set your safety limits as before. I recommend you set these a little higher ( > 10%) than the planner values, to give the controller enough control authority. +Keep in mind that you must still set your safety limits as before. It is recommended you set these a little higher ( > 10%) than the planner values, to give the controller enough control authority. ``` ..motor.config.current_lim = ..controller.config.vel_limit = ``` #### Usage -Use the `move_to_pos` function to move to an absolute position: +Make sure you are in position control mode. To activate the trajectory module, set the input mode to trajectory: ``` -..controller.move_to_pos(your_absolute_pos) +axis.controller.config.input_mode = INPUT_MODE_TRAP_TRAJ +``` + +Simply send a position command to execute the move: +``` +..controller.input_pos = ``` Use the `move_incremental` function to move to a relative position. From 20ba0ca18ef273addf9aaf23abe152a47d8b996b Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Wed, 20 Mar 2019 15:00:32 -0700 Subject: [PATCH 092/549] fix getting started TOC --- docs/getting-started.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index cff70a77..f1a3fc70 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -7,7 +7,20 @@ permalink: / # Getting Started ### Table of contents -autoauto- [Hardware Requirements](#hardware-requirements)auto- [Wiring up the ODrive](#wiring-up-the-odrive)auto- [Downloading and Installing Tools](#downloading-and-installing-tools)auto- [Firmware](#firmware)auto- [Start `odrivetool`](#start-odrivetool)auto- [Configure M0](#configure-m0)auto- [Position control of M0](#position-control-of-m0)auto- [Other control modes](#other-control-modes)auto- [What's next?](#whats-next)autoauto + + +- [Hardware Requirements](#hardware-requirements) +- [Wiring up the ODrive](#wiring-up-the-odrive) +- [Downloading and Installing Tools](#downloading-and-installing-tools) +- [Firmware](#firmware) +- [Start `odrivetool`](#start-odrivetool) +- [Configure M0](#configure-m0) +- [Position control of M0](#position-control-of-m0) +- [Other control modes](#other-control-modes) +- [Watchdog Timer](#watchdog-timer) +- [What's next?](#whats-next) + + ## Hardware Requirements From 4a4c4c836d08f493482991444b5b12fcfe325cfa Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 23 Mar 2019 09:32:18 +0100 Subject: [PATCH 093/549] Fix RTR != header.RTR issue --- Firmware/communication/interface_can.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 1ee2e7cc..44cd7e2d 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -124,7 +124,7 @@ bool ODriveCAN::read(CAN_message_t &rxmsg) { rxmsg.isExt = header.IDE; rxmsg.id = rxmsg.isExt ? header.ExtId : header.StdId; // If it's an extended message, pass the extended ID rxmsg.len = header.DLC; - rxmsg.rtr = (header.RTR == CAN_RTR_REMOTE) ? true : false; + rxmsg.rtr = header.RTR; return validRead; } From 0c68cd929875f4223a16975a231aee933372462b Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 23 Mar 2019 09:35:05 +0100 Subject: [PATCH 094/549] Fix error code for Estop --- Firmware/MotorControl/axis.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index b37628cc..7971f475 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -21,7 +21,7 @@ public: ERROR_CONTROLLER_FAILED = 0x200, ERROR_POS_CTRL_DURING_SENSORLESS = 0x400, ERROR_WATCHDOG_TIMER_EXPIRED = 0x800, - ERROR_ESTOP_REQUESTED = 0x800 + ERROR_ESTOP_REQUESTED = 0x1000 }; enum State_t { From 850325558fe743c1320cf82914685f38830d3979 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 23 Mar 2019 09:36:09 +0100 Subject: [PATCH 095/549] Feed Axis Watchdog when receiving a valid message --- Firmware/communication/can_simple.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 74d23c21..d6327449 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -40,6 +40,7 @@ void CANSimple::handle_can_message(CAN_message_t& msg) { } if (validAxis) { + axis->watchdog_feed(); switch (cmd) { case MSG_CO_NMT_CTRL: break; From 703d686ca4dc0f168bbd59930ab1244f2b56e564 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 24 Mar 2019 18:42:29 +0100 Subject: [PATCH 096/549] Update documentation --- docs/can-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/can-protocol.md b/docs/can-protocol.md index c66a5d7b..fd1c49d1 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -60,7 +60,7 @@ CMD ID | Name | Sender | Signals | Start byte 0x016 | Reboot ODrive | Master\*\*\* | | 0x017 | Get Vbus Voltage | Master\*\*\* | Vbus Voltage | 0 -\* Note: These messages are call & response. The Master node sends a message with no payload, and the axis responds with the same ID and specified payload. +\* Note: These messages are call & response. The Master node sends a message with the RTR bit set, and the axis responds with the same ID and specified payload. \*\* Note: These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple. \*\*\* Note: These messages can be sent to either address on a given ODrive board. From db2c02c680e8f09af59c71fa515e17e3e486db07 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Mon, 25 Mar 2019 20:04:45 +0100 Subject: [PATCH 097/549] Allow for regen current before braking --- Firmware/MotorControl/low_level.cpp | 8 ++++---- Firmware/MotorControl/odrive_main.h | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 6125c99c..d2b995db 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -590,10 +590,10 @@ void update_brake_current() { Ibus_sum += axes[i]->motor_.current_control_.Ibus; } } - float brake_current = -Ibus_sum; - // Clip negative values to 0.0f - if (brake_current < 0.0f) brake_current = 0.0f; - float brake_duty = brake_current * board_config.brake_resistance / vbus_voltage; + + // Don't start braking until -Ibus > regen_current_allowed + float brake_current = std::max(-Ibus_sum - board_config.max_regen_current, 0.0f); + float brake_duty = std::max(brake_current * std::abs(board_config.brake_resistance) / vbus_voltage, 0.0f); // Duty limit at 90% to allow bootstrap caps to charge // If brake_duty is NaN, this expression will also evaluate to false diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 677fb996..dc711015 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -71,6 +71,7 @@ struct BoardConfig_t { bool enable_uart = true; bool enable_i2c_instead_of_can = false; bool enable_ascii_protocol_on_usb = true; + float max_regen_current = 0.0f; #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 5 && HW_VERSION_VOLTAGE >= 48 float brake_resistance = 2.0f; // [ohm] #else From 09a164d0ca22ee4ccb909a75762239a12f6b3f49 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Mon, 25 Mar 2019 20:52:36 +0100 Subject: [PATCH 098/549] Add dc_bus_over_power error --- Firmware/MotorControl/axis.cpp | 14 ++++++++++++++ Firmware/MotorControl/axis.hpp | 1 + Firmware/MotorControl/odrive_main.h | 1 + 3 files changed, 16 insertions(+) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 80987c00..4f845eab 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -140,6 +140,20 @@ bool Axis::do_checks() { if (!(vbus_voltage <= board_config.dc_bus_overvoltage_trip_level)) error_ |= ERROR_DC_BUS_OVER_VOLTAGE; + // This is the same math that's used in update_brake_current(). Should we calculate IBus globally? + float Ibus_sum = 0.0f; + for (size_t i = 0; i < AXIS_COUNT; ++i) { + if (axes[i]->motor_.armed_state_ == Motor::ARMED_STATE_ARMED) { + Ibus_sum += axes[i]->motor_.current_control_.Ibus; + } + } + + if(board_config.power_supply_wattage >= 0.0f && + Ibus_sum * vbus_voltage > board_config.power_supply_wattage) + { + error_ |= ERROR_DC_BUS_OVER_POWER; + } + // Sub-components should use set_error which will propegate to this error_ motor_.do_checks(); encoder_.do_checks(); diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 9e78fdab..3f240b87 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -21,6 +21,7 @@ public: ERROR_CONTROLLER_FAILED = 0x200, ERROR_POS_CTRL_DURING_SENSORLESS = 0x400, ERROR_WATCHDOG_TIMER_EXPIRED = 0x800, + ERROR_DC_BUS_OVER_POWER = 0x1000, }; enum State_t { diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 677fb996..7065cd42 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -81,6 +81,7 @@ struct BoardConfig_t { // Date: Mon, 25 Mar 2019 20:54:38 +0100 Subject: [PATCH 099/549] Add power_supply_wattage to board config on protocol --- Firmware/communication/communication.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 79982e64..2558adab 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -158,6 +158,7 @@ static inline auto make_obj_tree() { make_protocol_property("enable_ascii_protocol_on_usb", &board_config.enable_ascii_protocol_on_usb), make_protocol_property("dc_bus_undervoltage_trip_level", &board_config.dc_bus_undervoltage_trip_level), make_protocol_property("dc_bus_overvoltage_trip_level", &board_config.dc_bus_overvoltage_trip_level), + make_protocol_property("power_supply_wattage", &board_config.power_supply_wattage), #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 make_protocol_object("gpio1_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[0])), make_protocol_object("gpio2_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[1])), From 8cc30a687da43e327c1a726cfe6a68ed1242e818 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Mon, 25 Mar 2019 20:58:30 +0100 Subject: [PATCH 100/549] Add max_regen_current to protocol --- Firmware/communication/communication.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 79982e64..30b511a5 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -152,6 +152,7 @@ static inline auto make_obj_tree() { ), make_protocol_object("config", make_protocol_property("brake_resistance", &board_config.brake_resistance), + make_protocol_property("max_regen_current", &board_config.max_regen_current), // TODO: changing this currently requires a reboot - fix this make_protocol_property("enable_uart", &board_config.enable_uart), make_protocol_property("enable_i2c_instead_of_can" , &board_config.enable_i2c_instead_of_can), // requires a reboot From ac6bf281098ee3bbe2eb16cecfc58666554b850b Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 18 Apr 2019 21:22:28 -0700 Subject: [PATCH 101/549] add find multiple option --- Firmware/fibre/python/fibre/discovery.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Firmware/fibre/python/fibre/discovery.py b/Firmware/fibre/python/fibre/discovery.py index a3751633..950fddea 100644 --- a/Firmware/fibre/python/fibre/discovery.py +++ b/Firmware/fibre/python/fibre/discovery.py @@ -112,18 +112,23 @@ def find_all(path, serial_number, def find_any(path="usb", serial_number=None, search_cancellation_token=None, channel_termination_token=None, - timeout=None, logger=Logger(verbose=False)): + timeout=None, logger=Logger(verbose=False), find_multiple=False): """ Blocks until the first matching Fibre node is connected and then returns that node """ - result = [ None ] + result = [] done_signal = Event(search_cancellation_token) def did_discover_object(obj): - result[0] = obj - done_signal.set() + result.append(obj) + if not find_multiple: + done_signal.set() + find_all(path, serial_number, did_discover_object, done_signal, channel_termination_token, logger) try: done_signal.wait(timeout=timeout) finally: done_signal.set() # terminate find_all - return result[0] + if find_multiple: + return result + else: + return result[0] From 088b8f876ba42cead1474e52373b3647d722a967 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 19 Apr 2019 00:15:06 -0700 Subject: [PATCH 102/549] allow expected timeout --- Firmware/fibre/python/fibre/discovery.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Firmware/fibre/python/fibre/discovery.py b/Firmware/fibre/python/fibre/discovery.py index 950fddea..aa6a210b 100644 --- a/Firmware/fibre/python/fibre/discovery.py +++ b/Firmware/fibre/python/fibre/discovery.py @@ -126,8 +126,12 @@ def find_any(path="usb", serial_number=None, find_all(path, serial_number, did_discover_object, done_signal, channel_termination_token, logger) try: done_signal.wait(timeout=timeout) + except TimeoutError: + if not find_multiple: + return None finally: done_signal.set() # terminate find_all + if find_multiple: return result else: From e64f934af8a9e1a10e002165b1698fbd3474c763 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 22 Apr 2019 22:10:59 -0700 Subject: [PATCH 103/549] change find multiple to count required odrives instead of fixed timeout --- Firmware/fibre/python/fibre/discovery.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Firmware/fibre/python/fibre/discovery.py b/Firmware/fibre/python/fibre/discovery.py index aa6a210b..6039d7e0 100644 --- a/Firmware/fibre/python/fibre/discovery.py +++ b/Firmware/fibre/python/fibre/discovery.py @@ -120,7 +120,10 @@ def find_any(path="usb", serial_number=None, done_signal = Event(search_cancellation_token) def did_discover_object(obj): result.append(obj) - if not find_multiple: + if find_multiple: + if len(result) >= int(find_multiple): + done_signal.set() + else: done_signal.set() find_all(path, serial_number, did_discover_object, done_signal, channel_termination_token, logger) From 35d07595ac3dc079b846b9af468519144046307f Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Tue, 23 Apr 2019 21:52:11 +0200 Subject: [PATCH 104/549] Fix bad struct and enums --- Firmware/MotorControl/axis.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index a7f9e5b3..0be68574 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -28,8 +28,8 @@ public: ERROR_CONTROLLER_FAILED = 0x200, ERROR_POS_CTRL_DURING_SENSORLESS = 0x400, ERROR_WATCHDOG_TIMER_EXPIRED = 0x800, - ERROR_MIN_ENDSTOP_PRESSED = 0x800, - ERROR_MAX_ENDSTOP_PRESSED = 0x1000 + ERROR_MIN_ENDSTOP_PRESSED = 0x1000, + ERROR_MAX_ENDSTOP_PRESSED = 0x2000 }; enum State_t { @@ -44,7 +44,8 @@ public: AXIS_STATE_CLOSED_LOOP_CONTROL = 8, // Date: Tue, 23 Apr 2019 22:03:25 +0200 Subject: [PATCH 105/549] Fix GPIO Subscribe issues --- Firmware/Board/v3/Inc/gpio.h | 3 +-- Firmware/Board/v3/Src/gpio.c | 5 ++--- Firmware/MotorControl/axis.cpp | 3 +-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Firmware/Board/v3/Inc/gpio.h b/Firmware/Board/v3/Inc/gpio.h index c7251132..403271b6 100644 --- a/Firmware/Board/v3/Inc/gpio.h +++ b/Firmware/Board/v3/Inc/gpio.h @@ -72,8 +72,7 @@ void MX_GPIO_Init(void); void SetGPIO12toUART(); bool GPIO_subscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin, - uint32_t pull_up_down, uint32_t interrupt_mode, - void (*callback)(void*), void* ctx); + uint32_t pull_up_down, void (*callback)(void*), void* ctx); void GPIO_unsubscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin); void GPIO_set_to_analog(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin); diff --git a/Firmware/Board/v3/Src/gpio.c b/Firmware/Board/v3/Src/gpio.c index 2a7133df..571dce7a 100644 --- a/Firmware/Board/v3/Src/gpio.c +++ b/Firmware/Board/v3/Src/gpio.c @@ -205,8 +205,7 @@ size_t n_subscriptions = 0; // on a rising edge of the GPIO. // @param pull_up_down: one of GPIO_NOPULL, GPIO_PULLUP or GPIO_PULLDOWN bool GPIO_subscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin, - uint32_t pull_up_down, uint32_t interrupt_mode, - void (*callback)(void*), void* ctx) { + uint32_t pull_up_down, void (*callback)(void*), void* ctx) { // Register handler (or reuse existing registration) // TODO: make thread safe @@ -232,7 +231,7 @@ bool GPIO_subscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin, // Set up GPIO GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = GPIO_pin; - GPIO_InitStruct.Mode = interrupt_mode; + GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; GPIO_InitStruct.Pull = pull_up_down; HAL_GPIO_Init(GPIO_port, &GPIO_InitStruct); diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 737c0ee3..f668befa 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -121,8 +121,7 @@ void Axis::set_step_dir_active(bool active) { HAL_GPIO_Init(dir_port_, &GPIO_InitStruct); // Subscribe to rising edges of the step GPIO - GPIO_subscribe(step_port_, step_pin_, GPIO_PULLDOWN, - GPIO_MODE_IT_FALLING, step_cb_wrapper, this); + GPIO_subscribe(step_port_, step_pin_, GPIO_PULLDOWN, step_cb_wrapper, this); step_dir_active_ = true; } else { From 351ec2437aa4d846912938f9e73947e4431cd609 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Mon, 25 Mar 2019 20:19:09 +0100 Subject: [PATCH 106/549] Make encoder properties read-only --- Firmware/MotorControl/encoder.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index c2d32841..90382fc4 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -97,16 +97,16 @@ public: auto make_protocol_definitions() { return make_protocol_member_list( make_protocol_property("error", &error_), - 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_), + make_protocol_ro_property("is_ready", &is_ready_), + make_protocol_ro_property("index_found", const_cast(&index_found_)), + make_protocol_ro_property("shadow_count", &shadow_count_), + make_protocol_ro_property("count_in_cpr", &count_in_cpr_), + make_protocol_ro_property("interpolation", &interpolation_), make_protocol_ro_property("phase", &phase_), - make_protocol_property("pos_estimate", &pos_estimate_), - make_protocol_property("pos_cpr", &pos_cpr_), + make_protocol_ro_property("pos_estimate", &pos_estimate_), + make_protocol_ro_property("pos_cpr", &pos_cpr_), make_protocol_ro_property("hall_state", &hall_state_), - make_protocol_property("vel_estimate", &vel_estimate_), + make_protocol_ro_property("vel_estimate", &vel_estimate_), make_protocol_ro_property("calib_scan_response", &calib_scan_response_), // make_protocol_property("pll_kp", &pll_kp_), // make_protocol_property("pll_ki", &pll_ki_), From 4b29c5154893f74a4ea4308ac6b0aaa02e006dbd Mon Sep 17 00:00:00 2001 From: Tobin Hall Date: Fri, 18 Jan 2019 14:45:07 +1300 Subject: [PATCH 107/549] Adding preliminary support for SPI absolute encoders. --- .gitignore | 10 +++ Firmware/MotorControl/board_config_v3.h | 3 + Firmware/MotorControl/encoder.cpp | 108 ++++++++++++++++++++++++ Firmware/MotorControl/encoder.hpp | 22 +++++ Firmware/MotorControl/low_level.cpp | 10 +++ 5 files changed, 153 insertions(+) diff --git a/.gitignore b/.gitignore index ae3506ce..248cb3d2 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,13 @@ tup.config /_site /.bundle + +ODrive\.config + +ODrive\.creator + +ODrive\.creator\.user + +ODrive\.files + +ODrive\.includes diff --git a/Firmware/MotorControl/board_config_v3.h b/Firmware/MotorControl/board_config_v3.h index 58a8b3e2..7051d411 100644 --- a/Firmware/MotorControl/board_config_v3.h +++ b/Firmware/MotorControl/board_config_v3.h @@ -36,6 +36,7 @@ typedef struct { uint16_t hallB_pin; GPIO_TypeDef* hallC_port; uint16_t hallC_pin; + SPI_HandleTypeDef* spi; } EncoderHardwareConfig_t; typedef struct { TIM_HandleTypeDef* timer; @@ -86,6 +87,7 @@ const BoardHardwareConfig_t hw_configs[2] = { { .hallB_pin = M0_ENC_B_Pin, .hallC_port = M0_ENC_Z_GPIO_Port, .hallC_pin = M0_ENC_Z_Pin, + .spi = &hspi3, }, .motor_config = { .timer = &htim1, @@ -125,6 +127,7 @@ const BoardHardwareConfig_t hw_configs[2] = { { .hallB_pin = M1_ENC_B_Pin, .hallC_port = M1_ENC_Z_GPIO_Port, .hallC_pin = M1_ENC_Z_Pin, + .spi = &hspi3, }, .motor_config = { .timer = &htim8, diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 95dc91f9..7896cf0b 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -12,6 +12,9 @@ Encoder::Encoder(const EncoderHardwareConfig_t& hw_config, if (config.pre_calibrated && (config.mode == Encoder::MODE_HALL || config.mode == Encoder::MODE_SINCOS)) { is_ready_ = true; } + + decode_abs_spi_cs_pin(); + HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); } static void enc_index_cb_wrapper(void* ctx) { @@ -21,6 +24,9 @@ static void enc_index_cb_wrapper(void* ctx) { void Encoder::setup() { HAL_TIM_Encoder_Start(hw_config_.timer, TIM_CHANNEL_ALL); set_idx_subscribe(); + + if(config_.mode & MODE_FLAG_ABS) + abs_spi_init(); } void Encoder::set_error(Error_t error) { @@ -294,9 +300,96 @@ void Encoder::sample_now() { } } +bool Encoder::abs_spi_init(){ + // Init cs pin + HAL_GPIO_DeInit(abs_spi_cs_port_, abs_spi_cs_pin_); + GPIO_InitTypeDef GPIO_InitStruct; + GPIO_InitStruct.Pin = abs_spi_cs_pin_; + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Pull = GPIO_PULLUP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(abs_spi_cs_port_, &GPIO_InitStruct); + + uint32_t cr1,cr2; + cr1 = hw_config_.spi->Instance->CR1; + cr2 = hw_config_.spi->Instance->CR2; + + SPI_HandleTypeDef * spi = hw_config_.spi; + spi->Init.Mode = SPI_MODE_MASTER; + spi->Init.Direction = SPI_DIRECTION_2LINES; + spi->Init.DataSize = SPI_DATASIZE_16BIT; + spi->Init.CLKPolarity = SPI_POLARITY_LOW; + spi->Init.CLKPhase = SPI_PHASE_2EDGE; + spi->Init.NSS = SPI_NSS_SOFT; + spi->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; + spi->Init.FirstBit = SPI_FIRSTBIT_MSB; + spi->Init.TIMode = SPI_TIMODE_DISABLE; + spi->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; + spi->Init.CRCPolynomial = 10; + + HAL_SPI_DeInit(spi); + HAL_SPI_Init(spi); + //stash our configuration + abs_spi_cr1 = hw_config_.spi->Instance->CR1; + abs_spi_cr2 = hw_config_.spi->Instance->CR2; + + hw_config_.spi->Instance->CR1 = cr1; + hw_config_.spi->Instance->CR2 = cr2; + return true; +} + +bool Encoder::abs_spi_start_transaction(){ + if (config_.mode & MODE_FLAG_ABS){ + //TODO semaphore take + + //apply the stashed configuration + hw_config_.spi->Instance->CR1 = abs_spi_cr1; + hw_config_.spi->Instance->CR2 = abs_spi_cr2; + HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_RESET); + HAL_SPI_TransmitReceive_DMA(hw_config_.spi,(uint8_t*)abs_spi_dma_tx_,(uint8_t*)abs_spi_dma_rx_,1); + } + return true; +} + +uint8_t parity(uint16_t v){ + v ^= v >> 8; + v ^= v >> 4; + v ^= v >> 2; + v ^= v >> 1; + return v & 1; +} +void Encoder::abs_spi_cb(){ + //TODO semaphore release + HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); + switch (config_.mode) { + case MODE_SPI_ABS_AMS: { + //TODO check parity + uint8_t parity_calc, parity_bit; + parity_calc = parity(abs_spi_dma_rx_[0]&0x7FFF); + parity_bit = abs_spi_dma_rx_[0] >>15; + if(parity_calc != parity_bit) + set_error(ERROR_ABS_SPI_COM_FAIL); + pos_abs_ = abs_spi_dma_rx_[0] & 0x3FFF; + }break; + + default: { + set_error(ERROR_UNSUPPORTED_ENCODER_MODE); + } break; + } + + abs_spi_pos_updated_ = true; + is_ready_ = true; +} + +void Encoder::decode_abs_spi_cs_pin(){ + abs_spi_cs_port_ = get_gpio_port_by_pin(config_.abs_spi_cs_gpio_pin); + abs_spi_cs_pin_ = get_gpio_pin_by_pin(config_.abs_spi_cs_gpio_pin); +} + bool Encoder::update() { // update internal encoder state. int32_t delta_enc = 0; + switch (config_.mode) { case MODE_INCREMENTAL: { //TODO: use count_in_cpr_ instead as shadow_count_ can overflow @@ -331,6 +424,18 @@ bool Encoder::update() { delta_enc -= 6283; } break; + case MODE_SPI_ABS_AMS: + case MODE_SPI_ABS_CUI:{ + if(abs_spi_pos_updated_ == false){ + set_error(ERROR_ABS_SPI_TIMEOUT); + } + abs_spi_pos_updated_ = false; + delta_enc = pos_abs_ - count_in_cpr_; + delta_enc = mod(delta_enc, config_.cpr); + if (delta_enc > config_.cpr/2) + delta_enc -= config_.cpr; + + }break; default: { set_error(ERROR_UNSUPPORTED_ENCODER_MODE); return false; @@ -341,6 +446,9 @@ bool Encoder::update() { count_in_cpr_ += delta_enc; count_in_cpr_ = mod(count_in_cpr_, config_.cpr); + if(config_.mode & MODE_FLAG_ABS) + count_in_cpr_ = pos_abs_; + //// run pll (for now pll is in units of encoder counts) // Predict current pos pos_estimate_ += current_meas_period * vel_estimate_; diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index c2d32841..482ba111 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -15,13 +15,18 @@ public: ERROR_UNSUPPORTED_ENCODER_MODE = 0x08, ERROR_ILLEGAL_HALL_STATE = 0x10, ERROR_INDEX_NOT_FOUND_YET = 0x20, + ERROR_ABS_SPI_TIMEOUT = 0x30, + ERROR_ABS_SPI_COM_FAIL = 0x40, }; enum Mode_t { MODE_INCREMENTAL, MODE_HALL, MODE_SINCOS + MODE_SPI_ABS_CUI = 0x100, + MODE_SPI_ABS_AMS = 0x101, }; + const uint32_t MODE_FLAG_ABS = 0x100; struct Config_t { Encoder::Mode_t mode = Encoder::MODE_INCREMENTAL; @@ -43,6 +48,7 @@ public: bool find_idx_on_lockin_only = false; // Only be sensitive during lockin scan constant vel state bool idx_search_unidirectional = false; // Only allow index search in known direction bool ignore_illegal_hall_state = false; // dont error on bad states like 000 or 111 + uint16_t abs_spi_cs_gpio_pin = 0; }; Encoder(const EncoderHardwareConfig_t& hw_config, @@ -86,6 +92,7 @@ public: float pll_kp_ = 0.0f; // [count/s / count] float pll_ki_ = 0.0f; // [(count/s^2) / count] float calib_scan_response_ = 0.0f; // debug report from offset calib + int32_t pos_abs_ = 0; int16_t tim_cnt_sample_ = 0; // // Updated by low_level pwm_adc_cb @@ -93,6 +100,18 @@ public: float sincos_sample_s_ = 0.0f; float sincos_sample_c_ = 0.0f; + bool abs_spi_init(); + bool abs_spi_start_transaction(); + void abs_spi_cb(); + void decode_abs_spi_cs_pin(); + uint16_t abs_spi_dma_tx_[2] = {0xFFFF, 0x0000}; + uint16_t abs_spi_dma_rx_[2]; + bool abs_spi_pos_updated_; + GPIO_TypeDef* abs_spi_cs_port_; + uint16_t abs_spi_cs_pin_; + uint32_t abs_spi_cr1; + uint32_t abs_spi_cr2; + // Communication protocol definitions auto make_protocol_definitions() { return make_protocol_member_list( @@ -108,6 +127,7 @@ public: make_protocol_ro_property("hall_state", &hall_state_), make_protocol_property("vel_estimate", &vel_estimate_), make_protocol_ro_property("calib_scan_response", &calib_scan_response_), + make_protocol_property("pos_abs", &pos_abs_), // make_protocol_property("pll_kp", &pll_kp_), // make_protocol_property("pll_ki", &pll_ki_), make_protocol_object("config", @@ -118,6 +138,8 @@ public: [](void* ctx) { static_cast(ctx)->set_idx_subscribe(); }, this), make_protocol_property("pre_calibrated", &config_.pre_calibrated, [](void* ctx) { static_cast(ctx)->check_pre_calibrated(); }, this), + make_protocol_property("abs_spi_cs_gpio_pin", &config_.abs_spi_cs_gpio_pin, + [](void* ctx) { static_cast(ctx)->decode_abs_spi_cs_pin(); }, 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/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 6125c99c..2a899906 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -496,6 +496,10 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { update_timings = true; // update timings of M0 else if (&axis == axes[0] && !counting_down) update_timings = true; // update timings of M1 + + if(current_meas_not_DC_CAL){ + axis.encoder_.abs_spi_start_transaction(); + } } // Load next timings for the motor that we're not currently sampling @@ -756,3 +760,9 @@ void start_analog_thread() osThreadDef(thread_def, analog_polling_thread, osPriorityLow, 0, 4*512); osThreadCreate(osThread(thread_def), NULL); } + + +void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) +{ + axes[0]->encoder_.abs_spi_cb(); +} From 7440d982e3c3fbba702034654188f8e7dfcfe8fb Mon Sep 17 00:00:00 2001 From: Tobin Hall Date: Mon, 28 Jan 2019 12:06:07 +1300 Subject: [PATCH 108/549] Adding detection of which axis is waiting for the data. --- Firmware/MotorControl/low_level.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 2a899906..58c48755 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -764,5 +764,8 @@ void start_analog_thread() void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { - axes[0]->encoder_.abs_spi_cb(); + if(hspi->pRxBuffPtr == (uint8_t*)axes[0]->encoder_.abs_spi_dma_rx_) + axes[0]->encoder_.abs_spi_cb(); + else if (hspi->pRxBuffPtr == (uint8_t*)axes[1]->encoder_.abs_spi_dma_rx_) + axes[1]->encoder_.abs_spi_cb(); } From 31a143a500e5b421b0888af9e2bd48c76a332053 Mon Sep 17 00:00:00 2001 From: Tobin Hall Date: Mon, 28 Jan 2019 12:10:43 +1300 Subject: [PATCH 109/549] Fixing error code assignment. Adding SPI ready check. --- Firmware/MotorControl/encoder.cpp | 13 ++++++++----- Firmware/MotorControl/encoder.hpp | 5 +++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 7896cf0b..b6c9bc3b 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -13,8 +13,10 @@ Encoder::Encoder(const EncoderHardwareConfig_t& hw_config, is_ready_ = true; } - decode_abs_spi_cs_pin(); - HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); + if(config.mode & Encoder::MODE_FLAG_ABS){ + decode_abs_spi_cs_pin(); + HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); + } } static void enc_index_cb_wrapper(void* ctx) { @@ -340,8 +342,10 @@ bool Encoder::abs_spi_init(){ bool Encoder::abs_spi_start_transaction(){ if (config_.mode & MODE_FLAG_ABS){ - //TODO semaphore take - + if(hw_config_.spi->State != HAL_SPI_STATE_READY){ + set_error(ERROR_ABS_SPI_NOT_READY); + return false; + } //apply the stashed configuration hw_config_.spi->Instance->CR1 = abs_spi_cr1; hw_config_.spi->Instance->CR2 = abs_spi_cr2; @@ -359,7 +363,6 @@ uint8_t parity(uint16_t v){ return v & 1; } void Encoder::abs_spi_cb(){ - //TODO semaphore release HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); switch (config_.mode) { case MODE_SPI_ABS_AMS: { diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 482ba111..39d4f48e 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -15,8 +15,9 @@ public: ERROR_UNSUPPORTED_ENCODER_MODE = 0x08, ERROR_ILLEGAL_HALL_STATE = 0x10, ERROR_INDEX_NOT_FOUND_YET = 0x20, - ERROR_ABS_SPI_TIMEOUT = 0x30, - ERROR_ABS_SPI_COM_FAIL = 0x40, + ERROR_ABS_SPI_TIMEOUT = 0x40, + ERROR_ABS_SPI_COM_FAIL = 0x80, + ERROR_ABS_SPI_NOT_READY = 0x100, }; enum Mode_t { From bb7ce30ba48ce75d17f209aad1f0e7db91ced22a Mon Sep 17 00:00:00 2001 From: Tobin Hall Date: Thu, 14 Mar 2019 16:58:15 +1300 Subject: [PATCH 110/549] Adding low pass filter to encoder read failures. Staggering SPI triggers --- Firmware/MotorControl/encoder.cpp | 24 +++++++++++++++++------- Firmware/MotorControl/encoder.hpp | 1 + Firmware/MotorControl/low_level.cpp | 3 ++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index b6c9bc3b..2bd88747 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -323,7 +323,7 @@ bool Encoder::abs_spi_init(){ spi->Init.CLKPolarity = SPI_POLARITY_LOW; spi->Init.CLKPhase = SPI_PHASE_2EDGE; spi->Init.NSS = SPI_NSS_SOFT; - spi->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; + spi->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; spi->Init.FirstBit = SPI_FIRSTBIT_MSB; spi->Init.TIMode = SPI_TIMODE_DISABLE; spi->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; @@ -366,21 +366,24 @@ void Encoder::abs_spi_cb(){ HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); switch (config_.mode) { case MODE_SPI_ABS_AMS: { - //TODO check parity uint8_t parity_calc, parity_bit; parity_calc = parity(abs_spi_dma_rx_[0]&0x7FFF); parity_bit = abs_spi_dma_rx_[0] >>15; - if(parity_calc != parity_bit) - set_error(ERROR_ABS_SPI_COM_FAIL); + + if(parity_calc == parity_bit){ pos_abs_ = abs_spi_dma_rx_[0] & 0x3FFF; + // We are going to ignore values all high or low + // This might happen in normal operation, but its unlikely + // The filter will handle these cases + if(pos_abs_ != 0 && pos_abs_ != 0x3FFF) + abs_spi_pos_updated_ = true; + } }break; default: { set_error(ERROR_UNSUPPORTED_ENCODER_MODE); } break; } - - abs_spi_pos_updated_ = true; is_ready_ = true; } @@ -430,8 +433,15 @@ bool Encoder::update() { case MODE_SPI_ABS_AMS: case MODE_SPI_ABS_CUI:{ if(abs_spi_pos_updated_ == false){ - set_error(ERROR_ABS_SPI_TIMEOUT); + // Low pass filter the error + spi_error_rate_ += current_meas_period * (1.0f - spi_error_rate_); + if (spi_error_rate_ > 0.005f) + set_error(ERROR_ABS_SPI_COM_FAIL); } + else + // Low pass filter the error + spi_error_rate_ += current_meas_period * (0.0f - spi_error_rate_); + abs_spi_pos_updated_ = false; delta_enc = pos_abs_ - count_in_cpr_; delta_enc = mod(delta_enc, config_.cpr); diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 39d4f48e..63f982be 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -94,6 +94,7 @@ public: float pll_ki_ = 0.0f; // [(count/s^2) / count] float calib_scan_response_ = 0.0f; // debug report from offset calib int32_t pos_abs_ = 0; + float spi_error_rate_ = 0.0f; int16_t tim_cnt_sample_ = 0; // // Updated by low_level pwm_adc_cb diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 58c48755..c1cd02cd 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -497,7 +497,8 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { else if (&axis == axes[0] && !counting_down) update_timings = true; // update timings of M1 - if(current_meas_not_DC_CAL){ + if((current_meas_not_DC_CAL && !axis_num) || + (axis_num && !current_meas_not_DC_CAL)){ axis.encoder_.abs_spi_start_transaction(); } } From 9e99863eb628760d5077afd5fbbe65313e7c93c9 Mon Sep 17 00:00:00 2001 From: Tobin Hall Date: Sat, 11 May 2019 11:11:22 +1200 Subject: [PATCH 111/549] Fix for hanging during configuration. --- Firmware/MotorControl/encoder.cpp | 34 +++++++++++++++++-------------- Firmware/MotorControl/encoder.hpp | 7 ++++--- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 2bd88747..b268028a 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -12,11 +12,6 @@ Encoder::Encoder(const EncoderHardwareConfig_t& hw_config, if (config.pre_calibrated && (config.mode == Encoder::MODE_HALL || config.mode == Encoder::MODE_SINCOS)) { is_ready_ = true; } - - if(config.mode & Encoder::MODE_FLAG_ABS){ - decode_abs_spi_cs_pin(); - HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); - } } static void enc_index_cb_wrapper(void* ctx) { @@ -27,8 +22,10 @@ void Encoder::setup() { HAL_TIM_Encoder_Start(hw_config_.timer, TIM_CHANNEL_ALL); set_idx_subscribe(); - if(config_.mode & MODE_FLAG_ABS) + if(config_.mode & MODE_FLAG_ABS){ + abs_spi_cs_pin_init(); abs_spi_init(); + } } void Encoder::set_error(Error_t error) { @@ -303,14 +300,8 @@ void Encoder::sample_now() { } bool Encoder::abs_spi_init(){ - // Init cs pin - HAL_GPIO_DeInit(abs_spi_cs_port_, abs_spi_cs_pin_); - GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Pin = abs_spi_cs_pin_; - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; - GPIO_InitStruct.Pull = GPIO_PULLUP; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; - HAL_GPIO_Init(abs_spi_cs_port_, &GPIO_InitStruct); + if ((config_.mode & MODE_FLAG_ABS) == 0x0) + return false; uint32_t cr1,cr2; cr1 = hw_config_.spi->Instance->CR1; @@ -387,9 +378,22 @@ void Encoder::abs_spi_cb(){ is_ready_ = true; } -void Encoder::decode_abs_spi_cs_pin(){ +void Encoder::abs_spi_cs_pin_init(){ + // Decode cs pin abs_spi_cs_port_ = get_gpio_port_by_pin(config_.abs_spi_cs_gpio_pin); abs_spi_cs_pin_ = get_gpio_pin_by_pin(config_.abs_spi_cs_gpio_pin); + + // Init cs pin + HAL_GPIO_DeInit(abs_spi_cs_port_, abs_spi_cs_pin_); + GPIO_InitTypeDef GPIO_InitStruct; + GPIO_InitStruct.Pin = abs_spi_cs_pin_; + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Pull = GPIO_PULLUP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(abs_spi_cs_port_, &GPIO_InitStruct); + + // Write pin high + HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); } bool Encoder::update() { diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 63f982be..d0023b9e 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -105,7 +105,7 @@ public: bool abs_spi_init(); bool abs_spi_start_transaction(); void abs_spi_cb(); - void decode_abs_spi_cs_pin(); + void abs_spi_cs_pin_init(); uint16_t abs_spi_dma_tx_[2] = {0xFFFF, 0x0000}; uint16_t abs_spi_dma_rx_[2]; bool abs_spi_pos_updated_; @@ -133,7 +133,8 @@ public: // make_protocol_property("pll_kp", &pll_kp_), // make_protocol_property("pll_ki", &pll_ki_), make_protocol_object("config", - make_protocol_property("mode", &config_.mode), + make_protocol_property("mode", &config_.mode, + [](void* ctx) { static_cast(ctx)->abs_spi_init(); }, this), make_protocol_property("use_index", &config_.use_index, [](void* ctx) { static_cast(ctx)->set_idx_subscribe(); }, this), make_protocol_property("find_idx_on_lockin_only", &config_.find_idx_on_lockin_only, @@ -141,7 +142,7 @@ public: make_protocol_property("pre_calibrated", &config_.pre_calibrated, [](void* ctx) { static_cast(ctx)->check_pre_calibrated(); }, this), make_protocol_property("abs_spi_cs_gpio_pin", &config_.abs_spi_cs_gpio_pin, - [](void* ctx) { static_cast(ctx)->decode_abs_spi_cs_pin(); }, this), + [](void* ctx) { static_cast(ctx)->abs_spi_cs_pin_init(); }, 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), From 0944595bfb32e004a229a846cd8e8b1c22e5d81a Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 11 May 2019 23:26:27 +0200 Subject: [PATCH 112/549] Fix missing comma in encoder mode --- Firmware/MotorControl/encoder.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index d0023b9e..e738f81a 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -23,7 +23,7 @@ public: enum Mode_t { MODE_INCREMENTAL, MODE_HALL, - MODE_SINCOS + MODE_SINCOS, MODE_SPI_ABS_CUI = 0x100, MODE_SPI_ABS_AMS = 0x101, }; From d84a5f64717a2cd873c641ca154555fb180d0471 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 12 May 2019 00:37:13 +0200 Subject: [PATCH 113/549] Fix buffer overflow issue in 'w' and 'r' commands --- Firmware/communication/ascii_protocol.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 1e1c9ba4..a9722bc6 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -216,7 +216,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& } else if (cmd[0] == 'r') { // read property char name[MAX_LINE_LENGTH]; - int numscan = sscanf(cmd, "r %" TO_STR(MAX_LINE_LENGTH) "s", name); + int numscan = sscanf(cmd, "r %255s", name); if (numscan < 1) { respond(response_channel, use_checksum, "invalid command format"); } else { @@ -236,7 +236,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& } else if (cmd[0] == 'w') { // write property char name[MAX_LINE_LENGTH]; char value[MAX_LINE_LENGTH]; - int numscan = sscanf(cmd, "w %" TO_STR(MAX_LINE_LENGTH) "s %" TO_STR(MAX_LINE_LENGTH) "s", name, value); + int numscan = sscanf(cmd, "w %255s %255s", name, value); if (numscan < 1) { respond(response_channel, use_checksum, "invalid command format"); } else { From 0598c2c995110283da036b6b886212756082f6a1 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Thu, 16 May 2019 00:03:37 +0200 Subject: [PATCH 114/549] Setup doctest --- Firmware/.vscode/c_cpp_properties.json | 3 ++- Firmware/MotorControl/main.cpp | 9 +++++++++ Firmware/Tests/test_runner.cpp | 18 ++++++++++++++++++ Firmware/Tupfile.lua | 8 +++++--- 4 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 Firmware/Tests/test_runner.cpp diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index c4a2f37c..7f0685e5 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -17,7 +17,8 @@ "${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" + "${workspaceRoot}/Board/v3/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F", + "C:/Tools/doctest/doctest" ], "defines": [ "STM32F405xx", diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 427792b3..5a90ce17 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -98,6 +98,7 @@ void enter_dfu_mode() { extern "C" { int odrive_main(void); + void vApplicationStackOverflowHook(xTaskHandle *pxTask, signed portCHAR *pcTaskName) { for (;;); // TODO: safe action } @@ -116,8 +117,16 @@ void vApplicationIdleHook(void) { } } +#include int odrive_main(void) { +doctest::Context context; + +auto vals = context.run(); +while(vals){ + +} + #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 if (board_config.enable_i2c_instead_of_can) { // Set up the direction GPIO as input diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp new file mode 100644 index 00000000..26238a00 --- /dev/null +++ b/Firmware/Tests/test_runner.cpp @@ -0,0 +1,18 @@ + + +#define DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CONFIG_USE_STD_HEADERS +#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#define DOCTEST_CONFIG_NO_WINDOWS_SEH +#define DOCTEST_CONFIG_NO_POSIX_SIGNALS +// #define DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS + +#include + +#include "odrive_main.h" + +TEST_CASE("[something]"){ + CHECK(mod(3, 10) == 3); +} \ No newline at end of file diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 994fd746..5cb2addc 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -176,12 +176,14 @@ build{ 'communication/interface_can.cpp', 'communication/interface_i2c.cpp', 'fibre/cpp/protocol.cpp', - 'FreeRTOS-openocd.c' + 'FreeRTOS-openocd.c', + 'Tests/test_runner.cpp' }, includes={ 'Drivers/DRV8301', 'MotorControl', 'fibre/cpp/include', - '.' + '.', + "C:/Tools/doctest/doctest" } -} +} \ No newline at end of file From 9315c4e1c724292e9c9dcb72f7b2a2c0c094e059 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 19 May 2019 21:18:18 +0200 Subject: [PATCH 115/549] Write and test CAN getSignal function --- Firmware/Tests/test_runner.cpp | 80 ++++++++++++++++++++++++++++++++-- Firmware/Tupfile.lua | 3 +- 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index 26238a00..dfaaf981 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -1,6 +1,6 @@ -#define DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #define DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING #define DOCTEST_CONFIG_USE_STD_HEADERS #define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS @@ -11,8 +11,80 @@ #include -#include "odrive_main.h" +using std::cout; +using std::endl; +struct can_Message_t { + uint32_t id = 0x000; // 11-bit max is 0x7ff, 29-bit max is 0x1FFFFFFF + bool isExt = false; + bool rtr = false; + uint8_t len = 8; + uint8_t buf[8] = {0, 0, 0, 0, 0, 0, 0, 0}; +}; -TEST_CASE("[something]"){ - CHECK(mod(3, 10) == 3); +struct can_Signal_t { + uint8_t startBit = 0; + uint8_t length = 16; + bool isIntel = true; + float factor = 1.0f; + float offset = 0.0f; +}; + +// Fetch a specific signal from the message +template +T getSignal(can_Message_t msg, uint8_t startBit, uint8_t length, bool isIntel, float factor, float offset) { + uint64_t tempVal = 0; + uint64_t mask = (1ULL << length) - 1; + + if (isIntel) { + std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); + tempVal = (tempVal >> startBit) & mask; + } else { + std::reverse(std::begin(msg.buf), std::end(msg.buf)); + std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); + tempVal = (tempVal >> (64 - startBit - length)) & mask; + } + + T retVal; + std::memcpy(&retVal, &tempVal, sizeof(T)); + return static_cast((retVal * factor) + offset); +} + +// template +// T can_getSignal(can_Message_t msg, can_Signal_t signal){ +// return can_getSignal(msg, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); +// } + +TEST_SUITE("getSignal") { + TEST_CASE("reverse") { + can_Message_t rxmsg; + rxmsg.id = 0x000; + rxmsg.isExt = false; + rxmsg.len = 8; + + rxmsg.buf[0] = 0x12; + rxmsg.buf[1] = 0x34; + + std::reverse(std::begin(rxmsg.buf), std::end(rxmsg.buf)); + CHECK(rxmsg.buf[0] == 0x00); + CHECK(rxmsg.buf[6] == 0x34); + CHECK(rxmsg.buf[7] == 0x12); + } + + TEST_CASE("getSignal") { + can_Message_t rxmsg; + + auto val = 0x1234; + std::memcpy(rxmsg.buf, &val, sizeof(val)); + + val = getSignal(rxmsg, 0, 16, true, 1, 0); + CHECK(val == 0x1234); + + val = getSignal(rxmsg, 0, 16, false, 1, 0); + CHECK(val == 0x3412); + + float myFloat = 1234.6789f; + std::memcpy(rxmsg.buf, &myFloat, sizeof(myFloat)); + auto floatVal = getSignal(rxmsg, 0, 32, true, 1, 0); + CHECK(floatVal == 1234.6789f); + } } \ No newline at end of file diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index e667d708..027db034 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -177,8 +177,7 @@ build{ 'communication/interface_can.cpp', 'communication/interface_i2c.cpp', 'fibre/cpp/protocol.cpp', - 'FreeRTOS-openocd.c', - 'Tests/test_runner.cpp' + 'FreeRTOS-openocd.c' }, includes={ 'Drivers/DRV8301', From b09e835647030c354beb729a85c2fb413a7fc4ad Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 19 May 2019 21:18:46 +0200 Subject: [PATCH 116/549] Remove doctest from STM builds --- Firmware/MotorControl/main.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index ed34ba20..88e3262b 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -126,16 +126,8 @@ void vApplicationIdleHook(void) { } } -#include int odrive_main(void) { -doctest::Context context; - -auto vals = context.run(); -while(vals){ - -} - #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 if (board_config.enable_i2c_instead_of_can) { // Set up the direction GPIO as input From 9503a9ce2981b21a9fdfa6151dbd1d8447f02dd6 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 19 May 2019 21:19:30 +0200 Subject: [PATCH 117/549] Formatting pass on CAN files --- Firmware/communication/can_simple.cpp | 107 +++++++++-------------- Firmware/communication/can_simple.hpp | 80 ++++++++++------- Firmware/communication/interface_can.cpp | 6 +- Firmware/communication/interface_can.hpp | 6 +- ODrive_Workspace.code-workspace | 13 ++- 5 files changed, 109 insertions(+), 103 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index d6327449..81502c8e 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -7,7 +7,7 @@ static const uint8_t NUM_NODE_ID_BITS = 6; static constexpr uint8_t NUM_CMD_ID_BITS = 11 - NUM_NODE_ID_BITS; -void CANSimple::handle_can_message(CAN_message_t& msg) { +void CANSimple::handle_can_message(can_Message_t& msg) { // This functional way of handling the messages is neat and is much cleaner from // a data security point of view, but it will require some tweaking to fix the syntax. // @@ -121,17 +121,17 @@ void CANSimple::handle_can_message(CAN_message_t& msg) { } } -void CANSimple::nmt_callback(Axis* axis, CAN_message_t& msg) { +void CANSimple::nmt_callback(Axis* axis, can_Message_t& msg) { // Not implemented } -void CANSimple::estop_callback(Axis* axis, CAN_message_t& msg) { +void CANSimple::estop_callback(Axis* axis, can_Message_t& msg) { axis->error_ |= Axis::ERROR_ESTOP_REQUESTED; } -void CANSimple::get_motor_error_callback(Axis* axis, CAN_message_t& msg) { +void CANSimple::get_motor_error_callback(Axis* axis, can_Message_t& msg) { if (msg.rtr) { - CAN_message_t txmsg; + can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_MOTOR_ERROR; // heartbeat ID txmsg.isExt = false; @@ -146,9 +146,9 @@ void CANSimple::get_motor_error_callback(Axis* axis, CAN_message_t& msg) { } } -void CANSimple::get_encoder_error_callback(Axis* axis, CAN_message_t& msg) { +void CANSimple::get_encoder_error_callback(Axis* axis, can_Message_t& msg) { if (msg.rtr) { - CAN_message_t txmsg; + can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_ENCODER_ERROR; // heartbeat ID txmsg.isExt = false; @@ -163,9 +163,9 @@ void CANSimple::get_encoder_error_callback(Axis* axis, CAN_message_t& msg) { } } -void CANSimple::get_sensorless_error_callback(Axis* axis, CAN_message_t& msg) { +void CANSimple::get_sensorless_error_callback(Axis* axis, can_Message_t& msg) { if (msg.rtr) { - CAN_message_t txmsg; + can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_SENSORLESS_ERROR; // heartbeat ID txmsg.isExt = false; @@ -180,20 +180,20 @@ void CANSimple::get_sensorless_error_callback(Axis* axis, CAN_message_t& msg) { } } -void CANSimple::set_axis_nodeid_callback(Axis* axis, CAN_message_t& msg) { +void CANSimple::set_axis_nodeid_callback(Axis* axis, can_Message_t& msg) { axis->config_.can_node_id = msg.buf[0] & 0x3F; // Node ID bitmask } -void CANSimple::set_axis_requested_state_callback(Axis* axis, CAN_message_t& msg) { - axis->requested_state_ = static_cast(get_16bit_val(msg, 0)); +void CANSimple::set_axis_requested_state_callback(Axis* axis, can_Message_t& msg) { + axis->requested_state_ = static_cast(can_getSignal(msg, 0, 16, true, 1, 0)); } -void CANSimple::set_axis_startup_config_callback(Axis* axis, CAN_message_t& msg) { +void CANSimple::set_axis_startup_config_callback(Axis* axis, can_Message_t& msg) { // Not Implemented } -void CANSimple::get_encoder_estimates_callback(Axis* axis, CAN_message_t& msg) { +void CANSimple::get_encoder_estimates_callback(Axis* axis, can_Message_t& msg) { if (msg.rtr) { - CAN_message_t txmsg; + can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_ENCODER_ESTIMATES; // heartbeat ID txmsg.isExt = false; @@ -222,9 +222,9 @@ void CANSimple::get_encoder_estimates_callback(Axis* axis, CAN_message_t& msg) { } } -void CANSimple::get_sensorless_estimates_callback(Axis* axis, CAN_message_t& msg) { +void CANSimple::get_sensorless_estimates_callback(Axis* axis, can_Message_t& msg) { if (msg.rtr) { - CAN_message_t txmsg; + can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_SENSORLESS_ESTIMATES; // heartbeat ID txmsg.isExt = false; @@ -253,9 +253,9 @@ void CANSimple::get_sensorless_estimates_callback(Axis* axis, CAN_message_t& msg } } -void CANSimple::get_encoder_count_callback(Axis* axis, CAN_message_t& msg) { +void CANSimple::get_encoder_count_callback(Axis* axis, can_Message_t& msg) { if (msg.rtr) { - CAN_message_t txmsg; + can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_ENCODER_COUNT; txmsg.isExt = false; @@ -275,46 +275,46 @@ void CANSimple::get_encoder_count_callback(Axis* axis, CAN_message_t& msg) { } } -void CANSimple::move_to_pos_callback(Axis* axis, CAN_message_t& msg) { - axis->controller_.move_to_pos(get_32bit_val(msg, 0)); +void CANSimple::move_to_pos_callback(Axis* axis, can_Message_t& msg) { + axis->controller_.move_to_pos(can_getSignal(msg, 0, 32, true, 1, 0)); } -void CANSimple::set_pos_setpoint_callback(Axis* axis, CAN_message_t& msg) { - axis->controller_.set_pos_setpoint(get_32bit_val(msg, 0), get_16bit_val(msg, 4) * 0.1f, get_16bit_val(msg, 6) * 0.01f); +void CANSimple::set_pos_setpoint_callback(Axis* axis, can_Message_t& msg) { + axis->controller_.set_pos_setpoint(can_getSignal(msg, 0, 32, true, 1, 0), can_getSignal(msg, 32, 16, true, 0.1f, 0), can_getSignal(msg, 48, 16, true, 0.01f, 0)); } -void CANSimple::set_vel_setpoint_callback(Axis* axis, CAN_message_t& msg) { - axis->controller_.set_vel_setpoint(get_32bit_val(msg, 0) * 0.01f, get_32bit_val(msg, 4) * 0.01f); +void CANSimple::set_vel_setpoint_callback(Axis* axis, can_Message_t& msg) { + axis->controller_.set_vel_setpoint(can_getSignal(msg, 0, 32, true, 0.01f, 0.0f), can_getSignal(msg, 4, 32, true, 0.01f, 0.0f)); } -void CANSimple::set_current_setpoint_callback(Axis* axis, CAN_message_t& msg) { - axis->controller_.set_current_setpoint(get_32bit_val(msg, 0) * 0.01f); +void CANSimple::set_current_setpoint_callback(Axis* axis, can_Message_t& msg) { + axis->controller_.set_current_setpoint(can_getSignal(msg, 0, 32, true, 0.01f, 0)); } -void CANSimple::set_vel_limit_callback(Axis* axis, CAN_message_t& msg) { - axis->controller_.config_.vel_limit = get_float(msg, 0); +void CANSimple::set_vel_limit_callback(Axis* axis, can_Message_t& msg) { + axis->controller_.config_.vel_limit = can_getSignal(msg, 0, 32, true, 1, 0); } -void CANSimple::start_anticogging_callback(Axis* axis, CAN_message_t& msg) { +void CANSimple::start_anticogging_callback(Axis* axis, can_Message_t& msg) { axis->controller_.start_anticogging_calibration(); } -void CANSimple::set_traj_vel_limit_callback(Axis* axis, CAN_message_t& msg) { - axis->trap_.config_.vel_limit = get_float(msg, 0); +void CANSimple::set_traj_vel_limit_callback(Axis* axis, can_Message_t& msg) { + axis->trap_.config_.vel_limit = can_getSignal(msg, 0, 32, true, 1, 0); } -void CANSimple::set_traj_accel_limits_callback(Axis* axis, CAN_message_t& msg) { - axis->trap_.config_.accel_limit = get_float(msg, 0); - axis->trap_.config_.decel_limit = get_float(msg, 4); +void CANSimple::set_traj_accel_limits_callback(Axis* axis, can_Message_t& msg) { + axis->trap_.config_.accel_limit = can_getSignal(msg, 0, 32, true, 1, 0); + axis->trap_.config_.decel_limit = can_getSignal(msg, 32, 32, true, 1, 0); } -void CANSimple::set_traj_A_per_css_callback(Axis* axis, CAN_message_t& msg) { - axis->trap_.config_.A_per_css = get_float(msg, 0); +void CANSimple::set_traj_A_per_css_callback(Axis* axis, can_Message_t& msg) { + axis->trap_.config_.A_per_css = can_getSignal(msg, 0, 32, true, 1, 0); } -void CANSimple::get_iq_callback(Axis* axis, CAN_message_t& msg) { +void CANSimple::get_iq_callback(Axis* axis, can_Message_t& msg) { if (msg.rtr) { - CAN_message_t txmsg; + can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_IQ; txmsg.isExt = false; @@ -340,9 +340,9 @@ void CANSimple::get_iq_callback(Axis* axis, CAN_message_t& msg) { } } -void CANSimple::get_vbus_voltage_callback(Axis* axis, CAN_message_t& msg) { +void CANSimple::get_vbus_voltage_callback(Axis* axis, can_Message_t& msg) { if (msg.rtr) { - CAN_message_t txmsg; + can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_VBUS_VOLTAGE; @@ -371,7 +371,7 @@ void CANSimple::get_vbus_voltage_callback(Axis* axis, CAN_message_t& msg) { } void CANSimple::send_heartbeat(Axis* axis) { - CAN_message_t txmsg; + can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_ODRIVE_HEARTBEAT; // heartbeat ID txmsg.isExt = false; @@ -397,27 +397,4 @@ uint8_t CANSimple::get_node_id(uint32_t msgID) { uint8_t CANSimple::get_cmd_id(uint32_t msgID) { return (msgID & 0x01F); // Bottom 5 bits -} - -int16_t CANSimple::get_16bit_val(CAN_message_t& msg, uint8_t start_byte) { - int16_t retVal = 0; - if (msg.len >= start_byte && size_t(msg.len - start_byte) >= sizeof(retVal)) - std::memcpy(&retVal, &(msg.buf[start_byte]), sizeof(retVal)); - return retVal; -} - -int32_t CANSimple::get_32bit_val(CAN_message_t& msg, uint8_t start_byte) { - int32_t retVal = 0; - if (msg.len >= start_byte && size_t(msg.len - start_byte) >= sizeof(retVal)) - std::memcpy(&retVal, &(msg.buf[start_byte]), sizeof(retVal)); - return retVal; -} - -float CANSimple::get_float(CAN_message_t& msg, uint8_t start_byte) { - int32_t val = get_32bit_val(msg, start_byte); - float retVal; - - static_assert(sizeof retVal == sizeof val); - std::memcpy(&retVal, &val, sizeof val); // Sexier int32_t -> float cast that isn't UB - return retVal; } \ No newline at end of file diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp index fd078c5a..9138e109 100644 --- a/Firmware/communication/can_simple.hpp +++ b/Firmware/communication/can_simple.hpp @@ -6,8 +6,8 @@ class CANSimple { public: enum { - MSG_CO_NMT_CTRL = 0x000, // CANOpen NMT Message REC - MSG_CO_HEARTBEAT_CMD = 0x700, // CANOpen NMT Heartbeat SEND + MSG_CO_NMT_CTRL = 0x000, // CANOpen NMT Message REC + MSG_CO_HEARTBEAT_CMD = 0x700, // CANOpen NMT Heartbeat SEND MSG_ODRIVE_HEARTBEAT = 0x001, MSG_ODRIVE_ESTOP, MSG_GET_MOTOR_ERROR, // Errors @@ -33,49 +33,67 @@ class CANSimple { MSG_GET_VBUS_VOLTAGE, }; - static void handle_can_message(CAN_message_t& msg); + static void handle_can_message(can_Message_t& msg); static void send_heartbeat(Axis* axis); private: - static void nmt_callback(Axis* axis, CAN_message_t& msg); - static void estop_callback(Axis* axis, CAN_message_t& msg); - static void get_motor_error_callback(Axis* axis, CAN_message_t& msg); - static void get_encoder_error_callback(Axis* axis, CAN_message_t& msg); - static void get_controller_error_callback(Axis* axis, CAN_message_t& msg); - static void get_sensorless_error_callback(Axis* axis, CAN_message_t& msg); - static void set_axis_nodeid_callback(Axis* axis, CAN_message_t& msg); - static void set_axis_requested_state_callback(Axis* axis, CAN_message_t& msg); - static void set_axis_startup_config_callback(Axis* axis, CAN_message_t& msg); - static void get_encoder_estimates_callback(Axis* axis, CAN_message_t& msg); - static void get_encoder_count_callback(Axis* axis, CAN_message_t& msg); - static void move_to_pos_callback(Axis* axis, CAN_message_t& msg); - static void set_pos_setpoint_callback(Axis* axis, CAN_message_t& msg); - static void set_vel_setpoint_callback(Axis* axis, CAN_message_t& msg); - static void set_current_setpoint_callback(Axis* axis, CAN_message_t& msg); - static void set_vel_limit_callback(Axis* axis, CAN_message_t& msg); - static void start_anticogging_callback(Axis* axis, CAN_message_t& msg); - static void set_traj_vel_limit_callback(Axis* axis, CAN_message_t& msg); - static void set_traj_accel_limits_callback(Axis* axis, CAN_message_t& msg); - static void set_traj_A_per_css_callback(Axis* axis, CAN_message_t& msg); - static void get_iq_callback(Axis* axis, CAN_message_t& msg); - static void get_sensorless_estimates_callback(Axis* axis, CAN_message_t& msg); - static void get_vbus_voltage_callback(Axis* axis, CAN_message_t& msg); - + static void nmt_callback(Axis* axis, can_Message_t& msg); + static void estop_callback(Axis* axis, can_Message_t& msg); + static void get_motor_error_callback(Axis* axis, can_Message_t& msg); + static void get_encoder_error_callback(Axis* axis, can_Message_t& msg); + static void get_controller_error_callback(Axis* axis, can_Message_t& msg); + static void get_sensorless_error_callback(Axis* axis, can_Message_t& msg); + static void set_axis_nodeid_callback(Axis* axis, can_Message_t& msg); + static void set_axis_requested_state_callback(Axis* axis, can_Message_t& msg); + static void set_axis_startup_config_callback(Axis* axis, can_Message_t& msg); + static void get_encoder_estimates_callback(Axis* axis, can_Message_t& msg); + static void get_encoder_count_callback(Axis* axis, can_Message_t& msg); + static void move_to_pos_callback(Axis* axis, can_Message_t& msg); + static void set_pos_setpoint_callback(Axis* axis, can_Message_t& msg); + static void set_vel_setpoint_callback(Axis* axis, can_Message_t& msg); + static void set_current_setpoint_callback(Axis* axis, can_Message_t& msg); + static void set_vel_limit_callback(Axis* axis, can_Message_t& msg); + static void start_anticogging_callback(Axis* axis, can_Message_t& msg); + static void set_traj_vel_limit_callback(Axis* axis, can_Message_t& msg); + static void set_traj_accel_limits_callback(Axis* axis, can_Message_t& msg); + static void set_traj_A_per_css_callback(Axis* axis, can_Message_t& msg); + static void get_iq_callback(Axis* axis, can_Message_t& msg); + static void get_sensorless_estimates_callback(Axis* axis, can_Message_t& msg); + static void get_vbus_voltage_callback(Axis* axis, can_Message_t& msg); // Utility functions static uint8_t get_node_id(uint32_t msgID); static uint8_t get_cmd_id(uint32_t msgID); - static int16_t get_16bit_val(CAN_message_t& msg, uint8_t start_byte); - static int32_t get_32bit_val(CAN_message_t& msg, uint8_t start_byte); - static float get_float(CAN_message_t& msg, uint8_t start_byte); + // Fetch a specific signal from the message // This functional way of handling the messages is neat and is much cleaner from // a data security point of view, but it will require some tweaking // - // const std::map> callback_map = { + // const std::map> callback_map = { // {0x000, std::bind(&CANSimple::heartbeat_callback, this, _1)} // }; }; + +#include +template +T can_getSignal(can_Message_t msg, uint8_t startBit, uint8_t length, bool isIntel, float factor, float offset) { + uint64_t tempVal = 0; + uint64_t mask = (1ULL << length) - 1; + + if (isIntel) { + std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); + tempVal = (tempVal >> startBit) & mask; + } else { + std::reverse(std::begin(msg.buf), std::end(msg.buf)); + std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); + tempVal = (tempVal >> (64 - startBit - length)) & mask; + } + + T retVal; + std::memcpy(&retVal, &tempVal, sizeof(T)); + return static_cast((retVal * factor) + offset); +} + #endif \ No newline at end of file diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 44cd7e2d..251dec43 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -26,7 +26,7 @@ void ODriveCAN::can_server_thread() { for (;;) { uint32_t status = HAL_CAN_GetError(handle_); if (status == HAL_CAN_ERROR_NONE) { - CAN_message_t rxmsg; + can_Message_t rxmsg; osSemaphoreWait(sem_can, 10); // Poll every 10ms regardless of sempahore status while (available()) { @@ -86,7 +86,7 @@ bool ODriveCAN::start_can_server() { } // Send a CAN message on the bus -uint32_t ODriveCAN::write(CAN_message_t &txmsg) { +uint32_t ODriveCAN::write(can_Message_t &txmsg) { if (HAL_CAN_GetError(handle_) == HAL_CAN_ERROR_NONE) { CAN_TxHeaderTypeDef header; header.StdId = txmsg.id; @@ -110,7 +110,7 @@ uint32_t ODriveCAN::available() { return (HAL_CAN_GetRxFifoFillLevel(handle_, CAN_RX_FIFO0) + HAL_CAN_GetRxFifoFillLevel(handle_, CAN_RX_FIFO1)); } -bool ODriveCAN::read(CAN_message_t &rxmsg) { +bool ODriveCAN::read(can_Message_t &rxmsg) { CAN_RxHeaderTypeDef header; bool validRead = false; if (HAL_CAN_GetRxFifoFillLevel(handle_, CAN_RX_FIFO0) > 0) { diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 68479a61..a19c953a 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -15,7 +15,7 @@ typedef struct { bool rtr = false; uint8_t len = 8; uint8_t buf[8] = {0, 0, 0, 0, 0, 0, 0, 0}; -} CAN_message_t; +} can_Message_t; // Anonymous enum for defining the most common CAN baud rates @@ -59,8 +59,8 @@ class ODriveCAN { // I/O Functions uint32_t available(); - uint32_t write(CAN_message_t &txmsg); - bool read(CAN_message_t &rxmsg); + uint32_t write(can_Message_t &txmsg); + bool read(can_Message_t &rxmsg); // Communication Protocol Handling auto make_protocol_definitions() { diff --git a/ODrive_Workspace.code-workspace b/ODrive_Workspace.code-workspace index d9644123..9c333d91 100644 --- a/ODrive_Workspace.code-workspace +++ b/ODrive_Workspace.code-workspace @@ -53,7 +53,18 @@ "chrono": "cpp", "condition_variable": "cpp", "future": "cpp", - "arm_math.h": "c" + "arm_math.h": "c", + "iostream": "cpp", + "cmath": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "ctime": "cpp", + "unordered_map": "cpp", + "fstream": "cpp", + "iomanip": "cpp", + "optional": "cpp", + "sstream": "cpp" } } } From 93458d127f4e819ffc49aaec85f74872b46395d0 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 19 May 2019 21:19:40 +0200 Subject: [PATCH 118/549] std::reverse requires C++17 --- Firmware/build.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/build.lua b/Firmware/build.lua index d4c7aad4..016f6d8e 100644 --- a/Firmware/build.lua +++ b/Firmware/build.lua @@ -85,7 +85,7 @@ function GCCToolchain(prefix, builddir, compiler_flags, linker_flags) end return { compile_c = function(src, flags, includes, outputs) gcc_generic_compiler(prefix..'gcc -std=c99', compiler_flags, calculate_stack_usage, src, flags, includes, outputs) end, - compile_cpp = function(src, flags, includes, outputs) gcc_generic_compiler(prefix..'g++ -std=c++14', compiler_flags, calculate_stack_usage, src, flags, includes, outputs) end, + compile_cpp = function(src, flags, includes, outputs) gcc_generic_compiler(prefix..'g++ -std=c++17 -Wno-register', compiler_flags, calculate_stack_usage, src, flags, includes, outputs) end, compile_asm = function(src, flags, includes, outputs) gcc_generic_compiler(prefix..'gcc -x assembler-with-cpp', compiler_flags, false, src, flags, includes, outputs) end, link = function(objects, output_name) output_name = builddir..'/'..output_name From ca460aaa21f4246697e7f2d0609ba6a561b47a68 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 19 May 2019 21:19:52 +0200 Subject: [PATCH 119/549] Add Tests folder to VSCode intellisense --- .gitignore | 2 ++ Firmware/.vscode/c_cpp_properties.json | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ae3506ce..ac7997d4 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,5 @@ tup.config /_site /.bundle + +*.exe diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index 2c96b614..12d9684c 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -19,6 +19,7 @@ "${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", + "${workspaceFolder}/Tests", "C:/Tools/doctest/doctest" ], "defines": [ @@ -32,7 +33,7 @@ "__packed=\"__attribute__((__packed__))\"", "__GNUC__" ], - "intelliSenseMode": "clang-x64", + "intelliSenseMode": "gcc-x64", "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" From 772c991274734fc2aba578e1621c75072e577027 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 19 May 2019 21:24:09 +0200 Subject: [PATCH 120/549] can_Message_t is a struct, no need to typedef it --- Firmware/communication/interface_can.hpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index a19c953a..c8506a8e 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -9,16 +9,23 @@ #define CAN_CLK_HZ (42000000) #define CAN_CLK_MHZ (42) -typedef struct { +struct can_Message_t { uint32_t id = 0x000; // 11-bit max is 0x7ff, 29-bit max is 0x1FFFFFFF bool isExt = false; bool rtr = false; uint8_t len = 8; uint8_t buf[8] = {0, 0, 0, 0, 0, 0, 0, 0}; -} can_Message_t; +} ; + +struct can_Signal_t { + uint8_t startBit = 0; + uint8_t length = 16; + bool isIntel = true; + float factor = 1.0f; + float offset = 0.0f; +}; // Anonymous enum for defining the most common CAN baud rates - enum { CAN_BAUD_125K = 125000, CAN_BAUD_250K = 250000, From c21fe04287387389617a887e9ab2e97c8e717f1b Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 19 May 2019 21:24:41 +0200 Subject: [PATCH 121/549] Use recursive includepath --- Firmware/.vscode/c_cpp_properties.json | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index 12d9684c..1886c707 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -3,23 +3,7 @@ { "name": "Win32", "includePath": [ - "${workspaceRoot}", - "${workspaceRoot}/fibre/cpp/include/**", - "${workspaceRoot}/MotorControl", - "${workspaceRoot}/communication", - "${workspaceRoot}/Drivers/DRV8301", - "${workspaceRoot}/communication", - "${workspaceRoot}/Board/v3/Inc", - "${workspaceRoot}/Board/v3/Drivers/CMSIS/Include", - "${workspaceRoot}/Board/v3/Drivers/CMSIS/Device/ST/STM32F4xx/Include", - "${workspaceRoot}/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc", - "${workspaceRoot}/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy", - "${workspaceRoot}/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Core/Inc", - "${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", - "${workspaceFolder}/Tests", + "${workspaceRoot}/**", "C:/Tools/doctest/doctest" ], "defines": [ From 6f2802746be36c17c9b50722fb145ab7afaea59b Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 19 May 2019 22:23:27 +0200 Subject: [PATCH 122/549] Move can_getSignal to interface_can, and add can_setSignal --- Firmware/communication/can_simple.hpp | 17 -------- Firmware/communication/interface_can.hpp | 49 ++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp index 9138e109..75553b87 100644 --- a/Firmware/communication/can_simple.hpp +++ b/Firmware/communication/can_simple.hpp @@ -76,24 +76,7 @@ class CANSimple { }; -#include -template -T can_getSignal(can_Message_t msg, uint8_t startBit, uint8_t length, bool isIntel, float factor, float offset) { - uint64_t tempVal = 0; - uint64_t mask = (1ULL << length) - 1; - if (isIntel) { - std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); - tempVal = (tempVal >> startBit) & mask; - } else { - std::reverse(std::begin(msg.buf), std::end(msg.buf)); - std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); - tempVal = (tempVal >> (64 - startBit - length)) & mask; - } - T retVal; - std::memcpy(&retVal, &tempVal, sizeof(T)); - return static_cast((retVal * factor) + offset); -} #endif \ No newline at end of file diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index c8506a8e..4b61cf38 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -86,6 +86,55 @@ class ODriveCAN { void set_baud_rate(uint32_t baudRate); }; +#include +template +T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { + uint64_t tempVal = 0; + uint64_t mask = (1ULL << length) - 1; + + if (isIntel) { + std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); + tempVal = (tempVal >> startBit) & mask; + } else { + std::reverse(std::begin(msg.buf), std::end(msg.buf)); + std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); + tempVal = (tempVal >> (64 - startBit - length)) & mask; + } + + T retVal; + std::memcpy(&retVal, &tempVal, sizeof(T)); + return static_cast((retVal * factor) + offset); +} + +template +void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { + T scaledVal = (val - offset) / factor; + uint64_t valAsBits = 0; + std::memcpy(&valAsBits, &scaledVal, sizeof(scaledVal)); + + uint64_t mask = (1ULL << length) - 1; + + if (isIntel) { + uint64_t data = 0; + std::memcpy(&data, msg.buf, sizeof(data)); + + data &= ~(mask << startBit); + data |= valAsBits << startBit; + + std::memcpy(msg.buf, &data, sizeof(data)); + } else { + uint64_t data = 0; + std::reverse(std::begin(msg.buf), std::end(msg.buf)); + std::memcpy(&data, msg.buf, sizeof(data)); + + data &= ~(mask << (64 - startBit - length)); + data |= valAsBits << (64 - startBit - length); + + std::memcpy(msg.buf, &data, sizeof(data)); + std::reverse(std::begin(msg.buf), std::end(msg.buf)); + } +} + DEFINE_ENUM_FLAG_OPERATORS(ODriveCAN::Error_t) #endif // __INTERFACE_CAN_HPP From 8d0f97125728a4a4afc749fec4e6881ea64c0f87 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 19 May 2019 22:23:35 +0200 Subject: [PATCH 123/549] Add tests for can_setSignal --- Firmware/Tests/test_runner.cpp | 80 +++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 10 deletions(-) diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index dfaaf981..cd00e93f 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -13,6 +13,7 @@ using std::cout; using std::endl; + struct can_Message_t { uint32_t id = 0x000; // 11-bit max is 0x7ff, 29-bit max is 0x1FFFFFFF bool isExt = false; @@ -22,16 +23,18 @@ struct can_Message_t { }; struct can_Signal_t { - uint8_t startBit = 0; - uint8_t length = 16; - bool isIntel = true; - float factor = 1.0f; - float offset = 0.0f; + const uint8_t startBit; + const uint8_t length; + const bool isIntel; + const float factor; + const float offset; }; + + // Fetch a specific signal from the message template -T getSignal(can_Message_t msg, uint8_t startBit, uint8_t length, bool isIntel, float factor, float offset) { +T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { uint64_t tempVal = 0; uint64_t mask = (1ULL << length) - 1; @@ -49,12 +52,45 @@ T getSignal(can_Message_t msg, uint8_t startBit, uint8_t length, bool isIntel, f return static_cast((retVal * factor) + offset); } +template +void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { + T scaledVal = (val - offset) / factor; + uint64_t valAsBits = 0; + std::memcpy(&valAsBits, &scaledVal, sizeof(scaledVal)); + + uint64_t mask = (1ULL << length) - 1; + + if (isIntel) { + uint64_t data = 0; + std::memcpy(&data, msg.buf, sizeof(data)); + + data &= ~(mask << startBit); + data |= valAsBits << startBit; + + std::memcpy(msg.buf, &data, sizeof(data)); + } else { + uint64_t data = 0; + std::reverse(std::begin(msg.buf), std::end(msg.buf)); + std::memcpy(&data, msg.buf, sizeof(data)); + + data &= ~(mask << (64 - startBit - length)); + data |= valAsBits << (64 - startBit - length); + + std::memcpy(msg.buf, &data, sizeof(data)); + std::reverse(std::begin(msg.buf), std::end(msg.buf)); + } +} + // template // T can_getSignal(can_Message_t msg, can_Signal_t signal){ // return can_getSignal(msg, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); // } -TEST_SUITE("getSignal") { +TEST_CASE("fake"){ + cout << endl; +} + +TEST_SUITE("CAN Functions") { TEST_CASE("reverse") { can_Message_t rxmsg; rxmsg.id = 0x000; @@ -76,15 +112,39 @@ TEST_SUITE("getSignal") { auto val = 0x1234; std::memcpy(rxmsg.buf, &val, sizeof(val)); - val = getSignal(rxmsg, 0, 16, true, 1, 0); + val = can_getSignal(rxmsg, 0, 16, true, 1, 0); CHECK(val == 0x1234); - val = getSignal(rxmsg, 0, 16, false, 1, 0); + val = can_getSignal(rxmsg, 0, 16, false, 1, 0); CHECK(val == 0x3412); float myFloat = 1234.6789f; std::memcpy(rxmsg.buf, &myFloat, sizeof(myFloat)); - auto floatVal = getSignal(rxmsg, 0, 32, true, 1, 0); + auto floatVal = can_getSignal(rxmsg, 0, 32, true, 1, 0); CHECK(floatVal == 1234.6789f); } + + TEST_CASE("setSignal") { + can_Message_t txmsg; + + can_setSignal(txmsg, 0x1234, 0, 16, true, 1.0f, 0.0f); + CHECK(can_getSignal(txmsg, 0, 16, true, 1.0f, 0.0f) == 0x1234); + + can_setSignal(txmsg, 0xABCD, 16, 16, true, 1.0f, 0.0f); + CHECK(can_getSignal(txmsg, 0, 16, true, 1.0f, 0.0f) == 0x1234); + CHECK(can_getSignal(txmsg, 16, 16, true, 1.0f, 0.0f) == 0xABCD); + + can_setSignal(txmsg, 1234.5678f, 32, 32, true, 1.0f, 0.0f); + CHECK(can_getSignal(txmsg, 0, 16, true, 1.0f, 0.0f) == 0x1234); + CHECK(can_getSignal(txmsg, 16, 16, true, 1.0f, 0.0f) == 0xABCD); + CHECK(can_getSignal(txmsg, 32, 32, true, 1.0f, 0.0f)); + + can_setSignal(txmsg, 0x1234, 0, 16, false, 1.0f, 0.0f); + CHECK(can_getSignal(txmsg, 0, 16, false, 1.0f, 0.0f) == 0x1234); + CHECK(can_getSignal(txmsg, 16, 16, true, 1.0f, 0.0f) == 0xABCD); + CHECK(can_getSignal(txmsg, 32, 32, true, 1.0f, 0.0f)); + + can_setSignal(txmsg, 234981.0f, 12, 32, false, 2.0f, 1.1f); + CHECK(can_getSignal(txmsg, 12, 32, false, 2.0f, 1.1f) == 234981.0f); + } } \ No newline at end of file From ae0a2a1923b4c464bba8f0b90c35bcb9eb0bf6f9 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 19 May 2019 22:24:13 +0200 Subject: [PATCH 124/549] can_Signal_t should use const, force initialization. --- Firmware/communication/interface_can.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 4b61cf38..58c32d76 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -18,11 +18,11 @@ struct can_Message_t { } ; struct can_Signal_t { - uint8_t startBit = 0; - uint8_t length = 16; - bool isIntel = true; - float factor = 1.0f; - float offset = 0.0f; + const uint8_t startBit; + const uint8_t length; + const bool isIntel; + const float factor; + const float offset; }; // Anonymous enum for defining the most common CAN baud rates From 4f7f35e753d3e72890f704f6f35aedac7cfbbeac Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 19 May 2019 22:24:24 +0200 Subject: [PATCH 125/549] Remove explicit includes from flylint --- ODrive_Workspace.code-workspace | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/ODrive_Workspace.code-workspace b/ODrive_Workspace.code-workspace index 9c333d91..a131f5cb 100644 --- a/ODrive_Workspace.code-workspace +++ b/ODrive_Workspace.code-workspace @@ -11,14 +11,7 @@ } ], "settings": { - "c-cpp-flylint.cppcheck.includePaths": [ - "${workspaceRoot}", - "${workspaceRoot}/fibre/cpp/include/fibre", - "${workspaceRoot}/MotorControl", - "${workspaceRoot}/Drivers/DRV8301", - "${workspaceRoot}/communication" - ], - "c-cpp-flylint.cppcheck.standard": ["c99","c++14"], + "c-cpp-flylint.cppcheck.standard": ["c99","c++17"], "files.associations": { "memory": "cpp", "utility": "cpp", From 21b9758198158f3351f89b05082cf06eb124856e Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 19 May 2019 22:24:40 +0200 Subject: [PATCH 126/549] Add AlignConsecutiveAssignments to formatting rules --- Firmware/.vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/.vscode/settings.json b/Firmware/.vscode/settings.json index 63c1463b..ffb1469b 100644 --- a/Firmware/.vscode/settings.json +++ b/Firmware/.vscode/settings.json @@ -1,5 +1,5 @@ { - "C_Cpp.clang_format_style": "{ BasedOnStyle: Google, IndentWidth: 4, ColumnLimit: 0 }", + "C_Cpp.clang_format_style": "{ BasedOnStyle: Google, IndentWidth: 4, ColumnLimit: 0, AlignConsecutiveAssignments: true }", "C_Cpp.intelliSenseEngine": "Default", "C_Cpp.intelliSenseEngineFallback": "Disabled", "files.exclude": { From ea7bd1ebc31b1e8e7a05b03ebd55d6082a8dc36f Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 19 May 2019 22:38:28 +0200 Subject: [PATCH 127/549] Add can_get/set function that take signal objects --- Firmware/Tests/test_runner.cpp | 19 ++++++++++++------- Firmware/communication/interface_can.hpp | 10 ++++++++++ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index cd00e93f..560cb5c7 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -81,21 +81,26 @@ void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, con } } -// template -// T can_getSignal(can_Message_t msg, can_Signal_t signal){ -// return can_getSignal(msg, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); -// } +template +T can_getSignal(can_Message_t msg, const can_Signal_t& signal) { + return can_getSignal(msg, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); +} -TEST_CASE("fake"){ +template +void can_setSignal(can_Message_t& msg, const T& val, const can_Signal_t& signal) { + can_setSignal(msg, val, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); +} + +TEST_CASE("fake") { cout << endl; } TEST_SUITE("CAN Functions") { TEST_CASE("reverse") { can_Message_t rxmsg; - rxmsg.id = 0x000; + rxmsg.id = 0x000; rxmsg.isExt = false; - rxmsg.len = 8; + rxmsg.len = 8; rxmsg.buf[0] = 0x12; rxmsg.buf[1] = 0x34; diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 58c32d76..28e3987b 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -135,6 +135,16 @@ void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, con } } +template +T can_getSignal(can_Message_t msg, const can_Signal_t& signal) { + return can_getSignal(msg, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); +} + +template +void can_setSignal(can_Message_t& msg, const T& val, const can_Signal_t& signal) { + can_setSignal(msg, val, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); +} + DEFINE_ENUM_FLAG_OPERATORS(ODriveCAN::Error_t) #endif // __INTERFACE_CAN_HPP From 9b86d0a63856e7b41bd621e7a32a81a92ee19bb5 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 19 May 2019 22:39:04 +0200 Subject: [PATCH 128/549] Formatting in test_runner.cpp --- Firmware/Tests/test_runner.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index 560cb5c7..3214f278 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -15,10 +15,10 @@ using std::cout; using std::endl; struct can_Message_t { - uint32_t id = 0x000; // 11-bit max is 0x7ff, 29-bit max is 0x1FFFFFFF - bool isExt = false; - bool rtr = false; - uint8_t len = 8; + uint32_t id = 0x000; // 11-bit max is 0x7ff, 29-bit max is 0x1FFFFFFF + bool isExt = false; + bool rtr = false; + uint8_t len = 8; uint8_t buf[8] = {0, 0, 0, 0, 0, 0, 0, 0}; }; @@ -30,13 +30,11 @@ struct can_Signal_t { const float offset; }; - - // Fetch a specific signal from the message template T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { uint64_t tempVal = 0; - uint64_t mask = (1ULL << length) - 1; + uint64_t mask = (1ULL << length) - 1; if (isIntel) { std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); @@ -54,7 +52,7 @@ T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, template void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { - T scaledVal = (val - offset) / factor; + T scaledVal = (val - offset) / factor; uint64_t valAsBits = 0; std::memcpy(&valAsBits, &scaledVal, sizeof(scaledVal)); From f09b86b26227e860a7705f62cc0ef5c4cb532c33 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 19 May 2019 22:49:18 +0200 Subject: [PATCH 129/549] Trigger an update to GPIO configuration on property write --- Firmware/MotorControl/endstop.cpp | 38 ++++++++++++++++++------------- Firmware/MotorControl/endstop.hpp | 11 +++++---- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index d01c361b..809e52f1 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -1,8 +1,8 @@ #include -Endstop::Endstop(Endstop::Config_t &config) +Endstop::Endstop(Endstop::Config_t& config) : config_(config) { - set_endstop_enabled(config_.enabled); + set_endstop_enabled(config_.enabled); } void Endstop::update() { @@ -10,14 +10,14 @@ void Endstop::update() { GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); auto last_pin_state = pin_state_; pin_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); - if(pin_state_ != last_pin_state){ + if (pin_state_ != last_pin_state) { debounce_timer_ = axis_->loop_counter_ * current_meas_period; } if (config_.enabled) { float now = axis_->loop_counter_ * current_meas_period; - if ((now - debounce_timer_) >= (config_.debounce_ms * 0.001f)) { // Debounce timer expired, take the new pin state - endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state - debounce_timer_ = now - (config_.debounce_ms * 0.001f); // Ensure timer doesn't have overflow issues + if ((now - debounce_timer_) >= (config_.debounce_ms * 0.001f)) { // Debounce timer expired, take the new pin state + endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state + debounce_timer_ = now - (config_.debounce_ms * 0.001f); // Ensure timer doesn't have overflow issues } else { endstop_state_ = endstop_state_; // Do nothing } @@ -30,15 +30,21 @@ bool Endstop::getEndstopState() { return endstop_state_; } -void Endstop::set_endstop_enabled(bool enable){ - uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); - GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); - if(enable){ - HAL_GPIO_DeInit(gpio_port, gpio_pin); - GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Pin = gpio_pin; - GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = config_.is_active_high ? GPIO_PULLDOWN : GPIO_PULLUP;; - HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); +void Endstop::update_endstop_config(){ + set_endstop_enabled(config_.enabled); +} + +void Endstop::set_endstop_enabled(bool enable) { + if (config_.gpio_num != 0) { + uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); + GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); + if (enable) { + HAL_GPIO_DeInit(gpio_port, gpio_pin); + GPIO_InitTypeDef GPIO_InitStruct; + GPIO_InitStruct.Pin = gpio_pin; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = config_.is_active_high ? GPIO_PULLDOWN : GPIO_PULLUP; + HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); + } } } \ No newline at end of file diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index 9ff3f079..cdca3fdc 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -16,19 +16,22 @@ class Endstop { Endstop::Config_t& config_; Axis* axis_ = nullptr; + void update_endstop_config(); void set_endstop_enabled(bool enable); - void update(); + void update(); bool getEndstopState(); bool endstop_state_ = false; auto make_protocol_definitions() { return make_protocol_member_list( - make_protocol_ro_property("endstop_state_", &endstop_state_), + make_protocol_ro_property("endstop_state", &endstop_state_), make_protocol_object("config", - make_protocol_property("gpio_num", &config_.gpio_num), - make_protocol_property("enabled", &config_.enabled), + make_protocol_property("gpio_num", &config_.gpio_num, + [](void* ctx) { static_cast(ctx)->update_endstop_config(); }, this), + make_protocol_property("enabled", &config_.enabled, + [](void* ctx) { static_cast(ctx)->update_endstop_config(); }, this), make_protocol_property("offset", &config_.offset), make_protocol_property("is_active_high", &config_.is_active_high), make_protocol_property("debounce_ms", &config_.debounce_ms))); From 301d68adc6d6f6de2c4669e7bf025cbc56c25a19 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Thu, 23 May 2019 18:56:34 +0200 Subject: [PATCH 130/549] Move anticogging_t into config, use a fixed size array and scale --- Firmware/.vscode/c_cpp_properties.json | 4 ++-- Firmware/MotorControl/axis.cpp | 11 --------- Firmware/MotorControl/controller.cpp | 33 +++++++++++++------------- Firmware/MotorControl/controller.hpp | 28 +++++++++------------- Firmware/MotorControl/encoder.cpp | 4 ++++ Firmware/MotorControl/encoder.hpp | 3 ++- Firmware/build.lua | 2 +- 7 files changed, 37 insertions(+), 48 deletions(-) diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index c4a2f37c..4a2f1f3b 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -30,10 +30,10 @@ "__packed=\"__attribute__((__packed__))\"", "__GNUC__" ], - "intelliSenseMode": "clang-x64", + "intelliSenseMode": "gcc-x64", "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" + "cppStandard": "c++17" }, { "name": "Linux", diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 80987c00..a83677ab 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -288,17 +288,6 @@ bool Axis::run_idle_loop() { // Infinite loop that does calibration and enters main control loop as appropriate void Axis::run_state_machine_loop() { - // Allocate the map for anti-cogging algorithm and initialize all values to 0.0f - // TODO: Move this somewhere else - // TODO: respect changes of CPR - int encoder_cpr = encoder_.config_.cpr; - controller_.anticogging_.cogging_map = (float*)malloc(encoder_cpr * sizeof(float)); - if (controller_.anticogging_.cogging_map != NULL) { - for (int i = 0; i < encoder_cpr; i++) { - controller_.anticogging_.cogging_map[i] = 0.0f; - } - } - // arm! motor_.arm(); diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index d295246c..5634d18b 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -1,6 +1,7 @@ #include "odrive_main.h" +#include Controller::Controller(Config_t& config) : config_(config) @@ -69,8 +70,8 @@ void Controller::move_incremental(float displacement, bool from_goal_point = tru void Controller::start_anticogging_calibration() { // Ensure the cogging map was correctly allocated earlier and that the motor is capable of calibrating - if (anticogging_.cogging_map != NULL && axis_->error_ == Axis::ERROR_NONE) { - anticogging_.calib_anticogging = true; + if (axis_->error_ == Axis::ERROR_NONE) { + config_.anticogging.calib_anticogging = true; } } @@ -82,20 +83,20 @@ void Controller::start_anticogging_calibration() { * This holding current is added as a feedforward term in the control loop. */ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) { - if (anticogging_.calib_anticogging && anticogging_.cogging_map != NULL) { - float pos_err = anticogging_.index - pos_estimate; - if (fabsf(pos_err) <= anticogging_.calib_pos_threshold && - fabsf(vel_estimate) < anticogging_.calib_vel_threshold) { - anticogging_.cogging_map[anticogging_.index++] = vel_integrator_current_; + if (config_.anticogging.calib_anticogging) { + float pos_err = config_.anticogging.index - pos_estimate; + if (fabsf(pos_err) <= config_.anticogging.calib_pos_threshold && + fabsf(vel_estimate) < config_.anticogging.calib_vel_threshold) { + config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_current_; } - if (anticogging_.index < axis_->encoder_.config_.cpr) { // TODO: remove the dependency on encoder CPR - set_pos_setpoint(anticogging_.index, 0.0f, 0.0f); + if (config_.anticogging.index < 3600) { + set_pos_setpoint(config_.anticogging.index * config_.anticogging.cogging_ratio, 0.0f, 0.0f); return false; } else { - anticogging_.index = 0; + config_.anticogging.index = 0; set_pos_setpoint(0.0f, 0.0f, 0.0f); // Send the motor home - anticogging_.use_anticogging = true; // We're good to go, enable anti-cogging - anticogging_.calib_anticogging = false; + config_.anticogging.use_anticogging = true; // We're good to go, enable anti-cogging + config_.anticogging.calib_anticogging = false; return true; } } @@ -103,9 +104,9 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) } bool Controller::update(float pos_estimate, float vel_estimate, float* current_setpoint_output) { - // Only runs if anticogging_.calib_anticogging is true; non-blocking + // Only runs if config_.anticogging.calib_anticogging is true; non-blocking anticogging_calibration(pos_estimate, vel_estimate); - float anticogging_pos = pos_estimate; + float anticogging_pos = pos_estimate / config_.anticogging.cogging_ratio; // Trajectory control if (config_.control_mode == CTRL_MODE_TRAJECTORY_CONTROL) { @@ -179,8 +180,8 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // Anti-cogging is enabled after calibration // We get the current position and apply a current feed-forward // ensuring that we handle negative encoder positions properly (-1 == motor->encoder.encoder_cpr - 1) - if (anticogging_.use_anticogging) { - Iq += anticogging_.cogging_map[mod(static_cast(anticogging_pos), axis_->encoder_.config_.cpr)]; + if (config_.anticogging.use_anticogging) { + Iq += config_.anticogging.cogging_map[std::clamp(mod(static_cast(anticogging_pos), axis_->encoder_.config_.cpr), 0, 3600)]; } float v_err = vel_des - vel_estimate; diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 020f34d0..2e2886f8 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -22,6 +22,16 @@ public: CTRL_MODE_TRAJECTORY_CONTROL = 4 }; + typedef struct { + int index = 0; + float cogging_map[3600]; + bool use_anticogging = false; + bool calib_anticogging = false; + float calib_pos_threshold = 1.0f; + float calib_vel_threshold = 1.0f; + float cogging_ratio = 1.0f; + } Anticogging_t; + struct Config_t { ControlMode_t control_mode = CTRL_MODE_POSITION_CONTROL; //see: Motor_control_mode_t float pos_gain = 20.0f; // [(counts/s) / counts] @@ -32,6 +42,7 @@ public: float vel_limit_tolerance = 1.2f; // ratio to vel_lim. 0.0f to disable float vel_ramp_rate = 10000.0f; // [(counts/s) / s] bool setpoints_in_cpr = false; + Anticogging_t anticogging; }; explicit Controller(Config_t& config); @@ -61,23 +72,6 @@ public: // - use python tools to Fourier transform and write back the smoothed map or Fourier coefficients // - make the calibration persistent - typedef struct { - int index; - float *cogging_map; - bool use_anticogging; - bool calib_anticogging; - float calib_pos_threshold; - float calib_vel_threshold; - } Anticogging_t; - Anticogging_t anticogging_ = { - .index = 0, - .cogging_map = nullptr, - .use_anticogging = false, - .calib_anticogging = false, - .calib_pos_threshold = 1.0f, - .calib_vel_threshold = 1.0f, - }; - Error_t error_ = ERROR_NONE; // variables exposed on protocol float pos_setpoint_ = 0.0f; diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 95dc91f9..27679c33 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -99,6 +99,10 @@ void Encoder::set_linear_count(int32_t count) { cpu_exit_critical(prim); } +void Encoder::cpr_changed_callback(){ + axis_->controller_.config_.anticogging.cogging_ratio = config_.cpr / 3600.0f; +} + // Function that sets the CPR circular tracking encoder count to a desired 32-bit value. // Note that this will get mod'ed down to [0, cpr) void Encoder::set_circular_count(int32_t count, bool update_offset) { diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index c2d32841..b6a3b3d8 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -67,6 +67,7 @@ public: void sample_now(); bool update(); + void cpr_changed_callback(); const EncoderHardwareConfig_t& hw_config_; @@ -119,7 +120,7 @@ public: 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("cpr", &config_.cpr, [](void* ctx) { static_cast(ctx)->cpr_changed_callback(); }, this), make_protocol_property("offset", &config_.offset), make_protocol_property("offset_float", &config_.offset_float), make_protocol_property("enable_phase_interpolation", &config_.enable_phase_interpolation), diff --git a/Firmware/build.lua b/Firmware/build.lua index d4c7aad4..016f6d8e 100644 --- a/Firmware/build.lua +++ b/Firmware/build.lua @@ -85,7 +85,7 @@ function GCCToolchain(prefix, builddir, compiler_flags, linker_flags) end return { compile_c = function(src, flags, includes, outputs) gcc_generic_compiler(prefix..'gcc -std=c99', compiler_flags, calculate_stack_usage, src, flags, includes, outputs) end, - compile_cpp = function(src, flags, includes, outputs) gcc_generic_compiler(prefix..'g++ -std=c++14', compiler_flags, calculate_stack_usage, src, flags, includes, outputs) end, + compile_cpp = function(src, flags, includes, outputs) gcc_generic_compiler(prefix..'g++ -std=c++17 -Wno-register', compiler_flags, calculate_stack_usage, src, flags, includes, outputs) end, compile_asm = function(src, flags, includes, outputs) gcc_generic_compiler(prefix..'gcc -x assembler-with-cpp', compiler_flags, false, src, flags, includes, outputs) end, link = function(objects, output_name) output_name = builddir..'/'..output_name From 2abae9034b7e9d1c1191bc3fc5f54d7c18c3f5e3 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Fri, 24 May 2019 23:54:38 +0200 Subject: [PATCH 131/549] Change how the watchdog_reset_ value is handled --- Firmware/MotorControl/axis.cpp | 27 ++++++--------------------- Firmware/MotorControl/axis.hpp | 8 +++++--- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 80987c00..41a125e4 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -28,7 +28,7 @@ Axis::Axis(const AxisHardwareConfig_t& hw_config, trap_.axis_ = this; decode_step_dir_pins(); - update_watchdog_settings(); + watchdog_feed(); } static void step_cb_wrapper(void* ctx) { @@ -89,21 +89,6 @@ 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) { @@ -159,16 +144,16 @@ bool Axis::do_updates() { // @brief Feed the watchdog to prevent watchdog timeouts. void Axis::watchdog_feed() { - watchdog_current_value_ = watchdog_reset_value_; + watchdog_current_value_ = get_watchdog_reset(); } -// @brief Check the watchdog timer for expiration. Also sets the watchdog error bit if expired. +// @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; + // reset value = 0 means watchdog disabled. + if (get_watchdog_reset() == 0) return true; // explicit check here to ensure that we don't underflow back to UINT32_MAX - if(watchdog_current_value_ > 0) { + if (watchdog_current_value_ > 0) { watchdog_current_value_--; return true; } else { diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 9e78fdab..e72f7232 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -181,6 +181,10 @@ public: bool run_closed_loop_control_loop(); bool run_idle_loop(); + constexpr uint32_t get_watchdog_reset() { + return static_cast(std::clamp(config_.watchdog_timeout, 0, UINT32_MAX / (current_meas_hz + 1)) * current_meas_hz); + } + void run_state_machine_loop(); const AxisHardwareConfig_t& hw_config_; @@ -212,7 +216,6 @@ public: 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 @@ -232,8 +235,7 @@ 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("watchdog_timeout", &config_.watchdog_timeout), 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, From bc805a86c9334ddefd558ff8146a751b4989e6b4 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 12:28:56 +0200 Subject: [PATCH 132/549] Write instructions for endstops branch --- Firmware/MotorControl/controller.hpp | 2 +- docs/Endstop_configuration.png | Bin 0 -> 20174 bytes docs/endstops.md | 73 +++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 docs/Endstop_configuration.png create mode 100644 docs/endstops.md diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 6bfeaa81..64adca26 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -32,7 +32,7 @@ public: float vel_limit_tolerance = 1.2f; // ratio to vel_lim. 0.0f to disable float vel_ramp_rate = 10000.0f; // [(counts/s) / s] bool setpoints_in_cpr = false; - float homing_speed = 2000.0f; // [counts/s] + float homing_speed = 2000.0f; // [counts/s] }; explicit Controller(Config_t& config); diff --git a/docs/Endstop_configuration.png b/docs/Endstop_configuration.png new file mode 100644 index 0000000000000000000000000000000000000000..56a618fffbea308cc3f1ccd764fca5c686d3a964 GIT binary patch literal 20174 zcmZ^~c_5VE`!|k65=9Ln$zH~i?E5>6tSK?Zn3Ay@%h+X?3S$e|Ny%>PhC#L%TiLSj z8nTAQzJ%wV-k0}6`s zAPS0e1{Z(}6f^HV?^94bOwdxlZ|FU-o>5#`rCVjTpUK4W!qGrvzC_yygTV_3(D*{s zkJ%76L?7oTyF++OG(iFwj6+m=^)!>;rEhoLlCM8MfT@rgm(rKg567@3Wofxg{I9OK zMm(ZYz0iVrb>$_9j++|Hta<^;_v$UD$zyf(g|A`@gJRwzPnztQw0kN`_I4C%h~H`o zP=Ta0o8c41wO;L$9MB!9%p7C=rd*KRsQ^k!x*1X(L=R@hY6AVhVy|E|%Xqk(fWKEa zrry2_x}r+{>0k+Y6VT~XM#VZ#CHnUXSnP8Ma0`&*IRp@-1p`P@AqYs**}|YjcL5Nr zS2M#fWE1miBeUomJPjJCtZiS68_E3{nYXjI=WTLdtOM*r2 zd`Vvtey=qD6ViewR?3!#Z2mg%ug!m2ch6O<>+6wGHsO!fGsm_a_zfimsN%lD9ex<9N z$X|SWgASFV$zmuayL}Ny(=AyQ8W7kD4cB}LDkn}tBfm%%tgI)OM4OF)llVSwzZ;)J zQ-Gg+#aO4}DDAle3Lq-Bl8f1QU7~=;>!Fb}3ctAvDi0&FPd~qfk+h*VZeC!7Z8ICU zj?5F5s}hLxBjr5M^atAF%W7|+Re|@0r0(oRs3Hkq6Y+@b)aN}>hQ^KT@EZ(zNC*}m z2WqHaZ>G)eP0U~o1qGvS-XS=KT!uwh$x>hCJEdX69!P-S=W0Ln3-f`~WLq2bS}74Q z%~Eb>cD`6A>@*53tcAt1Aillug>*a8p1Pl%2(|0}u_pafTC=}Ay|}%jZ>rAoJ@dO$ zKtalZg%ZaaHS}{&Z*95f?7ezci_rLxG496DPa7^PW2r&K)jT}+k|B4P5qvz4U0$yT zgUmKmjyVsuPxd3SYr>_Eh>r$66;*1GWjrHrRNDbZzD6W2*3a$TKYK`sUOi_vs^p!o<&-2d`SDJx6$ms&3a(2JdstVmxFNY-}MalNffdK~Tqf_w?nsQ*3l+ zJfl45N!3X>XLI66AzX0w68%$Li1P3_OT&6|X8e)R>oBda3eZh`mKn*zAm~K1c!K zw4=h>Oq8f_hobK?wz^fYFovag{de;5s7oU6r!Xy&7mxcf6On%iDO~GGlTEh!Jt2ul zDWFexQhP6RK+~NGPPC^VK}N$i<(*}?ydfjLRzY zVU6i)8Wwo~LAAEz?_C8Sb(ybjx4r?*mLY9(M<<-~FF+)hwF|9bpPj@$mN zC!WXV_YlBlu=3ZAWvq@ix?`U2tnLOA3qYJu>ZwtvgV&slLByerDNTNENO{xZTz|M` z{|~MESZ@!owBUYYWmX}};+|Jei@8YDowXL~i&%UHNdHMrz?$hDmGl$|U7wKR>za{# z@d)SENEk^KtyG>RKx~KJxSa7$qBN1+y`|?Lq(DlWJezeJrgRS3NX&%pF#F3!__|lw z`Wz?36(q)uFt04XlZ1Y@^|?BX{1SvaUU^d~R>YouTROIo{VKAcJukbQxa-;U)#ULL z7p|wIJTLX*Q(FlGz3d_NKek6a(9dS#E^s@toIj2 zILhLSt%u<8^n5`=U1GkRxU9P{^s^6baf_zXbCK(Mj98m^nOi(bh+;$b$m?9bKr{K} z?F`4qMuIEe{N?(hG&tTzZZ3uPeCo{BF!2tIPjID3Av}z;EHS3qPu!O&cSl2Aq*|^c zPCJ&N%gVa<3BHVY2~5M=I+Nq$Hf&*JBtlze6eoF3CB;&WCX~QYA}sDtx(HSL1d?Lm z9n2rruy!@Rtgxw>x<&$*f8r*^g1-LPsBgmr9p`~?ZwHQrvdyBa?`@~1c1MbG-91Ss z{Ag+Ztu^%W*sBO-!+uY`40i%MwfcG+0 zNvy9oNvSd%zUTj93D;@KVAhiIcr-)1UZwip)XC*czT}odbMC~>qQa4aYcSFfbZ8;q z@)Y`!dg-WDn68)CG!Of_`qzzU6k++j$tQb_d-(FzxjPm>-}=SNKjRfL>stKvBU}_X zJt#E#Fve`L0i~RAr@@L)#TpRmh3nGbXv8pn(qDg*r7xBx=I%!GgV1cXF&h1qG9pim zf2z=HCiuqb+p=yfZiUOuT{U*Bz%+e!SjlTu= z_0>Yno!}7KR{{pP@nv@JX97u@rf9XX)zCae6^Cy`fmd(8t_RR@Te3piDqcO6{TOmn zNqQk}xcR(j#d*a4%VTZ1feoHju;bqaIZ(2GU{bwMZ>D--yc+{cETb26@Bh^C1_9jt ze|gBiy(yO?E7=a$#CEz^D|%S#y`f6ml?L|O{woHvnIQor-%NkfuqpmK`I=sV*LLnE zwv!~m{R(RVq1J)EPIx=~13ra$t5eN(MbJ_T|G&NI3oA%7KP;AlmF$t<=5g-C#pu20 zvY#`c0_O3}nD5nU@*K8P}iC%fCrn0SBsGE++J5l`Q#Zy;!_i%ZNtz<+CBn zo->Os>rr!9*p-OmdE=Qo<+_MZ*@cC*1^SoQxS1obWmY6gp4G&yJ}A>uJ-UwCx*;XM zynnHF)N36Qr$75{eJ1@dpYd7U1K4*=%o(vQlVj?WpL~Pnjugru&>%^huN`Cd1y>9@ zxg2Bl5;vTbZTmZt5#66XA_*L-3zt$>};`GBlepO0UZXsQ&nDTxA<#%kIfYYE&p6>83)5lf+1P1+n5Lnh5mAHAj?JVz zQoRlDIrooxn`Pk=RgJ!Ig^GwL)+Dyz$1nNBmfX^-OiV8FHzb^Hw#b-DCiOc)Q`iWx z+|=_+r=w>dPsS<2`|L&I*kiSE(mz`}x~}C_5(O&p;L59D4dhc6rT4`>d@iCZy?N@) z3Q5-$tRP*6QkvO?IL%dr&|`1vVR=?9Wj}i{WQNYIxcNv;tL1{C`tZ!D+gYHnt zIQ%#Db*4HG(c?eq%O%Yj_L)DGcLS7>;cdN;m{twQ*ZbQB#p=Daw^N@NJERtAs@9u4 z6^dD|#zuLzB*c!@?wEOCwN?#yhh?DG;(r#drqO{yV&cDkBXu~wjazxvq7jj3#6A3vxda^yca3*zk zxKB$j+W=?aar2_GZ*t7osr_ZE?WO}#XaG`Z{umnB0sThv9bLE@Va5_LOwG%qrDUtS zk1ms)nbTJ>HmMx$Uc%-WRG-;NTm3$q&vgi>S-Und8L$3?V!|1_ygPtdT0o7IBX34% zzVr7x9h+?r*z3Qa7tcHAF_C zfwB3qHg7T|(RMI}5ts=Fr9r`ZW3u>h|pf6uv-Wq%{I0-A~xc|8ZIq1vJ#fiB& zj!F2{gDGUcS#ou~BhUs`er=MGNqP2NJTy|KB5TkMPRjVh-`>@o%Og-U9gStX`AqZs z-Qlc+XlYLw+Zis$y>sCk^P;OYmSUp@M#>2~jcrHT7C@^S}xAc*5NZ@W=9_3{(G-s5|ErOyf0N2Af!i`jN7{(EH>g@lEfN$XrY&Lzt#KI{DWpTgih);tkwf)o34lXGJfL{-5A0(Yr#%gUmMm5XT9 zW*Sd?CzbVmGi5yWjWIqO_`S|Gr6F+Kr>moXI)oYiT^sRWWGAHE{0qkEpwlUd749Tg zB0Du*Tccv{yYhj43Bh#pfua3U`q#4@-;i3j61;zW%pUg(m0!ylZoMju{Z*rC3G*5H z8463t>!)`Ey{ddZ<1Q|xAFE8e_?k#66t|!*x5~IKRvAR@?=XQg+zzjyYTZQ7%*>Hj zU;B{(ON8(4A)Lf$l@!Z zw_yVkdE@98&%vq@mS)g&A7Q2{&5_`u_s_pRxw8cV;-DMj7=9>UQ%#mW7B>QynZ>yG z1D(J56t-;1*GOCSDr%MBi3 zW(ZJxVP-*&j&96i0%O1!7)oM^7<@s8cWN~GimS3(ZyD0cajJCf(ezYzgEbxZXcaPk zu7RsW)1V@flY8Mi)UQcF(9-1pJLU!a0(VI*)9W5X+{UH=+{%|SDP52dgnPS(&4I=K zavXh8Pv7mh7a-i^uji2Tq>{xjA^`VAT?nhW_s*wmwGKKhv+L{(I*$;Jm=jf-TNGFVL4IlQ}ydEcZBKV`90!WVyMoE1>_#C=7yMg(6Dr z?C1J>uX&myQ!vi}{abis^u@1>+I{w8(5n^gH}Q@iYn(n9<9NCiX$u}AnPT%BKzQ-O z!toffE$WhZxHvPDIAl~c)J(G9EECeIK zn1xcuB$=mC&a4_j{L}Z(F(3r3L>LO;gq~#&{w^{0Wigw@=LGt<>=Q;rjOVuw^(N;J z!E5PQJ6}UswePg%m&?8XSU_BxhTmL2)sbO-uep0H(ckQ0KT>yHKIPiUpBq?B&)~v` zCgpE`?b`q?hp-+{EZy&x8EaZ5HV)Twlkvi5*w6384&xc^^MxH~r$y!Zy^`qep$*3g@hUOmEZy zy0dwmVtnbSqS-@ZWc8#4R|V=KJ^8vQ1LI}YyK-21JavME+|%W$*cIvSg2acr7d2## zVJ__bCh>e1Ytf_CgX;40FlRJ|EaJC+hpV@3Yy|`~dsM@iwCXu>*J%NiuH7Xr%n<46 z{pCFVVWKKl^KS0Ubp((leHv;X;z?a6y`o2FfLk>AvkpZ!Zg%-Wwj{%4K?BeKHt0>z zy$8BJ^^6r9r_`=9JWEyHdR4R;SD0P5_+d5l zozBwMZu=kXH4&7r?2j+PPFs8r;~2yI-=n#U_*dF>brrrD6L@&6Ma-q9!@>$T7GBXq z`K}gl$aCDfSAwoJbda(Be%f>w+2ExSXS5f+-s#agq5Kfve?Pvf^?aVvm1Bh;RERZr zIj4jjmTkhh2(~5lzGQl(dh76nj@8;FzRUg7%(}+ZG#%?4d>r!_Z!+LjtlH4+f{^)V z&}ncs6K&2SlcGDcQC1&?uOZ7_0-B7!83lUUMAED3t^|E@Jlei03X9dv;hI{JM)K2u zpLJr^wPM;aCta*K@VkahwYtBL067>CV64?gEoVJ#_-772ikZGft>F2C9V+~3)hUfd z<^5I=sEb>M2VLwy@T^;S7a>}%FMmrNXXHl%bW(f;E~aTY0;F4sL%KZ zRw|!zMr~ti5Zehy6-Pmc6nI$dsD~)no_iV;m}{@HvvF~9#{#^FJc{7-Q+&aFWe$KD zy)D7^Q^kzj0^5ycgP|&UHN>gNB>D>jL+7W1A^=pAJHDzlv{7N|&O~_yI^6N70F;s4 z>MY25nF4_g16@w(=iEBjhniU&dKnmB0bs)%@zMxRj!nTbol#n?`{rS!^sPI;7GB4FtU3&o&qy$z>e z5FIo+8m2VoWKM9ens_R_o1jU?`zjfaM8teH8}|FGKew)bf$l)NXE3-nq1y`Wf3Df-?=(&8M!vmr8j+(ccz#x z(N$NYjEH&jXYI9Iimo9=-|C8|;*Vf^U4QBA6n&5fIL8%T9Wl{_^l$sKNo&5O9crK)W z#dN%g$IVI_dwek|hB<2Mmnb=e0;ib#0~Jqk{-PF-YAUp zbrdJ1G&z-<2=(U#-?YYCyMZxHd@6#RO_S+2ua?bln<%7$rak+h9OH|7Utum1U?+SM z7$RNE%?oYAaKImDgk3XUpaqK#kE3mPvH5E;;63P00j{`06{u=R7t?MQ$W7=J>*ao4 z1d1FJhzEg8h~o<%UnE{xFQof|nI%6i%$!y=YZwlMT&x=;QaEnq-2*pNG47 z=Q^(;gKI(nAQki5cF zjys{HAFsF+CM3uzD!(nX&!`H0*#I&v^@RG>(+M&2BDXiA#TrUyg&c7Q-|&^D;vg;X z?qlh(3GVns(aP@%J?{#wBwgaitv6TZJ;f$W%dK)Mk#~6pTsH%l@YWT3!n?$H@066v zcc75#QTsUPkUd}Rty~9^Xl`+Ss`LXY8L)=ACFE=*BWco9N@u@~%RE)Fq2QOLU5Eq^ z^83-d>=FyeEGJ8}LdfS>#4%q^TSB3I7}tJ8F@xIFuQ$$6T@7~;`MV9&h<%I>M4d-w zvQm_pN$w6Rv<4~egpvo_PyC+#{HEeKXVqoGyh(=3|BBz}-F>KE5nbUo$A|5CnXgs( zy3;cpj`OrlAxP$=RMOH7aAOrxh_`&lR3WY<-f~*(XUrgDeQaVy%TH^j z$MBQZiWPWZ~pIuTji%@sP}_!1sP#gBgOEDQ?DvPN>oLOEZ7glaMd034eMqGuq1S zZUy6V^vxcb`GUeJR!*Ze2twSaoNva6Zj6^gO)tht^2py}P^l6*UAJTniE4{Ey`?_= zd_IzOe?1}V(9yD4WuuvkOXAW|C9KdbzAhPbGHv(M6BTN?fKLOhOMs7ZE7{2YOe64- zi2Hc{(?9nXwiZWx5`Si;&1}B8RYp|C+HPXmOi;xu@CZ3*)d+lY-GdgW6~vr9-}{w* zz67L8W1S|e8rvaon}bbCMW%orJ^)xbr{`fFC7>wWNc&S(m&z>puJ*vP{$J@o#ne5{ zRq{(j+pGmoe?7lc#5sy_7S1T|!Xwoj%!_gB7s4;;xAm4IC7z0p`hp7+{Ql4mBpUZB zOCOd3udh|T=yUXT6u8>xD;8%Wqj^u3RiOj3?oKe+1i%@?Pb&FVHKe0I=k7^edf_@U z!)g2u5M{Lk?v7kisSj!N9e9>*FCuX1?71nnwF4?#PILh0PCri1XtoHT_1|ZG_}QM@ zbBC6|giY?PKw?07^cEc*a~lt!RT)aZbii-I+jsN>h5KBqj` zmw8y9h22`q+rA{hm(* z3%k_41sw}4C4i1C3LKEpL-b&KdE1L?IV@o?suJ3Pc1~0!pRq?D2k}YCTTgEwNWq8# z0k>o6bB2NW{4O1^ypvw43BLlE`=@ZT#|jo@EKhdDO_duQ>m; z=IT_o#54Hgr%fMc>LUX@@b9aB4j`P5msf;TZFqmag$@bXu|O{LY1J_$j^iRxGSqw?ynLjZIDp<8S-uTU`H~ly ze!Grj&%zOW%u-U&sMyE6->X&ko!=xNIKh3koS0rI!eR2*h_)utuP#Gq)8Biir?M%3 zv(Y;cmcO?}kP>*wQd6E~&xZZDdoA;HqsGgM(VRtDN38FV6Jf~-K5~&sId!n^O=?Uz zF%JJ6!|gg@y}YCYb_(kI69EZE*`OOd2xj+wX+(7CH9i3cj8yvF6Q;%sRuLbV{HRuG z#;Jkdz_L8qvML5q#u;50l3>w1r6|22FF80VAiawN%(y zsHT)m#itp$>R-MJM#VrKHhis%*EdURaG;tV+E&$|dK<7)nx)P8TVNAX-lWJh`x+Hm z-;!X9^0Qa^o=1<@fGX-1t6A6R5-Im}3%=`-wAPGNYAqpZ2B>C!a;7%NJjcUfLfV>S zKo^Ldm-Tim=b1Awz4#AshkSSj1cj?AHqz%r$){*;aH0kes*axr(c<}uVknUcF#%L^ zYiQP;`G1Z(T??8p2$F!iI2ETU2wReC4f2tZUj3kmI5e9) zWcqiE+kEP_@QeCiA2Q`>8Sp_mj(3_puxyccjD}@uxHH#rrO134;VP$f>kW#2E7v{E0}83y!hbGI|LvDj0q@AL8%eg3kcTb4?lTt zhon>xjdX+pPha^xwVIrryaW#jm?i!k2U1Co zaqj#5bEEtU%iYpUxKna@qB8D7T+p@0@1o?!Hv-$8~~N#j9i2E}MJLpZ30!pmEy z?QNK*8<)wde$d(ikV?JSidUtXgNJudX#z?7c?9j|hpz*#`$RLRUIr=b#&=(87U5?s z^x%`4YcwX$h+)pB1LGC303!t{T%LM+{ko)_<4@PFzcPF*PVTZLts5YlGiaTJa?vtq zy`f*kNzn)DMjfSDC;<a@iC?2iOl zwCf10eEoIumNLJj6;9UMk0CS&dD}T2Q}_N$Vdpk5Ml_;x6BMFA9ssCHCo2@2?gsi- zLEKDTJXM8wPF@E{{d*w-z|``8pB$M3Tkx+otuWRl-p&`8Ry;Jsgac!rpTZShLNw#f zBQ{~>10UimlK61cz;3mXT=4a}z07+2lkq&TYv{p+_S2b8Kb)Xyx?Ja;x&!#u9%CKu zgLo1kQlAOG68@Pp5) z{xOBU4rPf0B#HnnOvo-=Ely5d*PzS?%##(Z(*9+z!j18;?t+e7JViC!F_`{BbmH=1)oXi9WE`W<90{sWkaE>r}NDoq$ry!`jD)s6Z0 z4h4Rbc7Md1_W^$QF~7 zws8a9RRNIAu8?8jzov&8FnmyIQD9R4nhN06{Jj877O+;VDHagxF~I(ak|+OPCQsOb zFc(^2=Cu^0llU5h4|-0IpQ%KOH)a@7;{l8#c(?|zW3BY!j#!T~7x zIk=1Gv4ie;{Qgw;+QRQQz>}Y$mc!%WLmH93iLmTfGQcih!tns~i>XWwt;|19xDA5- z)3D3;&(pj5U9U~45TTNMP(%a*57=~^`K3jaWb+Zu;6%u*5frtl0&GJNR(8n=YTWQ=E*gO+TSHrL@0kKtHs;?m1~Aoz?%yYY)tREK zMUyA947KzpK>m*0Aq|Y2Odk0)s4ECTHXdcf%8fs%3t!gB96q!ou&jvWsaL}pWjtu} zzgz<#t=?=Ctk$B$14KS7Tim3Fh|784cO0;n+L&=({RLRvnYQz2Iw)U94bfrj*KeL( z)vSL+=(zJF7C4|g^Bn)=O=Toradia4uTKX_v#Fh2;2y0eu5B7!KjKDxxrnf&|34TG zgj%f*WAl;uc(EXzGM?I0(Au$E|9LV_0PGLFO~BKY6taZlwA?=Zdh6~s75|}-Jr%{9 zG7=8V)fSr0U>e5nI^(NP$TO0)GkYF&s{yb1hZq0sUOo^$e8CuNDhAX%L); From 6ade7e47edfd2e5be237db2e9f9b5bbe4a0f8368 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 1 May 2020 11:35:33 +0200 Subject: [PATCH 362/549] update testing documentation --- Firmware/.vscode/launch.json | 20 +++++ docs/testing.md | 122 +++++++++++++++++++++++++++--- tools/odrive/tests/test_runner.py | 2 +- 3 files changed, 131 insertions(+), 13 deletions(-) diff --git a/Firmware/.vscode/launch.json b/Firmware/.vscode/launch.json index 48fecec2..5107086e 100644 --- a/Firmware/.vscode/launch.json +++ b/Firmware/.vscode/launch.json @@ -30,6 +30,26 @@ "interface/stlink-v2.cfg", "target/stm32f4x_stlink.cfg", ], + "svdFile": "${workspaceRoot}/Board/v3/STM32F40x.svd", + "cwd": "${workspaceRoot}" + }, + { + // For the Cortex-Debug extension + // ssh -t odrv -L3333:localhost:3333 bash -c "\"openocd '-f' 'interface/stlink-v2.cfg' '-f' 'target/stm32f4x_stlink.cfg'\"" + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "preLaunchCommands": [ + "load" + ], + "request": "launch", + "name": "Debug ODrive via external server", + "executable": "${workspaceRoot}/build/ODriveFirmware.elf", + "configFiles": [ + "interface/stlink-v2.cfg", + "target/stm32f4x_stlink.cfg", + ], + "svdFile": "${workspaceRoot}/Board/v3/STM32F40x.svd", "cwd": "${workspaceRoot}" }, ] diff --git a/docs/testing.md b/docs/testing.md index 347db2b1..46ba9b69 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -3,21 +3,119 @@ This section describes how to use the automated testing facilities. You don't have to do this as an end user. -They test the following aspects: - - System functions (communication interfaces, configuration storage) - - Functionality of the motor controller and state machine - - High speed and high load conditions - The testing facility consists of the following components: - * **Test rig:** In the simplest case this can be a single ODrive with a single motor and encoder pair. Can also be multiple ODrives with multiple axes, some of which may be mechanically coupled. + * **Test rig:** In the simplest case this can be a single ODrive optionally with a single motor and encoder pair. Can also be multiple ODrives with multiple axes, some of which may be mechanically coupled. * **Test host:** The PC on which the test script runs. All ODrives must be connected to the test host via USB. * **test-rig.yaml:** Describes your test rig. Make sure all values are correct. Incorrect values may physically break or fry your test setup. - * **run_tests.py:** This is the main script that runs all the tests. + * **test_runner.py:** This is the main script that runs all the tests. + * **..._test.py** The actual tests -## How to run +## The Tests -Example: + - `analog_input_test.py`: Analog Input + - `calibration_test.py`: Motor calibration, encoder offset calibration, encoder direction find, encoder index search + - `can_test.py`: Partial coverage of the commands described in [CAN Protocol](can-protocol) + - `closed_loop_test.py`: Velocity control, position control (TODO: sensorless control) + - `encoder_test.py`: Incremental encoder, hall effect encoder, sin/cos encoder, SPI encoders (AMS, CUI) + - `nvm_test.py`: Configuration storage + - `pwm_input_test.py`: PWM input + - `step_dir_test.py`: Step/dir input + - `uart_ascii_test.py`: Partial coverage of the commands described in [ASCII Protocol](ascii-protocol) + +All tests in a file can be run with e.g.: + + python3 uart_ascii_test.py --test-rig-yaml ../../test-rig-rpi.yaml + +See the following sections for a more detailed test flow description. + +## Our test rig + +Our test rig essentially consists of the following components: + + - an ODrive as the test subject + - a Teensy 4.0 to emulate external hardware such as encoders + - a Motor + Encoder pair for closed loop control tests + - a Raspberry Pi 4.0 as test host + - a CAN hat for the Raspberry Pi for CAN tests + +This document is therefore centered around this test rig layout. +If your test rig differs, you may be able to run some but not all of the tests. + +## How to set up a Raspberry Pi as testing host + + 1. Install Raspbian Lite on a Raspberry Pi 4.0. I used the NOOBS installer for this. + 2. Prepare the installation: + + sudo systemctl enable ssh + sudo systemctl start ssh + # Transfer your public key for passwordless SSH. All subsequent steps can be done via SSH. + sudo apt-get update + sudo apt-get upgrade + + 3. Add the following lines to `/boot/config.txt`: + - `enable_uart=1` + - `dtparam=spi=on` + - `dtoverlay=spi-bcm2835-overlay` + - `dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25` - Note: These oscillator and interrupt GPIO settings here are for the "RS485 CAN HAT" I have. There appear to be multiple versions, so they may be different from yours. Check the marking on the oscillator and the schematics. + + 4. Remove the following arguments from `/boot/cmdline.txt`: + - `console=serial0,115200` + + 5. Reboot. + + 6. Install the prerequisites: + + sudo apt-get install ipython3 python3-appdirs python3-yaml python3-usb python3-serial python3-can python3-scipy git openocd + # Optionally, to be able to compile the firmware: + sudo apt-get install gcc-arm-none-eabi + + 7. Install Teensyduino and teensy-loader-cli: + + sudo apt-get install libfontconfig libxft2 libusb-dev + + wget https://downloads.arduino.cc/arduino-1.8.12-linuxarm.tar.xz + tar -xf arduino-1.8.12-linuxarm.tar.xz + wget https://www.pjrc.com/teensy/td_151/TeensyduinoInstall.linuxarm + chmod +x TeensyduinoInstall.linuxarm + ./TeensyduinoInstall.linuxarm --dir=arduino-1.8.12 + sudo cp -R arduino-1.8.12 /usr/share/arduino + sudo ln -s /usr/share/arduino/arduino /usr/bin/arduino + + git clone https://github.com/PaulStoffregen/teensy_loader_cli + pushd teensy_loader_cli + sudo cp teensy_loader_cli /usr/bin/ + sudo ln -s /usr/bin/teensy_loader_cli /usr/bin/teensy-loader-cli + popd + + 8. Add the following lines to `/etc/udev/rules.d/49-stlinkv2`: + + SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", MODE:="0666" + SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", MODE:="0666" + + 9. `sudo ../../odrivetool udev-setup` + + 10. `sudo udevadm trigger` + + 11. Run once after every reboot: `sudo ipython3 --pdb test_runner.py -- --setup-host --test-rig-yaml ../../test-rig-rpi.yaml` + +## SSH testing flow + +Here's one possible workflow for developing on the local host and testing on a remote SSH host. + +We assume that the ODrive repo is at `/path/to/ODriveFirmware` and your testing host is configured under the SSH name `odrv`. + +To flash and start remote debugging: + + 1. Start OpenOCD remotely, along with a tunnel to localhost: `ssh -t odrv -L3333:localhost:3333 bash -c "\"openocd '-f' 'interface/stlink-v2.cfg' '-f' 'target/stm32f4x_stlink.cfg'\""` + You can keep this open for multiple debug sessions. Press Ctrl+C to quit. + 2. Compile the firmware + 3. In VSCode, select the run configuration "Debug ODrive via external server" and press Run. In contrast to the other configurations, this will flash the new firmware before dropping you into the debugger. + +To run a test: + + rsync -avh -e ssh /path/to/ODriveFirmware odrv:/opt/odrivetest --exclude="Firmware/build" --exclude="Firmware/.tup" --exclude=".git" --delete + + ssh odrv + > cd /opt/odrivetest/tools/odrive/tests/ + > ipython3 --pdb uart_ascii_test.py -- --test-rig-yaml ../../test-rig-rpi.yaml -``` -./run_tests.py --skip-boring-tests --ignore top-odrive.yellow bottom-odrive.yellow -``` diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index 56b9e663..d1697ec9 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -410,7 +410,7 @@ class TeensyComponent(Component): env['ARDUINO_COMPILE_DESTINATION'] = hexfile run_shell( ['arduino', '--board', 'teensy:avr:teensy40', '--verify', sketchfile], - logger, env = env, timeout = 60) + logger, env = env, timeout = 120) def program(self, hex_file_path: str, logger: Logger): """ From 90f5c1d0b15b4ae9088a21332fedeed01cfc2f03 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 1 May 2020 16:32:09 +0200 Subject: [PATCH 363/549] change velocity clamping in input filter mode as per: https://discordapp.com/channels/369667319280173067/369678934985408524/705227337230188595 --- Firmware/MotorControl/controller.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 3b450277..f4b7f84b 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -110,7 +110,8 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) } void Controller::update_filter_gains() { - input_filter_ki_ = 2.0f * config_.input_filter_bandwidth; // basic conversion to discrete time + float bandwidth = std::min(config_.input_filter_bandwidth, 0.25f * current_meas_hz); + input_filter_ki_ = 2.0f * bandwidth; // basic conversion to discrete time input_filter_kp_ = 0.25f * (input_filter_ki_ * input_filter_ki_); // Critically damped } @@ -175,7 +176,7 @@ bool Controller::update(float* current_setpoint_output) { float delta_vel = input_vel_ - vel_setpoint_; // Vel error float accel = input_filter_kp_*delta_pos + input_filter_ki_*delta_vel; // Feedback current_setpoint_ = accel * config_.inertia; // Accel - vel_setpoint_ += std::clamp(current_meas_period * accel, 2.0f * std::abs(delta_vel), -2.0f * std::abs(delta_vel)); // delta vel + vel_setpoint_ += current_meas_period * accel; // delta vel pos_setpoint_ += current_meas_period * vel_setpoint_; // Delta pos } break; case INPUT_MODE_MIRROR: { From c6bb19145707b236b36713aedd95e2ae5638dbcc Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 1 May 2020 16:01:14 -0700 Subject: [PATCH 364/549] improvements to pos filter analysis script --- analysis/filterpoles.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/analysis/filterpoles.py b/analysis/filterpoles.py index abdaee2c..ca57bc8f 100644 --- a/analysis/filterpoles.py +++ b/analysis/filterpoles.py @@ -5,15 +5,20 @@ from scipy.integrate import solve_ivp import matplotlib.pyplot as plt do_mass_spring = True -do_PLL = True +do_PLL = False -bandwidth = 1 +bandwidth = 10 pos_ref = 0 vel_ref = 0 init_pos = 1000 init_vel = 0 +plotend = 1 +plotfrequency = 1000.0 + +fig, ax1 = plt.subplots() + if do_mass_spring: # 2nd order system response with manipulation of velocity only # This is similar to a mass/spring/damper system @@ -36,10 +41,20 @@ if do_mass_spring: Xdot = [pos_dot, vel_dot] return Xdot - sol = solve_ivp(get_Xdot, (0.0, 10.0), [init_pos, init_vel], t_eval=np.linspace(0, 10, 100)) + sol = solve_ivp(get_Xdot, (0.0, plotend), [init_pos, init_vel], t_eval=np.linspace(0, plotend, plotend*plotfrequency)) - plt.plot(np.transpose(sol.t), np.transpose(sol.y[0,:]), label='physical mass pos') - plt.plot(np.transpose(sol.t), np.transpose(sol.y[1,:]), label='physical mass vel') + color = 'tab:red' + ax1.set_xlabel('time (s)') + ax1.set_ylabel('pos', color=color) + ax1.plot(np.transpose(sol.t), np.transpose(sol.y[0,:]), label='physical mass', color=color) + ax1.tick_params(axis='y', labelcolor=color) + + ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis + + color = 'tab:blue' + ax2.set_ylabel('vel', color=color) # we already handled the x-label with ax1 + ax2.plot(np.transpose(sol.t), np.transpose(sol.y[1,:]), label='physical mass', color=color) + ax2.tick_params(axis='y', labelcolor=color) if do_PLL: @@ -64,7 +79,7 @@ if do_PLL: Xdot = [pos_dot, vel_dot] return Xdot - sol = solve_ivp(get_Xdot, (0.0, 10.0), [init_pos, init_vel], t_eval=np.linspace(0, 10, 100)) + sol = solve_ivp(get_Xdot, (0.0, plotend), [init_pos, init_vel], t_eval=np.linspace(0, plotend, plotend*plotfrequency)) plt.plot(np.transpose(sol.t), np.transpose(sol.y[0,:]), label='PLL pos') plt.plot(np.transpose(sol.t), np.transpose(sol.y[1,:]), label='PLL vel') @@ -72,4 +87,4 @@ if do_PLL: plt.legend() -plt.show(block=False) \ No newline at end of file +plt.show(block=True) \ No newline at end of file From d002a74d246b199e4e6670acf0bb132196dd81b2 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 1 May 2020 16:37:20 -0700 Subject: [PATCH 365/549] rename psu current fault levels --- Firmware/MotorControl/axis.cpp | 6 +++--- Firmware/MotorControl/axis.hpp | 2 +- Firmware/MotorControl/odrive_main.h | 4 ++-- Firmware/communication/communication.cpp | 4 ++-- tools/odrive/enums.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 53b9925d..862c027e 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -174,11 +174,11 @@ bool Axis::do_checks() { } } - if (Ibus_sum > board_config.power_supply_max_current) { + if (Ibus_sum > board_config.dc_max_positive_current) { error_ |= ERROR_DC_BUS_OVER_CURRENT; } - if (Ibus_sum < board_config.power_supply_min_current) { - error_ |= ERROR_DC_BUS_UNDER_CURRENT; + if (Ibus_sum < board_config.dc_max_negative_current) { + error_ |= ERROR_DC_BUS_OVER_REGEN_CURRENT; } // Sub-components should use set_error which will propegate to this error_ diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index c5a87a67..5ccf0c58 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -24,7 +24,7 @@ public: ERROR_MIN_ENDSTOP_PRESSED = 0x1000, ERROR_MAX_ENDSTOP_PRESSED = 0x2000, ERROR_ESTOP_REQUESTED = 0x4000, - ERROR_DC_BUS_UNDER_CURRENT = 0x8000, // too much current pushed into the power supply + ERROR_DC_BUS_OVER_REGEN_CURRENT = 0x8000, // too much current pushed into the power supply ERROR_DC_BUS_OVER_CURRENT = 0x10000, // too much current pulled out of the power supply ERROR_HOMING_WITHOUT_ENDSTOP = 0x20000, // the min endstop was not enabled during homing }; diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index a505b3e1..e11c2a00 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -101,8 +101,8 @@ struct BoardConfig_t { //!< The closer the voltage approaches dc_bus_overvoltage_trip_level, //!< the more power is sunk into the resistor. //!< This feature is disabled if dc_bus_overvoltage_trip_level <= nominal_voltage. - float power_supply_max_current = INFINITY; // Max current [A] the power supply can source - float power_supply_min_current = -INFINITY; // Max current [A] the power supply can sink + float dc_max_positive_current = INFINITY; // Max current [A] the power supply can source + float dc_max_negative_current = -INFINITY; // Max current [A] the power supply can sink PWMMapping_t pwm_mappings[GPIO_COUNT]; PWMMapping_t analog_mappings[GPIO_COUNT]; }; diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index ea52ca87..d2aa1133 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -167,8 +167,8 @@ static inline auto make_obj_tree() { make_protocol_property("enable_ascii_protocol_on_usb", &board_config.enable_ascii_protocol_on_usb), make_protocol_property("dc_bus_undervoltage_trip_level", &board_config.dc_bus_undervoltage_trip_level), make_protocol_property("dc_bus_overvoltage_trip_level", &board_config.dc_bus_overvoltage_trip_level), - make_protocol_property("power_supply_min_current", &board_config.power_supply_min_current), - make_protocol_property("power_supply_max_current", &board_config.power_supply_max_current), + make_protocol_property("dc_max_negative_current", &board_config.dc_max_negative_current), + make_protocol_property("dc_max_positive_current", &board_config.dc_max_positive_current), #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 make_protocol_object("gpio1_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[0])), make_protocol_object("gpio2_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[1])), diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 1292e48b..99b21867 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -32,7 +32,7 @@ class errors: ERROR_MIN_ENDSTOP_PRESSED = 0x1000 ERROR_MAX_ENDSTOP_PRESSED = 0x2000 ERROR_ESTOP_REQUESTED = 0x4000 - ERROR_DC_BUS_UNDER_CURRENT = 0x8000 + ERROR_DC_BUS_OVER_REGEN_CURRENT = 0x8000 ERROR_DC_BUS_OVER_CURRENT = 0x10000 ERROR_HOMING_WITHOUT_ENDSTOP = 0x20000 From edb6a1611c321418b944aa663f3d5dd52d70a291 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 1 May 2020 17:11:47 -0700 Subject: [PATCH 366/549] changed max brake duty to 95 pct --- Firmware/MotorControl/low_level.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 689bb277..b044b9a0 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -593,16 +593,15 @@ void update_brake_current() { // Don't start braking until -Ibus > regen_current_allowed float brake_current = -Ibus_sum - board_config.max_regen_current; - float brake_duty = brake_current * std::abs(board_config.brake_resistance) / vbus_voltage; + float brake_duty = brake_current * board_config.brake_resistance / vbus_voltage; if (board_config.nominal_voltage < board_config.dc_bus_overvoltage_trip_level) { - brake_duty += std::max((vbus_voltage - board_config.nominal_voltage) / (board_config.dc_bus_overvoltage_trip_level / 0.9f - board_config.nominal_voltage), 0.0f); + brake_duty += std::max((vbus_voltage - board_config.nominal_voltage) / (board_config.dc_bus_overvoltage_trip_level / 0.9f - board_config.nominal_voltage), 0.0f); } - // Clamp the duty cycle - brake_duty = std::clamp(brake_duty, 0.0f, 0.9f); + // Duty limit at 95% to allow bootstrap caps to charge + brake_duty = std::clamp(brake_duty, 0.0f, 0.95f); - // Duty limit at 90% to allow bootstrap caps to charge // If brake_duty is NaN, this expression will also evaluate to false int high_on = static_cast(TIM_APB1_PERIOD_CLOCKS * (1.0f - brake_duty)); int low_off = high_on - TIM_APB1_DEADTIME_CLOCKS; From 1dc420a76d6f90844cbfd4a9f0f6e580a2ffc39c Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 4 May 2020 11:32:23 +0200 Subject: [PATCH 367/549] various changes to update_brake_current: - cleaner implementaiton of the overvoltage ramp with config variables separate from other functions - explicit enable flag for overvoltage ramp (disabled by default) - reinstate brake duty cycle NaN error - add informational flag for brake resistor saturation --- Firmware/MotorControl/low_level.cpp | 29 ++++++++++++++++-------- Firmware/MotorControl/low_level.h | 1 + Firmware/MotorControl/motor.hpp | 3 ++- Firmware/MotorControl/odrive_main.h | 26 ++++++++++++++++----- Firmware/communication/communication.cpp | 5 +++- 5 files changed, 46 insertions(+), 18 deletions(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index b044b9a0..afd1a5fa 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -36,6 +36,7 @@ const float adc_ref_voltage = 3.3f; // Arbitrary non-zero inital value to avoid division by zero if ADC reading is late float vbus_voltage = 12.0f; bool brake_resistor_armed = false; +bool brake_resistor_saturated = false; /* Private constant data -----------------------------------------------------*/ static const GPIO_TypeDef* GPIOs_to_samp[] = { GPIOA, GPIOB, GPIOC }; static const int num_GPIO = sizeof(GPIOs_to_samp) / sizeof(GPIOs_to_samp[0]); @@ -595,19 +596,27 @@ void update_brake_current() { float brake_current = -Ibus_sum - board_config.max_regen_current; float brake_duty = brake_current * board_config.brake_resistance / vbus_voltage; - if (board_config.nominal_voltage < board_config.dc_bus_overvoltage_trip_level) { - brake_duty += std::max((vbus_voltage - board_config.nominal_voltage) / (board_config.dc_bus_overvoltage_trip_level / 0.9f - board_config.nominal_voltage), 0.0f); + if (board_config.enable_dc_bus_overvoltage_ramp && (board_config.dc_bus_overvoltage_ramp_start < board_config.dc_bus_overvoltage_ramp_end)) { + brake_duty += std::fmax((vbus_voltage - board_config.dc_bus_overvoltage_ramp_start) / (board_config.dc_bus_overvoltage_ramp_end - board_config.dc_bus_overvoltage_ramp_start), 0.0f); } - // Duty limit at 95% to allow bootstrap caps to charge - brake_duty = std::clamp(brake_duty, 0.0f, 0.95f); - - // If brake_duty is NaN, this expression will also evaluate to false - int high_on = static_cast(TIM_APB1_PERIOD_CLOCKS * (1.0f - brake_duty)); - int low_off = high_on - TIM_APB1_DEADTIME_CLOCKS; - if (low_off < 0) low_off = 0; - safety_critical_apply_brake_resistor_timings(low_off, high_on); + if (!std::isnan(brake_duty)) { + if (brake_duty >= 0.95f) { + brake_resistor_saturated = true; + } + // Duty limit at 95% to allow bootstrap caps to charge + brake_duty = std::clamp(brake_duty, 0.0f, 0.95f); + + // If brake_duty is NaN, this expression will also evaluate to false + int high_on = static_cast(TIM_APB1_PERIOD_CLOCKS * (1.0f - brake_duty)); + int low_off = high_on - TIM_APB1_DEADTIME_CLOCKS; + if (low_off < 0) low_off = 0; + safety_critical_apply_brake_resistor_timings(low_off, high_on); + } else { + // Shuts off all motors AND brake resistor, sets error code on all motors. + low_level_fault(Motor::ERROR_BRAKE_DUTY_CYCLE_NAN); + } } diff --git a/Firmware/MotorControl/low_level.h b/Firmware/MotorControl/low_level.h index 503b98e1..43a3ac9b 100644 --- a/Firmware/MotorControl/low_level.h +++ b/Firmware/MotorControl/low_level.h @@ -23,6 +23,7 @@ extern const float adc_ref_voltage; /* Exported variables --------------------------------------------------------*/ extern float vbus_voltage; extern bool brake_resistor_armed; +extern bool brake_resistor_saturated; extern uint16_t adc_measurements_[ADC_CHANNEL_COUNT]; /* Exported macro ------------------------------------------------------------*/ /* Exported functions --------------------------------------------------------*/ diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 5d493437..b0f1cef1 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -23,7 +23,8 @@ public: ERROR_UNEXPECTED_TIMER_CALLBACK = 0x0200, ERROR_CURRENT_SENSE_SATURATION = 0x0400, ERROR_INVERTER_OVER_TEMP = 0x0800, - ERROR_CURRENT_LIMIT_VIOLATION = 0x1000 + ERROR_CURRENT_LIMIT_VIOLATION = 0x1000, + ERROR_BRAKE_DUTY_CYCLE_NAN = 0x2000, }; enum MotorType_t { diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index e11c2a00..f659a431 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -95,12 +95,26 @@ struct BoardConfig_t { // brake_duty_cycle += 0% + * vbus_voltage == dc_bus_overvoltage_ramp_end => brake_duty_cycle += 100% + */ + bool enable_dc_bus_overvoltage_ramp = false; + float dc_bus_overvoltage_ramp_start = 1.07f * HW_VERSION_VOLTAGE; //!< See `enable_dc_bus_overvoltage_ramp`. + //!< Do not set this lower than your usual vbus_voltage, + //!< unless you like fried brake resistors. + float dc_bus_overvoltage_ramp_end = 1.07f * HW_VERSION_VOLTAGE; //!< See `enable_dc_bus_overvoltage_ramp`. + //!< Must be larger than `dc_bus_overvoltage_ramp_start`, + //!< otherwise the ramp feature is disabled. + float dc_max_positive_current = INFINITY; // Max current [A] the power supply can source float dc_max_negative_current = -INFINITY; // Max current [A] the power supply can sink PWMMapping_t pwm_mappings[GPIO_COUNT]; diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index d2aa1133..582a4617 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -126,6 +126,7 @@ static inline auto make_obj_tree() { make_protocol_ro_property("fw_version_unreleased", &fw_version_unreleased), make_protocol_ro_property("user_config_loaded", const_cast(&user_config_loaded_)), make_protocol_ro_property("brake_resistor_armed", &brake_resistor_armed), + make_protocol_property("brake_resistor_saturated", &brake_resistor_saturated), make_protocol_object("system_stats", make_protocol_ro_property("uptime", &system_stats_.uptime), make_protocol_ro_property("min_heap_space", &system_stats_.min_heap_space), @@ -159,7 +160,6 @@ static inline auto make_obj_tree() { ), make_protocol_object("config", make_protocol_property("brake_resistance", &board_config.brake_resistance), - make_protocol_property("nominal_voltage", &board_config.nominal_voltage), make_protocol_property("max_regen_current", &board_config.max_regen_current), // TODO: changing this currently requires a reboot - fix this make_protocol_property("enable_uart", &board_config.enable_uart), @@ -167,6 +167,9 @@ static inline auto make_obj_tree() { make_protocol_property("enable_ascii_protocol_on_usb", &board_config.enable_ascii_protocol_on_usb), make_protocol_property("dc_bus_undervoltage_trip_level", &board_config.dc_bus_undervoltage_trip_level), make_protocol_property("dc_bus_overvoltage_trip_level", &board_config.dc_bus_overvoltage_trip_level), + make_protocol_property("enable_dc_bus_overvoltage_ramp", &board_config.enable_dc_bus_overvoltage_ramp), + make_protocol_property("dc_bus_overvoltage_ramp_start", &board_config.dc_bus_overvoltage_ramp_start), + make_protocol_property("dc_bus_overvoltage_ramp_end", &board_config.dc_bus_overvoltage_ramp_end), make_protocol_property("dc_max_negative_current", &board_config.dc_max_negative_current), make_protocol_property("dc_max_positive_current", &board_config.dc_max_positive_current), #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 From 23f513e99e43e20068179d37c811b86925747e3a Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 4 May 2020 16:26:52 +0200 Subject: [PATCH 368/549] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47e41b83..8d4d0077 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * Added support for Flylint VSCode Extension for static code analysis * Using an STM32F405 .svd file allows CortexDebug to view registers during debugging * Added scripts for building via docker. -* Brake resistor logic now attempts to clamp voltage according to `odrv.config.nominal_voltage` +* Brake resistor logic now attempts to clamp voltage according to `odrv.config.dc_bus_overvoltage_ramp_start` and `odrv.config.dc_bus_overvoltage_ramp_end` ### Changed * Changed ratiometric `motor.config.current_lim_tolerance` to absolute `motor.config.current_lim_margin` From b7ba3f11ce76a4bf7ef4f77b24e90eee420a9cce Mon Sep 17 00:00:00 2001 From: camrbuss Date: Mon, 4 May 2020 19:32:38 -0600 Subject: [PATCH 369/549] svd for FreeRTOS, state doc updates --- Firmware/.vscode/launch.json | 1 + docs/commands.md | 2 ++ docs/endstops.md | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Firmware/.vscode/launch.json b/Firmware/.vscode/launch.json index 48fecec2..f512f3e8 100644 --- a/Firmware/.vscode/launch.json +++ b/Firmware/.vscode/launch.json @@ -30,6 +30,7 @@ "interface/stlink-v2.cfg", "target/stm32f4x_stlink.cfg", ], + "svdFile": "${workspaceRoot}/Board/v3/STM32F40x.svd", "cwd": "${workspaceRoot}" }, ] diff --git a/docs/commands.md b/docs/commands.md index 17c595b1..69a54982 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -40,6 +40,8 @@ The current state of an axis is indicated by `.current_state`. The user ca * Can only be entered if the motor is calibrated (`.motor.is_calibrated`) or the motor direction is unspecified (`.motor.config.direction == 1`) 10. `AXIS_STATE_ENCODER_DIR_FIND` Run encoder direction search. * Can only be entered if the motor is calibrated (`.motor.is_calibrated`). + 11. `AXIS_STATE_HOMING` Run axis homing function. + * Endstops must be enabled to use this feature. ### Startup Procedure diff --git a/docs/endstops.md b/docs/endstops.md index 87455825..46b08d9c 100644 --- a/docs/endstops.md +++ b/docs/endstops.md @@ -51,7 +51,7 @@ The debouncing time for this endstop. Most switches exhibit some sort of bounce ### is_active_high This is how you configure the endstop to be either "NPN" or "PNP". An "NPN" configuration would be `is_active_high = False` whereas a PNP configuration is `is_active_high = True`. Refer to the following table for more information: -3D printer endstops (like those that come with a RAMPS 1.4) are typically configuration **4**. +3D printer endstops (like those that come with a RAMPS 1.4) are typically configuration **4**. Typically configuration **1** or **3** is preferred when using mechanical switches as the most common failure mode leaves the switch open. ![Endstop configuration](Endstop_configuration.png) From 87b3ad252ea3b10493d6e95b10ee7bd41cb5c04f Mon Sep 17 00:00:00 2001 From: camrbuss Date: Mon, 4 May 2020 20:03:25 -0600 Subject: [PATCH 370/549] CAN table merge Message and Signals --- docs/can-protocol.md | 91 +++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 61 deletions(-) diff --git a/docs/can-protocol.md b/docs/can-protocol.md index 7fb0c312..2527eada 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -32,72 +32,41 @@ Receive PDO 0x200 + nodeID = 0x223, which does not conflict with the range [0x20 Be careful that you don't assign too many nodeIDs per PDO group. Four CAN Simple nodes (32*4) is all of the available address space of a single PDO. If the bus is strictly ODrive CAN Simple nodes, a simple sequential Node ID assignment will work fine. ### Messages -CMD ID | Name | Sender | Signals | Start byte ---: | :-- | :-- | :-- | :-- -0x000 | CANOpen NMT Message\*\* | Master | - | - -0x001 | ODrive Heartbeat Message | Axis | Axis Error
Axis Current State | 0
4 -0x002 | ODrive Estop Message | Master | - | - -0x003 | Get Motor Error\* | Axis | Motor Error | 0 -0x004 | Get Encoder Error\* | Axis | Encoder Error | 0 -0x005 | Get Sensorless Error\* | Axis | Sensorless Error | 0 -0x006 | Set Axis Node ID | Master | Axis CAN Node ID | 0 -0x007 | Set Axis Requested State | Master | Axis Requested State | 0 -0x008 | Set Axis Startup Config | Master | - Not yet implemented - | - -0x009 | Get Encoder Estimates\* | Master | Encoder Pos Estimate
Encoder Vel Estimate | 0
4 -0x00A | Get Encoder Count\* | Master | Encoder Shadow Count
Encoder Count in CPR | 0
4 -0x00B | Set Controller Modes | Master | Control Mode
Input Mode | 0
4 -0x00C | Set Input Pos | Master | Input Pos
Vel FF
Current FF | 0
4
6 -0x00D | Set Input Vel | Master | Input Vel
Current FF | 0
4 -0x00E | Set Input Current | Master | Input Current | 0 -0x00F | Set Velocity Limit | Master | Velocity Limit | 0 -0x010 | Start Anticogging | Master | - | - -0x011 | Set Traj Vel Limit | Master | Traj Vel Limit | 0 -0x012 | Set Traj Accel Limits | Master | Traj Accel Limit
Traj Decel Limit | 0
4 -0x013 | Set Traj A per Count / s^2 | Master | Traj A per CSS | 0 -0x014 | Get IQ\* | Axis | Iq Setpoint
Iq Measured | 0
4 -0x015 | Get Sensorless Estimates\* | Master | Sensorless Pos Estimate
Sensorless Vel Estimate | 0
4 -0x016 | Reboot ODrive | Master\*\*\* | - | - -0x017 | Get Vbus Voltage | Master\*\*\* | Vbus Voltage | 0 -0x018 | Clear Errors | Master | - | - -0x700 | CANOpen Heartbeat Message\*\* | Slave | - | - + +CMD ID | Name | Sender | Signals | Start byte | Signal Type | Bits | Factor | Offset | Byte Order +--: | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- +0x000 | CANOpen NMT Message\*\* | Master | - | - | - | - | - | - | - +0x001 | ODrive Heartbeat Message | Axis | Axis Error
Axis Current State | 0
4 | Unsigned Int
Unsigned Int | 32
32 | 1
1 | 0
0 | Intel
Intel +0x002 | ODrive Estop Message | Master | - | - | - | - | - | - | - +0x003 | Get Motor Error\* | Axis | Motor Error | 0 | Unsigned Int | 32 | 1 | 0 | Intel +0x004 | Get Encoder Error\* | Axis | Encoder Error | 0 | Unsigned Int | 32 | 1 | 0 | Intel +0x005 | Get Sensorless Error\* | Axis | Sensorless Error | 0 | Unsigned Int | 32 | 1 | 0 | Intel +0x006 | Set Axis Node ID | Master | Axis CAN Node ID | 0 | Unsigned Int | 16 | 1 | 0 | Intel +0x007 | Set Axis Requested State | Master | Axis Requested State | 0 | Unsigned Int | 32 | 1 | 0 | Intel +0x008 | Set Axis Startup Config | Master | - Not yet implemented - | - | - | - | - | - | - +0x009 | Get Encoder Estimates\* | Master | Encoder Pos Estimate
Encoder Vel Estimate | 0
4 | IEEE 754 Float
IEEE 754 Float | 32
32 | 1
1 | 0
0 | Intel
Intel +0x00A | Get Encoder Count\* | Master | Encoder Shadow Count
Encoder Count in CPR | 0
4 | Signed Int
Signed Int | 32
32 | 1
1 | 0
0 | Intel
Intel +0x00B | Set Controller Modes | Master | Control Mode
Input Mode | 0
4 | Signed Int
Signed Int | 32
32 | 1
1 | 0
0 | Intel
Intel +0x00C | Set Input Pos | Master | Input Pos
Vel FF
Current FF | 0
4
6 | Signed Int
Signed Int
Signed Int | 32
16
16 | 1
0.1
0.01 | 0
0
0 | Intel
Intel
Intel +0x00D | Set Input Vel | Master | Input Vel
Current FF | 0
4 | Signed Int
Signed Int | 32
32 | 0.01
0.01 | 0
0 | Intel
Intel +0x00E | Set Input Current | Master | Input Current | 0 | Signed Int | 32 | 0.01 | 0 | Intel +0x00F | Set Velocity Limit | Master | Velocity Limit | 0 | IEEE 754 Float | 32 | 1 | 0 | Intel +0x010 | Start Anticogging | Master | - | - | - | - | - | - | - +0x011 | Set Traj Vel Limit | Master | Traj Vel Limit | 0 | IEEE 754 Float | 32 | 1 | 0 | Intel +0x012 | Set Traj Accel Limits | Master | Traj Accel Limit
Traj Decel Limit | 0
4 | IEEE 754 Float
IEEE 754 Float | 32
32 | 1
1 | 0
0 | Intel
Intel +0x013 | Set Traj A per Count / s^2 | Master | Traj A per CSS | 0 | IEEE 754 Float | 32 | 1 | 0 | Intel +0x014 | Get IQ\* | Axis | Iq Setpoint
Iq Measured | 0
4 | IEEE 754 Float
IEEE 754 Float | 32
32 | 1
1 | 0
0 | Intel
Intel +0x015 | Get Sensorless Estimates\* | Master | Sensorless Pos Estimate
Sensorless Vel Estimate | 0
4 | IEEE 754 Float
IEEE 754 Float | 32
32 | 1
1 | 0
0 | Intel
Intel +0x016 | Reboot ODrive | Master\*\*\* | - | - | - | - | - | - | - +0x017 | Get Vbus Voltage | Master\*\*\* | Vbus Voltage | 0 | IEEE 754 Float | 32 | 1 | 0 | Intel +0x018 | Clear Errors | Master | - | - | - | - | - | - | - +0x700 | CANOpen Heartbeat Message\*\* | Slave | - | - | - | - | - | - | - +-|-|-|----------------------------------|-|--------------------|-|-|-|_ \* Note: These messages are call & response. The Master node sends a message with the RTR bit set, and the axis responds with the same ID and specified payload. \*\* Note: These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple. \*\*\* Note: These messages can be sent to either address on a given ODrive board. ---- -### Signals -Name | Type | Bits | Factor | Offset | Byte Order -:-- | :-- | :--: | --: | :--: | :--: -Axis Error | Unsigned Int | 32 | 1 | 0 | Intel -Axis Current State | Unsigned Int | 32 | 1 | 0 | Intel -Motor Error | Unsigned Int | 32 | 1 | 0 | Intel -Encoder Error | Unsigned Int | 32 | 1 | 0 | Intel -Sensorless Error | Unsigned Int | 32 | 1 | 0 | Intel -Axis CAN Node ID | Unsigned Int | 16 | 1 | 0 | Intel -Axis Requested State | Unsigned Int | 32 | 1 | 0 | Intel -Encoder Pos Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel -Encoder Vel Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel -Encoder Shadow Count | Signed Int | 32 | 1 | 0 | Intel -Encoder Count In CPR | Signed Int | 32 | 1 | 0 | Intel -Control Mode | Signed Int | 32 | 1 | 0 | Intel -Input Mode | Signed Int | 32 | 1 | 0 | Intel -Input Pos | Signed Int | 32 | 1 | 0 | Intel -Vel FF | Signed Int | 16 | 0.1 | 0 | Intel -Current FF | Signed Int | 16 | 0.01 | 0 | Intel -Input Vel | Signed Int | 32 | 0.01 | 0 | Intel -Input Current | Signed Int | 32 | 0.01 | 0 | Intel -Velocity Limit | IEEE 754 Float | 32 | 1 | 0 | Intel -Traj Vel Limit | IEEE 754 Float | 32 | 1 | 0 | Intel -Traj Accel Limit | IEEE 754 Float | 32 | 1 | 0 | Intel -Traj Decel Limit | IEEE 754 Float | 32 | 1 | 0 | Intel -Traj A per CSS | IEEE 754 Float | 32 | 1 | 0 | Intel -Iq Setpoint | IEEE 754 Float | 32 | 1 | 0 | Intel -Iq Measured | IEEE 754 Float | 32 | 1 | 0 | Intel -Sensorless Pos Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel -Sensorless Vel Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel -Vbus Voltage | IEEE 754 Float | 32 | 1 | 0 | Intel - --- ## Configuring ODrive for CAN Configuration of the CAN parameters should be done via USB before putting the device on the bus. From 50ecdef8608bb334ba7f2d2232449e29c7ab2b8e Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 5 May 2020 18:16:06 +0200 Subject: [PATCH 371/549] move DC overcurrent/undercurrent checks to update_brake_current() The checks now take into account the brake current. Expose ibus on fibre. --- Firmware/MotorControl/axis.cpp | 15 -------- Firmware/MotorControl/axis.hpp | 2 -- Firmware/MotorControl/low_level.cpp | 44 ++++++++++++++++-------- Firmware/MotorControl/low_level.h | 1 + Firmware/MotorControl/motor.hpp | 2 ++ Firmware/MotorControl/odrive_main.h | 9 +++-- Firmware/communication/communication.cpp | 1 + tools/odrive/enums.py | 5 +-- 8 files changed, 43 insertions(+), 36 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 862c027e..b68ef01b 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -166,21 +166,6 @@ bool Axis::do_checks() { if (!(vbus_voltage <= board_config.dc_bus_overvoltage_trip_level)) error_ |= ERROR_DC_BUS_OVER_VOLTAGE; - // This is the same math that's used in update_brake_current(). Should we calculate IBus globally? - float Ibus_sum = 0.0f; - for (size_t i = 0; i < AXIS_COUNT; ++i) { - if (axes[i]->motor_.armed_state_ == Motor::ARMED_STATE_ARMED) { - Ibus_sum += axes[i]->motor_.current_control_.Ibus; - } - } - - if (Ibus_sum > board_config.dc_max_positive_current) { - error_ |= ERROR_DC_BUS_OVER_CURRENT; - } - if (Ibus_sum < board_config.dc_max_negative_current) { - error_ |= ERROR_DC_BUS_OVER_REGEN_CURRENT; - } - // Sub-components should use set_error which will propegate to this error_ motor_.do_checks(); // encoder_.do_checks(); diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 5ccf0c58..5b489a93 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -24,8 +24,6 @@ public: ERROR_MIN_ENDSTOP_PRESSED = 0x1000, ERROR_MAX_ENDSTOP_PRESSED = 0x2000, ERROR_ESTOP_REQUESTED = 0x4000, - ERROR_DC_BUS_OVER_REGEN_CURRENT = 0x8000, // too much current pushed into the power supply - ERROR_DC_BUS_OVER_CURRENT = 0x10000, // too much current pulled out of the power supply ERROR_HOMING_WITHOUT_ENDSTOP = 0x20000, // the min endstop was not enabled during homing }; diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index afd1a5fa..454fa339 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -35,6 +35,7 @@ const float adc_ref_voltage = 3.3f; // This value is updated by the DC-bus reading ADC. // Arbitrary non-zero inital value to avoid division by zero if ADC reading is late float vbus_voltage = 12.0f; +float ibus_ = 0.0f; // exposed for monitoring only bool brake_resistor_armed = false; bool brake_resistor_saturated = false; /* Private constant data -----------------------------------------------------*/ @@ -600,23 +601,38 @@ void update_brake_current() { brake_duty += std::fmax((vbus_voltage - board_config.dc_bus_overvoltage_ramp_start) / (board_config.dc_bus_overvoltage_ramp_end - board_config.dc_bus_overvoltage_ramp_start), 0.0f); } - if (!std::isnan(brake_duty)) { - if (brake_duty >= 0.95f) { - brake_resistor_saturated = true; - } - - // Duty limit at 95% to allow bootstrap caps to charge - brake_duty = std::clamp(brake_duty, 0.0f, 0.95f); - - // If brake_duty is NaN, this expression will also evaluate to false - int high_on = static_cast(TIM_APB1_PERIOD_CLOCKS * (1.0f - brake_duty)); - int low_off = high_on - TIM_APB1_DEADTIME_CLOCKS; - if (low_off < 0) low_off = 0; - safety_critical_apply_brake_resistor_timings(low_off, high_on); - } else { + if (std::isnan(brake_duty)) { // Shuts off all motors AND brake resistor, sets error code on all motors. low_level_fault(Motor::ERROR_BRAKE_DUTY_CYCLE_NAN); + return; } + + if (brake_duty >= 0.95f) { + brake_resistor_saturated = true; + } + + // Duty limit at 95% to allow bootstrap caps to charge + brake_duty = std::clamp(brake_duty, 0.0f, 0.95f); + + // Special handling to avoid the case 0.0/0.0 == NaN. + Ibus_sum += brake_duty ? (brake_duty * vbus_voltage / board_config.brake_resistance) : 0.0f; + + ibus_ = Ibus_sum; + + if (Ibus_sum > board_config.dc_max_positive_current) { + low_level_fault(Motor::ERROR_DC_BUS_OVER_CURRENT); + return; + } + if (Ibus_sum < board_config.dc_max_negative_current) { + low_level_fault(Motor::ERROR_DC_BUS_OVER_REGEN_CURRENT); + return; + } + + // If brake_duty is NaN, this expression will also evaluate to false + int high_on = static_cast(TIM_APB1_PERIOD_CLOCKS * (1.0f - brake_duty)); + int low_off = high_on - TIM_APB1_DEADTIME_CLOCKS; + if (low_off < 0) low_off = 0; + safety_critical_apply_brake_resistor_timings(low_off, high_on); } diff --git a/Firmware/MotorControl/low_level.h b/Firmware/MotorControl/low_level.h index 43a3ac9b..f0b72ecb 100644 --- a/Firmware/MotorControl/low_level.h +++ b/Firmware/MotorControl/low_level.h @@ -22,6 +22,7 @@ extern const float adc_full_scale; extern const float adc_ref_voltage; /* Exported variables --------------------------------------------------------*/ extern float vbus_voltage; +extern float ibus_; extern bool brake_resistor_armed; extern bool brake_resistor_saturated; extern uint16_t adc_measurements_[ADC_CHANNEL_COUNT]; diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index b0f1cef1..aba285cc 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -25,6 +25,8 @@ public: ERROR_INVERTER_OVER_TEMP = 0x0800, ERROR_CURRENT_LIMIT_VIOLATION = 0x1000, ERROR_BRAKE_DUTY_CYCLE_NAN = 0x2000, + ERROR_DC_BUS_OVER_REGEN_CURRENT = 0x4000, // too much current pushed into the power supply + ERROR_DC_BUS_OVER_CURRENT = 0x8000, // too much current pulled out of the power supply }; enum MotorType_t { diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index f659a431..ade4416c 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -101,11 +101,14 @@ struct BoardConfig_t { * the ODrive will sink more power than usual into the the brake resistor * in an attempt to bring the voltage down again. * - * This setting is active even if all motors are disarmed. - * * The brake duty cycle is increased by the following amount: * vbus_voltage == dc_bus_overvoltage_ramp_start => brake_duty_cycle += 0% * vbus_voltage == dc_bus_overvoltage_ramp_end => brake_duty_cycle += 100% + * + * Remarks: + * - This setting is active even if all motors are disarmed. + * - brake_resistance must be non-zero, otherwise this will immediately + * result in overcurrent */ bool enable_dc_bus_overvoltage_ramp = false; float dc_bus_overvoltage_ramp_start = 1.07f * HW_VERSION_VOLTAGE; //!< See `enable_dc_bus_overvoltage_ramp`. @@ -116,7 +119,7 @@ struct BoardConfig_t { //!< otherwise the ramp feature is disabled. float dc_max_positive_current = INFINITY; // Max current [A] the power supply can source - float dc_max_negative_current = -INFINITY; // Max current [A] the power supply can sink + float dc_max_negative_current = -0.000001f; // Max current [A] the power supply can sink. You most likely want a non-positive value here. Set to -INFINITY to disable. PWMMapping_t pwm_mappings[GPIO_COUNT]; PWMMapping_t analog_mappings[GPIO_COUNT]; }; diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 582a4617..fb033bdd 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -116,6 +116,7 @@ public: static inline auto make_obj_tree() { return make_protocol_member_list( make_protocol_ro_property("vbus_voltage", &vbus_voltage), + make_protocol_ro_property("ibus", &ibus_), make_protocol_ro_property("serial_number", &serial_number), make_protocol_ro_property("hw_version_major", &hw_version_major), make_protocol_ro_property("hw_version_minor", &hw_version_minor), diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 99b21867..1198272e 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -32,8 +32,6 @@ class errors: ERROR_MIN_ENDSTOP_PRESSED = 0x1000 ERROR_MAX_ENDSTOP_PRESSED = 0x2000 ERROR_ESTOP_REQUESTED = 0x4000 - ERROR_DC_BUS_OVER_REGEN_CURRENT = 0x8000 - ERROR_DC_BUS_OVER_CURRENT = 0x10000 ERROR_HOMING_WITHOUT_ENDSTOP = 0x20000 class motor: @@ -50,6 +48,9 @@ class errors: ERROR_UNEXPECTED_TIMER_CALLBACK = 0x0200 ERROR_CURRENT_SENSE_SATURATION = 0x0400 ERROR_CURRENT_LIMIT_VIOLATION = 0x1000 + ERROR_BRAKE_DUTY_CYCLE_NAN = 0x2000 + ERROR_DC_BUS_OVER_REGEN_CURRENT = 0x4000 + ERROR_DC_BUS_OVER_CURRENT = 0x8000 class encoder: ERROR_NONE = 0 From afe83430fee9f5f3ca1a1d220660080818bfcbb0 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 5 May 2020 18:16:45 +0200 Subject: [PATCH 372/549] add test for over-regen-current check --- tools/odrive/tests/closed_loop_test.py | 226 ++++++++++++++++--------- 1 file changed, 145 insertions(+), 81 deletions(-) diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index 2b9cff42..dd26b6a5 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -10,8 +10,9 @@ from test_runner import * from odrive.enums import * -class TestClosedLoopControl(): +class TestClosedLoopControlBase(): """ + Base class for close loop control tests. """ def get_test_cases(self, testrig: TestRig): @@ -27,10 +28,7 @@ class TestClosedLoopControl(): if encoder.impl in testrig.get_connected_components(motor): yield (odrive.axes[num], motor, encoder) - def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): - axis = axis_ctx.handle - time.sleep(1.0) # wait for PLLs to stabilize - + def prepare(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): # Make sure there are no funny configurations active logger.debug('Setting up clean configuration...') axis_ctx.parent.erase_config_and_reboot() @@ -58,96 +56,162 @@ class TestClosedLoopControl(): test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) test_assert_no_error(axis_ctx) - nominal_rps = 1.0 - nominal_vel = float(enc_ctx.yaml['cpr']) * nominal_rps - logger.debug(f'Testing closed loop velocity control at {nominal_rps} rounds/s...') - axis_ctx.handle.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL - axis_ctx.handle.controller.config.input_mode = INPUT_MODE_PASSTHROUGH - axis_ctx.handle.controller.input_vel = 0 - - request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) - axis_ctx.handle.controller.input_vel = nominal_vel - - data = record_log(lambda: [axis_ctx.handle.encoder.vel_estimate, axis_ctx.handle.encoder.pos_estimate], duration=5.0) - - test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) - test_assert_no_error(axis_ctx) - request_state(axis_ctx, AXIS_STATE_IDLE) - - # encoder.vel_estimate - slope, offset, fitted_curve = fit_line(data[:,(0,1)]) - test_assert_eq(slope, 0.0, range = nominal_vel * 0.02) - test_assert_eq(offset, nominal_vel, accuracy = 0.05) - test_curve_fit(data[:,(0,1)], fitted_curve, max_mean_err = nominal_vel * 0.3, inlier_range = nominal_vel * 0.5, max_outliers = len(data[:,0]) * 0.1) - - # encoder.pos_estimate - slope, offset, fitted_curve = fit_line(data[:,(0,2)]) - test_assert_eq(slope, nominal_vel, accuracy = 0.01) - test_curve_fit(data[:,(0,2)], fitted_curve, max_mean_err = nominal_vel * 0.01, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) + # Return a context that can be used in a with-statement. + class safe_terminator(): + def __enter__(self): + pass + def __exit__(self, exc_type, exc_val, exc_tb): + logger.debug('clearing config...') + axis_ctx.parent.erase_config_and_reboot() + return safe_terminator() - logger.debug(f'Testing closed loop position control...') +class TestClosedLoopControl(TestClosedLoopControlBase): + """ + Tests position and velocity control + """ + + def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): + with self.prepare(axis_ctx, motor_ctx, enc_ctx, logger): + nominal_rps = 1.0 + nominal_vel = float(enc_ctx.yaml['cpr']) * nominal_rps + logger.debug(f'Testing closed loop velocity control at {nominal_rps} rounds/s...') + + axis_ctx.handle.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL + axis_ctx.handle.controller.config.input_mode = INPUT_MODE_PASSTHROUGH + axis_ctx.handle.controller.input_vel = 0 + + request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + axis_ctx.handle.controller.input_vel = nominal_vel + + data = record_log(lambda: [axis_ctx.handle.encoder.vel_estimate, axis_ctx.handle.encoder.pos_estimate], duration=5.0) + + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) + test_assert_no_error(axis_ctx) + request_state(axis_ctx, AXIS_STATE_IDLE) + + # encoder.vel_estimate + slope, offset, fitted_curve = fit_line(data[:,(0,1)]) + test_assert_eq(slope, 0.0, range = nominal_vel * 0.02) + test_assert_eq(offset, nominal_vel, accuracy = 0.05) + test_curve_fit(data[:,(0,1)], fitted_curve, max_mean_err = nominal_vel * 0.3, inlier_range = nominal_vel * 0.5, max_outliers = len(data[:,0]) * 0.1) + + # encoder.pos_estimate + slope, offset, fitted_curve = fit_line(data[:,(0,2)]) + test_assert_eq(slope, nominal_vel, accuracy = 0.01) + test_curve_fit(data[:,(0,2)], fitted_curve, max_mean_err = nominal_vel * 0.01, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) + + + logger.debug(f'Testing closed loop position control...') + + axis_ctx.handle.controller.config.control_mode = CTRL_MODE_POSITION_CONTROL + axis_ctx.handle.controller.input_pos = 0 + axis_ctx.handle.controller.config.vel_limit = float(enc_ctx.yaml['cpr']) * 5.0 # max 5 rps + axis_ctx.handle.encoder.set_linear_count(0) + + request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + + # Test small position changes + axis_ctx.handle.controller.input_pos = 5000 + time.sleep(0.3) + test_assert_no_error(axis_ctx) + test_assert_eq(axis_ctx.handle.encoder.pos_estimate, 5000, range=2000) # large range needed because of cogging torque + axis_ctx.handle.controller.input_pos = -5000 + time.sleep(0.3) + test_assert_no_error(axis_ctx) + test_assert_eq(axis_ctx.handle.encoder.pos_estimate, -5000, range=2000) + + axis_ctx.handle.controller.input_pos = 0 + time.sleep(0.3) + + nominal_vel = float(enc_ctx.yaml['cpr']) * 5.0 + axis_ctx.handle.controller.input_pos = nominal_vel * 2.0 # 10 turns (takes 2 seconds) + + # Test large position change with bounded velocity + data = record_log(lambda: [axis_ctx.handle.encoder.vel_estimate, axis_ctx.handle.encoder.pos_estimate], duration=4.0) + + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) + test_assert_no_error(axis_ctx) + request_state(axis_ctx, AXIS_STATE_IDLE) + + data_motion = data[data[:,0] < 1.9] + data_still = data[data[:,0] > 2.1] + + # encoder.vel_estimate + slope, offset, fitted_curve = fit_line(data_motion[:,(0,1)]) + test_assert_eq(slope, 0.0, range = nominal_vel * 0.05) + test_assert_eq(offset, nominal_vel, accuracy = 0.05) + test_curve_fit(data_motion[:,(0,1)], fitted_curve, max_mean_err = nominal_vel * 0.05, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) + + # encoder.pos_estimate + slope, offset, fitted_curve = fit_line(data_motion[:,(0,2)]) + test_assert_eq(slope, nominal_vel, accuracy = 0.01) + test_curve_fit(data_motion[:,(0,2)], fitted_curve, max_mean_err = nominal_vel * 0.01, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) + + # encoder.vel_estimate + slope, offset, fitted_curve = fit_line(data_still[:,(0,1)]) + test_assert_eq(slope, 0.0, range = nominal_vel * 0.05) + test_assert_eq(offset, 0.0, range = nominal_vel * 0.05) + test_curve_fit(data_still[:,(0,1)], fitted_curve, max_mean_err = nominal_vel * 0.05, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) + + # encoder.pos_estimate + slope, offset, fitted_curve = fit_line(data_still[:,(0,2)]) + test_assert_eq(slope, 0.0, range = nominal_vel * 0.05) + test_assert_eq(offset, nominal_vel*2, range = nominal_vel * 0.02) + test_curve_fit(data_still[:,(0,2)], fitted_curve, max_mean_err = nominal_vel * 0.01, inlier_range = nominal_vel * 0.01, max_outliers = len(data[:,0]) * 0.01) + + +class TestRegenProtection(TestClosedLoopControlBase): + """ + Tries to brake with a disabled brake resistor. + This should result in a low level error disabling all power outputs. + """ + + def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): + with self.prepare(axis_ctx, motor_ctx, enc_ctx, logger): + nominal_rps = 6.0 + nominal_vel = float(enc_ctx.yaml['cpr']) * nominal_rps - axis_ctx.handle.controller.config.control_mode = CTRL_MODE_POSITION_CONTROL - axis_ctx.handle.controller.input_pos = 0 - axis_ctx.handle.controller.config.vel_limit = float(enc_ctx.yaml['cpr']) * 5.0 # max 5 rps - axis_ctx.handle.encoder.set_linear_count(0) + # Accept a bit of noise on Ibus + axis_ctx.parent.handle.config.dc_max_negative_current = -0.2 - request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + logger.debug(f'Brake control test from {nominal_rps} rounds/s...') + + axis_ctx.handle.controller.config.vel_limit = float(enc_ctx.yaml['cpr']) * 10.0 # max 10 rps + axis_ctx.handle.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL + axis_ctx.handle.controller.config.input_mode = INPUT_MODE_PASSTHROUGH - # Test small position changes - axis_ctx.handle.controller.input_pos = 5000 - time.sleep(0.3) - test_assert_no_error(axis_ctx) - test_assert_eq(axis_ctx.handle.encoder.pos_estimate, 5000, range=2000) # large range needed because of cogging torque - axis_ctx.handle.controller.input_pos = -5000 - time.sleep(0.3) - test_assert_no_error(axis_ctx) - test_assert_eq(axis_ctx.handle.encoder.pos_estimate, -5000, range=2000) - - axis_ctx.handle.controller.input_pos = 0 - time.sleep(0.3) + request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) - nominal_vel = float(enc_ctx.yaml['cpr']) * 5.0 - axis_ctx.handle.controller.input_pos = nominal_vel * 2.0 # 10 turns (takes 2 seconds) - - # Test large position change with bounded velocity - data = record_log(lambda: [axis_ctx.handle.encoder.vel_estimate, axis_ctx.handle.encoder.pos_estimate], duration=4.0) - - test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) - test_assert_no_error(axis_ctx) - request_state(axis_ctx, AXIS_STATE_IDLE) + # accelerate... + axis_ctx.handle.controller.input_vel = nominal_vel + time.sleep(1.0) + test_assert_no_error(axis_ctx) - data_motion = data[data[:,0] < 1.9] - data_still = data[data[:,0] > 2.1] + # ... and brake + axis_ctx.handle.controller.input_vel = 0 + time.sleep(1.0) + test_assert_no_error(axis_ctx) - # encoder.vel_estimate - slope, offset, fitted_curve = fit_line(data_motion[:,(0,1)]) - test_assert_eq(slope, 0.0, range = nominal_vel * 0.05) - test_assert_eq(offset, nominal_vel, accuracy = 0.05) - test_curve_fit(data_motion[:,(0,1)], fitted_curve, max_mean_err = nominal_vel * 0.05, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) + # once more, but this time without brake resistor + axis_ctx.parent.handle.config.brake_resistance = 0 - # encoder.pos_estimate - slope, offset, fitted_curve = fit_line(data_motion[:,(0,2)]) - test_assert_eq(slope, nominal_vel, accuracy = 0.01) - test_curve_fit(data_motion[:,(0,2)], fitted_curve, max_mean_err = nominal_vel * 0.01, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) + # accelerate... + axis_ctx.handle.controller.input_vel = nominal_vel + time.sleep(1.0) + test_assert_no_error(axis_ctx) - # encoder.vel_estimate - slope, offset, fitted_curve = fit_line(data_still[:,(0,1)]) - test_assert_eq(slope, 0.0, range = nominal_vel * 0.05) - test_assert_eq(offset, 0.0, range = nominal_vel * 0.05) - test_curve_fit(data_still[:,(0,1)], fitted_curve, max_mean_err = nominal_vel * 0.05, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) - - # encoder.pos_estimate - slope, offset, fitted_curve = fit_line(data_still[:,(0,2)]) - test_assert_eq(slope, 0.0, range = nominal_vel * 0.05) - test_assert_eq(offset, nominal_vel*2, range = nominal_vel * 0.02) - test_curve_fit(data_still[:,(0,2)], fitted_curve, max_mean_err = nominal_vel * 0.01, inlier_range = nominal_vel * 0.01, max_outliers = len(data[:,0]) * 0.01) + # ... and brake + axis_ctx.handle.controller.input_vel = 0 # this should fail almost instantaneously + time.sleep(0.1) + test_assert_eq(axis_ctx.handle.error, errors.axis.ERROR_MOTOR_DISARMED | errors.axis.ERROR_BRAKE_RESISTOR_DISARMED) + test_assert_eq(axis_ctx.handle.motor.error, errors.motor.ERROR_DC_BUS_OVER_REGEN_CURRENT) if __name__ == '__main__': test_runner.run([ - TestClosedLoopControl() + TestClosedLoopControl(), + TestRegenProtection(), ]) From 7290e55525fb8e241c0f9dbcdcfa31ca5af832f5 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 5 May 2020 20:30:03 -0400 Subject: [PATCH 373/549] Convert Axes to a std::array --- Firmware/MotorControl/main.cpp | 2 +- Firmware/MotorControl/odrive_main.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 71b6425e..a596c8b2 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -23,7 +23,7 @@ bool user_config_loaded_; SystemStats_t system_stats_; -Axis *axes[AXIS_COUNT]; +std::array axes; ODriveCAN *odCAN = nullptr; typedef Config< diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index ade4416c..121ff024 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -132,7 +132,7 @@ class Motor; class ODriveCAN; constexpr size_t AXIS_COUNT = 2; -extern Axis *axes[AXIS_COUNT]; +extern std::array axes; extern ODriveCAN *odCAN; // if you use the oscilloscope feature you can bump up this value From bf6d47f9505dcdd73dc03b776356e50cd7e5f0b0 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 5 May 2020 21:37:47 -0400 Subject: [PATCH 374/549] Add some missing reference & symbols --- Firmware/MotorControl/encoder.cpp | 2 +- Firmware/MotorControl/encoder.hpp | 2 +- Firmware/MotorControl/main.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index dcb1bbf9..72986105 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -3,7 +3,7 @@ Encoder::Encoder(const EncoderHardwareConfig_t& hw_config, - Config_t& config, Motor::Config_t motor_config) : + Config_t& config, const Motor::Config_t& motor_config) : hw_config_(hw_config), config_(config) { diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 872bf810..28830c38 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -56,7 +56,7 @@ public: }; Encoder(const EncoderHardwareConfig_t& hw_config, - Config_t& config, Motor::Config_t motor_config); + Config_t& config, const Motor::Config_t& motor_config); void setup(); void set_error(Error_t error); diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index a596c8b2..f49d02d6 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -242,7 +242,7 @@ int odrive_main(void) { axes[i]->setup(); } - for(auto axis : axes){ + for(auto& axis : axes){ axis->encoder_.setup(); } From eb944018e338cb8a8d7b06513ef561f2fb8a3f06 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 5 May 2020 18:27:56 +0200 Subject: [PATCH 375/549] update documentation --- CHANGELOG.md | 2 +- docs/testing.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d4d0077..e0543398 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * Gain scheduling for anti-hunt when close to 0 position error * Velocity Limiting in Current Control mode according to `vel_limit` and `vel_gain` * Regen current limiting according to `max_regen_limit`, in Amps -* DC Bus hard current limiting according to `power_supply_min_current` and `power_supply_max_current` +* DC Bus hard current limiting according to `dc_max_negative_current` and `dc_max_positive_current` * Unit Testing with Doctest has been started for select algorithms, see [Firmware/Tests/test_runner.cpp](Firmware/Tests/test_runner.cpp) * Added support for Flylint VSCode Extension for static code analysis * Using an STM32F405 .svd file allows CortexDebug to view registers during debugging diff --git a/docs/testing.md b/docs/testing.md index 46ba9b69..771d4e44 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -15,7 +15,7 @@ The testing facility consists of the following components: - `analog_input_test.py`: Analog Input - `calibration_test.py`: Motor calibration, encoder offset calibration, encoder direction find, encoder index search - `can_test.py`: Partial coverage of the commands described in [CAN Protocol](can-protocol) - - `closed_loop_test.py`: Velocity control, position control (TODO: sensorless control) + - `closed_loop_test.py`: Velocity control, position control (TODO: sensorless control), brake regen current protection - `encoder_test.py`: Incremental encoder, hall effect encoder, sin/cos encoder, SPI encoders (AMS, CUI) - `nvm_test.py`: Configuration storage - `pwm_input_test.py`: PWM input From 35d1a24314c5434771704716e93a4e514392e285 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 5 May 2020 19:38:40 +0200 Subject: [PATCH 376/549] improve encoder tests --- tools/odrive/tests/encoder_test.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/tools/odrive/tests/encoder_test.py b/tools/odrive/tests/encoder_test.py index b9af8ed6..59eac451 100644 --- a/tools/odrive/tests/encoder_test.py +++ b/tools/odrive/tests/encoder_test.py @@ -43,22 +43,22 @@ class TestEncoderBase(): # encoder.shadow_count slope, offset, fitted_curve = fit_line(data[:,(0,1)]) test_assert_eq(slope, true_cps, accuracy=0.005) - test_curve_fit(data[:,(0,1)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.02, max_outliers = len(data[:,0]) * 0.02) + test_curve_fit(data[:,(0,1)], fitted_curve, max_mean_err = true_cpr * 0.02, inlier_range = true_cpr * 0.02, max_outliers = len(data[:,0]) * 0.02) # encoder.count_in_cpr slope, offset, fitted_curve = fit_sawtooth(data[:,(0,2)], true_cpr if reverse else 0, 0 if reverse else true_cpr) test_assert_eq(slope, true_cps, accuracy=0.005) - test_curve_fit(data[:,(0,2)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.02, max_outliers = len(data[:,0]) * 0.02) + test_curve_fit(data[:,(0,2)], fitted_curve, max_mean_err = true_cpr * 0.02, inlier_range = true_cpr * 0.02, max_outliers = len(data[:,0]) * 0.02) # encoder.phase slope, offset, fitted_curve = fit_sawtooth(data[:,(0,3)], pi if reverse else -pi, -pi if reverse else pi, sigma=5) test_assert_eq(slope / 7, 2*pi*true_rps, accuracy=0.05) - test_curve_fit(data[:,(0,3)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.02, max_outliers = len(data[:,0]) * 0.02) + test_curve_fit(data[:,(0,3)], fitted_curve, max_mean_err = true_cpr * 0.02, inlier_range = true_cpr * 0.02, max_outliers = len(data[:,0]) * 0.02) # encoder.pos_estimate slope, offset, fitted_curve = fit_line(data[:,(0,4)]) test_assert_eq(slope, true_cps, accuracy=0.005) - test_curve_fit(data[:,(0,4)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.02, max_outliers = len(data[:,0]) * 0.02) + test_curve_fit(data[:,(0,4)], fitted_curve, max_mean_err = true_cpr * 0.02, inlier_range = true_cpr * 0.02, max_outliers = len(data[:,0]) * 0.02) # encoder.pos_cpr slope, offset, fitted_curve = fit_sawtooth(data[:,(0,5)], true_cpr if reverse else 0, 0 if reverse else true_cpr) @@ -120,7 +120,7 @@ class TestIncrementalEncoder(TestEncoderBase): yield (encoder, valid_combinations) - def run_test(self, enc: EncoderComponent, teensy: TeensyComponent, teensy_gpio_a: TeensyGpio, teensy_gpio_b: TeensyGpio, logger: Logger): + def run_test(self, enc: ODriveEncoderComponent, teensy: TeensyComponent, teensy_gpio_a: TeensyGpio, teensy_gpio_b: TeensyGpio, logger: Logger): true_cps = 8192*-0.5 # counts per second generated by the virtual encoder code = teensy_incremental_encoder_emulation_code.replace("{enc_a}", str(teensy_gpio_a.num)).replace("{enc_b}", str(teensy_gpio_b.num)) @@ -180,7 +180,7 @@ class TestSinCosEncoder(TestEncoderBase): yield (odrive.encoders[0], valid_combinations) - def run_test(self, enc: EncoderComponent, teensy: TeensyComponent, teensy_gpio_sin: TeensyGpio, teensy_gpio_cos: TeensyGpio, logger: Logger): + def run_test(self, enc: ODriveEncoderComponent, teensy: TeensyComponent, teensy_gpio_sin: TeensyGpio, teensy_gpio_cos: TeensyGpio, logger: Logger): code = teensy_sin_cos_encoder_emulation_code.replace("{enc_sin}", str(teensy_gpio_sin.num)).replace("{enc_cos}", str(teensy_gpio_cos.num)) teensy.compile_and_program(code) @@ -247,7 +247,7 @@ class TestHallEffectEncoder(TestEncoderBase): yield (encoder, valid_combinations) - def run_test(self, enc: EncoderComponent, teensy: TeensyComponent, teensy_gpio_a: TeensyGpio, teensy_gpio_b: TeensyGpio, teensy_gpio_c: TeensyGpio, logger: Logger): + def run_test(self, enc: ODriveEncoderComponent, teensy: TeensyComponent, teensy_gpio_a: TeensyGpio, teensy_gpio_b: TeensyGpio, teensy_gpio_c: TeensyGpio, logger: Logger): true_cpr = 90 true_rps = -1.0 @@ -432,7 +432,7 @@ class TestSpiEncoder(TestEncoderBase): yield (encoder, 7, valid_combinations) - def run_test(self, enc: EncoderComponent, odrive_ncs_gpio: int, teensy: TeensyComponent, teensy_gpio_sck: TeensyGpio, teensy_gpio_miso: TeensyGpio, teensy_gpio_mosi: TeensyGpio, teensy_gpio_ncs: TeensyGpio, teensy_gpio_reset: TeensyGpio, reset_gpio: LinuxGpioComponent, logger: Logger): + def run_test(self, enc: ODriveEncoderComponent, odrive_ncs_gpio: int, teensy: TeensyComponent, teensy_gpio_sck: TeensyGpio, teensy_gpio_miso: TeensyGpio, teensy_gpio_mosi: TeensyGpio, teensy_gpio_ncs: TeensyGpio, teensy_gpio_reset: TeensyGpio, reset_gpio: LinuxGpioComponent, logger: Logger): true_cpr = 16384 true_rps = 1.0 @@ -452,6 +452,11 @@ class TestSpiEncoder(TestEncoderBase): enc.handle.config.mode = self.mode enc.handle.config.abs_spi_cs_gpio_pin = odrive_ncs_gpio enc.handle.config.cpr = true_cpr + # Also put the other encoder into SPI mode to make it more interesting + other_enc = enc.parent.encoders[1 - enc.num] + other_enc.handle.config.mode = self.mode + other_enc.handle.config.abs_spi_cs_gpio_pin = odrive_ncs_gpio + other_enc.handle.config.cpr = true_cpr enc.parent.save_config_and_reboot() time.sleep(1.0) @@ -499,8 +504,8 @@ class TestSpiEncoder(TestEncoderBase): if __name__ == '__main__': test_runner.run([ - #TestIncrementalEncoder(), - #TestSinCosEncoder(), + TestIncrementalEncoder(), + TestSinCosEncoder(), TestHallEffectEncoder(), TestSpiEncoder(ENCODER_MODE_SPI_ABS_AMS), TestSpiEncoder(ENCODER_MODE_SPI_ABS_CUI), From 8ab2e0530e56bed9db8494e35b9764703d49ac34 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 5 May 2020 20:01:39 +0200 Subject: [PATCH 377/549] add helper to pass through encoder --- tools/odrive/tests/not_a_test.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 tools/odrive/tests/not_a_test.py diff --git a/tools/odrive/tests/not_a_test.py b/tools/odrive/tests/not_a_test.py new file mode 100644 index 00000000..7be3e995 --- /dev/null +++ b/tools/odrive/tests/not_a_test.py @@ -0,0 +1,30 @@ + +import test_runner + +from fibre.utils import Logger +from test_runner import * + +class EncoderPassthrough(): + """ + Does nothing except passing encoder0 through. + """ + + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + for num in range(1): + encoders = testrig.get_connected_components({ + 'a': (odrive.encoders[num].a, False), + 'b': (odrive.encoders[num].b, False), + 'z': (odrive.encoders[num].z, False) + }, EncoderComponent) + motors = testrig.get_connected_components(odrive.axes[num], MotorComponent) + + for motor, encoder in itertools.product(motors, encoders): + if encoder.impl in testrig.get_connected_components(motor): + yield (odrive.axes[num], motor, encoder) + + def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): + logger.debug(f'Encoder {axis_ctx.num} was passed through') + +if __name__ == '__main__': + test_runner.run(EncoderPassthrough()) From 484c5ab9dad2ff5429d7233f10e907395efb5813 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 6 May 2020 11:23:46 +0200 Subject: [PATCH 378/549] clarify comments --- Firmware/MotorControl/low_level.cpp | 1 - Firmware/MotorControl/odrive_main.h | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 454fa339..8236d39e 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -628,7 +628,6 @@ void update_brake_current() { return; } - // If brake_duty is NaN, this expression will also evaluate to false int high_on = static_cast(TIM_APB1_PERIOD_CLOCKS * (1.0f - brake_duty)); int low_off = high_on - TIM_APB1_DEADTIME_CLOCKS; if (low_off < 0) low_off = 0; diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 121ff024..2dbb82a6 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -106,9 +106,9 @@ struct BoardConfig_t { * vbus_voltage == dc_bus_overvoltage_ramp_end => brake_duty_cycle += 100% * * Remarks: - * - This setting is active even if all motors are disarmed. - * - brake_resistance must be non-zero, otherwise this will immediately - * result in overcurrent + * - This setting is active even when all motors are disarmed. + * - brake_resistance must be non-zero, otherwise this will result in an + * overcurrent fault as soon as vbus_voltage exceeds dc_bus_overvoltage_ramp_start. */ bool enable_dc_bus_overvoltage_ramp = false; float dc_bus_overvoltage_ramp_start = 1.07f * HW_VERSION_VOLTAGE; //!< See `enable_dc_bus_overvoltage_ramp`. From 9155edfb1795657ea39741c1962ebc754c15548e Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 6 May 2020 19:19:26 +0200 Subject: [PATCH 379/549] Fix a numerical issue in the trajectory planner that could cause sudden jumps of the position setpoint. If certain inputs were passed to trajectory planner, an expression inside the trajectory planner which is an argument to sqrtf() could become negative due to finite floating point accuracy. This led to Vr_ == NaN and then Tf_ == 0, causing the trajectory to jump to the final setpoint instantaneously. To the user, this manifested as a sudden increase in velocity (limited by controller.config.vel_limit) and/or an overcurrent fault. This bug was likely to show up when constantly sending trajectory setpoints while moving in the negative direction. See also: https://discourse.odriverobotics.com/t/move-to-pos-not-works-well/4626 --- CHANGELOG.md | 1 + Firmware/MotorControl/trapTraj.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0543398..f671d0f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * Fix IPython `RuntimeWarning` that would occur every time `odrivetool` was started. * Reboot on `erase_configuration()`. This avoids unexpected behavior of a subsequent `save_configuration()` call, since the configuration is only erased from NVM, not from RAM. * Change `motor.get_inverter_temp()` to use a property which was already being sampled at `motor.inverter_temp` +* Fixed a numerical issue in the trajectory planner that could cause sudden jumps of the position setpoint # Releases ## [0.4.11] - 2019-07-25 diff --git a/Firmware/MotorControl/trapTraj.cpp b/Firmware/MotorControl/trapTraj.cpp index 2067c35e..dd7f64a2 100644 --- a/Firmware/MotorControl/trapTraj.cpp +++ b/Firmware/MotorControl/trapTraj.cpp @@ -44,7 +44,7 @@ bool TrapezoidalTrajectory::planTrapezoidal(float Xf, float Xi, float Vi, // Are we displacing enough to reach cruising speed? if (s*dX < s*dXmin) { // Short move (triangle profile) - Vr_ = s * sqrtf((Dr_*SQ(Vi) + 2*Ar_*Dr_*dX) / (Dr_ - Ar_)); + Vr_ = s * sqrtf(std::fmax((Dr_*SQ(Vi) + 2*Ar_*Dr_*dX) / (Dr_ - Ar_), 0.0f)); Ta_ = std::max(0.0f, (Vr_ - Vi) / Ar_); Td_ = std::max(0.0f, -Vr_ / Dr_); Tv_ = 0.0f; From 33e00f767db58786f3f0b88794c5e4997f9b8a06 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 6 May 2020 19:23:31 +0200 Subject: [PATCH 380/549] add test for trapezoidal trajectory --- Firmware/Tests/test_trap_traj.cpp | 235 ++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 Firmware/Tests/test_trap_traj.cpp diff --git a/Firmware/Tests/test_trap_traj.cpp b/Firmware/Tests/test_trap_traj.cpp new file mode 100644 index 00000000..b5d30014 --- /dev/null +++ b/Firmware/Tests/test_trap_traj.cpp @@ -0,0 +1,235 @@ + +#include +#include +#include +#include +#include + +#include "MotorControl/utils.hpp" + +// TODO: This is currently a copy-paste of the real code due to non-trivial +// include dependencies. Should include real code. + +class TrapezoidalTrajectory { +public: + struct Step_t { + float Y; + float Yd; + float Ydd; + }; + + explicit TrapezoidalTrajectory(); + bool planTrapezoidal(float Xf, float Xi, float Vi, + float Vmax, float Amax, float Dmax); + Step_t eval(float t); + + float Xi_; + float Xf_; + float Vi_; + + float Ar_; + float Vr_; + float Dr_; + + float Ta_; + float Tv_; + float Td_; + float Tf_; + + float yAccel_; + + float t_; +}; + + + +// A sign function where input 0 has positive sign (not 0) +float sign_hard(float val) { + return (std::signbit(val)) ? -1.0f : 1.0f; +} + +// Symbol Description +// Ta, Tv and Td Duration of the stages of the AL profile +// Xi and Vi Adapted initial conditions for the AL profile +// Xf Position set-point +// s Direction (sign) of the trajectory +// Vmax, Amax, Dmax and jmax Kinematic bounds +// Ar, Dr and Vr Reached values of acceleration and velocity + +TrapezoidalTrajectory::TrapezoidalTrajectory() {} + +bool TrapezoidalTrajectory::planTrapezoidal(float Xf, float Xi, float Vi, + float Vmax, float Amax, float Dmax) { + float dX = Xf - Xi; // Distance to travel + float stop_dist = (Vi * Vi) / (2.0f * Dmax); // Minimum stopping distance + float dXstop = std::copysign(stop_dist, Vi); // Minimum stopping displacement + float s = sign_hard(dX - dXstop); // Sign of coast velocity (if any) + Ar_ = s * Amax; // Maximum Acceleration (signed) + Dr_ = -s * Dmax; // Maximum Deceleration (signed) + Vr_ = s * Vmax; // Maximum Velocity (signed) + + // If we start with a speed faster than cruising, then we need to decel instead of accel + // aka "double deceleration move" in the paper + if ((s * Vi) > (s * Vr_)) { + Ar_ = -s * Amax; + } + + // Time to accel/decel to/from Vr (cruise speed) + Ta_ = (Vr_ - Vi) / Ar_; + Td_ = -Vr_ / Dr_; + + // Integral of velocity ramps over the full accel and decel times to get + // minimum displacement required to reach cuising speed + float dXmin = 0.5f*Ta_*(Vr_ + Vi) + 0.5f*Td_*Vr_; + + // Are we displacing enough to reach cruising speed? + if (s*dX < s*dXmin) { + // Short move (triangle profile) + Vr_ = s * sqrtf(std::fmax((Dr_*SQ(Vi) + 2*Ar_*Dr_*dX) / (Dr_ - Ar_), 0.0f)); + //Vr_ = s * sqrtf((Dr_*SQ(Vi) + 2*Ar_*Dr_*dX) / (Dr_ - Ar_)); + Ta_ = std::max(0.0f, (Vr_ - Vi) / Ar_); + Td_ = std::max(0.0f, -Vr_ / Dr_); + Tv_ = 0.0f; + } else { + // Long move (trapezoidal profile) + Tv_ = (dX - dXmin) / Vr_; + } + + // Fill in the rest of the values used at evaluation-time + Tf_ = Ta_ + Tv_ + Td_; + Xi_ = Xi; + Xf_ = Xf; + Vi_ = Vi; + yAccel_ = Xi + Vi*Ta_ + 0.5f*Ar_*SQ(Ta_); // pos at end of accel phase + + return true; +} + +TrapezoidalTrajectory::Step_t TrapezoidalTrajectory::eval(float t) { + Step_t trajStep; + if (t < 0.0f) { // Initial Condition + trajStep.Y = Xi_; + trajStep.Yd = Vi_; + trajStep.Ydd = 0.0f; + } else if (t < Ta_) { // Accelerating + trajStep.Y = Xi_ + Vi_*t + 0.5f*Ar_*SQ(t); + trajStep.Yd = Vi_ + Ar_*t; + trajStep.Ydd = Ar_; + } else if (t < Ta_ + Tv_) { // Coasting + trajStep.Y = yAccel_ + Vr_*(t - Ta_); + trajStep.Yd = Vr_; + trajStep.Ydd = 0.0f; + } else if (t < Tf_) { // Deceleration + float td = t - Tf_; + trajStep.Y = Xf_ + 0.5f*Dr_*SQ(td); + trajStep.Yd = Dr_*td; + trajStep.Ydd = Dr_; + } else if (t >= Tf_) { // Final Condition + trajStep.Y = Xf_; + trajStep.Yd = 0.0f; + trajStep.Ydd = 0.0f; + } else { + // TODO: report error here + } + + return trajStep; +} + +static_assert(sizeof(float) * CHAR_BIT == 32); + + +void run_trajectory_test(float goal, float position, float velocity, float Vmax, float Amax, float Dmax) { + float dt = 0.000125f; + int replan_interval = 10; // must be > 2 (see note below) + float t = 0.0f; + float Vmax_test = std::max(Vmax, std::abs(velocity)); + + TrapezoidalTrajectory traj{}; + + int replan_counter = 0; + + do { + if (replan_counter <= 0) { + CHECK(traj.planTrapezoidal(goal, position, velocity, Vmax, Amax, Dmax)); + t = 0.0f; + replan_counter = replan_interval; + } else { + replan_counter--; + } + + TrapezoidalTrajectory::Step_t step = traj.eval(t); + t += dt; + + //std::cerr << "vel: " << step.Yd << ", pos: " << step.Y << "\n"; + + // Check if acceleration within bounds + if (velocity >= 0.0f) { + CHECK(step.Ydd <= Amax); + CHECK(step.Ydd >= -Dmax); + CHECK((step.Yd - velocity) / dt <= Amax * 1.002f); + CHECK((step.Yd - velocity) / dt >= -Dmax * 1.002f); + } else { + CHECK(step.Ydd <= Dmax); + CHECK(step.Ydd >= -Amax); + CHECK((step.Yd - velocity) / dt <= Dmax * 1.002f); + CHECK((step.Yd - velocity) / dt >= -Amax * 1.002f); + } + + // Check if velocity within bounds + CHECK(step.Yd >= -Vmax_test); + CHECK(step.Yd <= Vmax_test); + CHECK((step.Y - position) / dt >= -Vmax_test * 1.002f); + CHECK((step.Y - position) / dt <= Vmax_test * 1.002f); + velocity = step.Yd; + + // Check if position is making progress + // TODO: the trajectory planner currently needs three "warm-up" iterations + // until its position makes progress. This should probably be revisited. + // TODO: this is disabled currently because there are legitimate trajectories + // where the position first moves in the wrong direction. + //if ((replan_counter < replan_interval - 2) && (t <= traj.Tf_)) { + // CHECK(std::abs(step.Y - goal) < std::abs(position - goal)); + //} + position = step.Y; + + } while (t <= traj.Tf_); + + CHECK(position >= goal - 1.0f); + CHECK(position <= goal + 1.0f); + CHECK(velocity >= -Dmax * dt); + CHECK(velocity <= Dmax * dt); +} + + +TEST_SUITE("Trajectory Planner") { + // these form a triangle trajectory because 2*v^2/(2*a) = 2 * 27712^2 / (2*22288) = 34456 > 16384 + TEST_CASE("neg-dir-triangle") { + run_trajectory_test(-8192.0f, 8192.0f, 0.0f, 27712.0f, 22288.0f, 22288.0f); + } + TEST_CASE("pos-dir-triangle") { + run_trajectory_test(8192.0f, -8192.0f, 0.0f, 27712.0f, 22288.0f, 22288.0f); + } + + // these form a trapezoid trajectory because 2*v^2/(2*a) = 2 * 27712^2 / (2*22288) = 34456 < 16384 + TEST_CASE("neg-dir-trapezoid") { + run_trajectory_test(-25000.0f, 25000.0f, 0.0f, 27712.0f, 22288.0f, 22288.0f); + } + TEST_CASE("pos-dir-trapezoid") { + run_trajectory_test(25000.0f, -25000.0f, 0.0f, 27712.0f, 22288.0f, 22288.0f); + } + + // for the following tests note that v^2/(2*a) = 27712^2 / (2*22288) = 17227 > 16384 + TEST_CASE("neg-dir-not-enough-braking-distance") { + run_trajectory_test(-8192.0f, 8192.0f, -27712.0f, 27712.0f, 22288.0f, 22288.0f); + } + TEST_CASE("pos-dir-not-enough-braking-distance") { + run_trajectory_test(8192.0f, -8192.0f, 27712.0f, 27712.0f, 22288.0f, 22288.0f); + } + + TEST_CASE("neg-dir-over-speed") { + run_trajectory_test(-8192.0f, 8192.0f, -40000.0f, 27712.0f, 22288.0f, 22288.0f); + } + TEST_CASE("pos-dir-over-speed") { + run_trajectory_test(8192.0f, -8192.0f, 40000.0f, 27712.0f, 22288.0f, 22288.0f); + } +} From c0a0bfe1fcc007dc5b9111942f69568932ef95f3 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 6 May 2020 19:19:26 +0200 Subject: [PATCH 381/549] Fix a numerical issue in the trajectory planner that could cause sudden jumps of the position setpoint. If certain inputs were passed to trajectory planner, an expression inside the trajectory planner which is an argument to sqrtf() could become negative due to finite floating point accuracy. This led to Vr_ == NaN and then Tf_ == 0, causing the trajectory to jump to the final setpoint instantaneously. To the user, this manifested as a sudden increase in velocity (limited by controller.config.vel_limit) and/or an overcurrent fault. This bug was likely to show up when constantly sending trajectory setpoints while moving in the negative direction. See also: https://discourse.odriverobotics.com/t/move-to-pos-not-works-well/4626 --- CHANGELOG.md | 4 ++++ Firmware/MotorControl/trapTraj.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8102a27e..a162270d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ Please add a note of your changes below this heading if you make a Pull Request. # Releases +## [0.4.12] - 2020-05-06 +### Changed +* Fixed a numerical issue in the trajectory planner that could cause sudden jumps of the position setpoint + ## [0.4.11] - 2019-07-25 ### Added * Separate lockin configs for sensorless, index search, and general. diff --git a/Firmware/MotorControl/trapTraj.cpp b/Firmware/MotorControl/trapTraj.cpp index f1e41aa5..ea244cd2 100644 --- a/Firmware/MotorControl/trapTraj.cpp +++ b/Firmware/MotorControl/trapTraj.cpp @@ -44,7 +44,7 @@ bool TrapezoidalTrajectory::planTrapezoidal(float Xf, float Xi, float Vi, // Are we displacing enough to reach cruising speed? if (s*dX < s*dXmin) { // Short move (triangle profile) - Vr_ = s * sqrtf((Dr_*SQ(Vi) + 2*Ar_*Dr_*dX) / (Dr_ - Ar_)); + Vr_ = s * sqrtf(std::fmax((Dr_*SQ(Vi) + 2*Ar_*Dr_*dX) / (Dr_ - Ar_), 0.0f)); Ta_ = std::max(0.0f, (Vr_ - Vi) / Ar_); Td_ = std::max(0.0f, -Vr_ / Dr_); Tv_ = 0.0f; From 4778ada84c9735854ab7a437622182b2ead8f44d Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 7 May 2020 12:00:50 -0700 Subject: [PATCH 382/549] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a162270d..5c9c5611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ Please add a note of your changes below this heading if you make a Pull Request. # Releases ## [0.4.12] - 2020-05-06 -### Changed +### Fixed * Fixed a numerical issue in the trajectory planner that could cause sudden jumps of the position setpoint ## [0.4.11] - 2019-07-25 From 9ccca4352fa570daf70162b854a5642d5fa1c3c5 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 7 May 2020 12:14:38 -0700 Subject: [PATCH 383/549] make pypi script release by default --- tools/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/setup.py b/tools/setup.py index 986d9d0d..118f0ae5 100644 --- a/tools/setup.py +++ b/tools/setup.py @@ -32,7 +32,7 @@ to publish packages with the name odrive. """ # Set to true to make the current release -is_release = False +is_release = True # Set to true to make an official post-release, rather than dev of new version is_post_release = False From ffda24a2d8c8f8de39e999218a0a2f596ff71ff1 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 7 May 2020 12:41:42 -0700 Subject: [PATCH 384/549] update max_regen_limit to max_regen_current in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f671d0f7..fa561d56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * [CAN Communication with CANSimple stack](can-protocol.md) * Gain scheduling for anti-hunt when close to 0 position error * Velocity Limiting in Current Control mode according to `vel_limit` and `vel_gain` -* Regen current limiting according to `max_regen_limit`, in Amps +* Regen current limiting according to `max_regen_current`, in Amps * DC Bus hard current limiting according to `dc_max_negative_current` and `dc_max_positive_current` * Unit Testing with Doctest has been started for select algorithms, see [Firmware/Tests/test_runner.cpp](Firmware/Tests/test_runner.cpp) * Added support for Flylint VSCode Extension for static code analysis From 19647db3e6e797d19e7c38769fe6022027e82346 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 7 May 2020 13:25:36 -0700 Subject: [PATCH 385/549] update changelog, add debug freeze of tim13 --- CHANGELOG.md | 4 ++-- Firmware/MotorControl/low_level.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa561d56..f37e0555 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,15 +20,15 @@ Please add a note of your changes below this heading if you make a Pull Request. * Velocity Limiting in Current Control mode according to `vel_limit` and `vel_gain` * Regen current limiting according to `max_regen_current`, in Amps * DC Bus hard current limiting according to `dc_max_negative_current` and `dc_max_positive_current` +* Brake resistor logic now attempts to clamp voltage according to `odrv.config.dc_bus_overvoltage_ramp_start` and `odrv.config.dc_bus_overvoltage_ramp_end` * Unit Testing with Doctest has been started for select algorithms, see [Firmware/Tests/test_runner.cpp](Firmware/Tests/test_runner.cpp) * Added support for Flylint VSCode Extension for static code analysis * Using an STM32F405 .svd file allows CortexDebug to view registers during debugging * Added scripts for building via docker. -* Brake resistor logic now attempts to clamp voltage according to `odrv.config.dc_bus_overvoltage_ramp_start` and `odrv.config.dc_bus_overvoltage_ramp_end` ### Changed * Changed ratiometric `motor.config.current_lim_tolerance` to absolute `motor.config.current_lim_margin` -* Moved `controller.vel_ramp_enable` into `controller.config`. +* Moved `controller.vel_ramp_enable` to INPUT_MODE_VEL_RAMP. * Anticogging map is temporarily forced to 0.1 deg precision, but saves with the config * Some Encoder settings have been made read-only * Cleaned up VSCode C/C++ Configuration settings on Windows with recursive includePath diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 8236d39e..1140ca1e 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -214,6 +214,7 @@ void start_adc_pwm() { // Ensure that debug halting of the core doesn't leave the motor PWM running __HAL_DBGMCU_FREEZE_TIM1(); __HAL_DBGMCU_FREEZE_TIM8(); + __HAL_DBGMCU_FREEZE_TIM13(); start_pwm(&htim1); start_pwm(&htim8); From 6863507570f61d183525c181f52f06b6d924f381 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 7 May 2020 18:03:55 -0400 Subject: [PATCH 386/549] Use std::clamp instead of manual clamping in some spots --- Firmware/MotorControl/controller.cpp | 3 +-- Firmware/MotorControl/motor.cpp | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index f4b7f84b..beb93528 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -256,8 +256,7 @@ bool Controller::update(float* current_setpoint_output) { // Velocity limiting float vel_lim = config_.vel_limit; if (config_.enable_vel_limit) { - if (vel_des > vel_lim) vel_des = vel_lim; - if (vel_des < -vel_lim) vel_des = -vel_lim; + vel_des = std::clamp(vel_des, -vel_lim, vel_lim); } // Check for overspeed fault (done in this module (controller) for cohesion with vel_lim) diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 39115713..48b66c98 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -447,9 +447,8 @@ bool Motor::update(float current_setpoint, float phase, float phase_vel) { // TODO: 2-norm vs independent clamping (current could be sqrt(2) bigger) float ilim = effective_current_lim(); - // TODO: use std::clamp (C++17) - float id = MACRO_MIN(MACRO_MAX(current_control_.Id_setpoint, -ilim), ilim); - float iq = MACRO_MIN(MACRO_MAX(current_setpoint, -ilim), ilim); + float id = std::clamp(current_control_.Id_setpoint, -ilim, ilim); + float iq = std::clamp(current_setpoint, -ilim, ilim); if (config_.motor_type == MOTOR_TYPE_ACIM) { // Note that the effect of the current commands on the real currents is actually 1.5 PWM cycles later @@ -460,7 +459,7 @@ bool Motor::update(float current_setpoint, float phase, float phase_vel) { float abs_iq = fabsf(iq); float gain = abs_iq > id ? config_.acim_autoflux_attack_gain : config_.acim_autoflux_decay_gain; id += gain * (abs_iq - id) * current_meas_period; - id = MACRO_MIN(MACRO_MAX(id, config_.acim_autoflux_min_Id), ilim); + id = std::clamp(id, config_.acim_autoflux_min_Id, ilim); current_control_.Id_setpoint = id; } From f635ee85cf1830fd170c29d29595af71f2385319 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 7 May 2020 18:05:40 -0400 Subject: [PATCH 387/549] Convert an if-else tree to a switch statement for clarity --- Firmware/MotorControl/motor.cpp | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 48b66c98..223aba1c 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -484,22 +484,11 @@ bool Motor::update(float current_setpoint, float phase, float phase_vel) { float pwm_phase = phase + 1.5f * current_meas_period * phase_vel; // Execute current command - // TODO: move this into the mot - if (config_.motor_type == MOTOR_TYPE_HIGH_CURRENT) { - if(!FOC_current(id, iq, phase, pwm_phase)){ - return false; - } - } else if (config_.motor_type == MOTOR_TYPE_ACIM) { - if(!FOC_current(id, iq, phase, pwm_phase)){ - return false; - } - } else if (config_.motor_type == MOTOR_TYPE_GIMBAL) { - //In gimbal motor mode, current is reinterptreted as voltage. - if(!FOC_voltage(id, iq, pwm_phase)) - return false; - } else { - set_error(ERROR_NOT_IMPLEMENTED_MOTOR_TYPE); - return false; + switch(config_.motor_type){ + case MOTOR_TYPE_HIGH_CURRENT: return FOC_current(id, iq, phase, pwm_phase); break; + case MOTOR_TYPE_ACIM: return FOC_current(id, iq, phase, pwm_phase); break; + case MOTOR_TYPE_GIMBAL: return FOC_voltage(id, iq, pwm_phase); break; + default: set_error(ERROR_NOT_IMPLEMENTED_MOTOR_TYPE); return false; break; } return true; } From ef669a8668160f2dfd68dcaf07f4431dd3180c37 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 8 May 2020 10:26:30 +0200 Subject: [PATCH 388/549] log SPI timings --- Firmware/MotorControl/encoder.cpp | 4 ++++ Firmware/MotorControl/motor.hpp | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 72986105..77b809aa 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -312,6 +312,7 @@ void Encoder::sample_now() { case MODE_SPI_ABS_CUI: case MODE_SPI_ABS_AEAT: { + axis_->motor_.log_timing(Motor::TIMING_LOG_SAMPLE_NOW); // Do nothing } break; @@ -347,6 +348,7 @@ bool Encoder::abs_spi_init(){ bool Encoder::abs_spi_start_transaction(){ if (mode_ & MODE_FLAG_ABS){ + axis_->motor_.log_timing(Motor::TIMING_LOG_SPI_START); if(hw_config_.spi->State != HAL_SPI_STATE_READY){ set_error(ERROR_ABS_SPI_NOT_READY); return false; @@ -375,6 +377,8 @@ uint8_t cui_parity(uint16_t v) { void Encoder::abs_spi_cb(){ HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); + axis_->motor_.log_timing(Motor::TIMING_LOG_SPI_END); + uint16_t pos; switch (mode_) { diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index aba285cc..8c627aa7 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -101,6 +101,9 @@ public: TIMING_LOG_IDX_SEARCH, TIMING_LOG_FOC_VOLTAGE, TIMING_LOG_FOC_CURRENT, + TIMING_LOG_SPI_START, + TIMING_LOG_SAMPLE_NOW, + TIMING_LOG_SPI_END, TIMING_LOG_NUM_SLOTS }; @@ -240,7 +243,10 @@ public: make_protocol_ro_property("TIMING_LOG_ENC_CALIB", &timing_log_[TIMING_LOG_ENC_CALIB]), make_protocol_ro_property("TIMING_LOG_IDX_SEARCH", &timing_log_[TIMING_LOG_IDX_SEARCH]), make_protocol_ro_property("TIMING_LOG_FOC_VOLTAGE", &timing_log_[TIMING_LOG_FOC_VOLTAGE]), - make_protocol_ro_property("TIMING_LOG_FOC_CURRENT", &timing_log_[TIMING_LOG_FOC_CURRENT]) + make_protocol_ro_property("TIMING_LOG_FOC_CURRENT", &timing_log_[TIMING_LOG_FOC_CURRENT]), + make_protocol_ro_property("TIMING_LOG_SPI_START", &timing_log_[TIMING_LOG_SPI_START]), + make_protocol_ro_property("TIMING_LOG_SAMPLE_NOW", &timing_log_[TIMING_LOG_SAMPLE_NOW]), + make_protocol_ro_property("TIMING_LOG_SPI_END", &timing_log_[TIMING_LOG_SPI_END]) ), make_protocol_object("config", make_protocol_property("pre_calibrated", &config_.pre_calibrated, From 8cbee27a5d6a0bea0cf8f7d5661f4d32260ce224 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 8 May 2020 10:37:29 +0200 Subject: [PATCH 389/549] disable overvoltage ramp if brake_resistance == 0 --- Firmware/MotorControl/low_level.cpp | 2 +- Firmware/MotorControl/odrive_main.h | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 1140ca1e..63652e42 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -598,7 +598,7 @@ void update_brake_current() { float brake_current = -Ibus_sum - board_config.max_regen_current; float brake_duty = brake_current * board_config.brake_resistance / vbus_voltage; - if (board_config.enable_dc_bus_overvoltage_ramp && (board_config.dc_bus_overvoltage_ramp_start < board_config.dc_bus_overvoltage_ramp_end)) { + if (board_config.enable_dc_bus_overvoltage_ramp && (board_config.brake_resistance > 0.0f) && (board_config.dc_bus_overvoltage_ramp_start < board_config.dc_bus_overvoltage_ramp_end)) { brake_duty += std::fmax((vbus_voltage - board_config.dc_bus_overvoltage_ramp_start) / (board_config.dc_bus_overvoltage_ramp_end - board_config.dc_bus_overvoltage_ramp_start), 0.0f); } diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 2dbb82a6..f76ac009 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -106,9 +106,8 @@ struct BoardConfig_t { * vbus_voltage == dc_bus_overvoltage_ramp_end => brake_duty_cycle += 100% * * Remarks: - * - This setting is active even when all motors are disarmed. - * - brake_resistance must be non-zero, otherwise this will result in an - * overcurrent fault as soon as vbus_voltage exceeds dc_bus_overvoltage_ramp_start. + * - This feature is active even when all motors are disarmed. + * - This feature is disabled if `brake_resistance` is non-positive. */ bool enable_dc_bus_overvoltage_ramp = false; float dc_bus_overvoltage_ramp_start = 1.07f * HW_VERSION_VOLTAGE; //!< See `enable_dc_bus_overvoltage_ramp`. From 011fb8637775a612cb85539421f54243f4d3ca4c Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 8 May 2020 11:02:04 +0200 Subject: [PATCH 390/549] make note on timing --- Firmware/MotorControl/low_level.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 63652e42..fda04cf4 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -262,6 +262,20 @@ void start_pwm(TIM_HandleTypeDef* htim) { HAL_TIM_PWM_Start_IT(htim, TIM_CHANNEL_4); } +/* + * Initial intention of this function: + * Synchronize TIM1, TIM8 and TIM13 such that: + * 1. The triangle waveform of TIM1 leads the triangle waveform of TIM8 by a + * 90° phase shift. + * 2. The timer update events of TIM1 and TIM8 are symmetrically interleaved. + * 3. Each TIM13 reload coincides with a TIM1 lower update event. + * + * However right now this function only ensures point (1) and (3) but because + * TIM1 and TIM3 only trigger an update on every third reload, this does not + * imply (or even allow for) (2). + * + * TODO: revisit the timing topic in general. + */ void sync_timers(TIM_HandleTypeDef* htim_a, TIM_HandleTypeDef* htim_b, uint16_t TIM_CLOCKSOURCE_ITRx, uint16_t count_offset, TIM_HandleTypeDef* htim_refbase) { @@ -495,6 +509,10 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { else if (&axis == axes[0] && !counting_down) update_timings = true; // update timings of M1 + // TODO: this is out of place here. However when moving it somewhere + // else we have to consider the timing requirements to prevent the SPI + // transfers of axis0 and axis1 from conflicting. + // Also see comment on sync_timers. if((current_meas_not_DC_CAL && !axis_num) || (axis_num && !current_meas_not_DC_CAL)){ axis.encoder_.abs_spi_start_transaction(); From cf1b9439cba74121dd5ba481c7bec5387aef4c91 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 8 May 2020 18:32:35 +0200 Subject: [PATCH 391/549] add test for velocity limit in current control mode --- docs/testing.md | 2 +- tools/odrive/tests/closed_loop_test.py | 87 +++++++++++++++++++++++++- tools/odrive/tests/test_runner.py | 22 ++++++- 3 files changed, 107 insertions(+), 4 deletions(-) diff --git a/docs/testing.md b/docs/testing.md index 771d4e44..05191ab4 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -15,7 +15,7 @@ The testing facility consists of the following components: - `analog_input_test.py`: Analog Input - `calibration_test.py`: Motor calibration, encoder offset calibration, encoder direction find, encoder index search - `can_test.py`: Partial coverage of the commands described in [CAN Protocol](can-protocol) - - `closed_loop_test.py`: Velocity control, position control (TODO: sensorless control), brake regen current protection + - `closed_loop_test.py`: Velocity control, position control (TODO: sensorless control), brake regen current hard limit, current control with velocity limiting - `encoder_test.py`: Incremental encoder, hall effect encoder, sin/cos encoder, SPI encoders (AMS, CUI) - `nvm_test.py`: Configuration storage - `pwm_input_test.py`: PWM input diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index dd26b6a5..cc07ab2a 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -2,7 +2,7 @@ import test_runner import time -from math import pi +from math import pi, inf import os from fibre.utils import Logger @@ -63,6 +63,8 @@ class TestClosedLoopControlBase(): pass def __exit__(self, exc_type, exc_val, exc_tb): logger.debug('clearing config...') + axis_ctx.handle.requested_state = AXIS_STATE_IDLE + time.sleep(0.005) axis_ctx.parent.erase_config_and_reboot() return safe_terminator() @@ -209,9 +211,92 @@ class TestRegenProtection(TestClosedLoopControlBase): test_assert_eq(axis_ctx.handle.motor.error, errors.motor.ERROR_DC_BUS_OVER_REGEN_CURRENT) +class TestVelLimitInCurrentControl(TestClosedLoopControlBase): + """ + Ensures that the current setpoint in current control is always within the + parallelogram that arises from -Ilim, +Ilim, vel_limit and vel_gain. + """ + + def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): + with self.prepare(axis_ctx, motor_ctx, enc_ctx, logger): + max_rps = 20.0 + max_vel = float(enc_ctx.yaml['cpr']) * max_rps + absolute_max_vel = max_vel * 1.2 + max_current = 3.0 + + axis_ctx.handle.controller.config.vel_gain /= 10 # reduce the slope to make it easier to see what's going on + vel_gain = axis_ctx.handle.controller.config.vel_gain + logger.debug(f'vel gain is {vel_gain}') + + axis_ctx.handle.controller.config.vel_limit = max_vel + axis_ctx.handle.controller.config.vel_limit_tolerance = inf # disable hard limit on velocity + axis_ctx.handle.motor.config.current_lim = max_current + axis_ctx.handle.controller.config.control_mode = CTRL_MODE_CURRENT_CONTROL + + # Returns the expected limited setpoint for a given velocity and current + def get_expected_setpoint(input_setpoint, velocity): + return clamp(clamp(input_setpoint, (velocity + max_vel) * -vel_gain, (velocity - max_vel) * -vel_gain), -max_current, max_current) + + def data_getter(): + # sample velocity twice to avoid systematic bias + velocity0 = axis_ctx.handle.encoder.vel_estimate + current_setpoint = axis_ctx.handle.motor.current_control.Iq_setpoint + velocity1 = axis_ctx.handle.encoder.vel_estimate + velocity = ((velocity0 + velocity1) / 2) + # Abort immediately if the absolute limits are exceeded + test_assert_within(current_setpoint, -max_current, max_current) + test_assert_within(velocity, -absolute_max_vel, absolute_max_vel) + return input_current, velocity, current_setpoint, get_expected_setpoint(input_current, velocity) + + axis_ctx.handle.controller.input_current = input_current = 0.0 + request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + + # Move the system around its operating envelope + axis_ctx.handle.controller.input_current = input_current = 2.0 + dataA = record_log(data_getter, duration=1.0) + axis_ctx.handle.controller.input_current = input_current = -2.0 + dataA = np.concatenate([dataA, record_log(data_getter, duration=1.0)]) + axis_ctx.handle.controller.input_current = input_current = 4.0 + dataA = np.concatenate([dataA, record_log(data_getter, duration=1.0)]) + axis_ctx.handle.controller.input_current = input_current = -4.0 + dataA = np.concatenate([dataA, record_log(data_getter, duration=1.0)]) + + # Shrink the operating envelope while motor is moving faster than the envelope allows + max_rps = 5.0 + max_vel = float(enc_ctx.yaml['cpr']) * max_rps + axis_ctx.handle.controller.config.vel_limit = max_vel + + # Move the system around its operating envelope + axis_ctx.handle.controller.input_current = input_current = 2.0 + dataB = record_log(data_getter, duration=1.0) + axis_ctx.handle.controller.input_current = input_current = -2.0 + dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)]) + axis_ctx.handle.controller.input_current = input_current = 4.0 + dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)]) + axis_ctx.handle.controller.input_current = input_current = -4.0 + dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)]) + + # Try the shrink maneuver again at positive velocity + axis_ctx.handle.controller.config.vel_limit = 20.0 * float(enc_ctx.yaml['cpr']) + axis_ctx.handle.controller.input_current = 4.0 + time.sleep(0.5) + axis_ctx.handle.controller.config.vel_limit = max_vel + + axis_ctx.handle.controller.input_current = input_current = 2.0 + dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)]) + + test_assert_no_error(axis_ctx) + + axis_ctx.handle.requested_state=1 + + test_curve_fit(dataA[:,(0,3)], dataA[:,4], max_mean_err=0.02, inlier_range=0.05, max_outliers=len(dataA[:,0]*0.01)) + test_curve_fit(dataB[:,(0,3)], dataB[:,4], max_mean_err=0.1, inlier_range=0.2, max_outliers=len(dataB[:,0])*0.01) + + if __name__ == '__main__': test_runner.run([ TestClosedLoopControl(), TestRegenProtection(), + TestVelLimitInCurrentControl() ]) diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index d1697ec9..80bb363d 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -48,6 +48,21 @@ def test_assert_eq(observed, expected, range=None, accuracy=None): if observed != expected: raise TestFailed("value mismatch: expected {} but observed {}".format(expected, observed)) +def test_assert_within(observed, lower_bound, upper_bound, accuracy=0.0): + """ + Checks if the value is within the closed interval [lower_bound, upper_bound] + The permissible range can be expanded in both direction by the coefficiont "accuracy". + I.e. accuracy of 1.0 would expand the range by a total factor of 3.0 + """ + + lower_bound, upper_bound = ( + (lower_bound - (upper_bound - lower_bound) * accuracy), + (upper_bound + (upper_bound - lower_bound) * accuracy) + ) + + if (observed < lower_bound) or (observed > upper_bound): + raise TestFailed(f"the oberved value {observed} is outside the interval [{lower_bound}, {upper_bound}]") + # Other utils -----------------------------------------------------------------# @@ -74,6 +89,9 @@ def all_unique(lst): def modpm(val, range): return ((val + (range / 2)) % range) - (range / 2) +def clamp(val, lower_bound, upper_bound): + return min(max(val, lower_bound), upper_bound) + def record_log(data_getter, duration=5.0): logger.debug(f"Recording log for {duration}s...") data = [] @@ -82,9 +100,9 @@ def record_log(data_getter, duration=5.0): data.append((time.monotonic() - start,) + tuple(data_getter())) return np.array(data) -def save_log(data): +def save_log(data, id=None): import json - filename = '/tmp/log.json' + filename = '/tmp/log{}.json'.format('' if id is None else str(id)) with open(filename, 'w+') as fp: json.dump(data.tolist(), fp, indent=2) print(f'data saved to {filename}') From f31ccb5cd2c2248aac0c5f43d2c3a17b030b6cc4 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 8 May 2020 18:23:11 -0700 Subject: [PATCH 392/549] Update Gemfile.lock --- docs/Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 860837ad..93af7d5e 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -134,7 +134,7 @@ GEM jekyll (~> 3.3) jekyll-remote-theme (0.2.3) jekyll (~> 3.5) - rubyzip (>= 1.2.2, < 3.0) + rubyzip (>= 1.3.0, < 3.0) typhoeus (>= 0.7, < 2.0) jekyll-sass-converter (1.5.2) sass (~> 3.4) @@ -221,7 +221,7 @@ GEM ruby-enum (0.7.2) i18n ruby_dep (1.5.0) - rubyzip (1.2.2) + rubyzip (1.3.0) safe_yaml (1.0.4) sass (3.5.6) sass-listen (~> 4.0.0) From 573ad8f7f2eef2e6fe589b3398a80acdf30528ee Mon Sep 17 00:00:00 2001 From: camrbuss Date: Fri, 8 May 2020 19:44:06 -0600 Subject: [PATCH 393/549] input_mode and control_mode --- docs/commands.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 69a54982..59c27673 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -65,14 +65,26 @@ Possible values are: * `CTRL_MODE_POSITION_CONTROL` * `CTRL_MODE_VELOCITY_CONTROL` * `CTRL_MODE_CURRENT_CONTROL` -* `CTRL_MODE_TRAJECTORY_CONTROL` * `CTRL_MODE_VOLTAGE_CONTROL` - this one is not normally used. -# Control Commands +### Control Commands * `.controller.input_pos = ` * `.controller.input_vel = ` * `.controller.input_current = ` +### Input Mode +To modify the way the control command affects the motor, you can use the input mode. The default input mode is pass through. +If you want a different mode, you can change `.controller.config.input_mode`. +Possible values are: +* `INPUT_MODE_INACTIVE` +* `INPUT_MODE_PASSTHROUGH` +* `INPUT_MODE_VEL_RAMP` +* `INPUT_MODE_POS_FILTER` +* `INPUT_MODE_MIX_CHANNELS` +* `INPUT_MODE_TRAP_TRAJ` +* `INPUT_MODE_CURRENT_RAMP` +* `INPUT_MODE_MIRROR` + ## System monitoring commands ### Encoder position and velocity From 141d1edd35e3c54c79b209570172809061c1c5fd Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 12 May 2020 01:30:40 -0400 Subject: [PATCH 394/549] Change UART baudrate via fibre --- CHANGELOG.md | 1 + Firmware/MotorControl/main.cpp | 7 ++++++- Firmware/MotorControl/odrive_main.h | 1 + Firmware/communication/communication.cpp | 1 + docs/interfaces.md | 2 +- 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f37e0555..e2b4f6be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * Added support for Flylint VSCode Extension for static code analysis * Using an STM32F405 .svd file allows CortexDebug to view registers during debugging * Added scripts for building via docker. +* Added ability to change uart baudrate via fibre ### Changed * Changed ratiometric `motor.config.current_lim_tolerance` to absolute `motor.config.current_lim_margin` diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index f49d02d6..d3864ed0 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -3,6 +3,7 @@ #include "odrive_main.h" #include "nvm_config.hpp" +#include "usart.h" #include "freertos_vars.h" #include #include @@ -123,7 +124,7 @@ void enter_dfu_mode() { } extern "C" int construct_objects(){ - #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 +#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 if (board_config.enable_i2c_instead_of_can) { // Set up the direction GPIO as input GPIO_InitTypeDef GPIO_InitStruct; @@ -147,6 +148,10 @@ extern "C" int construct_objects(){ #endif MX_CAN1_Init(); + HAL_UART_DeInit(&huart4); + huart4.Init.BaudRate = board_config.uart_baudrate; + HAL_UART_Init(&huart4); + // Init general user ADC on some GPIOs. GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 2dbb82a6..b1c30de2 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -122,6 +122,7 @@ struct BoardConfig_t { float dc_max_negative_current = -0.000001f; // Max current [A] the power supply can sink. You most likely want a non-positive value here. Set to -INFINITY to disable. PWMMapping_t pwm_mappings[GPIO_COUNT]; PWMMapping_t analog_mappings[GPIO_COUNT]; + uint32_t uart_baudrate = 115200; }; extern BoardConfig_t board_config; extern bool user_config_loaded_; diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index fb033bdd..c318040b 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -164,6 +164,7 @@ static inline auto make_obj_tree() { make_protocol_property("max_regen_current", &board_config.max_regen_current), // TODO: changing this currently requires a reboot - fix this make_protocol_property("enable_uart", &board_config.enable_uart), + make_protocol_property("uart_baudrate", &board_config.uart_baudrate), // requires a reboot make_protocol_property("enable_i2c_instead_of_can" , &board_config.enable_i2c_instead_of_can), // requires a reboot make_protocol_property("enable_ascii_protocol_on_usb", &board_config.enable_ascii_protocol_on_usb), make_protocol_property("dc_bus_undervoltage_trip_level", &board_config.dc_bus_undervoltage_trip_level), diff --git a/docs/interfaces.md b/docs/interfaces.md index 13dccec4..01173e93 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -164,7 +164,7 @@ The endpoint pairs `0x01, 0x81` and `0x03, 0x83` behave exactly identical, only If you plan to access the USB endpoints directly it is recommended that you use interface 2. The other interfaces (the ones associated with the CDC device) are usually claimed by the CDC driver of the host OS, so their endpoints cannot be used without first detaching the CDC driver. ### UART -Baud rate: 115200 +Baud rate: 115200 by default. See `odrv0.config.uart_baudrate` to change value. Requires a restart. Pinout: * GPIO 1: Tx (connect to Rx of other device) * GPIO 2: Rx (connect to Tx of other device) From 08f5045ace23c8ed28175a275bc265ddea22bf8e Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 13 May 2020 11:22:20 +0200 Subject: [PATCH 395/549] add documentation for `uart_baudrate` --- Firmware/MotorControl/odrive_main.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index b1c30de2..d2a56896 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -122,6 +122,31 @@ struct BoardConfig_t { float dc_max_negative_current = -0.000001f; // Max current [A] the power supply can sink. You most likely want a non-positive value here. Set to -INFINITY to disable. PWMMapping_t pwm_mappings[GPIO_COUNT]; PWMMapping_t analog_mappings[GPIO_COUNT]; + + /** + * Defines the baudrate used on the UART interface. + * Some baudrates will have a small timing error due to hardware limitations. + * + * Here's an (incomplete) list of baudrates for ODrive v3.x: + * + * Configured | Actual | Error [%] + * -------------|---------------|----------- + * 1.2 KBps | 1.2 KBps | 0 + * 2.4 KBps | 2.4 KBps | 0 + * 9.6 KBps | 9.6 KBps | 0 + * 19.2 KBps | 19.195 KBps | 0.02 + * 38.4 KBps | 38.391 KBps | 0.02 + * 57.6 KBps | 57.613 KBps | 0.02 + * 115.2 KBps | 115.068 KBps | 0.11 + * 230.4 KBps | 230.769 KBps | 0.16 + * 460.8 KBps | 461.538 KBps | 0.16 + * 921.6 KBps | 913.043 KBps | 0.93 + * 1.792 MBps | 1.826 MBps | 1.9 + * 1.8432 MBps | 1.826 MBps | 0.93 + * + * For more information refer to Section 30.3.4 and Table 142 (the column with f_PCLK = 42 MHz) in the STM datasheet: + * https://www.st.com/content/ccc/resource/technical/document/reference_manual/3d/6d/5a/66/b4/99/40/d4/DM00031020.pdf/files/DM00031020.pdf/jcr:content/translations/en.DM00031020.pdf + */ uint32_t uart_baudrate = 115200; }; extern BoardConfig_t board_config; From 312180fc3116dd269d1b75cf089ebb044af7ac6e Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 13 May 2020 11:24:38 +0200 Subject: [PATCH 396/549] add test for `uart_baudrate` setting --- docs/testing.md | 2 +- tools/odrive/tests/uart_ascii_test.py | 65 ++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/docs/testing.md b/docs/testing.md index 771d4e44..ae01361a 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -113,7 +113,7 @@ To flash and start remote debugging: To run a test: - rsync -avh -e ssh /path/to/ODriveFirmware odrv:/opt/odrivetest --exclude="Firmware/build" --exclude="Firmware/.tup" --exclude=".git" --delete + rsync -avh -e ssh /path/to/ODriveFirmware/ odrv:/opt/odrivetest --exclude="Firmware/build" --exclude="Firmware/.tup" --exclude=".git" --delete ssh odrv > cd /opt/odrivetest/tools/odrive/tests/ diff --git a/tools/odrive/tests/uart_ascii_test.py b/tools/odrive/tests/uart_ascii_test.py index 86b2f4ab..91d61709 100644 --- a/tools/odrive/tests/uart_ascii_test.py +++ b/tools/odrive/tests/uart_ascii_test.py @@ -31,6 +31,10 @@ def reset_state(ser): ser.flushInput() # discard response class TestUartAscii(): + """ + Tests the most important functions of the ASCII protocol. + """ + def get_test_cases(self, testrig: TestRig): for odrive in testrig.get_components(ODriveComponent): ports = list(testrig.get_connected_components({ @@ -40,10 +44,6 @@ class TestUartAscii(): yield (odrive, ports) def run_test(self, odrive: ODriveComponent, port: SerialPortComponent, logger: Logger): - """ - Tests the most important functions of the ASCII protocol. - """ - logger.debug('Enabling UART...') # GPIOs might be in use by something other than UART and some components # might be configured so that they would fail in the later test. @@ -138,7 +138,11 @@ class TestUartAscii(): # TODO: test cases for 't', 'ss', 'se', 'sr' commands -class TestUartBurnIn(): +class TestUartBaudrate(): + """ + Tests if the UART baudrate setting works as intended. + """ + def get_test_cases(self, testrig: TestRig): for odrive in testrig.get_components(ODriveComponent): ports = list(testrig.get_connected_components({ @@ -148,10 +152,47 @@ class TestUartBurnIn(): yield (odrive, ports) def run_test(self, odrive: ODriveComponent, port: SerialPortComponent, logger: Logger): - """ - Tests if the ASCII protocol can handle 64kB of random data being thrown at it. - """ + odrive.handle.axis0.config.enable_step_dir = False + odrive.handle.config.enable_uart = True + odrive.handle.config.uart_baudrate = 9600 + odrive.save_config_and_reboot() + + # Control test: talk to the ODrive with the wrong baudrate + with port.open(115200) as ser: + # reset port to known state + reset_state(ser) + + ser.write(b'r vbus_voltage\n') + test_assert_eq(ser.readline().strip(), b'') + + with port.open(9600) as ser: + # reset port to known state + reset_state(ser) + + # Check if protocol works + ser.write(b'r vbus_voltage\n') + response = float(ser.readline().strip()) + test_assert_eq(response, odrive.handle.vbus_voltage, accuracy=0.1) + + odrive.handle.config.uart_baudrate = 115200 + odrive.save_config_and_reboot() + + +class TestUartBurnIn(): + """ + Tests if the ASCII protocol can handle 64kB of random data being thrown at it. + """ + + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + ports = list(testrig.get_connected_components({ + 'rx': (odrive.gpio1, True), + 'tx': (odrive.gpio2, False) + }, SerialPortComponent)) + yield (odrive, ports) + + def run_test(self, odrive: ODriveComponent, port: SerialPortComponent, logger: Logger): odrive.handle.axis0.config.enable_step_dir = False odrive.handle.config.enable_uart = True @@ -170,6 +211,10 @@ class TestUartBurnIn(): class TestUartNoise(): + """ + Tests if the UART can handle invalid signals. + """ + def get_test_cases(self, testrig: TestRig): for odrive in testrig.get_components(ODriveComponent): # For every ODrive, find a connected serial port which has a teensy @@ -200,9 +245,6 @@ class TestUartNoise(): yield (odrive, ports) def run_test(self, odrive: ODriveComponent, port: SerialPortComponent, noise_enable: LinuxGpioComponent, logger: Logger): - """ - Tests if the UART can handle invalid signals. - """ noise_enable.config(output=True) noise_enable.write(False) time.sleep(0.1) @@ -244,6 +286,7 @@ class TestUartNoise(): if __name__ == '__main__': test_runner.run([ TestUartAscii(), + TestUartBaudrate(), TestUartBurnIn(), TestUartNoise(), ]) From 7148de710abb5701dd2e24ca8e549121870fefa5 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Wed, 13 May 2020 17:45:50 -0700 Subject: [PATCH 397/549] Update odrivetool.md --- docs/odrivetool.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/odrivetool.md b/docs/odrivetool.md index cb8059c3..dddd5ea1 100644 --- a/docs/odrivetool.md +++ b/docs/odrivetool.md @@ -88,7 +88,7 @@ To compile firmware from source, refer to the [developer guide](developer-guide) * On some machines you will need to unplug and plug back in the USB cable to make the PC understand that we switched from regular mode to bootloader mode. * If the DFU script can't find the device, try forcing it into DFU mode. -
How to force DFU mode (ODrive v3.5)
+
How to force DFU mode (ODrive v3.5 and newer)
Flick the DIP switch that "DFU, RUN" to "DFU" and power cycle the board. After you're done upgrading firmware, don't forget to put the switch back into the "RUN" position and power cycle the board again.
From da7f7192833e0f02501378e1505989ed3fa3a137 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 14 May 2020 11:02:36 +0200 Subject: [PATCH 398/549] improve naming consistency between code and fibre Consistent naming makes code autogeneration easier. This commit does not claim that the exported names of the variables were more sensible than the in-code names. However changing the exported names can break external tools and needs to happen in a controlled and documented way. --- Firmware/MotorControl/axis.cpp | 16 ++++----- Firmware/MotorControl/axis.hpp | 30 ++++++++-------- Firmware/MotorControl/controller.cpp | 34 +++++++++---------- Firmware/MotorControl/controller.hpp | 32 ++++++++--------- Firmware/MotorControl/encoder.cpp | 2 +- Firmware/MotorControl/encoder.hpp | 8 ++--- Firmware/MotorControl/low_level.cpp | 2 +- Firmware/MotorControl/motor.cpp | 2 +- Firmware/MotorControl/motor.hpp | 8 ++--- .../MotorControl/sensorless_estimator.hpp | 6 ++-- Firmware/Tests/test_can.cpp | 6 ++-- Firmware/communication/ascii_protocol.cpp | 8 ++--- Firmware/communication/can_simple.cpp | 10 +++--- Firmware/communication/interface_can.cpp | 12 +++---- Firmware/communication/interface_can.hpp | 12 +++---- docs/commands.md | 8 ++--- docs/getting-started.md | 8 ++--- docs/hoverboard.md | 2 +- docs/input_modes.md | 18 +++++----- tools/odrive/enums.py | 8 ++--- tools/odrive/tests/can_test.py | 4 +-- tools/odrive/tests/closed_loop_test.py | 8 ++--- tools/odrive/tests/endstop_test.py | 2 +- tools/odrive/tests/uart_ascii_test.py | 8 ++--- tools/setup_hall_as_index.py | 2 +- 25 files changed, 128 insertions(+), 128 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index b68ef01b..c1d11a4b 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -25,7 +25,7 @@ Axis::Axis(int axis_num, sensorless_estimator_(sensorless_estimator), controller_(controller), motor_(motor), - trap_(trap), + trap_traj_(trap), min_endstop_(min_endstop), max_endstop_(max_endstop) { @@ -33,7 +33,7 @@ Axis::Axis(int axis_num, sensorless_estimator_.axis_ = this; controller_.axis_ = this; motor_.axis_ = this; - trap_.axis_ = this; + trap_traj_.axis_ = this; min_endstop_.axis_ = this; max_endstop_.axis_ = this; decode_step_dir_pins(); @@ -173,7 +173,7 @@ bool Axis::do_checks() { // controller_.do_checks(); // Check for endstop presses - bool vel_dependent_stopping = (current_state_ == AXIS_STATE_HOMING) && (controller_.config_.control_mode >= Controller::CTRL_MODE_VELOCITY_CONTROL); + bool vel_dependent_stopping = (current_state_ == AXIS_STATE_HOMING) && (controller_.config_.control_mode >= Controller::CONTROL_MODE_VELOCITY_CONTROL); if (min_endstop_.config_.enabled && min_endstop_.get_state() && (!vel_dependent_stopping || controller_.vel_setpoint_ < 0.0f)) { error_ |= ERROR_MIN_ENDSTOP_PRESSED; } else if (max_endstop_.config_.enabled && max_endstop_.get_state() && (!vel_dependent_stopping || controller_.vel_setpoint_ > 0.0f)) { @@ -331,8 +331,8 @@ bool Axis::run_closed_loop_control_loop() { // Slowly drive in the negative direction at homing_speed until the min endstop is pressed // When pressed, set the linear count to the offset (default 0), and then go to position 0 bool Axis::run_homing() { - Controller::ControlMode_t stored_control_mode = controller_.config_.control_mode; - Controller::InputMode_t stored_input_mode = controller_.config_.input_mode; + Controller::ControlMode stored_control_mode = controller_.config_.control_mode; + Controller::InputMode stored_input_mode = controller_.config_.input_mode; // TODO: theoretically this check should be inside the update loop, // otherwise someone could disable the endstop while homing is in progress. @@ -340,7 +340,7 @@ bool Axis::run_homing() { return error_ |= ERROR_HOMING_WITHOUT_ENDSTOP, false; } - controller_.config_.control_mode = Controller::CTRL_MODE_VELOCITY_CONTROL; + controller_.config_.control_mode = Controller::CONTROL_MODE_VELOCITY_CONTROL; controller_.config_.input_mode = Controller::INPUT_MODE_VEL_RAMP; controller_.input_pos_ = 0.0f; @@ -381,7 +381,7 @@ bool Axis::run_homing() { // Set our current position in encoder counts to make control more logical encoder_.set_linear_count(static_cast(controller_.pos_setpoint_)); - controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; + controller_.config_.control_mode = Controller::CONTROL_MODE_POSITION_CONTROL; controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ; controller_.input_pos_ = 0.0f; @@ -499,7 +499,7 @@ void Axis::run_state_machine_loop() { case AXIS_STATE_LOCKIN_SPIN: { if (!motor_.is_calibrated_ || motor_.config_.direction==0) goto invalid_state_label; - status = run_lockin_spin(config_.lockin); + status = run_lockin_spin(config_.general_lockin); } break; case AXIS_STATE_SENSORLESS_CONTROL: { diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 5b489a93..63925753 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -7,7 +7,7 @@ class Axis { public: - enum Error_t { + enum Error { ERROR_NONE = 0x00, ERROR_INVALID_STATE = 0x01, //error_ |= Axis::ERROR_CONTROLLER_FAILED; } @@ -50,11 +50,11 @@ bool Controller::select_encoder(size_t encoder_num) { } void Controller::move_to_pos(float goal_point) { - axis_->trap_.planTrapezoidal(goal_point, pos_setpoint_, vel_setpoint_, - axis_->trap_.config_.vel_limit, - axis_->trap_.config_.accel_limit, - axis_->trap_.config_.decel_limit); - axis_->trap_.t_ = 0.0f; + axis_->trap_traj_.planTrapezoidal(goal_point, pos_setpoint_, vel_setpoint_, + axis_->trap_traj_.config_.vel_limit, + axis_->trap_traj_.config_.accel_limit, + axis_->trap_traj_.config_.decel_limit); + axis_->trap_traj_.t_ = 0.0f; trajectory_done_ = false; } @@ -90,7 +90,7 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_current_; } if (config_.anticogging.index < 3600) { - config_.control_mode = CTRL_MODE_POSITION_CONTROL; + config_.control_mode = CONTROL_MODE_POSITION_CONTROL; input_pos_ = config_.anticogging.index * axis_->encoder_.getCoggingRatio(); input_vel_ = 0.0f; input_current_ = 0.0f; @@ -98,7 +98,7 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) return false; } else { config_.anticogging.index = 0; - config_.control_mode = CTRL_MODE_POSITION_CONTROL; + config_.control_mode = CONTROL_MODE_POSITION_CONTROL; input_pos_ = 0.0f; // Send the motor home input_vel_ = 0.0f; input_current_ = 0.0f; @@ -200,19 +200,19 @@ bool Controller::update(float* current_setpoint_output) { if (trajectory_done_) break; - if (axis_->trap_.t_ > axis_->trap_.Tf_) { + if (axis_->trap_traj_.t_ > axis_->trap_traj_.Tf_) { // Drop into position control mode when done to avoid problems on loop counter delta overflow - config_.control_mode = CTRL_MODE_POSITION_CONTROL; + config_.control_mode = CONTROL_MODE_POSITION_CONTROL; pos_setpoint_ = input_pos_; vel_setpoint_ = 0.0f; current_setpoint_ = 0.0f; trajectory_done_ = true; } else { - TrapezoidalTrajectory::Step_t traj_step = axis_->trap_.eval(axis_->trap_.t_); + TrapezoidalTrajectory::Step_t traj_step = axis_->trap_traj_.eval(axis_->trap_traj_.t_); pos_setpoint_ = traj_step.Y; vel_setpoint_ = traj_step.Yd; current_setpoint_ = traj_step.Ydd * config_.inertia; - axis_->trap_.t_ += current_meas_period; + axis_->trap_traj_.t_ += current_meas_period; } anticogging_pos = pos_setpoint_; // FF the position setpoint instead of the pos_estimate } break; @@ -227,7 +227,7 @@ bool Controller::update(float* current_setpoint_output) { // TODO Decide if we want to use encoder or pll position here float gain_scheduling_multiplier = 1.0f; float vel_des = vel_setpoint_; - if (config_.control_mode >= CTRL_MODE_POSITION_CONTROL) { + if (config_.control_mode >= CONTROL_MODE_POSITION_CONTROL) { float pos_err; if (!pos_estimate_src) { set_error(ERROR_INVALID_ESTIMATE); @@ -292,12 +292,12 @@ bool Controller::update(float* current_setpoint_output) { // Anti-cogging is enabled after calibration // We get the current position and apply a current feed-forward // ensuring that we handle negative encoder positions properly (-1 == motor->encoder.encoder_cpr - 1) - if (anticogging_valid_ && config_.anticogging.enable) { + if (anticogging_valid_ && config_.anticogging.anticogging_enabled) { Iq += config_.anticogging.cogging_map[std::clamp(mod(static_cast(anticogging_pos), 3600), 0, 3600)]; } float v_err = 0.0f; - if (config_.control_mode >= CTRL_MODE_VELOCITY_CONTROL) { + if (config_.control_mode >= CONTROL_MODE_VELOCITY_CONTROL) { if (!vel_estimate_src) { set_error(ERROR_INVALID_ESTIMATE); return false; @@ -311,7 +311,7 @@ bool Controller::update(float* current_setpoint_output) { } // Velocity limiting in current mode - if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL && config_.enable_current_vel_limit) { + if (config_.control_mode < CONTROL_MODE_VELOCITY_CONTROL && config_.enable_current_mode_vel_limit) { if (!vel_estimate_src) { set_error(ERROR_INVALID_ESTIMATE); return false; @@ -334,7 +334,7 @@ bool Controller::update(float* current_setpoint_output) { } // Velocity integrator (behaviour dependent on limiting) - if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL) { + if (config_.control_mode < CONTROL_MODE_VELOCITY_CONTROL) { // reset integral if not in use vel_integrator_current_ = 0.0f; } else { diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 56a60020..293b892a 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -7,7 +7,7 @@ class Controller { public: - enum Error_t { + enum Error { ERROR_NONE = 0, ERROR_OVERSPEED = 0x01, ERROR_INVALID_INPUT_MODE = 0x02, @@ -19,14 +19,14 @@ public: // Note: these should be sorted from lowest level of control to // highest level of control, to allow "<" style comparisons. - enum ControlMode_t{ - CTRL_MODE_VOLTAGE_CONTROL = 0, - CTRL_MODE_CURRENT_CONTROL = 1, - CTRL_MODE_VELOCITY_CONTROL = 2, - CTRL_MODE_POSITION_CONTROL = 3 + enum ControlMode{ + CONTROL_MODE_VOLTAGE_CONTROL = 0, + CONTROL_MODE_CURRENT_CONTROL = 1, + CONTROL_MODE_VELOCITY_CONTROL = 2, + CONTROL_MODE_POSITION_CONTROL = 3 }; - enum InputMode_t{ + enum InputMode{ INPUT_MODE_INACTIVE, INPUT_MODE_PASSTHROUGH, INPUT_MODE_VEL_RAMP, @@ -45,12 +45,12 @@ public: float calib_pos_threshold = 1.0f; float calib_vel_threshold = 1.0f; float cogging_ratio = 1.0f; - bool enable = true; + bool anticogging_enabled = true; } Anticogging_t; struct Config_t { - ControlMode_t control_mode = CTRL_MODE_POSITION_CONTROL; //see: ControlMode_t - InputMode_t input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t + ControlMode control_mode = CONTROL_MODE_POSITION_CONTROL; //see: ControlMode + InputMode input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode float pos_gain = 20.0f; // [(counts/s) / counts] float vel_gain = 5.0f / 10000.0f; // [A/(counts/s)] // float vel_gain = 5.0f / 200.0f, // [A/(rad/s)] @@ -68,7 +68,7 @@ public: bool enable_gain_scheduling = false; bool enable_vel_limit = true; bool enable_overspeed_error = true; - bool enable_current_vel_limit = true; // enable velocity limit in current control mode (requires a valid velocity estimator) + bool enable_current_mode_vel_limit = true; // enable velocity limit in current control mode (requires a valid velocity estimator) uint8_t axis_to_mirror = -1; float mirror_ratio = 1.0f; uint8_t load_encoder_axis = -1; // default depends on Axis number and is set in load_configuration() @@ -76,7 +76,7 @@ public: explicit Controller(Config_t& config); void reset(); - void set_error(Error_t error); + void set_error(Error error); void input_pos_updated(); bool select_encoder(size_t encoder_num); @@ -95,7 +95,7 @@ public: Config_t& config_; Axis* axis_ = nullptr; // set by Axis constructor - Error_t error_ = ERROR_NONE; + Error error_ = ERROR_NONE; float* pos_estimate_src_ = nullptr; bool* pos_estimate_valid_src_ = nullptr; @@ -138,7 +138,7 @@ public: make_protocol_property("gain_scheduling_width", &config_.gain_scheduling_width), make_protocol_object("config", make_protocol_property("enable_vel_limit", &config_.enable_vel_limit), - make_protocol_property("enable_current_mode_vel_limit", &config_.enable_current_vel_limit), + make_protocol_property("enable_current_mode_vel_limit", &config_.enable_current_mode_vel_limit), make_protocol_property("enable_gain_scheduling", &config_.enable_gain_scheduling), make_protocol_property("enable_overspeed_error", &config_.enable_overspeed_error), make_protocol_property("control_mode", &config_.control_mode), @@ -164,13 +164,13 @@ public: make_protocol_property("calib_pos_threshold", &config_.anticogging.calib_pos_threshold), make_protocol_property("calib_vel_threshold", &config_.anticogging.calib_vel_threshold), make_protocol_ro_property("cogging_ratio", &config_.anticogging.cogging_ratio), - make_protocol_property("anticogging_enabled", &config_.anticogging.enable))), + make_protocol_property("anticogging_enabled", &config_.anticogging.anticogging_enabled))), make_protocol_function("move_incremental", *this, &Controller::move_incremental, "displacement", "from_goal_point"), make_protocol_function("start_anticogging_calibration", *this, &Controller::start_anticogging_calibration) ); } }; -DEFINE_ENUM_FLAG_OPERATORS(Controller::Error_t) +DEFINE_ENUM_FLAG_OPERATORS(Controller::Error) #endif // __CONTROLLER_HPP diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 77b809aa..c21683e7 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -35,7 +35,7 @@ void Encoder::setup() { } } -void Encoder::set_error(Error_t error) { +void Encoder::set_error(Error error) { vel_estimate_valid_ = false; pos_estimate_valid_ = false; error_ |= error; diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 28830c38..77ad84b1 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -7,7 +7,7 @@ class Encoder { public: - enum Error_t { + enum Error { ERROR_NONE = 0, ERROR_UNSTABLE_GAIN = 0x01, ERROR_CPR_POLEPAIRS_MISMATCH = 0x02, @@ -59,7 +59,7 @@ public: Config_t& config, const Motor::Config_t& motor_config); void setup(); - void set_error(Error_t error); + void set_error(Error error); bool do_checks(); void enc_index_cb(); @@ -81,7 +81,7 @@ public: Config_t& config_; Axis* axis_ = nullptr; // set by Axis constructor - Error_t error_ = ERROR_NONE; + Error error_ = ERROR_NONE; bool index_found_ = false; bool is_ready_ = false; int32_t shadow_count_ = 0; @@ -171,6 +171,6 @@ public: } }; -DEFINE_ENUM_FLAG_OPERATORS(Encoder::Error_t) +DEFINE_ENUM_FLAG_OPERATORS(Encoder::Error) #endif // __ENCODER_HPP diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index fda04cf4..f0e32f0c 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -84,7 +84,7 @@ static uint16_t GPIO_port_samples [2][num_GPIO]; */ // @brief Floats ALL phases immediately and disarms both motors and the brake resistor. -void low_level_fault(Motor::Error_t error) { +void low_level_fault(Motor::Error error) { // Disable all motors NOW! for (size_t i = 0; i < AXIS_COUNT; ++i) { safety_critical_disarm_motor_pwm(axes[i]->motor_); diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 223aba1c..6b8c8d0f 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -137,7 +137,7 @@ bool Motor::check_DRV_fault() { return true; } -void Motor::set_error(Motor::Error_t error){ +void Motor::set_error(Motor::Error error){ error_ |= error; axis_->error_ |= Axis::ERROR_MOTOR_FAILED; safety_critical_disarm_motor_pwm(*this); diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 8c627aa7..67a0751d 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -9,7 +9,7 @@ class Motor { public: - enum Error_t { + enum Error { ERROR_NONE = 0, ERROR_PHASE_RESISTANCE_OUT_OF_RANGE = 0x0001, ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE = 0x0002, @@ -128,7 +128,7 @@ public: void update_current_controller_gains(); void DRV8301_setup(); bool check_DRV_fault(); - void set_error(Error_t error); + void set_error(Error error); bool do_checks(); float get_inverter_temp(); bool update_thermal_limits(float fet_temp); @@ -163,7 +163,7 @@ public: uint16_t timing_log_[TIMING_LOG_NUM_SLOTS] = { 0 }; // variables exposed on protocol - Error_t error_ = ERROR_NONE; + Error error_ = ERROR_NONE; // Do not write to this variable directly! // It is for exclusive use by the safety_critical_... functions. ArmedState_t armed_state_ = ARMED_STATE_DISARMED; @@ -279,6 +279,6 @@ public: } }; -DEFINE_ENUM_FLAG_OPERATORS(Motor::Error_t) +DEFINE_ENUM_FLAG_OPERATORS(Motor::Error) #endif // __MOTOR_HPP diff --git a/Firmware/MotorControl/sensorless_estimator.hpp b/Firmware/MotorControl/sensorless_estimator.hpp index e47db893..dd48c705 100644 --- a/Firmware/MotorControl/sensorless_estimator.hpp +++ b/Firmware/MotorControl/sensorless_estimator.hpp @@ -3,7 +3,7 @@ class SensorlessEstimator { public: - enum Error_t { + enum Error { ERROR_NONE = 0, ERROR_UNSTABLE_GAIN = 0x01, }; @@ -22,7 +22,7 @@ public: Config_t& config_; // TODO: expose on protocol - Error_t error_ = ERROR_NONE; + Error error_ = ERROR_NONE; float phase_ = 0.0f; // [rad] float pll_pos_ = 0.0f; // [rad] float vel_estimate_ = 0.0f; // [rad/s] @@ -51,6 +51,6 @@ public: } }; -DEFINE_ENUM_FLAG_OPERATORS(SensorlessEstimator::Error_t) +DEFINE_ENUM_FLAG_OPERATORS(SensorlessEstimator::Error) #endif /* __SENSORLESS_ESTIMATOR_HPP */ diff --git a/Firmware/Tests/test_can.cpp b/Firmware/Tests/test_can.cpp index ef2ffecf..5a0db2fe 100644 --- a/Firmware/Tests/test_can.cpp +++ b/Firmware/Tests/test_can.cpp @@ -5,7 +5,7 @@ #include "communication/can_helpers.hpp" -enum InputMode_t { +enum InputMode { INPUT_MODE_INACTIVE, INPUT_MODE_PASSTHROUGH, INPUT_MODE_VEL_RAMP, @@ -84,7 +84,7 @@ TEST_SUITE("CAN Functions") { can_Message_t rxmsg; rxmsg.buf[0] = INPUT_MODE_MIX_CHANNELS; rxmsg.buf[1] = INPUT_MODE_PASSTHROUGH; - CHECK(static_cast(can_getSignal(rxmsg, 0, 8, true, 1, 0)) == INPUT_MODE_MIX_CHANNELS); - CHECK(static_cast(can_getSignal(rxmsg, 8, 8, true, 1, 0)) == INPUT_MODE_PASSTHROUGH); + CHECK(static_cast(can_getSignal(rxmsg, 0, 8, true, 1, 0)) == INPUT_MODE_MIX_CHANNELS); + CHECK(static_cast(can_getSignal(rxmsg, 8, 8, true, 1, 0)) == INPUT_MODE_PASSTHROUGH); } } \ No newline at end of file diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index f0ee9f9c..628dea63 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -97,7 +97,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "invalid motor %u", motor_number); } else { Axis* axis = axes[motor_number]; - axis->controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; + axis->controller_.config_.control_mode = Controller::CONTROL_MODE_POSITION_CONTROL; axis->controller_.input_pos_ = pos_setpoint; if (numscan >= 3) axis->controller_.input_vel_ = vel_feed_forward; @@ -117,7 +117,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "invalid motor %u", motor_number); } else { Axis* axis = axes[motor_number]; - axis->controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; + axis->controller_.config_.control_mode = Controller::CONTROL_MODE_POSITION_CONTROL; axis->controller_.input_pos_ = pos_setpoint; if (numscan >= 3) axis->controller_.config_.vel_limit = vel_limit; @@ -137,7 +137,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "invalid motor %u", motor_number); } else { Axis* axis = axes[motor_number]; - axis->controller_.config_.control_mode = Controller::CTRL_MODE_VELOCITY_CONTROL; + axis->controller_.config_.control_mode = Controller::CONTROL_MODE_VELOCITY_CONTROL; axis->controller_.input_vel_ = vel_setpoint; if (numscan >= 3) axis->controller_.input_current_ = current_feed_forward; @@ -154,7 +154,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "invalid motor %u", motor_number); } else { Axis* axis = axes[motor_number]; - axis->controller_.config_.control_mode = Controller::CTRL_MODE_CURRENT_CONTROL; + axis->controller_.config_.control_mode = Controller::CONTROL_MODE_CURRENT_CONTROL; axis->controller_.input_current_ = current_setpoint; axis->watchdog_feed(); } diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index da22bb70..5ff8009a 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -295,8 +295,8 @@ void CANSimple::set_input_current_callback(Axis* axis, can_Message_t& msg) { } void CANSimple::set_controller_modes_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.config_.control_mode = static_cast(can_getSignal(msg, 0, 32, true)); - axis->controller_.config_.input_mode = static_cast(can_getSignal(msg, 32, 32, true)); + axis->controller_.config_.control_mode = static_cast(can_getSignal(msg, 0, 32, true)); + axis->controller_.config_.input_mode = static_cast(can_getSignal(msg, 32, 32, true)); } void CANSimple::set_vel_limit_callback(Axis* axis, can_Message_t& msg) { @@ -308,12 +308,12 @@ void CANSimple::start_anticogging_callback(Axis* axis, can_Message_t& msg) { } void CANSimple::set_traj_vel_limit_callback(Axis* axis, can_Message_t& msg) { - axis->trap_.config_.vel_limit = can_getSignal(msg, 0, 32, true); + axis->trap_traj_.config_.vel_limit = can_getSignal(msg, 0, 32, true); } void CANSimple::set_traj_accel_limits_callback(Axis* axis, can_Message_t& msg) { - axis->trap_.config_.accel_limit = can_getSignal(msg, 0, 32, true); - axis->trap_.config_.decel_limit = can_getSignal(msg, 32, 32, true); + axis->trap_traj_.config_.accel_limit = can_getSignal(msg, 0, 32, true); + axis->trap_traj_.config_.decel_limit = can_getSignal(msg, 32, 32, true); } void CANSimple::set_traj_A_per_css_callback(Axis* axis, can_Message_t& msg) { diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index bf7f9ff0..339ef9ff 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -57,7 +57,7 @@ static void can_server_thread_wrapper(void *ctx) { bool ODriveCAN::start_can_server() { HAL_StatusTypeDef status; - set_baud_rate(config_.baud); + set_baud_rate(config_.baud_rate); status = HAL_CAN_Init(handle_); @@ -136,25 +136,25 @@ void ODriveCAN::set_baud_rate(uint32_t baudRate) { switch (baudRate) { case CAN_BAUD_125K: handle_->Init.Prescaler = 16; // 21 TQ's - config_.baud = baudRate; + config_.baud_rate = baudRate; reinit_can(); break; case CAN_BAUD_250K: handle_->Init.Prescaler = 8; // 21 TQ's - config_.baud = baudRate; + config_.baud_rate = baudRate; reinit_can(); break; case CAN_BAUD_500K: handle_->Init.Prescaler = 4; // 21 TQ's - config_.baud = baudRate; + config_.baud_rate = baudRate; reinit_can(); break; case CAN_BAUD_1000K: handle_->Init.Prescaler = 2; // 21 TQ's - config_.baud = baudRate; + config_.baud_rate = baudRate; reinit_can(); break; @@ -172,7 +172,7 @@ void ODriveCAN::reinit_can() { status = HAL_CAN_ActivateNotification(handle_, CAN_IT_RX_FIFO0_MSG_PENDING); } -void ODriveCAN::set_error(Error_t error) { +void ODriveCAN::set_error(Error error) { error_ |= error; } // This function is called by each axis. diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index ffb29bb7..fca28822 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -26,11 +26,11 @@ enum CAN_Protocol_t { class ODriveCAN { public: struct Config_t { - uint32_t baud = CAN_BAUD_250K; + uint32_t baud_rate = CAN_BAUD_250K; CAN_Protocol_t protocol = CAN_PROTOCOL_SIMPLE; }; - enum Error_t { + enum Error { ERROR_NONE = 0x00, ERROR_DUPLICATE_CAN_IDS = 0x01 }; @@ -40,7 +40,7 @@ class ODriveCAN { // Thread Relevant Data osThreadId thread_id_; const uint32_t stack_size_ = 1024; // Bytes - Error_t error_ = ERROR_NONE; + Error error_ = ERROR_NONE; volatile bool thread_id_valid_ = false; bool start_can_server(); @@ -48,7 +48,7 @@ class ODriveCAN { void send_heartbeat(Axis *axis); void reinit_can(); - void set_error(Error_t error); + void set_error(Error error); // I/O Functions uint32_t available(); @@ -60,7 +60,7 @@ class ODriveCAN { return make_protocol_member_list( make_protocol_property("error", &error_), make_protocol_object("config", - make_protocol_ro_property("baud_rate", &config_.baud)), + make_protocol_ro_property("baud_rate", &config_.baud_rate)), make_protocol_property("can_protocol", &config_.protocol), make_protocol_function("set_baud_rate", *this, &ODriveCAN::set_baud_rate, "baudRate")); } @@ -73,6 +73,6 @@ class ODriveCAN { }; -DEFINE_ENUM_FLAG_OPERATORS(ODriveCAN::Error_t) +DEFINE_ENUM_FLAG_OPERATORS(ODriveCAN::Error) #endif // __INTERFACE_CAN_HPP diff --git a/docs/commands.md b/docs/commands.md index 33f7eefc..24fb3fdc 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -55,10 +55,10 @@ See [state machine](#state-machine) for a description of each state. The default control mode is position control. If you want a different mode, you can change `.controller.config.control_mode`. Possible values are: -* `CTRL_MODE_POSITION_CONTROL` -* `CTRL_MODE_VELOCITY_CONTROL` -* `CTRL_MODE_CURRENT_CONTROL` -* `CTRL_MODE_VOLTAGE_CONTROL` - this one is not normally used. +* `CONTROL_MODE_POSITION_CONTROL` +* `CONTROL_MODE_VELOCITY_CONTROL` +* `CONTROL_MODE_CURRENT_CONTROL` +* `CONTROL_MODE_VOLTAGE_CONTROL` - this one is not normally used. ### Input Mode The default input mode is `INPUT_MODE_PASSTHROUGH`. diff --git a/docs/getting-started.md b/docs/getting-started.md index 05fd2f94..3287dcab 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -337,20 +337,20 @@ Note that in this mode `encoder.pos_cpr` is used for feedback in stead of `encod If you try to increment the axis with a large step in one go that exceeds `cpr/2` steps, the motor will go to the same angle around the wrong way. This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a virtual CPR that is an integer times larger than your encoder's actual CPR. Set `encoder.config.cpr = N * your_enc_cpr`, where N is some integer. Choose N to give you an appropriate circular space for your application. ### Velocity control -Set `axis.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL`.
+Set `axis.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL`.
You can now control the velocity with `axis.controller.input_vel = 5000` [count/s]. ### Ramped velocity control -Set `axis.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL`.
+Set `axis.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL`.
Set the velocity ramp rate (acceleration): `axis.controller.config.vel_ramp_rate = 2000` [counts/s^2]
Activate the ramped velocity mode: `axis.controller.config.input_mode = INPUT_MODE_VEL_RAMP`.
You can now control the velocity with `axis.controller.input_vel = 5000` [count/s]. ### Current control -Set `axis.controller.config.control_mode = CTRL_MODE_CURRENT_CONTROL`.
+Set `axis.controller.config.control_mode = CONTROL_MODE_CURRENT_CONTROL`.
You can now control the current with `axis.controller.input_current = 3` [A]. -Note: If you exceed `vel_limit` in current control mode, the current is reduced. To disable this, set `axis.controller.enable_current_vel_limit = False`. +Note: If you exceed `vel_limit` in current control mode, the current is reduced. To disable this, set `axis.controller.enable_current_mode_vel_limit = False`. ## Watchdog Timer Each axis has a configurable watchdog timer that can stop the motors if the diff --git a/docs/hoverboard.md b/docs/hoverboard.md index 91b50764..93938613 100644 --- a/docs/hoverboard.md +++ b/docs/hoverboard.md @@ -52,7 +52,7 @@ odrv0.axis0.controller.config.pos_gain = 1 odrv0.axis0.controller.config.vel_gain = 0.02 odrv0.axis0.controller.config.vel_integrator_gain = 0.1 odrv0.axis0.controller.config.vel_limit = 1000 -odrv0.axis0.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL +odrv0.axis0.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL ``` In the next step we are going to start powering the motor and so we want to make sure that some of the above settings that require a reboot are applied first. diff --git a/docs/input_modes.md b/docs/input_modes.md index 32d7df86..be449e24 100644 --- a/docs/input_modes.md +++ b/docs/input_modes.md @@ -30,10 +30,10 @@ Pass `input_xxx` through to `xxx_setpoint` directly. * `input_current` ### Valid Control modes: -* `CTRL_MODE_VOLTAGE_CONTROL` -* `CTRL_MODE_CURRENT_CONTROL` -* `CTRL_MODE_VELOCITY_CONTROL` -* `CTRL_MODE_POSITION_CONTROL` +* `CONTROL_MODE_VOLTAGE_CONTROL` +* `CONTROL_MODE_CURRENT_CONTROL` +* `CONTROL_MODE_VELOCITY_CONTROL` +* `CONTROL_MODE_POSITION_CONTROL` ## INPUT_MODE_VEL_RAMP Ramps a velocity command from the current value to the target value. @@ -46,7 +46,7 @@ Ramps a velocity command from the current value to the target value. * `input_vel` ### Valid Control Modes: -* `CTRL_MODE_VELOCITY_CONTROL` +* `CONTROL_MODE_VELOCITY_CONTROL` ## INPUT_MODE_POS_FILTER Implements a 2nd order position tracking filter. Inteded for use with step/dir interface, but can also be used with position-only commands. @@ -62,7 +62,7 @@ Result of a step command from 1000 to 0 * `input_pos` ### Valid Control modes: -* `CTRL_MODE_POSITION_CONTROL` +* `CONTROL_MODE_POSITION_CONTROL` ## INPUT_MODE_MIX_CHANNELS Not Implemented. @@ -83,7 +83,7 @@ Implementes an online trapezoidal trajectory planner. * `input_pos` ### Valid Control Modes: -* `CTRL_MODE_POSITION_CONTROL` +* `CONTROL_MODE_POSITION_CONTROL` ## INPUT_MODE_CURRENT_RAMP Ramp a current command from the current value to the target value. @@ -95,7 +95,7 @@ Ramp a current command from the current value to the target value. * `input_current` ### Valid Control Modes: -* `CTRL_MODE_CURRENT_CONTROL` +* `CONTROL_MODE_CURRENT_CONTROL` ## INPUT_MODE_MIRROR Implements "electronic mirroring". This is like electronic camming, but you can only mirror exactly the movements of the other motor, according to a fixed ratio @@ -110,4 +110,4 @@ Implements "electronic mirroring". This is like electronic camming, but you can * None. Inputs are taken directly from the other axis encoder estimates ### Valid Control modes -* `CTRL_MODE_POSITION_CONTROL` +* `CONTROL_MODE_POSITION_CONTROL` diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 1198272e..e3d3ebd0 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -75,10 +75,10 @@ MOTOR_TYPE_HIGH_CURRENT = 0 #MOTOR_TYPE_LOW_CURRENT = 1 MOTOR_TYPE_GIMBAL = 2 -CTRL_MODE_VOLTAGE_CONTROL = 0 -CTRL_MODE_CURRENT_CONTROL = 1 -CTRL_MODE_VELOCITY_CONTROL = 2 -CTRL_MODE_POSITION_CONTROL = 3 +CONTROL_MODE_VOLTAGE_CONTROL = 0 +CONTROL_MODE_CURRENT_CONTROL = 1 +CONTROL_MODE_VELOCITY_CONTROL = 2 +CONTROL_MODE_POSITION_CONTROL = 3 INPUT_MODE_INACTIVE = 0 INPUT_MODE_PASSTHROUGH = 1 diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index e69a8bd9..164dd251 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -171,13 +171,13 @@ class TestSimpleCAN(): test_assert_eq(axis.controller.input_vel, 2.0, range=0.01) test_assert_eq(axis.controller.input_current, 3.0, range=0.001) - axis.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL + axis.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL my_cmd('set_input_vel', input_vel=-10.0, cur_ff=30.1234) fence() test_assert_eq(axis.controller.input_vel, -10.0, range=0.01) test_assert_eq(axis.controller.input_current, 30.1234, range=0.01) - axis.controller.config.control_mode = CTRL_MODE_CURRENT_CONTROL + axis.controller.config.control_mode = CONTROL_MODE_CURRENT_CONTROL my_cmd('set_input_current', input_current=3.1415) fence() test_assert_eq(axis.controller.input_current, 3.1415, range=0.01) diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index cc07ab2a..01cc003a 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -80,7 +80,7 @@ class TestClosedLoopControl(TestClosedLoopControlBase): nominal_vel = float(enc_ctx.yaml['cpr']) * nominal_rps logger.debug(f'Testing closed loop velocity control at {nominal_rps} rounds/s...') - axis_ctx.handle.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL + axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL axis_ctx.handle.controller.config.input_mode = INPUT_MODE_PASSTHROUGH axis_ctx.handle.controller.input_vel = 0 @@ -107,7 +107,7 @@ class TestClosedLoopControl(TestClosedLoopControlBase): logger.debug(f'Testing closed loop position control...') - axis_ctx.handle.controller.config.control_mode = CTRL_MODE_POSITION_CONTROL + axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_POSITION_CONTROL axis_ctx.handle.controller.input_pos = 0 axis_ctx.handle.controller.config.vel_limit = float(enc_ctx.yaml['cpr']) * 5.0 # max 5 rps axis_ctx.handle.encoder.set_linear_count(0) @@ -181,7 +181,7 @@ class TestRegenProtection(TestClosedLoopControlBase): logger.debug(f'Brake control test from {nominal_rps} rounds/s...') axis_ctx.handle.controller.config.vel_limit = float(enc_ctx.yaml['cpr']) * 10.0 # max 10 rps - axis_ctx.handle.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL + axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL axis_ctx.handle.controller.config.input_mode = INPUT_MODE_PASSTHROUGH request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) @@ -231,7 +231,7 @@ class TestVelLimitInCurrentControl(TestClosedLoopControlBase): axis_ctx.handle.controller.config.vel_limit = max_vel axis_ctx.handle.controller.config.vel_limit_tolerance = inf # disable hard limit on velocity axis_ctx.handle.motor.config.current_lim = max_current - axis_ctx.handle.controller.config.control_mode = CTRL_MODE_CURRENT_CONTROL + axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_CURRENT_CONTROL # Returns the expected limited setpoint for a given velocity and current def get_expected_setpoint(input_setpoint, velocity): diff --git a/tools/odrive/tests/endstop_test.py b/tools/odrive/tests/endstop_test.py index a823549c..fd07ae8d 100644 --- a/tools/odrive/tests/endstop_test.py +++ b/tools/odrive/tests/endstop_test.py @@ -7,7 +7,7 @@ odrv0 = odrive.find_any() print('Odrive found') odrv0.axis1.controller.config.vel_limit = 50000 -odrv0.axis1.controller.config.control_mode = CTRL_MODE_POSITION_CONTROL +odrv0.axis1.controller.config.control_mode = CONTROL_MODE_POSITION_CONTROL odrv0.axis1.controller.config.input_mode = INPUT_MODE_PASSTHROUGH odrv0.axis1.encoder.config.cpr = 2400 odrv0.axis1.encoder.config.bandwidth = 1000 diff --git a/tools/odrive/tests/uart_ascii_test.py b/tools/odrive/tests/uart_ascii_test.py index 91d61709..17cf85f8 100644 --- a/tools/odrive/tests/uart_ascii_test.py +++ b/tools/odrive/tests/uart_ascii_test.py @@ -96,7 +96,7 @@ class TestUartAscii(): ser.write(b'c 0 12.5\n') test_assert_eq(ser.readline(), b'') test_assert_eq(odrive.handle.axis0.controller.input_current, 12.5, accuracy=0.001) - test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CTRL_MODE_CURRENT_CONTROL) + test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_CURRENT_CONTROL) odrive.handle.axis0.controller.input_vel = 0 odrive.handle.axis0.controller.input_current = 0 @@ -104,7 +104,7 @@ class TestUartAscii(): test_assert_eq(ser.readline(), b'') test_assert_eq(odrive.handle.axis0.controller.input_vel, 567.8, accuracy=0.001) test_assert_eq(odrive.handle.axis0.controller.input_current, 12.5, accuracy=0.001) - test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CTRL_MODE_VELOCITY_CONTROL) + test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_VELOCITY_CONTROL) odrive.handle.axis0.controller.input_pos = 0 odrive.handle.axis0.controller.input_vel = 0 @@ -114,7 +114,7 @@ class TestUartAscii(): test_assert_eq(odrive.handle.axis0.controller.input_pos, 123.4, accuracy=0.001) test_assert_eq(odrive.handle.axis0.controller.input_vel, 567.8, accuracy=0.001) test_assert_eq(odrive.handle.axis0.controller.input_current, 12.5, accuracy=0.001) - test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CTRL_MODE_POSITION_CONTROL) + test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_POSITION_CONTROL) odrive.handle.axis0.controller.input_pos = 0 odrive.handle.axis0.controller.config.vel_limit = 0 @@ -124,7 +124,7 @@ class TestUartAscii(): test_assert_eq(odrive.handle.axis0.controller.input_pos, 123.4, accuracy=0.001) test_assert_eq(odrive.handle.axis0.controller.config.vel_limit, 567.8, accuracy=0.001) test_assert_eq(odrive.handle.axis0.motor.config.current_lim, 12.5, accuracy=0.001) - test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CTRL_MODE_POSITION_CONTROL) + test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_POSITION_CONTROL) ser.write(b'f 0\n') response = ser.readline().strip() diff --git a/tools/setup_hall_as_index.py b/tools/setup_hall_as_index.py index c01de4ff..3c21f2cb 100644 --- a/tools/setup_hall_as_index.py +++ b/tools/setup_hall_as_index.py @@ -30,7 +30,7 @@ for ax in axes: ax.encoder.config.find_idx_on_lockin_only = True ax.encoder.config.idx_search_unidirectional = True - ax.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL + ax.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL ax.controller.config.vel_limit = 10000 ax.controller.config.vel_gain = 0.002205736003816127 ax.controller.config.vel_integrator_gain = 0.022057360038161278 From e4d70a22b70cf4270b97ef89673087284a1a023d Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 14 May 2020 11:31:30 +0200 Subject: [PATCH 399/549] Initial implementation of interface autogenerator. TODO: - write to endpoints from float (for PWM/analog input) - ascii protocol --- Firmware/.gitignore | 1 + Firmware/MotorControl/axis.cpp | 4 +- Firmware/MotorControl/axis.hpp | 127 +- Firmware/MotorControl/controller.hpp | 90 +- Firmware/MotorControl/encoder.hpp | 86 +- Firmware/MotorControl/endstop.hpp | 21 +- Firmware/MotorControl/low_level.cpp | 32 +- Firmware/MotorControl/main.cpp | 81 +- Firmware/MotorControl/motor.cpp | 2 +- Firmware/MotorControl/motor.hpp | 147 +-- Firmware/MotorControl/odrive_main.h | 91 +- .../MotorControl/sensorless_estimator.hpp | 26 +- Firmware/MotorControl/trapTraj.hpp | 10 - Firmware/Tupfile.lua | 16 +- Firmware/ascii_type_info_template.j2 | 90 ++ Firmware/build.lua | 2 +- Firmware/communication/ascii_protocol.cpp | 17 +- Firmware/communication/can_simple.cpp | 2 +- Firmware/communication/communication.cpp | 161 +-- Firmware/communication/communication.h | 4 - Firmware/communication/interface_can.cpp | 10 +- Firmware/communication/interface_can.hpp | 35 +- Firmware/communication/interface_usb.cpp | 2 +- Firmware/fibre/cpp/endpoints_template.j2 | 71 + Firmware/fibre/cpp/function_stubs_template.j2 | 40 + Firmware/fibre/cpp/include/fibre/bufptr.hpp | 93 ++ .../fibre/cpp/include/fibre/cpp_utils.hpp | 1145 ++++++++++++++++- Firmware/fibre/cpp/include/fibre/protocol.hpp | 748 ++--------- .../fibre/cpp/include/fibre/simple_serdes.hpp | 77 ++ Firmware/fibre/cpp/interfaces_template.j2 | 67 + Firmware/fibre/cpp/protocol.cpp | 91 +- Firmware/interface_generator.py | 589 +++++++++ Firmware/odrive-interface.yaml | 662 ++++++++++ docs/interface-definition-file.md | 133 ++ 34 files changed, 3321 insertions(+), 1452 deletions(-) create mode 100644 Firmware/ascii_type_info_template.j2 create mode 100644 Firmware/fibre/cpp/endpoints_template.j2 create mode 100644 Firmware/fibre/cpp/function_stubs_template.j2 create mode 100644 Firmware/fibre/cpp/include/fibre/bufptr.hpp create mode 100644 Firmware/fibre/cpp/include/fibre/simple_serdes.hpp create mode 100644 Firmware/fibre/cpp/interfaces_template.j2 create mode 100644 Firmware/interface_generator.py create mode 100644 Firmware/odrive-interface.yaml create mode 100644 docs/interface-definition-file.md diff --git a/Firmware/.gitignore b/Firmware/.gitignore index 496462db..a4c86dc2 100644 --- a/Firmware/.gitignore +++ b/Firmware/.gitignore @@ -1,5 +1,6 @@ #build folder +autogen/ build/ deploy/ .dep/ diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index c1d11a4b..71edd5f1 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -161,9 +161,9 @@ bool Axis::do_checks() { if ((current_state_ != AXIS_STATE_IDLE) && (motor_.armed_state_ == Motor::ARMED_STATE_DISARMED)) // motor got disarmed in something other than the idle loop error_ |= ERROR_MOTOR_DISARMED; - if (!(vbus_voltage >= board_config.dc_bus_undervoltage_trip_level)) + if (!(vbus_voltage >= odrv.config_.dc_bus_undervoltage_trip_level)) error_ |= ERROR_DC_BUS_UNDER_VOLTAGE; - if (!(vbus_voltage <= board_config.dc_bus_overvoltage_trip_level)) + if (!(vbus_voltage <= odrv.config_.dc_bus_overvoltage_trip_level)) error_ |= ERROR_DC_BUS_OVER_VOLTAGE; // Sub-components should use set_error which will propegate to this error_ diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 63925753..31cc118e 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -5,43 +5,8 @@ #error "This file should not be included directly. Include odrive_main.h instead." #endif -class Axis { +class Axis : public AxisIntf { public: - enum Error { - ERROR_NONE = 0x00, - ERROR_INVALID_STATE = 0x01, //decode_step_dir_pins(); } + void set_dir_gpio_pin(uint16_t value) { dir_gpio_pin = value; parent->decode_step_dir_pins(); } }; struct Homing_t { @@ -98,13 +68,6 @@ public: M_SIGNAL_PH_CURRENT_MEAS = 1u << 0 }; - enum LockinState_t { - LOCKIN_STATE_INACTIVE, - LOCKIN_STATE_RAMP, - LOCKIN_STATE_ACCELERATE, - LOCKIN_STATE_CONST_VEL, - }; - Axis(int axis_num, const AxisHardwareConfig_t& hw_config, Config_t& config, @@ -143,7 +106,7 @@ public: sensorless_estimator_.error_ = SensorlessEstimator::ERROR_NONE; encoder_.error_ = Encoder::ERROR_NONE; - error_ = Axis::ERROR_NONE; + error_ = ERROR_NONE; } // True if there are no errors @@ -250,85 +213,17 @@ public: GPIO_TypeDef* dir_port_; uint16_t dir_pin_; - State_t requested_state_ = AXIS_STATE_STARTUP_SEQUENCE; - std::array task_chain_ = { AXIS_STATE_UNDEFINED }; - State_t& current_state_ = task_chain_.front(); + AxisState requested_state_ = AXIS_STATE_STARTUP_SEQUENCE; + std::array task_chain_ = { AXIS_STATE_UNDEFINED }; + AxisState& current_state_ = task_chain_.front(); uint32_t loop_counter_ = 0; - LockinState_t lockin_state_ = LOCKIN_STATE_INACTIVE; + LockinState lockin_state_ = LOCKIN_STATE_INACTIVE; Homing_t homing_; uint32_t last_heartbeat_ = 0; // watchdog uint32_t watchdog_current_value_= 0; - - // Communication protocol definitions - auto make_protocol_definitions() { - return make_protocol_member_list( - make_protocol_property("error", &error_), - make_protocol_ro_property("step_dir_active", &step_dir_active_), - make_protocol_ro_property("current_state", ¤t_state_), - make_protocol_property("requested_state", &requested_state_), - make_protocol_ro_property("loop_counter", &loop_counter_), - make_protocol_ro_property("lockin_state", &lockin_state_), - make_protocol_property("is_homed", &homing_.is_homed), - make_protocol_object("config", - make_protocol_property("startup_motor_calibration", &config_.startup_motor_calibration), - make_protocol_property("startup_encoder_index_search", &config_.startup_encoder_index_search), - make_protocol_property("startup_encoder_offset_calibration", &config_.startup_encoder_offset_calibration), - make_protocol_property("startup_closed_loop_control", &config_.startup_closed_loop_control), - make_protocol_property("startup_sensorless_control", &config_.startup_sensorless_control), - make_protocol_property("startup_homing", &config_.startup_homing), - make_protocol_property("enable_step_dir", &config_.enable_step_dir), - make_protocol_property("step_dir_always_on", &config_.step_dir_always_on), - make_protocol_property("counts_per_step", &config_.counts_per_step), - make_protocol_property("watchdog_timeout", &config_.watchdog_timeout), - make_protocol_property("enable_watchdog", &config_.enable_watchdog), - 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, - [](void* ctx) { static_cast(ctx)->decode_step_dir_pins(); }, this), - make_protocol_object("calibration_lockin", - make_protocol_property("current", &config_.calibration_lockin.current), - make_protocol_property("ramp_time", &config_.calibration_lockin.ramp_time), - make_protocol_property("ramp_distance", &config_.calibration_lockin.ramp_distance), - make_protocol_property("accel", &config_.calibration_lockin.accel), - make_protocol_property("vel", &config_.calibration_lockin.vel)), - make_protocol_object("sensorless_ramp", - make_protocol_property("current", &config_.sensorless_ramp.current), - make_protocol_property("ramp_time", &config_.sensorless_ramp.ramp_time), - make_protocol_property("ramp_distance", &config_.sensorless_ramp.ramp_distance), - make_protocol_property("accel", &config_.sensorless_ramp.accel), - make_protocol_property("vel", &config_.sensorless_ramp.vel), - make_protocol_property("finish_distance", &config_.sensorless_ramp.finish_distance), - make_protocol_property("finish_on_vel", &config_.sensorless_ramp.finish_on_vel), - make_protocol_property("finish_on_distance", &config_.sensorless_ramp.finish_on_distance), - make_protocol_property("finish_on_enc_idx", &config_.sensorless_ramp.finish_on_enc_idx)), - make_protocol_object("general_lockin", - make_protocol_property("current", &config_.general_lockin.current), - make_protocol_property("ramp_time", &config_.general_lockin.ramp_time), - make_protocol_property("ramp_distance", &config_.general_lockin.ramp_distance), - make_protocol_property("accel", &config_.general_lockin.accel), - make_protocol_property("vel", &config_.general_lockin.vel), - make_protocol_property("finish_distance", &config_.general_lockin.finish_distance), - make_protocol_property("finish_on_vel", &config_.general_lockin.finish_on_vel), - make_protocol_property("finish_on_distance", &config_.general_lockin.finish_on_distance), - make_protocol_property("finish_on_enc_idx", &config_.general_lockin.finish_on_enc_idx)), - make_protocol_property("can_node_id", &config_.can_node_id), - make_protocol_property("can_heartbeat_rate_ms", &config_.can_heartbeat_rate_ms)), - make_protocol_object("motor", motor_.make_protocol_definitions()), - 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_traj_.make_protocol_definitions()), - make_protocol_object("min_endstop", min_endstop_.make_protocol_definitions()), - make_protocol_object("max_endstop", max_endstop_.make_protocol_definitions()), - make_protocol_function("watchdog_feed", *this, &Axis::watchdog_feed), - make_protocol_function("clear_errors", *this, &Axis::clear_errors) - ); - } }; -DEFINE_ENUM_FLAG_OPERATORS(Axis::Error) - #endif /* __AXIS_HPP */ diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 293b892a..f1529d96 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -5,38 +5,8 @@ #error "This file should not be included directly. Include odrive_main.h instead." #endif -class Controller { +class Controller : public ControllerIntf { public: - enum Error { - ERROR_NONE = 0, - ERROR_OVERSPEED = 0x01, - ERROR_INVALID_INPUT_MODE = 0x02, - ERROR_UNSTABLE_GAIN = 0x04, - ERROR_INVALID_MIRROR_AXIS = 0x08, - ERROR_INVALID_LOAD_ENCODER = 0x10, - ERROR_INVALID_ESTIMATE = 0x20, - }; - - // Note: these should be sorted from lowest level of control to - // highest level of control, to allow "<" style comparisons. - enum ControlMode{ - CONTROL_MODE_VOLTAGE_CONTROL = 0, - CONTROL_MODE_CURRENT_CONTROL = 1, - CONTROL_MODE_VELOCITY_CONTROL = 2, - CONTROL_MODE_POSITION_CONTROL = 3 - }; - - enum InputMode{ - INPUT_MODE_INACTIVE, - INPUT_MODE_PASSTHROUGH, - INPUT_MODE_VEL_RAMP, - INPUT_MODE_POS_FILTER, - INPUT_MODE_MIX_CHANNELS, - INPUT_MODE_TRAP_TRAJ, - INPUT_MODE_CURRENT_RAMP, - INPUT_MODE_MIRROR, - }; - typedef struct { uint32_t index = 0; float cogging_map[3600]; @@ -48,7 +18,7 @@ public: bool anticogging_enabled = true; } Anticogging_t; - struct Config_t { + struct Config_t : ConfigIntf { ControlMode control_mode = CONTROL_MODE_POSITION_CONTROL; //see: ControlMode InputMode input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode float pos_gain = 20.0f; // [(counts/s) / counts] @@ -72,6 +42,10 @@ public: uint8_t axis_to_mirror = -1; float mirror_ratio = 1.0f; uint8_t load_encoder_axis = -1; // default depends on Axis number and is set in load_configuration() + + // custom setters + Controller* parent; + void set_input_filter_bandwidth(float value) { input_filter_bandwidth = value; parent->update_filter_gains(); } }; explicit Controller(Config_t& config); @@ -121,56 +95,8 @@ public: bool anticogging_valid_ = false; - // Communication protocol definitions - auto make_protocol_definitions() { - return make_protocol_member_list( - make_protocol_property("error", &error_), - make_protocol_property("input_pos", &input_pos_, - [](void* ctx) { static_cast(ctx)->input_pos_updated(); }, this), - make_protocol_property("input_vel", &input_vel_), - make_protocol_property("input_current", &input_current_), - make_protocol_ro_property("pos_setpoint", &pos_setpoint_), - make_protocol_ro_property("vel_setpoint", &vel_setpoint_), - make_protocol_ro_property("current_setpoint", ¤t_setpoint_), - make_protocol_ro_property("trajectory_done", &trajectory_done_), - make_protocol_property("vel_integrator_current", &vel_integrator_current_), - make_protocol_property("anticogging_valid", &anticogging_valid_), - make_protocol_property("gain_scheduling_width", &config_.gain_scheduling_width), - make_protocol_object("config", - make_protocol_property("enable_vel_limit", &config_.enable_vel_limit), - make_protocol_property("enable_current_mode_vel_limit", &config_.enable_current_mode_vel_limit), - make_protocol_property("enable_gain_scheduling", &config_.enable_gain_scheduling), - make_protocol_property("enable_overspeed_error", &config_.enable_overspeed_error), - make_protocol_property("control_mode", &config_.control_mode), - make_protocol_property("input_mode", &config_.input_mode), - make_protocol_property("pos_gain", &config_.pos_gain), - make_protocol_property("vel_gain", &config_.vel_gain), - make_protocol_property("vel_integrator_gain", &config_.vel_integrator_gain), - make_protocol_property("vel_limit", &config_.vel_limit), - make_protocol_property("vel_limit_tolerance", &config_.vel_limit_tolerance), - make_protocol_property("vel_ramp_rate", &config_.vel_ramp_rate), - make_protocol_property("current_ramp_rate", &config_.current_ramp_rate), - make_protocol_property("homing_speed", &config_.homing_speed), - make_protocol_property("inertia", &config_.inertia), - make_protocol_property("axis_to_mirror", &config_.axis_to_mirror), - make_protocol_property("mirror_ratio", &config_.mirror_ratio), - make_protocol_property("load_encoder_axis", &config_.load_encoder_axis), - make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth, - [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this), - make_protocol_object("anticogging", - make_protocol_ro_property("index", &config_.anticogging.index), - make_protocol_property("pre_calibrated", &config_.anticogging.pre_calibrated), - make_protocol_ro_property("calib_anticogging", &config_.anticogging.calib_anticogging), - make_protocol_property("calib_pos_threshold", &config_.anticogging.calib_pos_threshold), - make_protocol_property("calib_vel_threshold", &config_.anticogging.calib_vel_threshold), - make_protocol_ro_property("cogging_ratio", &config_.anticogging.cogging_ratio), - make_protocol_property("anticogging_enabled", &config_.anticogging.anticogging_enabled))), - make_protocol_function("move_incremental", *this, &Controller::move_incremental, "displacement", "from_goal_point"), - make_protocol_function("start_anticogging_calibration", *this, &Controller::start_anticogging_calibration) - ); - } + // custom setters + void set_input_pos(float value) { input_pos_ = value; input_pos_updated(); } }; -DEFINE_ENUM_FLAG_OPERATORS(Controller::Error) - #endif // __CONTROLLER_HPP diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 77ad84b1..04471e62 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -5,33 +5,12 @@ #error "This file should not be included directly. Include odrive_main.h instead." #endif -class Encoder { +class Encoder : public EncoderIntf { public: - enum Error { - ERROR_NONE = 0, - ERROR_UNSTABLE_GAIN = 0x01, - ERROR_CPR_POLEPAIRS_MISMATCH = 0x02, - ERROR_NO_RESPONSE = 0x04, - ERROR_UNSUPPORTED_ENCODER_MODE = 0x08, - ERROR_ILLEGAL_HALL_STATE = 0x10, - ERROR_INDEX_NOT_FOUND_YET = 0x20, - ERROR_ABS_SPI_TIMEOUT = 0x40, - ERROR_ABS_SPI_COM_FAIL = 0x80, - ERROR_ABS_SPI_NOT_READY = 0x100, - }; - - enum Mode_t { - MODE_INCREMENTAL, - MODE_HALL, - MODE_SINCOS, - MODE_SPI_ABS_CUI = 0x100, //!< compatible with CUI AMT23xx - MODE_SPI_ABS_AMS = 0x101, //!< compatible with AMS AS5047P, AS5048A/AS5048B (no daisy chain support) - MODE_SPI_ABS_AEAT = 0x102, //!< not yet implemented - }; const uint32_t MODE_FLAG_ABS = 0x100; - struct Config_t { - Encoder::Mode_t mode = Encoder::MODE_INCREMENTAL; + struct Config_t : EncoderIntf::ConfigIntf { + Mode mode = MODE_INCREMENTAL; bool use_index = false; bool pre_calibrated = false; // If true, this means the offset stored in // configuration is valid and does not need @@ -53,6 +32,14 @@ public: uint16_t abs_spi_cs_gpio_pin = 1; uint16_t sincos_gpio_pin_sin = 3; uint16_t sincos_gpio_pin_cos = 4; + + // custom setters + Encoder* parent = nullptr; + void set_use_index(bool value) { use_index = value; parent->set_idx_subscribe(); } + void set_find_idx_on_lockin_only(bool value) { find_idx_on_lockin_only = value; parent->set_idx_subscribe(); } + void set_abs_spi_cs_gpio_pin(uint16_t value) { abs_spi_cs_gpio_pin = value; parent->abs_spi_cs_pin_init(); } + void set_pre_calibrated(bool value) { pre_calibrated = value; parent->check_pre_calibrated(); } + void set_bandwidth(float value) { bandwidth = value; parent->update_pll_gains(); } }; Encoder(const EncoderHardwareConfig_t& hw_config, @@ -113,7 +100,7 @@ public: uint16_t abs_spi_dma_tx_[1] = {0xFFFF}; uint16_t abs_spi_dma_rx_[1]; bool abs_spi_pos_updated_ = false; - Mode_t mode_ = MODE_INCREMENTAL; + Mode mode_ = MODE_INCREMENTAL; GPIO_TypeDef* abs_spi_cs_port_; uint16_t abs_spi_cs_pin_; uint32_t abs_spi_cr1; @@ -122,55 +109,6 @@ public: constexpr float getCoggingRatio(){ return config_.cpr / 3600.0f; } - - // Communication protocol definitions - 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_ro_property("shadow_count", &shadow_count_), - make_protocol_ro_property("count_in_cpr", &count_in_cpr_), - make_protocol_ro_property("interpolation", &interpolation_), - make_protocol_ro_property("phase", &phase_), - make_protocol_ro_property("pos_estimate", &pos_estimate_), - make_protocol_ro_property("pos_cpr", &pos_cpr_), - make_protocol_ro_property("hall_state", &hall_state_), - make_protocol_ro_property("vel_estimate", &vel_estimate_), - make_protocol_ro_property("calib_scan_response", &calib_scan_response_), - make_protocol_property("pos_abs", &pos_abs_), - make_protocol_ro_property("spi_error_rate", &spi_error_rate_), - - make_protocol_object("config", - make_protocol_property("mode", &config_.mode), - make_protocol_property("use_index", &config_.use_index, - [](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("abs_spi_cs_gpio_pin", &config_.abs_spi_cs_gpio_pin, - [](void* ctx) { static_cast(ctx)->abs_spi_cs_pin_init(); }, 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), - make_protocol_property("pre_calibrated", &config_.pre_calibrated, - [](void* ctx) { static_cast(ctx)->check_pre_calibrated(); }, this), - make_protocol_property("offset_float", &config_.offset_float), - make_protocol_property("enable_phase_interpolation", &config_.enable_phase_interpolation), - make_protocol_property("bandwidth", &config_.bandwidth, - [](void* ctx) { static_cast(ctx)->update_pll_gains(); }, this), - make_protocol_property("calib_range", &config_.calib_range), - make_protocol_property("calib_scan_distance", &config_.calib_scan_distance), - make_protocol_property("calib_scan_omega", &config_.calib_scan_omega), - make_protocol_property("idx_search_unidirectional", &config_.idx_search_unidirectional), - make_protocol_property("ignore_illegal_hall_state", &config_.ignore_illegal_hall_state), - make_protocol_property("sincos_gpio_pin_sin", &config_.sincos_gpio_pin_sin), - make_protocol_property("sincos_gpio_pin_cos", &config_.sincos_gpio_pin_cos) - ), - make_protocol_function("set_linear_count", *this, &Encoder::set_linear_count, "count") - ); - } }; -DEFINE_ENUM_FLAG_OPERATORS(Encoder::Error) - #endif // __ENCODER_HPP diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index 6983fa6c..e9412f89 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -11,6 +11,12 @@ class Endstop { bool enabled = false; bool is_active_high = false; bool pullup = true; + + // custom setters + Endstop* parent = nullptr + void set_gpio_num(uint16_t value) { gpio_num = value; parent->update_config(); } + void set_enabled(uint32_t value) { enabled = value; parent->update_config(); } + void set_debounce_ms(uint32_t value) { debounce_ms = value; parent->update_config(); } }; explicit Endstop(Endstop::Config_t& config); @@ -26,21 +32,6 @@ class Endstop { bool endstop_state_ = false; - auto make_protocol_definitions() { - return make_protocol_member_list( - make_protocol_ro_property("endstop_state", &endstop_state_), - make_protocol_object("config", - make_protocol_property("gpio_num", &config_.gpio_num, - [](void* ctx) { static_cast(ctx)->update_config(); }, this), - make_protocol_property("enabled", &config_.enabled, - [](void* ctx) { static_cast(ctx)->update_config(); }, this), - make_protocol_property("offset", &config_.offset), - make_protocol_property("is_active_high", &config_.is_active_high), - make_protocol_property("pullup", &config_.pullup), - make_protocol_property("debounce_ms", &config_.debounce_ms, - [](void* ctx) { static_cast(ctx)->update_config(); }, this))); - } - private: bool pin_state_ = false; float pos_when_pressed_ = 0.0f; diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index f0e32f0c..5c2886cf 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -613,11 +613,11 @@ void update_brake_current() { } // Don't start braking until -Ibus > regen_current_allowed - float brake_current = -Ibus_sum - board_config.max_regen_current; - float brake_duty = brake_current * board_config.brake_resistance / vbus_voltage; + float brake_current = -Ibus_sum - odrv.config_.max_regen_current; + float brake_duty = brake_current * odrv.config_.brake_resistance / vbus_voltage; - if (board_config.enable_dc_bus_overvoltage_ramp && (board_config.brake_resistance > 0.0f) && (board_config.dc_bus_overvoltage_ramp_start < board_config.dc_bus_overvoltage_ramp_end)) { - brake_duty += std::fmax((vbus_voltage - board_config.dc_bus_overvoltage_ramp_start) / (board_config.dc_bus_overvoltage_ramp_end - board_config.dc_bus_overvoltage_ramp_start), 0.0f); + if (odrv.config_.enable_dc_bus_overvoltage_ramp && (odrv.config_.brake_resistance > 0.0f) && (odrv.config_.dc_bus_overvoltage_ramp_start < odrv.config_.dc_bus_overvoltage_ramp_end)) { + brake_duty += std::fmax((vbus_voltage - odrv.config_.dc_bus_overvoltage_ramp_start) / (odrv.config_.dc_bus_overvoltage_ramp_end - odrv.config_.dc_bus_overvoltage_ramp_start), 0.0f); } if (std::isnan(brake_duty)) { @@ -634,15 +634,15 @@ void update_brake_current() { brake_duty = std::clamp(brake_duty, 0.0f, 0.95f); // Special handling to avoid the case 0.0/0.0 == NaN. - Ibus_sum += brake_duty ? (brake_duty * vbus_voltage / board_config.brake_resistance) : 0.0f; + Ibus_sum += brake_duty ? (brake_duty * vbus_voltage / odrv.config_.brake_resistance) : 0.0f; ibus_ = Ibus_sum; - if (Ibus_sum > board_config.dc_max_positive_current) { + if (Ibus_sum > odrv.config_.dc_max_positive_current) { low_level_fault(Motor::ERROR_DC_BUS_OVER_CURRENT); return; } - if (Ibus_sum < board_config.dc_max_negative_current) { + if (Ibus_sum < odrv.config_.dc_max_negative_current) { low_level_fault(Motor::ERROR_DC_BUS_OVER_REGEN_CURRENT); return; } @@ -715,7 +715,7 @@ void pwm_in_init() { #else int gpio_num = 4; { #endif - if (is_endpoint_ref_valid(board_config.pwm_mappings[gpio_num - 1].endpoint)) { + if (fibre::is_endpoint_ref_valid(odrv.config_.pwm_mappings[gpio_num - 1].endpoint)) { GPIO_InitStruct.Pin = get_gpio_pin_by_pin(gpio_num); HAL_GPIO_DeInit(get_gpio_port_by_pin(gpio_num), get_gpio_pin_by_pin(gpio_num)); HAL_GPIO_Init(get_gpio_port_by_pin(gpio_num), &GPIO_InitStruct); @@ -742,14 +742,10 @@ void handle_pulse(int gpio_num, uint32_t high_time) { if (high_time > PWM_MAX_HIGH_TIME) high_time = PWM_MAX_HIGH_TIME; float fraction = (float)(high_time - PWM_MIN_HIGH_TIME) / (float)(PWM_MAX_HIGH_TIME - PWM_MIN_HIGH_TIME); - float value = board_config.pwm_mappings[gpio_num - 1].min + - (fraction * (board_config.pwm_mappings[gpio_num - 1].max - board_config.pwm_mappings[gpio_num - 1].min)); + float value = odrv.config_.pwm_mappings[gpio_num - 1].min + + (fraction * (odrv.config_.pwm_mappings[gpio_num - 1].max - odrv.config_.pwm_mappings[gpio_num - 1].min)); - Endpoint* endpoint = get_endpoint(board_config.pwm_mappings[gpio_num - 1].endpoint); - if (!endpoint) - return; - - endpoint->set_from_float(value); + fibre::set_endpoint_from_float(odrv.config_.pwm_mappings[gpio_num - 1].endpoint, value); } void pwm_in_cb(int channel, uint32_t timestamp) { @@ -780,16 +776,16 @@ static void update_analog_endpoint(const struct PWMMapping_t *map, int gpio) { float fraction = get_adc_voltage(get_gpio_port_by_pin(gpio), get_gpio_pin_by_pin(gpio)) / 3.3f; float value = map->min + (fraction * (map->max - map->min)); - get_endpoint(map->endpoint)->set_from_float(value); + fibre::set_endpoint_from_float(map->endpoint, value); } static void analog_polling_thread(void *) { while (true) { for (int i = 0; i < GPIO_COUNT; i++) { - struct PWMMapping_t *map = &board_config.analog_mappings[i]; + struct PWMMapping_t *map = &odrv.config_.analog_mappings[i]; - if (is_endpoint_ref_valid(map->endpoint)) + if (fibre::is_endpoint_ref_valid(map->endpoint)) update_analog_endpoint(map, i + 1); } osDelay(10); diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index d3864ed0..5ea0a827 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -10,7 +10,6 @@ #include #include -BoardConfig_t board_config; ODriveCAN::Config_t can_config; Encoder::Config_t encoder_configs[AXIS_COUNT]; SensorlessEstimator::Config_t sensorless_configs[AXIS_COUNT]; @@ -20,12 +19,10 @@ Axis::Config_t axis_configs[AXIS_COUNT]; TrapezoidalTrajectory::Config_t trap_configs[AXIS_COUNT]; Endstop::Config_t min_endstop_configs[AXIS_COUNT]; Endstop::Config_t max_endstop_configs[AXIS_COUNT]; -bool user_config_loaded_; - -SystemStats_t system_stats_; std::array axes; ODriveCAN *odCAN = nullptr; +ODrive odrv{}; typedef Config< BoardConfig_t, @@ -39,9 +36,9 @@ typedef Config< Endstop::Config_t[AXIS_COUNT], Axis::Config_t[AXIS_COUNT]> ConfigFormat; -void save_configuration(void) { +void ODrive::save_configuration(void) { if (ConfigFormat::safe_store_config( - &board_config, + &odrv.config_, &can_config, &encoder_configs, &sensorless_configs, @@ -53,7 +50,7 @@ void save_configuration(void) { &axis_configs)) { printf("saving configuration failed\r\n"); osDelay(5); } else { - user_config_loaded_ = true; + odrv.user_config_loaded_ = true; } } @@ -61,7 +58,7 @@ extern "C" int load_configuration(void) { // Try to load configs if (NVM_init() || ConfigFormat::safe_load_config( - &board_config, + &odrv.config_, &can_config, &encoder_configs, &sensorless_configs, @@ -72,7 +69,7 @@ extern "C" int load_configuration(void) { &max_endstop_configs, &axis_configs)) { //If loading failed, restore defaults - board_config = BoardConfig_t(); + odrv.config_ = BoardConfig_t(); can_config = ODriveCAN::Config_t(); for (size_t i = 0; i < AXIS_COUNT; ++i) { encoder_configs[i] = Encoder::Config_t(); @@ -89,12 +86,12 @@ extern "C" int load_configuration(void) { controller_configs[i].load_encoder_axis = i; } } else { - user_config_loaded_ = true; + odrv.user_config_loaded_ = true; } - return user_config_loaded_; + return odrv.user_config_loaded_; } -void erase_configuration(void) { +void ODrive::erase_configuration(void) { NVM_erase(); // FIXME: this reboot is a workaround because we don't want the next save_configuration @@ -105,8 +102,8 @@ void erase_configuration(void) { NVIC_SystemReset(); } -void enter_dfu_mode() { - if ((hw_version_major == 3) && (hw_version_minor >= 5)) { +void ODrive::enter_dfu_mode() { + if ((hw_version_major_ == 3) && (hw_version_minor_ >= 5)) { __asm volatile ("CPSID I\n\t":::"memory"); // disable interrupts _reboot_cookie = 0xDEADBEEF; NVIC_SystemReset(); @@ -125,7 +122,7 @@ void enter_dfu_mode() { extern "C" int construct_objects(){ #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 - if (board_config.enable_i2c_instead_of_can) { + if (odrv.config_.enable_i2c_instead_of_can) { // Set up the direction GPIO as input GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; @@ -149,7 +146,7 @@ extern "C" int construct_objects(){ MX_CAN1_Init(); HAL_UART_DeInit(&huart4); - huart4.Init.BaudRate = board_config.uart_baudrate; + huart4.Init.BaudRate = odrv.config_.uart_baudrate; HAL_UART_Init(&huart4); // Init general user ADC on some GPIOs. @@ -170,7 +167,7 @@ extern "C" int construct_objects(){ #endif // Construct all objects. - odCAN = new ODriveCAN(&hcan1, can_config); + odCAN = new ODriveCAN(can_config, &hcan1); for (size_t i = 0; i < AXIS_COUNT; ++i) { Encoder *encoder = new Encoder(hw_configs[i].encoder_config, encoder_configs[i], motor_configs[i]); @@ -184,8 +181,14 @@ extern "C" int construct_objects(){ Endstop *max_endstop = new Endstop(max_endstop_configs[i]); axes[i] = new Axis(i, hw_configs[i].axis_config, axis_configs[i], *encoder, *sensorless_estimator, *controller, *motor, *trap, *min_endstop, *max_endstop); + + controller_configs[i].parent = controller; + encoder_configs[i].parent = encoder; + motor_configs[i].parent = motor; + min_endstop_configs[i].parent = min_endstop; + max_endstop_configs[i].parent = max_endstop; + axis_configs[i].parent = axes[i]; } - initTree(); return 0; } @@ -199,27 +202,27 @@ void vApplicationStackOverflowHook(xTaskHandle *pxTask, signed portCHAR *pcTaskN for (;;); // TODO: safe action } void vApplicationIdleHook(void) { - if (system_stats_.fully_booted) { - system_stats_.uptime = xTaskGetTickCount(); - system_stats_.min_heap_space = xPortGetMinimumEverFreeHeapSize(); - system_stats_.min_stack_space_comms = uxTaskGetStackHighWaterMark(comm_thread) * sizeof(StackType_t); - system_stats_.min_stack_space_axis0 = uxTaskGetStackHighWaterMark(axes[0]->thread_id_) * sizeof(StackType_t); - system_stats_.min_stack_space_axis1 = uxTaskGetStackHighWaterMark(axes[1]->thread_id_) * sizeof(StackType_t); - system_stats_.min_stack_space_usb = uxTaskGetStackHighWaterMark(usb_thread) * sizeof(StackType_t); - system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread) * sizeof(StackType_t); - system_stats_.min_stack_space_usb_irq = uxTaskGetStackHighWaterMark(usb_irq_thread) * sizeof(StackType_t); - system_stats_.min_stack_space_startup = uxTaskGetStackHighWaterMark(defaultTaskHandle) * sizeof(StackType_t); - system_stats_.min_stack_space_can = uxTaskGetStackHighWaterMark(odCAN->thread_id_) * sizeof(StackType_t); + if (odrv.system_stats_.fully_booted) { + odrv.system_stats_.uptime = xTaskGetTickCount(); + odrv.system_stats_.min_heap_space = xPortGetMinimumEverFreeHeapSize(); + odrv.system_stats_.min_stack_space_comms = uxTaskGetStackHighWaterMark(comm_thread) * sizeof(StackType_t); + odrv.system_stats_.min_stack_space_axis0 = uxTaskGetStackHighWaterMark(axes[0]->thread_id_) * sizeof(StackType_t); + odrv.system_stats_.min_stack_space_axis1 = uxTaskGetStackHighWaterMark(axes[1]->thread_id_) * sizeof(StackType_t); + odrv.system_stats_.min_stack_space_usb = uxTaskGetStackHighWaterMark(usb_thread) * sizeof(StackType_t); + odrv.system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread) * sizeof(StackType_t); + odrv.system_stats_.min_stack_space_usb_irq = uxTaskGetStackHighWaterMark(usb_irq_thread) * sizeof(StackType_t); + odrv.system_stats_.min_stack_space_startup = uxTaskGetStackHighWaterMark(defaultTaskHandle) * sizeof(StackType_t); + odrv.system_stats_.min_stack_space_can = uxTaskGetStackHighWaterMark(odCAN->thread_id_) * sizeof(StackType_t); // Actual usage, in bytes, so we don't have to math - system_stats_.stack_usage_axis0 = axes[0]->stack_size_ - system_stats_.min_stack_space_axis0; - system_stats_.stack_usage_axis1 = axes[1]->stack_size_ - system_stats_.min_stack_space_axis1; - system_stats_.stack_usage_comms = stack_size_comm_thread - system_stats_.min_stack_space_comms; - system_stats_.stack_usage_usb = stack_size_usb_thread - system_stats_.min_stack_space_usb; - system_stats_.stack_usage_uart = stack_size_uart_thread - system_stats_.min_stack_space_uart; - system_stats_.stack_usage_usb_irq = stack_size_usb_irq_thread - system_stats_.min_stack_space_usb_irq; - system_stats_.stack_usage_startup = stack_size_default_task - system_stats_.min_stack_space_startup; - system_stats_.stack_usage_can = odCAN->stack_size_ - system_stats_.min_stack_space_can; + odrv.system_stats_.stack_usage_axis0 = axes[0]->stack_size_ - odrv.system_stats_.min_stack_space_axis0; + odrv.system_stats_.stack_usage_axis1 = axes[1]->stack_size_ - odrv.system_stats_.min_stack_space_axis1; + odrv.system_stats_.stack_usage_comms = stack_size_comm_thread - odrv.system_stats_.min_stack_space_comms; + odrv.system_stats_.stack_usage_usb = stack_size_usb_thread - odrv.system_stats_.min_stack_space_usb; + odrv.system_stats_.stack_usage_uart = stack_size_uart_thread - odrv.system_stats_.min_stack_space_uart; + odrv.system_stats_.stack_usage_usb_irq = stack_size_usb_irq_thread - odrv.system_stats_.min_stack_space_usb_irq; + odrv.system_stats_.stack_usage_startup = stack_size_default_task - odrv.system_stats_.min_stack_space_startup; + odrv.system_stats_.stack_usage_can = odCAN->stack_size_ - odrv.system_stats_.min_stack_space_can; } } } @@ -230,7 +233,7 @@ int odrive_main(void) { // TODO: make dynamically reconfigurable #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 - if (board_config.enable_uart) { + if (odrv.config_.enable_uart) { SetGPIO12toUART(); } #endif @@ -271,6 +274,6 @@ int odrive_main(void) { start_analog_thread(); - system_stats_.fully_booted = true; + odrv.system_stats_.fully_booted = true; return 0; } diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 6b8c8d0f..f924f13f 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -127,7 +127,7 @@ bool Motor::check_DRV_fault() { GPIO_PinState nFAULT_state = HAL_GPIO_ReadPin(gate_driver_config_.nFAULT_port, gate_driver_config_.nFAULT_pin); if (nFAULT_state == GPIO_PIN_RESET) { // Update DRV Fault Code - drv_fault_ = DRV8301_getFaultType(&gate_driver_); + gate_driver_exported_.drv_fault = (GateDriverIntf::DrvFault)DRV8301_getFaultType(&gate_driver_); // Update/Cache all SPI device registers // DRV_SPI_8301_Vars_t* local_regs = &gate_driver_regs_; // local_regs->RcvCmd = true; diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 67a0751d..88a735e3 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -7,35 +7,8 @@ #include "drv8301.h" -class Motor { +class Motor : public MotorIntf { public: - enum Error { - ERROR_NONE = 0, - ERROR_PHASE_RESISTANCE_OUT_OF_RANGE = 0x0001, - ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE = 0x0002, - ERROR_ADC_FAILED = 0x0004, - ERROR_DRV_FAULT = 0x0008, - ERROR_CONTROL_DEADLINE_MISSED = 0x0010, - ERROR_NOT_IMPLEMENTED_MOTOR_TYPE = 0x0020, - ERROR_BRAKE_CURRENT_OUT_OF_RANGE = 0x0040, - ERROR_MODULATION_MAGNITUDE = 0x0080, - ERROR_BRAKE_DEADTIME_VIOLATION = 0x0100, - ERROR_UNEXPECTED_TIMER_CALLBACK = 0x0200, - ERROR_CURRENT_SENSE_SATURATION = 0x0400, - ERROR_INVERTER_OVER_TEMP = 0x0800, - ERROR_CURRENT_LIMIT_VIOLATION = 0x1000, - ERROR_BRAKE_DUTY_CYCLE_NAN = 0x2000, - ERROR_DC_BUS_OVER_REGEN_CURRENT = 0x4000, // too much current pushed into the power supply - ERROR_DC_BUS_OVER_CURRENT = 0x8000, // too much current pulled out of the power supply - }; - - enum MotorType_t { - MOTOR_TYPE_HIGH_CURRENT = 0, - // MOTOR_TYPE_LOW_CURRENT = 1, //Not yet implemented - MOTOR_TYPE_GIMBAL = 2, - MOTOR_TYPE_ACIM = 3, - }; - struct Iph_BC_t { float phB; float phC; @@ -65,7 +38,7 @@ public: // NOTE: for gimbal motors, all units of A are instead V. // example: vel_gain is [V/(count/s)] instead of [A/(count/s)] // example: current_lim and calibration_current will instead determine the maximum voltage applied to the motor. - struct Config_t { + struct Config_t : public ConfigIntf { bool pre_calibrated = false; // can be set to true to indicate that all values here are valid int32_t pole_pairs = 7; float calibration_current = 10.0f; // [A] @@ -73,7 +46,7 @@ public: float phase_inductance = 0.0f; // to be set by measure_phase_inductance float phase_resistance = 0.0f; // to be set by measure_phase_resistance int32_t direction = 0; // 1 or -1 (0 = unspecified) - MotorType_t motor_type = MOTOR_TYPE_HIGH_CURRENT; + MotorType motor_type = MOTOR_TYPE_HIGH_CURRENT; // Read out max_allowed_current to see max supported value for current_lim. // float current_lim = 70.0f; //[A] float current_lim = 10.0f; //[A] @@ -89,6 +62,16 @@ public: bool acim_autoflux_enable = false; float acim_autoflux_attack_gain = 10.0f; float acim_autoflux_decay_gain = 1.0f; + + // custom property setters + Motor* parent = nullptr; + void set_pre_calibrated(bool value) { + pre_calibrated = value; + parent->is_calibrated_ = parent->is_calibrated_ || parent->config_.pre_calibrated; + } + void set_phase_inductance(float value) { phase_inductance = value; parent->update_current_controller_gains(); } + void set_phase_resistance(float value) { phase_resistance = value; parent->update_current_controller_gains(); } + void set_current_control_bandwidth(float value) { current_control_bandwidth = value; parent->update_current_controller_gains(); } }; enum TimingLog_t { @@ -107,13 +90,6 @@ public: TIMING_LOG_NUM_SLOTS }; - enum ArmedState_t { - ARMED_STATE_DISARMED, - ARMED_STATE_WAITING_FOR_TIMINGS, - ARMED_STATE_WAITING_FOR_UPDATE, - ARMED_STATE_ARMED, - }; - Motor(const MotorHardwareConfig_t& hw_config, const GateDriverHardwareConfig_t& gate_driver_config, Config_t& config); @@ -160,13 +136,17 @@ public: bool next_timings_valid_ = false; uint16_t last_cpu_time_ = 0; int timing_log_index_ = 0; - uint16_t timing_log_[TIMING_LOG_NUM_SLOTS] = { 0 }; + struct { + uint16_t& operator[](size_t idx) { return content[idx]; } + uint16_t& get(size_t idx) { return content[idx]; } + uint16_t content[TIMING_LOG_NUM_SLOTS]; + } timing_log_; // variables exposed on protocol Error error_ = ERROR_NONE; // Do not write to this variable directly! // It is for exclusive use by the safety_critical_... functions. - ArmedState_t armed_state_ = ARMED_STATE_DISARMED; + ArmedState armed_state_ = ARMED_STATE_DISARMED; bool is_calibrated_ = config_.pre_calibrated; Iph_BC_t current_meas_ = {0.0f, 0.0f}; Iph_BC_t DC_calib_ = {0.0f, 0.0f}; @@ -190,95 +170,12 @@ public: .async_phase_vel = 0.0f, .async_phase_offset = 0.0f, }; - DRV8301_FaultType_e drv_fault_ = DRV8301_FaultType_NoFault; + struct : GateDriverIntf { + DrvFault drv_fault = DRV_FAULT_NO_FAULT; + } gate_driver_exported_; DRV_SPI_8301_Vars_t gate_driver_regs_; //Local view of DRV registers (initialized by DRV8301_setup) float thermal_current_lim_ = 10.0f; //[A] float inverter_temp_ = 20.0f; - - // Communication protocol definitions - auto make_protocol_definitions() { - return make_protocol_member_list( - make_protocol_property("error", &error_), - make_protocol_ro_property("armed_state", &armed_state_), - make_protocol_ro_property("is_calibrated", &is_calibrated_), - make_protocol_ro_property("current_meas_phB", ¤t_meas_.phB), - make_protocol_ro_property("current_meas_phC", ¤t_meas_.phC), - make_protocol_property("DC_calib_phB", &DC_calib_.phB), - make_protocol_property("DC_calib_phC", &DC_calib_.phC), - make_protocol_property("phase_current_rev_gain", &phase_current_rev_gain_), - make_protocol_ro_property("thermal_current_lim", &thermal_current_lim_), - make_protocol_ro_property("inverter_temp", &inverter_temp_), - make_protocol_object("current_control", - make_protocol_property("p_gain", ¤t_control_.p_gain), - make_protocol_property("i_gain", ¤t_control_.i_gain), - make_protocol_property("v_current_control_integral_d", ¤t_control_.v_current_control_integral_d), - make_protocol_property("v_current_control_integral_q", ¤t_control_.v_current_control_integral_q), - make_protocol_property("Ibus", ¤t_control_.Ibus), - make_protocol_property("final_v_alpha", ¤t_control_.final_v_alpha), - make_protocol_property("final_v_beta", ¤t_control_.final_v_beta), - make_protocol_property("Id_setpoint", ¤t_control_.Id_setpoint), - make_protocol_ro_property("Iq_setpoint", ¤t_control_.Iq_setpoint), - make_protocol_property("Iq_measured", ¤t_control_.Iq_measured), - make_protocol_property("Id_measured", ¤t_control_.Id_measured), - make_protocol_property("I_measured_report_filter_k", ¤t_control_.I_measured_report_filter_k), - make_protocol_ro_property("max_allowed_current", ¤t_control_.max_allowed_current), - make_protocol_ro_property("overcurrent_trip_level", ¤t_control_.overcurrent_trip_level), - make_protocol_property("acim_rotor_flux", ¤t_control_.acim_rotor_flux), - make_protocol_ro_property("async_phase_vel", ¤t_control_.async_phase_vel), - make_protocol_property("async_phase_offset", ¤t_control_.async_phase_offset) - ), - make_protocol_object("gate_driver", - make_protocol_ro_property("drv_fault", &drv_fault_) - // make_protocol_ro_property("status_reg_1", &gate_driver_regs_.Stat_Reg_1_Value), - // make_protocol_ro_property("status_reg_2", &gate_driver_regs_.Stat_Reg_2_Value), - // make_protocol_ro_property("ctrl_reg_1", &gate_driver_regs_.Ctrl_Reg_1_Value), - // make_protocol_ro_property("ctrl_reg_2", &gate_driver_regs_.Ctrl_Reg_2_Value) - ), - make_protocol_object("timing_log", - make_protocol_ro_property("TIMING_LOG_GENERAL", &timing_log_[TIMING_LOG_GENERAL]), - make_protocol_ro_property("TIMING_LOG_ADC_CB_I", &timing_log_[TIMING_LOG_ADC_CB_I]), - make_protocol_ro_property("TIMING_LOG_ADC_CB_DC", &timing_log_[TIMING_LOG_ADC_CB_DC]), - make_protocol_ro_property("TIMING_LOG_MEAS_R", &timing_log_[TIMING_LOG_MEAS_R]), - make_protocol_ro_property("TIMING_LOG_MEAS_L", &timing_log_[TIMING_LOG_MEAS_L]), - make_protocol_ro_property("TIMING_LOG_ENC_CALIB", &timing_log_[TIMING_LOG_ENC_CALIB]), - make_protocol_ro_property("TIMING_LOG_IDX_SEARCH", &timing_log_[TIMING_LOG_IDX_SEARCH]), - make_protocol_ro_property("TIMING_LOG_FOC_VOLTAGE", &timing_log_[TIMING_LOG_FOC_VOLTAGE]), - make_protocol_ro_property("TIMING_LOG_FOC_CURRENT", &timing_log_[TIMING_LOG_FOC_CURRENT]), - make_protocol_ro_property("TIMING_LOG_SPI_START", &timing_log_[TIMING_LOG_SPI_START]), - make_protocol_ro_property("TIMING_LOG_SAMPLE_NOW", &timing_log_[TIMING_LOG_SAMPLE_NOW]), - make_protocol_ro_property("TIMING_LOG_SPI_END", &timing_log_[TIMING_LOG_SPI_END]) - ), - make_protocol_object("config", - make_protocol_property("pre_calibrated", &config_.pre_calibrated, - [](void* ctx) { static_cast(ctx)->is_calibrated_ = - static_cast(ctx)->is_calibrated_ || static_cast(ctx)->config_.pre_calibrated; }, this), - make_protocol_property("pole_pairs", &config_.pole_pairs), - make_protocol_property("calibration_current", &config_.calibration_current), - make_protocol_property("resistance_calib_max_voltage", &config_.resistance_calib_max_voltage), - make_protocol_property("phase_inductance", &config_.phase_inductance, - [](void* ctx) { static_cast(ctx)->update_current_controller_gains(); }, this), - make_protocol_property("phase_resistance", &config_.phase_resistance, - [](void* ctx) { static_cast(ctx)->update_current_controller_gains(); }, this), - make_protocol_property("direction", &config_.direction), - make_protocol_property("motor_type", &config_.motor_type), - make_protocol_property("current_lim", &config_.current_lim), - make_protocol_property("current_lim_margin", &config_.current_lim_margin), - make_protocol_property("inverter_temp_limit_lower", &config_.inverter_temp_limit_lower), - make_protocol_property("inverter_temp_limit_upper", &config_.inverter_temp_limit_upper), - make_protocol_property("requested_current_range", &config_.requested_current_range), - make_protocol_property("current_control_bandwidth", &config_.current_control_bandwidth, - [](void* ctx) { static_cast(ctx)->update_current_controller_gains(); }, this), - make_protocol_property("acim_slip_velocity", &config_.acim_slip_velocity), - make_protocol_property("acim_gain_min_flux", &config_.acim_gain_min_flux), - make_protocol_property("acim_autoflux_min_Id", &config_.acim_autoflux_min_Id), - make_protocol_property("acim_autoflux_enable", &config_.acim_autoflux_enable), - make_protocol_property("acim_autoflux_attack_gain", &config_.acim_autoflux_attack_gain), - make_protocol_property("acim_autoflux_decay_gain", &config_.acim_autoflux_decay_gain) - ) - ); - } }; -DEFINE_ENUM_FLAG_OPERATORS(Motor::Error) - #endif // __MOTOR_HPP diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 6ae7594e..a3eb4447 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -10,6 +10,8 @@ #ifdef __cplusplus #include +#include +#include extern "C" { #endif @@ -41,11 +43,13 @@ static const int current_meas_hz = CURRENT_MEAS_HZ; // extern const float elec_rad_per_enc; extern uint32_t _reboot_cookie; -extern bool user_config_loaded_; extern uint64_t serial_number; extern char serial_number_str[13]; +#ifdef __cplusplus +} + typedef struct { bool fully_booted; uint32_t uptime; // [ms] @@ -67,11 +71,10 @@ typedef struct { uint32_t stack_usage_usb_irq; uint32_t stack_usage_startup; uint32_t stack_usage_can; -} SystemStats_t; -extern SystemStats_t system_stats_; -#ifdef __cplusplus -} + USBStats_t& usb = usb_stats_; + I2CStats_t& i2c = i2c_stats_; +} SystemStats_t; struct PWMMapping_t { endpoint_ref_t endpoint; @@ -148,8 +151,6 @@ struct BoardConfig_t { */ uint32_t uart_baudrate = 115200; }; -extern BoardConfig_t board_config; -extern bool user_config_loaded_; // Forward Declarations class Axis; @@ -176,6 +177,7 @@ inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b) { return reinterpret_cast inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b) { return reinterpret_cast(reinterpret_cast&>(a) ^= static_cast>(b)); } \ inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast(~static_cast>(a)); } +#include "autogen/interfaces.hpp" // ODrive specific includes #include @@ -190,12 +192,79 @@ inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast(~static_c #include #include -#endif // __cplusplus +#include "autogen/version.h" // general system functions defined in main.cpp -void save_configuration(void); -void erase_configuration(void); -void enter_dfu_mode(void); +class ODrive : public OdriveIntf { +public: + void save_configuration() override; + void erase_configuration() override; + void reboot() override { NVIC_SystemReset(); } + void enter_dfu_mode() override; + + float get_oscilloscope_val(uint32_t index) override { + return oscilloscope[index]; + } + + float get_adc_voltage(uint32_t gpio) override { + return ::get_adc_voltage(get_gpio_port_by_pin(gpio), get_gpio_pin_by_pin(gpio)); + } + + int32_t test_function(int32_t delta) override { + static int cnt = 0; + return cnt += delta; + } + + Axis& get_axis(int num) { return *axes[num]; } + ODriveCAN& get_can() { return *odCAN; } + + float& vbus_voltage_ = ::vbus_voltage; // TODO: make this the actual variable + float& ibus_ = ::ibus_; // TODO: make this the actual variable + + const uint64_t& serial_number_ = ::serial_number; + +#if HW_VERSION_MAJOR == 3 + // Determine start address of the OTP struct: + // The OTP is organized into 16-byte blocks. + // If the first block starts with "0xfe" we use the first block. + // If the first block starts with "0x00" and the second block starts with "0xfe", + // we use the second block. This gives the user the chance to screw up once. + // If none of the above is the case, we consider the OTP invalid (otp_ptr will be NULL). + const uint8_t* otp_ptr = + (*(uint8_t*)FLASH_OTP_BASE == 0xfe) ? (uint8_t*)FLASH_OTP_BASE : + (*(uint8_t*)FLASH_OTP_BASE != 0x00) ? NULL : + (*(uint8_t*)(FLASH_OTP_BASE + 0x10) != 0xfe) ? NULL : + (uint8_t*)(FLASH_OTP_BASE + 0x10); + + // Read hardware version from OTP if available, otherwise fall back + // to software defined version. + const uint8_t hw_version_major_ = otp_ptr ? otp_ptr[3] : HW_VERSION_MAJOR; + const uint8_t hw_version_minor_ = otp_ptr ? otp_ptr[4] : HW_VERSION_MINOR; + const uint8_t hw_version_variant_ = otp_ptr ? otp_ptr[5] : HW_VERSION_VOLTAGE; +#else +#error "not implemented" +#endif + + // the corresponding macros are defined in the autogenerated version.h + const uint8_t fw_version_major_ = FW_VERSION_MAJOR; + const uint8_t fw_version_minor_ = FW_VERSION_MINOR; + const uint8_t fw_version_revision_ = FW_VERSION_REVISION; + const uint8_t fw_version_unreleased_ = FW_VERSION_UNRELEASED; // 0 for official releases, 1 otherwise + + bool& brake_resistor_armed_ = ::brake_resistor_armed; // TODO: make this the actual variable + bool& brake_resistor_saturated_ = ::brake_resistor_saturated; // TODO: make this the actual variable + + SystemStats_t system_stats_; + + BoardConfig_t config_; + bool user_config_loaded_; + + uint32_t test_property_ = 0; +}; + +extern ODrive odrv; // defined in main.cpp + +#endif // __cplusplus #endif /* __ODRIVE_MAIN_H */ diff --git a/Firmware/MotorControl/sensorless_estimator.hpp b/Firmware/MotorControl/sensorless_estimator.hpp index dd48c705..99c339be 100644 --- a/Firmware/MotorControl/sensorless_estimator.hpp +++ b/Firmware/MotorControl/sensorless_estimator.hpp @@ -1,13 +1,8 @@ #ifndef __SENSORLESS_ESTIMATOR_HPP #define __SENSORLESS_ESTIMATOR_HPP -class SensorlessEstimator { +class SensorlessEstimator : public SensorlessEstimatorIntf { public: - enum Error { - ERROR_NONE = 0, - ERROR_UNSTABLE_GAIN = 0x01, - }; - struct Config_t { float observer_gain = 1000.0f; // [rad/s] float pll_bandwidth = 1000.0f; // [rad/s] @@ -32,25 +27,6 @@ public: float flux_state_[2] = {0.0f, 0.0f}; // [Vs] float V_alpha_beta_memory_[2] = {0.0f, 0.0f}; // [V] bool estimator_good_ = false; - - // Communication protocol definitions - auto make_protocol_definitions() { - return make_protocol_member_list( - make_protocol_property("error", &error_), - make_protocol_property("phase", &phase_), - make_protocol_property("pll_pos", &pll_pos_), - make_protocol_property("vel_estimate", &vel_estimate_), - // make_protocol_property("pll_kp", &pll_kp_), - // make_protocol_property("pll_ki", &pll_ki_), - make_protocol_object("config", - make_protocol_property("observer_gain", &config_.observer_gain), - make_protocol_property("pll_bandwidth", &config_.pll_bandwidth), - make_protocol_property("pm_flux_linkage", &config_.pm_flux_linkage) - ) - ); - } }; -DEFINE_ENUM_FLAG_OPERATORS(SensorlessEstimator::Error) - #endif /* __SENSORLESS_ESTIMATOR_HPP */ diff --git a/Firmware/MotorControl/trapTraj.hpp b/Firmware/MotorControl/trapTraj.hpp index fd142da5..c3df57b9 100644 --- a/Firmware/MotorControl/trapTraj.hpp +++ b/Firmware/MotorControl/trapTraj.hpp @@ -20,16 +20,6 @@ public: float Vmax, float Amax, float Dmax); Step_t eval(float t); - auto make_protocol_definitions() { - return make_protocol_member_list( - make_protocol_object("config", - make_protocol_property("vel_limit", &config_.vel_limit), - make_protocol_property("accel_limit", &config_.accel_limit), - make_protocol_property("decel_limit", &config_.decel_limit) - ) - ); - } - Axis* axis_ = nullptr; // set by Axis constructor Config_t& config_; diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 4577bde1..dec3547d 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -1,6 +1,17 @@ tup.include('build.lua') +tup.frule{inputs={'fibre/cpp/interfaces_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/interfaces.hpp'} +tup.frule{inputs={'fibre/cpp/function_stubs_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/function_stubs.hpp'} +tup.frule{inputs={'fibre/cpp/endpoints_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/endpoints.hpp'} +tup.frule{inputs={'ascii_type_info_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/ascii_type_info.hpp'} + +tup.frule{ + command='python ../tools/odrive/version.py --output %o', + outputs={'autogen/version.h'} +} + + -- Switch between board versions boardversion = tup.getconfig("BOARD_VERSION") if boardversion == "v3.1" then @@ -86,7 +97,6 @@ if tup.getconfig("STRICT") == "true" then FLAGS += '-Werror' end - -- C-specific flags FLAGS += '-D__weak="__attribute__((weak))"' FLAGS += '-D__packed="__attribute__((__packed__))"' @@ -145,10 +155,6 @@ build{ includes=stm_includes } -tup.frule{ - command='python ../tools/odrive/version.py --output %o', - outputs={'build/version.h'} -} build{ name='ODriveFirmware', diff --git a/Firmware/ascii_type_info_template.j2 b/Firmware/ascii_type_info_template.j2 new file mode 100644 index 00000000..200eb1ba --- /dev/null +++ b/Firmware/ascii_type_info_template.j2 @@ -0,0 +1,90 @@ +/*[# This is the original template, thus the warning below does not apply to this file #] + * ============================ WARNING ============================ + * ==== This is an autogenerated file. ==== + * ==== Any changes to this file will be lost when recompiling. ==== + * ================================================================= + * + * This file contains support functions for the ODrive ASCII protocol. + * + * TODO: might generalize this as an approach to runtime introspection. + */ + + +class TypeInfo; + +struct PropertyInfo { + const char * name; + void*(*getter)(void*); + TypeInfo* type_info; +}; + +class TypeInfo { +public: + TypeInfo(const PropertyInfo* property_table, size_t property_table_length) + : property_table_(property_table), property_table_length_(property_table_length) {} + + //virtual bool read_string(void* ctx) { return false; }; + //virtual bool write_string(void* ctx) { return false; }; + + const PropertyInfo* get_property_info(const char * name, size_t length) { + for (const PropertyInfo* prop = property_table_; prop < (property_table_ + property_table_length_); ++prop) { + if (!strncmp(name, prop->name, length)) { + return prop; + } + } + return nullptr; + } + +private: + const PropertyInfo* property_table_; + size_t property_table_length_; +}; + + +class Introspectable { +public: + Introspectable(void* obj, TypeInfo* type_info) : obj_(obj), type_info_(type_info) {} + + Introspectable get_child(const char * path, size_t length) { + Introspectable current = *this; + + const char * begin = path; + const char * end = path + length; + + while ((begin < end) && current.obj_ && current.type_info_) { + const char * end_of_token = std::find(begin, end, '.'); + const PropertyInfo* prop_info = current.type_info_->get_property_info(begin, end_of_token - begin); + if (prop_info) { + current = Introspectable{(*prop_info->getter)(obj_), prop_info->type_info}; + } else { + current = Introspectable{nullptr, nullptr}; + } + begin = std::min(end, end_of_token + 1); + } + + return current; + }; + +private: + void* obj_; + TypeInfo* type_info_; +}; + +[% for intf in interfaces.values() %] + +template +struct [[intf.fullname | to_pascal_case]]TypeInfo : TypeInfo { + static const PropertyInfo property_table[]; + static const TypeInfo singleton; +}; + +template +const PropertyInfo [[intf.name | to_pascal_case]]TypeInfo::property_table[] = { +[%- for property in intf.attributes.values() %] + {"[[property.name]]", [](void* obj){ return (void*)static_cast<[[property.type.c_type]]*>(&((T*)obj)->[[property.name | to_snake_case]]); }, [[property.type.fullname | to_pascal_case]]TypeInfo().[[property.name | to_snake_case]])>::singleton}, +[%- endfor %] +}; +template +const TypeInfo [[intf.fullname | to_pascal_case]]TypeInfo::singleton{[[intf.fullname | to_pascal_case]]TypeInfo::property_table, sizeof([[intf.fullname | to_pascal_case]]TypeInfo::property_table) / sizeof([[intf.fullname | to_pascal_case]]TypeInfo::property_table[0])}; + +[% endfor %] diff --git a/Firmware/build.lua b/Firmware/build.lua index 016f6d8e..0f91697f 100644 --- a/Firmware/build.lua +++ b/Firmware/build.lua @@ -72,7 +72,7 @@ function GCCToolchain(prefix, builddir, compiler_flags, linker_flags) else extra_outputs = {} end - if src == 'communication/communication.cpp' then extra_inputs = 'build/version.h' end -- TODO: fix hack + extra_inputs = {'autogen/version.h', 'autogen/interfaces.hpp', 'autogen/function_stubs.hpp', 'autogen/endpoints.hpp'} -- TODO: fix hack tup.frule{ inputs= { src, extra_inputs=extra_inputs }, command=compiler..' -c %f '.. diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 628dea63..4eaaf611 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -8,12 +8,15 @@ /* Includes ------------------------------------------------------------------*/ #include "odrive_main.h" -#include "../build/version.h" // autogenerated based on Git state +#include "../autogen/version.h" // autogenerated based on Git state #include "communication.h" #include "ascii_protocol.hpp" #include #include +//#include "autogen/interfaces.hpp" +//#include "autogen/ascii_type_info.hpp" + /* Private macros ------------------------------------------------------------*/ /* Private typedef -----------------------------------------------------------*/ /* Global constant data ------------------------------------------------------*/ @@ -208,19 +211,20 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& // respond(response_channel, use_checksum, "Signature: %#x", STM_ID_GetSignature()); // respond(response_channel, use_checksum, "Revision: %#x", STM_ID_GetRevision()); // respond(response_channel, use_checksum, "Flash Size: %#x KiB", STM_ID_GetFlashSize()); - respond(response_channel, use_checksum, "Hardware version: %d.%d-%dV", HW_VERSION_MAJOR, HW_VERSION_MINOR, HW_VERSION_VOLTAGE); - respond(response_channel, use_checksum, "Firmware version: %d.%d.%d", FW_VERSION_MAJOR, FW_VERSION_MINOR, FW_VERSION_REVISION); + respond(response_channel, use_checksum, "Hardware version: %d.%d-%dV", odrv.hw_version_major_, odrv.hw_version_minor_, odrv.hw_version_variant_); + respond(response_channel, use_checksum, "Firmware version: %d.%d.%d", odrv.fw_version_major_, odrv.fw_version_minor_, odrv.fw_version_revision_); respond(response_channel, use_checksum, "Serial number: %s", serial_number_str); } else if (cmd[0] == 's'){ // System if(cmd[1] == 's') { // Save config - save_configuration(); + odrv.save_configuration(); } else if (cmd[1] == 'e'){ // Erase config - erase_configuration(); + odrv.erase_configuration(); } else if (cmd[1] == 'r'){ // Reboot - NVIC_SystemReset(); + odrv.reboot(); } +#if 0 } else if (cmd[0] == 'r') { // read property char name[MAX_LINE_LENGTH]; int numscan = sscanf(cmd, "r %255s", name); @@ -256,6 +260,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "not implemented"); } } +#endif } else if (cmd[0] == 'u') { // Update axis watchdog. unsigned motor_number; diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 5ff8009a..4ec91660 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -188,7 +188,7 @@ void CANSimple::set_axis_nodeid_callback(Axis* axis, can_Message_t& msg) { } void CANSimple::set_axis_requested_state_callback(Axis* axis, can_Message_t& msg) { - axis->requested_state_ = static_cast(can_getSignal(msg, 0, 16, true)); + axis->requested_state_ = static_cast(can_getSignal(msg, 0, 16, true)); } void CANSimple::set_axis_startup_config_callback(Axis* axis, can_Message_t& msg) { // Not Implemented diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index c318040b..4e732a9c 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -13,7 +13,7 @@ #include "utils.hpp" #include "gpio_utils.hpp" -#include "../build/version.h" // autogenerated based on Git state +#include "../autogen/version.h" // autogenerated based on Git state #include #include @@ -36,50 +36,11 @@ char serial_number_str[13]; // 12 digits + null termination /* Private constant data -----------------------------------------------------*/ /* Private variables ---------------------------------------------------------*/ -#if HW_VERSION_MAJOR == 3 -// Determine start address of the OTP struct: -// The OTP is organized into 16-byte blocks. -// If the first block starts with "0xfe" we use the first block. -// If the first block starts with "0x00" and the second block starts with "0xfe", -// we use the second block. This gives the user the chance to screw up once. -// If none of the above is the case, we consider the OTP invalid (otp_ptr will be NULL). -const uint8_t* otp_ptr = - (*(uint8_t*)FLASH_OTP_BASE == 0xfe) ? (uint8_t*)FLASH_OTP_BASE : - (*(uint8_t*)FLASH_OTP_BASE != 0x00) ? NULL : - (*(uint8_t*)(FLASH_OTP_BASE + 0x10) != 0xfe) ? NULL : - (uint8_t*)(FLASH_OTP_BASE + 0x10); - -// Read hardware version from OTP if available, otherwise fall back -// to software defined version. -const uint8_t hw_version_major = otp_ptr ? otp_ptr[3] : HW_VERSION_MAJOR; -const uint8_t hw_version_minor = otp_ptr ? otp_ptr[4] : HW_VERSION_MINOR; -const uint8_t hw_version_variant = otp_ptr ? otp_ptr[5] : HW_VERSION_VOLTAGE; -#else -#error "not implemented" -#endif - -// the corresponding macros are defined in the autogenerated version.h -const uint8_t fw_version_major = FW_VERSION_MAJOR; -const uint8_t fw_version_minor = FW_VERSION_MINOR; -const uint8_t fw_version_revision = FW_VERSION_REVISION; -const uint8_t fw_version_unreleased = FW_VERSION_UNRELEASED; // 0 for official releases, 1 otherwise - osThreadId comm_thread; const uint32_t stack_size_comm_thread = 4096; // Bytes volatile bool endpoint_list_valid = false; -static uint32_t test_property = 0; - /* Private function prototypes -----------------------------------------------*/ - -auto make_protocol_definitions(PWMMapping_t& mapping) { - return make_protocol_member_list( - make_protocol_property("endpoint", &mapping.endpoint), - make_protocol_property("min", &mapping.min), - make_protocol_property("max", &mapping.max) - ); -} - /* Function implementations --------------------------------------------------*/ void init_communication(void) { @@ -96,120 +57,6 @@ void init_communication(void) { float oscilloscope[OSCILLOSCOPE_SIZE] = {0}; size_t oscilloscope_pos = 0; -// Helper class because the protocol library doesn't yet -// support non-member functions -// TODO: make this go away -class StaticFunctions { -public: - void save_configuration_helper() { save_configuration(); } - void erase_configuration_helper() { erase_configuration(); } - void NVIC_SystemReset_helper() { NVIC_SystemReset(); } - void enter_dfu_mode_helper() { enter_dfu_mode(); } - float get_oscilloscope_val(uint32_t index) { return oscilloscope[index]; } - float get_adc_voltage_(uint32_t gpio) { return get_adc_voltage(get_gpio_port_by_pin(gpio), get_gpio_pin_by_pin(gpio)); } - int32_t test_function(int32_t delta) { static int cnt = 0; return cnt += delta; } -} static_functions; - -// When adding new functions/variables to the protocol, be careful not to -// blow the communication stack. You can check comm_stack_info to see -// how much headroom you have. -static inline auto make_obj_tree() { - return make_protocol_member_list( - make_protocol_ro_property("vbus_voltage", &vbus_voltage), - make_protocol_ro_property("ibus", &ibus_), - make_protocol_ro_property("serial_number", &serial_number), - make_protocol_ro_property("hw_version_major", &hw_version_major), - make_protocol_ro_property("hw_version_minor", &hw_version_minor), - make_protocol_ro_property("hw_version_variant", &hw_version_variant), - make_protocol_ro_property("fw_version_major", &fw_version_major), - make_protocol_ro_property("fw_version_minor", &fw_version_minor), - make_protocol_ro_property("fw_version_revision", &fw_version_revision), - make_protocol_ro_property("fw_version_unreleased", &fw_version_unreleased), - make_protocol_ro_property("user_config_loaded", const_cast(&user_config_loaded_)), - make_protocol_ro_property("brake_resistor_armed", &brake_resistor_armed), - make_protocol_property("brake_resistor_saturated", &brake_resistor_saturated), - make_protocol_object("system_stats", - make_protocol_ro_property("uptime", &system_stats_.uptime), - make_protocol_ro_property("min_heap_space", &system_stats_.min_heap_space), - make_protocol_ro_property("min_stack_space_axis0", &system_stats_.min_stack_space_axis0), - make_protocol_ro_property("min_stack_space_axis1", &system_stats_.min_stack_space_axis1), - make_protocol_ro_property("min_stack_space_comms", &system_stats_.min_stack_space_comms), - make_protocol_ro_property("min_stack_space_usb", &system_stats_.min_stack_space_usb), - make_protocol_ro_property("min_stack_space_uart", &system_stats_.min_stack_space_uart), - make_protocol_ro_property("min_stack_space_can", &system_stats_.min_stack_space_can), - make_protocol_ro_property("min_stack_space_usb_irq", &system_stats_.min_stack_space_usb_irq), - make_protocol_ro_property("min_stack_space_startup", &system_stats_.min_stack_space_startup), - make_protocol_ro_property("stack_usage_axis0", &system_stats_.stack_usage_axis0), - make_protocol_ro_property("stack_usage_axis1", &system_stats_.stack_usage_axis1), - make_protocol_ro_property("stack_usage_comms", &system_stats_.stack_usage_comms), - make_protocol_ro_property("stack_usage_usb", &system_stats_.stack_usage_usb), - make_protocol_ro_property("stack_usage_uart", &system_stats_.stack_usage_uart), - make_protocol_ro_property("stack_usage_usb_irq", &system_stats_.stack_usage_usb_irq), - make_protocol_ro_property("stack_usage_startup", &system_stats_.stack_usage_startup), - make_protocol_ro_property("stack_usage_can", &system_stats_.stack_usage_can), - make_protocol_object("usb", - make_protocol_ro_property("rx_cnt", &usb_stats_.rx_cnt), - make_protocol_ro_property("tx_cnt", &usb_stats_.tx_cnt), - make_protocol_ro_property("tx_overrun_cnt", &usb_stats_.tx_overrun_cnt) - ), - make_protocol_object("i2c", - make_protocol_ro_property("addr", &i2c_stats_.addr), - make_protocol_ro_property("addr_match_cnt", &i2c_stats_.addr_match_cnt), - make_protocol_ro_property("rx_cnt", &i2c_stats_.rx_cnt), - make_protocol_ro_property("error_cnt", &i2c_stats_.error_cnt) - ) - ), - make_protocol_object("config", - make_protocol_property("brake_resistance", &board_config.brake_resistance), - make_protocol_property("max_regen_current", &board_config.max_regen_current), - // TODO: changing this currently requires a reboot - fix this - make_protocol_property("enable_uart", &board_config.enable_uart), - make_protocol_property("uart_baudrate", &board_config.uart_baudrate), // requires a reboot - make_protocol_property("enable_i2c_instead_of_can" , &board_config.enable_i2c_instead_of_can), // requires a reboot - make_protocol_property("enable_ascii_protocol_on_usb", &board_config.enable_ascii_protocol_on_usb), - make_protocol_property("dc_bus_undervoltage_trip_level", &board_config.dc_bus_undervoltage_trip_level), - make_protocol_property("dc_bus_overvoltage_trip_level", &board_config.dc_bus_overvoltage_trip_level), - make_protocol_property("enable_dc_bus_overvoltage_ramp", &board_config.enable_dc_bus_overvoltage_ramp), - make_protocol_property("dc_bus_overvoltage_ramp_start", &board_config.dc_bus_overvoltage_ramp_start), - make_protocol_property("dc_bus_overvoltage_ramp_end", &board_config.dc_bus_overvoltage_ramp_end), - make_protocol_property("dc_max_negative_current", &board_config.dc_max_negative_current), - make_protocol_property("dc_max_positive_current", &board_config.dc_max_positive_current), -#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 - make_protocol_object("gpio1_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[0])), - make_protocol_object("gpio2_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[1])), - make_protocol_object("gpio3_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[2])), -#endif - make_protocol_object("gpio4_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[3])), - - make_protocol_object("gpio3_analog_mapping", make_protocol_definitions(board_config.analog_mappings[2])), - make_protocol_object("gpio4_analog_mapping", make_protocol_definitions(board_config.analog_mappings[3])) - ), - make_protocol_object("axis0", axes[0]->make_protocol_definitions()), - make_protocol_object("axis1", axes[1]->make_protocol_definitions()), - make_protocol_object("can", odCAN->make_protocol_definitions()), - make_protocol_property("test_property", &test_property), - make_protocol_function("test_function", static_functions, &StaticFunctions::test_function, "delta"), - make_protocol_function("get_oscilloscope_val", static_functions, &StaticFunctions::get_oscilloscope_val, "index"), - make_protocol_function("get_adc_voltage", static_functions, &StaticFunctions::get_adc_voltage_, "gpio"), - make_protocol_function("save_configuration", static_functions, &StaticFunctions::save_configuration_helper), - make_protocol_function("erase_configuration", static_functions, &StaticFunctions::erase_configuration_helper), - make_protocol_function("reboot", static_functions, &StaticFunctions::NVIC_SystemReset_helper), - make_protocol_function("enter_dfu_mode", static_functions, &StaticFunctions::enter_dfu_mode_helper) - ); -} - -using tree_type = decltype(make_obj_tree()); -uint8_t tree_buffer[sizeof(tree_type)]; - - -void initTree(){ - // TODO: this is supposed to use the move constructor, but currently - // the compiler uses the copy-constructor instead. Thus the make_obj_tree - // ends up with a stupid stack size of around 8000 bytes. Fix this. - auto tree_ptr = new (tree_buffer) tree_type(make_obj_tree()); - fibre_publish(*tree_ptr); -} - // Thread to handle deffered processing of USB interrupt, and // read commands out of the UART DMA circular buffer void communication_task(void * ctx) { @@ -220,7 +67,7 @@ void communication_task(void * ctx) { start_uart_server(); start_usb_server(); - if (board_config.enable_i2c_instead_of_can) { + if (odrv.config_.enable_i2c_instead_of_can) { start_i2c_server(); } else { odCAN->start_can_server(); @@ -245,3 +92,7 @@ int _write(int file, const char* data, int len) { #endif return len; } + + +#include "../autogen/function_stubs.hpp" +#include "../autogen/endpoints.hpp" diff --git a/Firmware/communication/communication.h b/Firmware/communication/communication.h index 85519b39..6987aa11 100644 --- a/Firmware/communication/communication.h +++ b/Firmware/communication/communication.h @@ -17,10 +17,6 @@ extern "C" { extern osThreadId comm_thread; extern const uint32_t stack_size_comm_thread; -extern const uint8_t hw_version_major; -extern const uint8_t hw_version_minor; -extern const uint8_t hw_version_variant; - void init_communication(void); void initTree(); void communication_task(void * ctx); diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 339ef9ff..659958c7 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -16,9 +16,9 @@ // std::unordered_map ctxMap; // Constructor is called by communication.cpp and the handle is assigned appropriately -ODriveCAN::ODriveCAN(CAN_HandleTypeDef *handle, ODriveCAN::Config_t &config) - : handle_{handle}, - config_{config} { +ODriveCAN::ODriveCAN(ODriveCAN::Config_t &config, CAN_HandleTypeDef *handle) + : config_{config}, + handle_{handle} { // ctxMap[handle_] = this; } @@ -32,7 +32,7 @@ void ODriveCAN::can_server_thread() { while (available()) { read(rxmsg); switch (config_.protocol) { - case CAN_PROTOCOL_SIMPLE: + case Config_t::PROTOCOL_SIMPLE: CANSimple::handle_can_message(rxmsg); break; } @@ -183,7 +183,7 @@ void ODriveCAN::send_heartbeat(Axis *axis) { uint32_t now = osKernelSysTick(); if ((now - axis->last_heartbeat_) >= axis->config_.can_heartbeat_rate_ms) { switch (config_.protocol) { - case CAN_PROTOCOL_SIMPLE: + case Config_t::PROTOCOL_SIMPLE: CANSimple::send_heartbeat(axis); break; } diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index fca28822..44791a33 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -19,23 +19,14 @@ enum { CAN_BAUD_1M = 1000000 }; -enum CAN_Protocol_t { - CAN_PROTOCOL_SIMPLE -}; - -class ODriveCAN { +class ODriveCAN : public OdriveIntf::CanIntf { public: - struct Config_t { + struct Config_t : ConfigIntf { uint32_t baud_rate = CAN_BAUD_250K; - CAN_Protocol_t protocol = CAN_PROTOCOL_SIMPLE; + Protocol protocol = PROTOCOL_SIMPLE; }; - enum Error { - ERROR_NONE = 0x00, - ERROR_DUPLICATE_CAN_IDS = 0x01 - }; - - ODriveCAN(CAN_HandleTypeDef *handle, ODriveCAN::Config_t &config); + ODriveCAN(ODriveCAN::Config_t &config, CAN_HandleTypeDef *handle); // Thread Relevant Data osThreadId thread_id_; @@ -55,24 +46,12 @@ class ODriveCAN { uint32_t write(can_Message_t &txmsg); bool read(can_Message_t &rxmsg); - // Communication Protocol Handling - auto make_protocol_definitions() { - return make_protocol_member_list( - make_protocol_property("error", &error_), - make_protocol_object("config", - make_protocol_ro_property("baud_rate", &config_.baud_rate)), - make_protocol_property("can_protocol", &config_.protocol), - make_protocol_function("set_baud_rate", *this, &ODriveCAN::set_baud_rate, "baudRate")); - } - - private: - CAN_HandleTypeDef *handle_ = nullptr; ODriveCAN::Config_t &config_; +private: + CAN_HandleTypeDef *handle_ = nullptr; + void set_baud_rate(uint32_t baudRate); }; - -DEFINE_ENUM_FLAG_OPERATORS(ODriveCAN::Error) - #endif // __INTERFACE_CAN_HPP diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index 21273dee..83632bcd 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -127,7 +127,7 @@ static void usb_server_thread(void * ctx) { // CDC Interface if (CDC_interface.data_pending) { CDC_interface.data_pending = false; - if (board_config.enable_ascii_protocol_on_usb) { + if (odrv.config_.enable_ascii_protocol_on_usb) { ASCII_protocol_parse_stream(CDC_interface.rx_buf, CDC_interface.rx_len, usb_stream_output); } else { diff --git a/Firmware/fibre/cpp/endpoints_template.j2 b/Firmware/fibre/cpp/endpoints_template.j2 new file mode 100644 index 00000000..98c5eb1d --- /dev/null +++ b/Firmware/fibre/cpp/endpoints_template.j2 @@ -0,0 +1,71 @@ +/*[# This is the original template, thus the warning below does not apply to this file #] + * ============================ WARNING ============================ + * ==== This is an autogenerated file. ==== + * ==== Any changes to this file will be lost when recompiling. ==== + * ================================================================= + * + * This file contains the toplevel handler for Fibre v0.1 endpoint operations. + * + * This endpoint-oriented approach will be deprecated in Fibre v0.2 in favor of + * a function-oriented approach and a more powerful object model. + * + */ +#ifndef __FIBRE_INTERFACES_HPP +#define __FIBRE_INTERFACES_HPP + +namespace fibre { + +const unsigned char embedded_json[] = [[embedded_endpoint_definitions | to_c_string]]; +const size_t embedded_json_length = sizeof(embedded_json) - 1; +const uint16_t json_crc_ = calc_crc16(PROTOCOL_VERSION, embedded_json, embedded_json_length); +const uint32_t json_version_id_ = (json_crc_ << 16) | calc_crc16(json_crc_, embedded_json, embedded_json_length); + +// Note: with -Og this function reserves a huge amount of stack space because it +// reserves separate space for the stack frame of each of the inlined functions. +// The minimum known set of flags to prevent this is `-O1 -fipa-sra`. +// `-O2` is a superset of this so that's what we use here. +bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) __attribute__ ((optimize(2))); + +bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) { + switch (idx) { +[%- for endpoint in endpoints %] + case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_type]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_type]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break; +[%- endfor %] + default: return false; + } +} + +bool is_endpoint_ref_valid(endpoint_ref_t endpoint_ref) { + if (endpoint_ref.json_crc != json_crc_) { + return false; + } + + switch (endpoint_ref.endpoint_id) { +[%- for endpoint in endpoints %] + case [[endpoint.id]]: return true; +[%- endfor %] + default: return false; + } +} + +bool set_endpoint_from_float(endpoint_ref_t endpoint_ref, float value) { + if (endpoint_ref.json_crc != json_crc_) { + return false; + } + + return false; + // TODO: implement + /*cbufptr_t input_buffer{}; + bufptr_t output_buffer{}; + + switch (idx) { +[%- for endpoint in endpoints %] + case [[endpoint.id]]: return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_type]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %]&input_buffer, &output_buffer); +[%- endfor %] + default: return false; + }*/ +} + +} + +#endif // __FIBRE_INTERFACES_HPP \ No newline at end of file diff --git a/Firmware/fibre/cpp/function_stubs_template.j2 b/Firmware/fibre/cpp/function_stubs_template.j2 new file mode 100644 index 00000000..759b49eb --- /dev/null +++ b/Firmware/fibre/cpp/function_stubs_template.j2 @@ -0,0 +1,40 @@ +/*[# This is the original template, thus the warning below does not apply to this file #] + * ============================ WARNING ============================ + * ==== This is an autogenerated file. ==== + * ==== Any changes to this file will be lost when recompiling. ==== + * ================================================================= + * + * This file contains serializing/deserializing stubs for the functions defined + * in your interface file. + * + */ + +#include + +[% for intf in interfaces.values() %] +[% for func in intf.functions.values() %] +static inline bool [[func.fullname | to_snake_case]]([% for arg in func.in.values() %]std::optional<[[arg.type.c_type]]> in_[[arg.name]], [% endfor %][% for arg in func.out.values() %][[arg.type.c_type]]* out_[[arg.name]], [% endfor %]fibre::cbufptr_t* input_buffer, fibre::bufptr_t* output_buffer) { +[%- if func.in %] + bool success = [% for arg in func.in.values() %](in_[[arg.name]].has_value() || (in_[[arg.name]] = fibre::Codec<[[arg.type.c_type]]>::decode(input_buffer)).has_value()[% if arg.optional %] || true[% endif %])[% if not loop.last %] + && [% endif %][% endfor %]; +[%- else %] + bool success = true; +[%- endif %] + if (!success) { + return false; + } +[%- if func.implementation %] + [% if func.out %]std::tuple<[% for arg in func.out.values() %][[arg.type.c_type]][[', ' if not loop.last]][% endfor %]> ret = [% endif %][[func.implementation]]([% for arg in func.in.values() %]in_[[arg.name]][% if not arg.optional %].value()[% endif %][[', ' if not loop.last]][% endfor %]); +[%- else %] + [% if func.out %]std::tuple<[% for arg in func.out.values() %][[arg.type.c_type]][[', ' if not loop.last]][% endfor %]> ret = [% endif %]in_[[(func.in.values() | first).name]].value()->[[func.name]]([% for arg in func.in.values() | skip_first %]in_[[arg.name]][% if not arg.optional %].value()[% endif %][[', ' if not loop.last]][% endfor %]); +[%- endif %] +[%- if func.out %] + return [% for arg in func.out.values() %]((out_[[arg.name]] && ((*out_[[arg.name]] = std::get<[[loop.index0]]>(ret)), true)) || fibre::Codec<[[arg.type.c_type]]>::encode(std::get<[[loop.index0]]>(ret), output_buffer))[% if not loop.last %] + && [% endif %][% endfor %]; +[%- else %] + return true; +[%- endif %] +} +[% endfor %] +[% endfor %] + diff --git a/Firmware/fibre/cpp/include/fibre/bufptr.hpp b/Firmware/fibre/cpp/include/fibre/bufptr.hpp new file mode 100644 index 00000000..2ce3fefb --- /dev/null +++ b/Firmware/fibre/cpp/include/fibre/bufptr.hpp @@ -0,0 +1,93 @@ +#ifndef __FIBRE_BUFPTR_HPP +#define __FIBRE_BUFPTR_HPP + +namespace fibre { + +static inline bool soft_assert(bool expr) { return expr; } // TODO: implement + +/** + * @brief Holds a reference to a buffer and a length. + * Since this class implements begin() and end(), you can use it with many + * standard algorithms that operate on iterable objects. + */ +template +struct generic_bufptr_t { + using iterator = T*; + using const_iterator = const T*; + + generic_bufptr_t(T* begin, size_t length) : begin_(begin), end_(begin + length) {} + + generic_bufptr_t(T* begin, T* end) : begin_(begin), end_(end) {} + + generic_bufptr_t() : begin_(nullptr), end_(nullptr) {} + + template + generic_bufptr_t(T (&begin)[I]) : generic_bufptr_t(begin, I) {} + + generic_bufptr_t(const std::vector>& vector) + : generic_bufptr_t(vector.data(), vector.size()) {} + + generic_bufptr_t(const generic_bufptr_t>& other) + : generic_bufptr_t(other.begin_, other.end_) {} + + generic_bufptr_t& operator+=(size_t num) { + if (!soft_assert(num <= size())) { + num = size(); + } + begin_ += num; + return *this; + } + + generic_bufptr_t operator++(int) { + generic_bufptr_t result = *this; + *this += 1; + return result; + } + + T& operator*() { + return *begin_; + } + + generic_bufptr_t take(size_t num) const { + if (!soft_assert(num <= size())) { + num = size(); + } + generic_bufptr_t result = {begin_, num}; + return result; + } + + generic_bufptr_t skip(size_t num, size_t* processed_bytes = nullptr) const { + if (!soft_assert(num <= size())) { + num = size(); + } + if (processed_bytes) + (*processed_bytes) += num; + return {begin_ + num, end_}; + } + + size_t size() const { + return end_ - begin_; + } + + bool empty() const { + return size() == 0; + } + + T*& begin() { return begin_; } + T*& end() { return end_; } + T* const & begin() const { return begin_; } + T* const & end() const { return end_; } + T& front() const { return *begin(); } + T& back() const { return *(end() - 1); } + T& operator[](size_t idx) { return *(begin() + idx); } + + T* begin_; + T* end_; +}; + +using cbufptr_t = generic_bufptr_t; +using bufptr_t = generic_bufptr_t; + +} + +#endif // __FIBRE_BUFPTR_HPP diff --git a/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp b/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp index 4b97f367..b2d84256 100644 --- a/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp +++ b/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp @@ -1,6 +1,3 @@ -#ifndef __CPP_UTILS_HPP -#define __CPP_UTILS_HPP - /* ## Advanced C++ Topics @@ -78,8 +75,20 @@ public: */ -// Backport definitions from C++14 -#if __cplusplus <= 201103L +#ifndef __CPP_UTILS_HPP +#define __CPP_UTILS_HPP + +#include +#include +#include +#include +//#include +#include +#include + +/* Backport features from C++14 and C++17 ------------------------------------*/ + +#if __cplusplus < 201402L namespace std { template< class T > using underlying_type_t = typename underlying_type::type; @@ -87,9 +96,377 @@ namespace std { // source: http://en.cppreference.com/w/cpp/types/enable_if template< bool B, class T = void > using enable_if_t = typename enable_if::type; + + // source: https://en.cppreference.com/w/cpp/types/conditional + template< bool B, class T, class F > + using conditional_t = typename conditional::type; + + // source: http://en.cppreference.com/w/cpp/utility/tuple/tuple_element + template + using tuple_element_t = typename tuple_element::type; + + // source: https://en.cppreference.com/w/cpp/types/remove_cv + template< class T > + using remove_cv_t = typename remove_cv::type; + template< class T > + using remove_const_t = typename remove_const::type; + template< class T > + using remove_volatile_t = typename remove_volatile::type; + template< class T > + using remove_reference_t = typename remove_reference::type; + + template< class T > + using decay_t = typename decay::type; + + // integer_sequence implementation adapted from + // https://stackoverflow.com/questions/17424477/implementation-c14-make-integer-sequence + + /// Class template integer_sequence + template + struct integer_sequence { + using type = integer_sequence; + typedef _Tp value_type; + static constexpr size_t size() noexcept { return sizeof...(_Idx); } + }; + + template + struct _merge_and_renumber; + + template + struct _merge_and_renumber, integer_sequence<_Tp, I2...>> + : integer_sequence<_Tp, I1..., (sizeof...(I1)+I2)...> + { }; + + template + struct make_integer_sequence + : _merge_and_renumber::type, + typename make_integer_sequence<_Tp, N - N/2>::type> + { }; + + template struct make_integer_sequence<_Tp, 0> : integer_sequence<_Tp> { }; + template struct make_integer_sequence<_Tp, 1> : integer_sequence<_Tp, 0> { }; + + /// Alias template index_sequence + template + using index_sequence = integer_sequence; + + /// Alias template make_index_sequence + template + using make_index_sequence = typename make_integer_sequence::type; } #endif +namespace fibre { + // Creates the index sequence { IFrom, IFrom + 1, IFrom + 2, ..., ITo - 1 } + template + struct make_integer_sequence_from_to_impl { + using type = typename make_integer_sequence_from_to_impl<_Tp, IFrom, ITo - 1, ITo - 1, I...>::type; + }; + + template + struct make_integer_sequence_from_to_impl<_Tp, IFrom, IFrom, I...> { + using type = std::index_sequence; + }; + + template + using make_integer_sequence_from_to = typename make_integer_sequence_from_to_impl<_Tp, IFrom, ITo>::type; +} + +#if __cplusplus < 201703L +namespace std { +//template>{}, int> = 0> +//using enable_ + +template struct invoke_result_impl; + +template +struct invoke_result_impl>{}>, + Fn, Args...> { + typedef decltype(std::mem_fn(std::declval())(std::declval()...)) type; +}; + +template +struct invoke_result_impl>{}>, + Fn, Args...> { + typedef decltype(std::declval()(std::declval()...)) type; +}; + +template +using invoke_result = invoke_result_impl; + +template +using invoke_result_t = typename invoke_result::type; + +template>{}, int> = 0 > +constexpr invoke_result_t invoke(Fn&& f, Args&&... args) + noexcept(noexcept(std::mem_fn(f)(std::forward(args)...))) +{ + return std::mem_fn(f)(std::forward(args)...); +} + +template>{}, int> = 0> +constexpr invoke_result_t invoke(Fn&& f, Args&&... args) + noexcept(noexcept(std::forward(f)(std::forward(args)...))) +{ + return std::forward(f)(std::forward(args)...); +} +} + +namespace std { +namespace detail { +template +struct apply_result_impl; + +// TODO: apply_result is not part of C++17, therefore we should move this out of +// the #if block +template +struct apply_result_impl> { + //typedef std::invoke_result_t...> type; + typedef std::invoke_result_t(std::declval()))...> type; +}; + +template +using apply_result = apply_result_impl>::value>>; + +template +using apply_result_t = typename apply_result::type; + +template +constexpr apply_result_t apply_impl( F&& f, Tuple&& t, std::index_sequence ) +{ + return std::invoke(std::forward(f), std::get(std::forward(t))...); +} +} // namespace detail + +template +constexpr detail::apply_result_t apply(F&& f, Tuple&& t) +{ + return detail::apply_impl(std::forward(f), std::forward(t), + std::make_index_sequence>::value>{}); +} +} + + +namespace std { + +template +struct identity { using type = T; }; + +template +struct overload_resolver; + +template<> +struct overload_resolver<> { void operator()() const; }; + +template +struct overload_resolver : overload_resolver { + using overload_resolver::operator(); + identity operator()(T) const; +}; + +template +struct index_of : integral_constant::value + 1)> {}; + +template +struct index_of : integral_constant {}; + +/** + * @brief Heavily simplified version of the C++17 std::variant. + * Whatever compiles should work as one would expect from the C++17 variant. + */ +template +class variant; + +// Empty variant is ill-formed. Only used for clean recursion here. +template<> +class variant<> { +public: + using storage_t = char[0]; + storage_t content_; + + static void selective_destructor(char* storage, size_t index) { + throw; + } + + static void selective_copy_constuctor(char* target, const char* source, size_t index) { + throw; + } + + static bool selective_eq(const char* lhs, const char* rhs, size_t index) { + throw; + } + + static bool selective_neq(const char* lhs, const char* rhs, size_t index) { + throw; + } + + template + static void selective_invoke_const(const char* content, size_t index, TFunc functor, TArgs&&... args) { + throw; + } + + template + static void selective_invoke(const char* content, size_t index, TFunc functor, TArgs&&... args) { + throw; + } +}; + +template +class variant { +public: + using storage_t = char[sizeof(T) > sizeof(typename variant::storage_t) ? sizeof(T) : sizeof(typename variant::storage_t)]; + + static void selective_copy_constuctor(char* target, const char* source, size_t index) { + if (index == 0) { + new ((T*)target) T{*(T*)source}; // in-place construction using first type's copy constructor + } else { + variant::selective_copy_constuctor(target, source, index - 1); + } + } + + static void selective_destructor(char* storage, size_t index) { + if (index == 0) { + ((T*)storage)->~T(); + } else { + variant::selective_destructor(storage, index - 1); + } + } + + static bool selective_eq(const char* lhs, const char* rhs, size_t index) { + if (index == 0) { + return ((*(T*)lhs) == (*(T*)rhs)); + } else { + return variant::selective_eq(lhs, rhs, index - 1); + } + } + + static bool selective_neq(const char* lhs, const char* rhs, size_t index) { + if (index == 0) { + return ((*(T*)lhs) != (*(T*)rhs)); + } else { + return variant::selective_neq(lhs, rhs, index - 1); + } + } + + template + static void selective_invoke_const(const char* content, size_t index, TFunc functor, TArgs&&... args) { + if (index == 0) { + functor(*(T*)content, std::forward(args)...); + } else { + variant::selective_invoke_const(content, index - 1, functor, std::forward(args)...); + } + } + + template + static void selective_invoke(char* content, size_t index, TFunc functor, TArgs&&... args) { + if (index == 0) { + functor(*(T*)content, std::forward(args)...); + } else { + variant::selective_invoke(content, index - 1, functor, std::forward(args)...); + } + } + + variant() : index_(0) { + new ((T*)content_) T{}; // in-place construction using first type's default constructor + } + + variant(const variant & other) : index_(other.index_) { + selective_copy_constuctor(content_, other.content_, index_); + } + + variant(variant&& other) : index_(other.index_) { + // TODO: implement + selective_copy_constuctor(content_, other.content_, index_); + } + + // Find the best match out of `T, Ts...` with `TArg` as the argument. + template + using best_match = decltype(overload_resolver()(std::declval())); + + template::type> //, typename=typename std::enable_if_t, variant>::value)>, typename TTarget=decltype(indicator_func(std::forward(std::declval()))), typename TIndex=index_of> + variant(TArg&& arg) { + new ((TTarget*)content_) TTarget{std::forward(arg)}; + index_ = index_of::value; + } + + ~variant() { + selective_destructor(content_, index_); + } + + inline variant& operator=(const variant & other) { + selective_destructor(content_, index_); + index_ = other.index_; + selective_copy_constuctor(content_, other.content_, index_); + return *this; + } + + inline bool operator==(const variant& rhs) const { + return (index_ == rhs.index_) && selective_eq(this->content_, rhs.content_, index_); + } + + inline bool operator!=(const variant& rhs) const { + return (index_ != rhs.index_) || selective_neq(this->content_, rhs.content_, index_); + } + + template + void invoke(TFunc functor, TArgs&&... args) const { + selective_invoke_const(content_, index_, functor, std::forward(args)...); + } + + template + void invoke(TFunc functor, TArgs&&... args) { + selective_invoke(content_, index_, functor, std::forward(args)...); + } + + storage_t content_; + size_t index_; + + size_t index() const { return index_; } +}; + +template +std::tuple_element_t>& get(std::variant& val) { + if (val.index() != I) + throw; + using T = std::tuple_element_t>; + return *((T*)val.content_); +} + +template +T& get(std::variant& val) { + constexpr size_t index = std::index_of::value; + return std::get(val); +} + +} // namespace std + +#endif + +/* Stuff that should be in the STL but isn't ---------------------------------*/ + +// source: https://en.cppreference.com/w/cpp/experimental/to_array +namespace detail { +template +constexpr std::array, N> + to_array_impl(T (&a)[N], std::index_sequence) +{ + return { {a[I]...} }; +} + +template +constexpr std::array, N> to_array(T (&a)[N]) +{ + return detail::to_array_impl(a, std::make_index_sequence{}); +} +} + + + +/* Custom utils --------------------------------------------------------------*/ + // @brief Supports various queries on a list of types template class TypeChecker; @@ -112,6 +489,7 @@ public: return std::is_base_of::value && TypeChecker::template all_are(); } + constexpr static const size_t count = TypeChecker::count + 1; }; template<> @@ -125,11 +503,17 @@ public: constexpr static inline bool all_are() { return std::true_type::value; } + constexpr static const size_t count = 0; }; +template +TypeChecker make_type_checker(Ts ...) { + return TypeChecker(); +} + #include #define ENABLE_IF(...) \ - typename = std::enable_if_t<__VA_ARGS__> + typename = typename std::enable_if_t<__VA_ARGS__> #define ENABLE_IF_SAME(a, b, type) \ template typename std::enable_if_t::value, type> @@ -151,15 +535,83 @@ class function_traits { public: template static TRet invoke(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple packed_args, TUnpackedArgs ... args) { - return invoke(obj, func_ptr, packed_args, args..., std::get(packed_args)); + return invoke(obj, func_ptr, packed_args, std::forward(args)..., std::get(packed_args)); } template static TRet invoke(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple packed_args, TArgs ... args) { - return (obj.*func_ptr)(args...); + return (obj.*func_ptr)(std::forward(args)...); } }; + +/* @brief return_type::type represents the C++ native return type +* of a function returning 0 or more arguments. +* +* For an empty TypeList, the return type is void. For a list with +* one type, the return type is equal to that type. For a list with +* more than one items, the return type is a tuple. +*/ +template +struct return_type; + +template<> +struct return_type<> { typedef void type; }; +template +struct return_type { typedef T type; }; +template +struct return_type { typedef std::tuple type; }; + + + +template +struct static_function_traits; + +// TODO: All invoke-related functions should be superseeded by a proper std::apply implementation +#if 0 +template +struct static_function_traits, std::tuple> { + using TRet = typename return_type::type; + + //template + //static std::tuple invoke(std::tuple packed_args, TUnpackedInputs ... args) { + // return invoke(packed_args, args..., std::get(packed_args)); + //} + + template + static std::tuple invoke(std::tuple& packed_args) { + return invoke_impl(packed_args, std::make_index_sequence()); + } + + template + static std::tuple invoke_impl(std::tuple packed_args, std::index_sequence) { + return invoke_impl_2(std::get(packed_args)...); + } + + //template + //static std::enable_if_t<(sizeof...(TOutputs) == 0), std::tuple> + template>*/> + static std::enable_if_t<(IOutputs == 0), std::tuple> + invoke_impl_2(TInputs ... args) { + Function(args...); + return std::make_tuple<>(); + } + + //template + //static std::enable_if_t<(sizeof...(TOutputs) == 1), std::tuple> + template>*/> + static std::enable_if_t<(IOutputs == 1), std::tuple> + invoke_impl_2(TInputs ... args) { + return std::make_tuple(Function(args...)); + } +// +// template= 2)> +// static /* std::enable_if_t= 2, */ std::tuple //> +// invoke_impl_2(std::tuple packed_args, TInputs ... args) { +// return Function(args...); +// } +}; + /* @brief Invoke a class member function with a variable number of arguments that are supplied as a tuple Example usage: @@ -180,4 +632,681 @@ TRet invoke_function_with_tuple(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std: return function_traits::template invoke<0>(obj, func_ptr, packed_args); } +template(*Function)(TIn...)> +std::tuple invoke_with_tuples(std::tuple inputs) { + static_function_traits::template invoke<0>(inputs); +} +#endif + + +template +struct sum_impl; +template +struct sum_impl { static constexpr TInt value = 0; }; +template +struct sum_impl { static constexpr TInt value = I + sum_impl::value; }; + +template +using sum = sum_impl; + + +// source: https://akrzemi1.wordpress.com/2017/05/18/asserts-in-constexpr-functions/ +#if defined NDEBUG +# define X_ASSERT(CHECK) void(0) +#else +# define X_ASSERT(CHECK) \ + ( (CHECK) ? void(0) : []{assert(!#CHECK);}() ) +#endif + +template +struct for_each_in_tuple_result_impl; + +template +struct for_each_in_tuple_result_impl> { + typedef std::tuple(std::declval())(std::get(std::declval())))...> type; +}; + +template +using for_each_in_tuple_result = for_each_in_tuple_result_impl>::value>>; + +template +using for_each_in_tuple_result_t = typename for_each_in_tuple_result::type; + +template +for_each_in_tuple_result_t for_each_in_tuple_impl(Fn&& f, Tuple&& t, std::index_sequence) { + return for_each_in_tuple_result_t(std::forward(f)(std::get(t))...); +} + +template +for_each_in_tuple_result_t for_each_in_tuple(Fn&& f, Tuple&& t) { + return for_each_in_tuple_impl(std::forward(f), std::forward(t), std::make_index_sequence>::value>{}); +} +//template +//for_each_in_tuple_result_t for_each_in_tuple(Fn&& f, Tuple&& t) { +// return 5; +//} + + +/* constexpr strings --------------------------------------------------------*/ +/* adapted from: +* https://akrzemi1.wordpress.com/2017/06/28/compile-time-string-concatenation/ +*/ + + +// TODO: the functionality +// sstring::substring, sstring::get_last_part and sstring::after_last_index_of and sstring::last_index_of +// was removed during refactoring. Add again if needed. + +/** + * @brief Represents a string that is known at compile time by encoding it as a + * type. + */ +template +struct sstring { + static constexpr const char chars[] = {CHARS..., 0}; + static constexpr const char* c_str() { return chars; } + static constexpr size_t size() { return sizeof...(CHARS); } + static constexpr std::array as_array() { return {CHARS...}; } + + template + constexpr bool operator==(const sstring & other) { + return as_array() == other.as_array(); + } +}; +template +constexpr const char sstring::chars[/*sizeof...(CHARS) + 1*/]; + +template +struct sstring_concat_impl; + +template +struct sstring_concat_impl, sstring> { + using type = sstring; +}; + +/** @brief Represents the result type of concatenating two static strings */ +template +using sstring_concat_t = typename sstring_concat_impl::type; + +/** @brief Concatenates two static strings */ +template +constexpr sstring operator+(sstring, sstring) { + return {}; +} + + +/** @brief Helper class for the MAKE_SSTRING macro */ +template +struct sstring_builder; + +template +struct sstring_builder<0, CHAR, CHARS...> { + using type = sstring<>; +}; + +template +struct sstring_builder { + using type = sstring_concat_t, typename sstring_builder::type>; +}; + +template +using sstring_builder_t = typename sstring_builder::type; + +#define MACRO_GET_1(str, i) \ + (sizeof(str) > (i) ? str[(i)] : 0) + +#define MACRO_GET_4(str, i) \ + MACRO_GET_1(str, i+0), \ + MACRO_GET_1(str, i+1), \ + MACRO_GET_1(str, i+2), \ + MACRO_GET_1(str, i+3) + +#define MACRO_GET_16(str, i) \ + MACRO_GET_4(str, i+0), \ + MACRO_GET_4(str, i+4), \ + MACRO_GET_4(str, i+8), \ + MACRO_GET_4(str, i+12) + +#define MACRO_GET_64(str, i) \ + MACRO_GET_16(str, i+0), \ + MACRO_GET_16(str, i+16), \ + MACRO_GET_16(str, i+32), \ + MACRO_GET_16(str, i+48) + +/** + * @brief Builds a compile-time string type from a string literal. + * + * Passing more than 64 characters will prune the string. + * + * Usage: + * MAKE_SSTRING("hello world") my_str{}; + * or + * auto my_str = MAKE_SSTRING("hello world"){}; + * + * Both examples create a compile-time variable "my_str" of which the type + * itself stores the content "hello world". + */ +#define MAKE_SSTRING(literal) sstring_builder_t + +namespace std { +template +static std::ostream& operator<<(std::ostream& stream, const sstring& val) { + stream << val.chars; + return stream; +} +} + +template +struct join_sstring_impl; + +template +struct join_sstring_impl> { + using type = sstring<>; +}; + +template +struct join_sstring_impl, sstring> { + using type = sstring; +}; + +template +struct join_sstring_impl, sstring, TStr...> { + using type = sstring_concat_t, typename join_sstring_impl, TStr...>::type>; +}; + +template +using join_sstring_t = typename join_sstring_impl::type; + +template +constexpr join_sstring_t join_sstring(const TDelimiter& delimiter, const TStr& ... str) { + return {}; +} + +template +using sstring_arr = std::tuple...>; + + +// source: https://stackoverflow.com/questions/40159732/return-other-value-if-key-not-found-in-the-map +template +TValue& get_or(std::unordered_map& m, const TKey& key, TValue& default_value) { + auto it = m.find(key); + if (it == m.end()) { + return default_value; + } else { + return it->second; + } +} +template +TValue* get_ptr(std::unordered_map& m, const TKey& key) { + auto it = m.find(key); + if (it == m.end()) + return nullptr; + else + return &(it->second); +} + +template +std::true_type is_complete_impl(T *); +std::false_type is_complete_impl(...); + +/** @brief is_complete resolves to std::true_type if T is complete + * and to std::false_type otherwise. This can be used to check if a certain template + * specialization exists. + **/ +template +using is_complete = decltype(is_complete_impl(std::declval())); + +template +struct dynamic_get_impl { + template + static TRet* get(size_t i, TTuple& t) { + if (i == I::value) + return &static_cast(std::get(t)); + else if (i > I::value) + return dynamic_get_impl, TRet, Ts...>::get(i, t); + return nullptr; // this should not happen + } +}; + +template +struct dynamic_get_impl, TRet, Ts...> { + static TRet* get(size_t i, const std::tuple& t) { + return nullptr; + } +}; + +template +TRet* dynamic_get(size_t i, std::tuple& t) { + return dynamic_get_impl, TRet, Ts...>::get(i, t); +} + +template +TRet* dynamic_get(size_t i, const std::tuple& t) { + return dynamic_get_impl, TRet, Ts...>::get(i, t); +} + +/* Hex to numbers ------------------------------------------------------------*/ + +template +constexpr size_t hex_digits() { + return (std::numeric_limits::digits + 3) / 4; +} + +/* @brief Converts a hexadecimal digit to a uint8_t. +* @param output If not null, the digit's value is stored in this output +* Returns true if the char is a valid hex digit, false otherwise +*/ +static bool hex_digit_to_byte(char ch, uint8_t* output) { + uint8_t nil_output = 0; + if (!output) + output = &nil_output; + if (ch >= '0' && ch <= '9') + return (*output) = ch - '0', true; + if (ch >= 'a' && ch <= 'f') + return (*output) = ch - 'a' + 10, true; + if (ch >= 'A' && ch <= 'F') + return (*output) = ch - 'A' + 10, true; + return false; +} + +/* @brief Converts a hex string to an integer +* @param output If not null, the result is stored in this output +* Returns true if the string represents a valid hex value, false otherwise. +*/ +template +bool hex_string_to_int(const char * str, size_t length, TInt* output) { + constexpr size_t N_DIGITS = hex_digits(); + TInt result = 0; + if (length > N_DIGITS) + length = N_DIGITS; + for (size_t i = 0; i < length && str[i]; i++) { + uint8_t digit = 0; + if (!hex_digit_to_byte(str[i], &digit)) + return false; + result <<= 4; + result += digit; + } + if (output) + *output = result; + return true; +} + +template +bool hex_string_to_int(const char * str, TInt* output) { + return hex_string_to_int(str, hex_digits(), output); +} + +template +bool hex_string_to_int_arr(const char * str, size_t length, TInt (&output)[ICount]) { + for (size_t i = 0; i < ICount; i++) { + if (!hex_string_to_int(&str[i * hex_digits()], &output[i])) + return false; + } + return true; +} + +template +bool hex_string_to_int_arr(const char * str, TInt (&output)[ICount]) { + return hex_string_to_int_arr(str, hex_digits() * ICount, output); +} + +namespace fibre { + +// TODO: move to print_utils.hpp +template +class HexPrinter { +public: + HexPrinter(T val, bool prefix) : val_(val) /*, prefix_(prefix)*/ { + const char digits[] = "0123456789abcdef"; + size_t prefix_length = prefix ? 2 : 0; + if (prefix) { + str[0] = '0'; + str[1] = 'x'; + } + str[prefix_length + hex_digits()] = '\0'; + + for (size_t i = 0; i < hex_digits(); ++i) { + str[prefix_length + hex_digits() - i - 1] = digits[val & 0xf]; + val >>= 4; + } + } + std::string to_string() const { return str; } + void to_string(char* buf) const { + for (size_t i = 0; (i < sizeof(str)) && str[i]; ++i) + buf[i] = str[i]; + } + + T val_; + //bool prefix_; + char str[hex_digits() + 3]; // 3 additional characters 0x and \0 +}; + +template +std::ostream& operator<<(std::ostream& stream, const HexPrinter& printer) { + // TODO: specialize for char + return stream << printer.to_string(); +} + +template +HexPrinter as_hex(T val, bool prefix = true) { return HexPrinter(val, prefix); } + +template +class HexArrayPrinter { +public: + HexArrayPrinter(T* ptr, size_t length) : ptr_(ptr), length_(length) {} + T* ptr_; + size_t length_; +}; + +template +std::ostream& operator<<(std::ostream& stream, const HexArrayPrinter& printer) { + for (size_t pos = 0; pos < printer.length_; ++pos) { + stream << " " << as_hex(printer.ptr_[pos]); + if (((pos + 1) % 16) == 0) + stream << std::endl; + } + return stream; +} + +template +HexArrayPrinter as_hex(T (&val)[ILength]) { return HexArrayPrinter(val, ILength); } + +} + + +template +class simple_iterator : std::iterator { + TDereferenceable *container_; + size_t i_; +public: + using reference = TResult; + explicit simple_iterator(TDereferenceable& container, size_t pos) : container_(&container), i_(pos) {} + simple_iterator& operator++() { ++i_; return *this; } + simple_iterator operator++(int) { simple_iterator retval = *this; ++(*this); return retval; } + bool operator==(simple_iterator other) const { return (container_ == other.container_) && (i_ == other.i_); } + bool operator!=(simple_iterator other) const { return !(*this == other); } + bool operator<(simple_iterator other) const { return i_ < other.i_; } + bool operator>(simple_iterator other) const { return i_ > other.i_; } + bool operator<=(simple_iterator other) const { return (*this < other) || (*this == other); } + bool operator>=(simple_iterator other) const { return (*this > other) || (*this == other); } + TResult operator*() const { return (*container_)[i_]; } +}; + + + +/** + * @brief Extracts the argument types of a function signature and provides them + * as a std::tuple. + * TODO: if an STL alternative exists, use that + */ +template +struct args_of; + +template +struct args_of { + using type = std::tuple; +}; + +template +struct args_of> { + using type = std::tuple; +}; + +template +struct args_of { + using type = std::tuple; +}; + +template +struct args_of : public args_of {}; + +template +using args_of_t = typename args_of::type; + +/** + * @brief Extracts the return type of a function signature + * + * This is provided because std::result_of is deprecated since C++17 + */ +template +struct result_of; + +template +struct result_of { + using type = TRet; +}; + +template +struct result_of { + using type = TRet; +}; + +template +struct result_of { + using type = TRet; +}; + +template +using result_of_t = typename result_of::type; + + +/** + * @brief Returns the type that results when concatenating multiple tuples + */ +template +using tuple_cat_t = decltype(std::tuple_cat(std::declval()...)); + +template +constexpr std::array array_cat_impl(std::array arr1, std::array arr2, std::index_sequence, std::index_sequence) { + return { arr1[PACK1]..., arr2[PACK2]... }; +} + +template +constexpr std::array array_cat(std::array arr1, std::array arr2) { + return array_cat_impl(arr1, arr2, std::make_index_sequence(), std::make_index_sequence()); +} + +/** + * @brief Returns the type that results when concatenating multiple tuples + */ +template +using tuple_cat_t = decltype(std::tuple_cat(std::declval()...)); + + +/** + * @brief Ensures that a given type is wrapped in a tuple + */ +template +struct as_tuple { + using type = std::tuple; +}; + +template<> +struct as_tuple { + using type = std::tuple<>; +}; + +template +struct as_tuple> { + using type = std::tuple; +}; + +template +using as_tuple_t = typename as_tuple::type; + +/** + * @brief Removes a reference OR pointer from the given type. + * + * This is similar to std::remove_reference, however it can also remove a + * pointer and it does not work for types that are neither a reference or + * a pointer. + */ +template +struct remove_ref_or_ptr { + static_assert(std::is_reference() || std::is_pointer(), "the type T is neither a reference or a pointer"); +}; + +template +struct remove_ref_or_ptr { using type = T; }; + +template +struct remove_ref_or_ptr { using type = T; }; + +template +using remove_ref_or_ptr_t = typename remove_ref_or_ptr::type; + +/** + * @brief Applies remove_ref_or_ptr_t to every type of a tuple type + */ +template +struct remove_refs_or_ptrs_from_tuple; + +template +struct remove_refs_or_ptrs_from_tuple> { + using type = std::tuple...>; +}; + +template +using remove_refs_or_ptrs_from_tuple_t = typename remove_refs_or_ptrs_from_tuple::type; + +/** + * @brief The convert(val) function returns a reference or a pointer to val + * depending on TTo. + * TODO: this could be a functor + */ +template +struct add_ref_or_ptr; + +template +struct add_ref_or_ptr { + static T& convert(T& value) { + return value; + } +}; + +template +struct add_ref_or_ptr { + static T* convert(T& value) { + return &value; + } +}; + + +/** + * @brief The convert() function turns a given tuple of values into a tuple of + * pointers or references based on the template argument TTo. + */ +template +struct add_ref_or_ptr_to_tuple; + +template +struct add_ref_or_ptr_to_tuple> { + template + static std::tuple convert_impl(std::tuple&& t, std::index_sequence) { + using to_type = std::tuple; + to_type result(add_ref_or_ptr>::convert(std::get(t))...); + return result; + } + + template + static std::tuple convert(std::tuple&& t) { + static_assert(sizeof...(TFrom) == sizeof...(TTo), "both tuples must have the same size"); + return convert_impl(std::forward>(t), std::make_index_sequence()); + } +}; + +template +struct add_ptrs_to_tuple_type; + +template +struct add_ptrs_to_tuple_type> { + using type = std::tuple; +}; + +template +using add_ptrs_to_tuple_t = typename add_ptrs_to_tuple_type::type; + +template +struct add_refs_to_tuple_type; + +template +struct add_refs_to_tuple_type> { + using type = std::tuple; +}; + +template +using add_refs_to_tuple_t = typename add_refs_to_tuple_type::type; + + +template struct is_tuple: std::false_type {}; +template struct is_tuple>: std::true_type {}; + + +template +struct tuple_select_type_impl; + +template +struct tuple_select_type_impl, TTuple> { + using type = std::tuple...>; +}; + +template +typename tuple_select_type_impl, TTuple>::type +tuple_select_impl(TTuple tuple, std::index_sequence) { + return typename tuple_select_type_impl, TTuple>::type(std::get(tuple)...); +}; + + +template +struct tuple_take_type { + static_assert(I <= std::tuple_size::value, "cannot take more elements than tuple size"); + using type = typename tuple_select_type_impl, TTuple>::type; +}; + +template +using tuple_take_t = typename tuple_take_type::type; + +/** + * @brief Returns the first I elements from the tuple as a tuple. + * The resulting type is tuple_take_t. + * See also: tuple_skip + */ +template +tuple_take_t tuple_take(TTuple tuple) { + return tuple_select_impl(tuple, std::make_index_sequence{}); +}; + + +template +struct tuple_skip_type { + static_assert(I <= std::tuple_size::value, "cannot skip more elements than tuple size"); + using type = typename tuple_select_type_impl::value>, TTuple>::type; +}; + +template +using tuple_skip_t = typename tuple_skip_type::type; + +/** + * @brief Returns all but the first I elements from the tuple as a tuple. + * The resulting type is tuple_skip_t. + * See also: tuple_take + */ +template +tuple_skip_t tuple_skip(TTuple tuple) { + return tuple_select_impl(tuple, fibre::make_integer_sequence_from_to::value>{}); +}; + +template +struct repeat_type_impl { + using type = typename repeat_type_impl::type; +}; + +template +struct repeat_type_impl<0, T, Ts...> { + using type = std::tuple; +}; + +template +using repeat_t = typename repeat_type_impl::type; + #endif // __CPP_UTILS_HPP diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index 883ba544..30a0b8a8 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -13,8 +13,12 @@ see protocol.md for the protocol specification #include //#include #include +#include +#include #include "crc.hpp" #include "cpp_utils.hpp" +#include "bufptr.hpp" +#include "simple_serdes.hpp" // Note that this option cannot be used to debug UART because it prints on UART //#define DEBUG_FIBRE @@ -63,9 +67,6 @@ struct ReceiverState { /*******************************************************/ - -#include - constexpr uint16_t PROTOCOL_VERSION = 1; // This value must not be larger than USB_TX_DATA_SIZE defined in usbd_cdc_if.h @@ -78,11 +79,22 @@ constexpr uint32_t PROTOCOL_SERVER_TIMEOUT_MS = 10; typedef struct { uint16_t json_crc = 0; - uint16_t node_id = 0; uint16_t endpoint_id = 0; } endpoint_ref_t; -#include + +namespace fibre { +// These symbols are defined in the autogenerated endpoints.hpp +extern const unsigned char embedded_json[]; +extern const size_t embedded_json_length; +extern const uint16_t json_crc_; +extern const uint32_t json_version_id_; +bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer); +bool endpoint0_handler(cbufptr_t* input_buffer, bufptr_t* output_buffer); +bool is_endpoint_ref_valid(endpoint_ref_t endpoint_ref); +bool set_endpoint_from_float(endpoint_ref_t endpoint_ref, float value); +} + template::value>> inline size_t write_le(T value, uint8_t* buffer){ @@ -326,168 +338,65 @@ private: }; -// @brief Endpoint request handler -// -// When passed a valid endpoint context, implementing functions shall handle an -// endpoint read/write request by reading the provided input data and filling in -// output data. The exact semantics of this function depends on the corresponding -// endpoint's specification. -// -// @param input: pointer to the input data -// @param input_length: number of available input bytes -// @param output: The stream where to write the output to. Can be null. -// The handler shall abort as soon as the stream returns -// a non-zero error code on write. -typedef std::function EndpointHandler; - - -// @brief Default endpoint handler for const types -// @return: True if endpoint was written to, False otherwise -template -std::enable_if_t::value && std::is_const::value, bool> -default_readwrite_endpoint_handler(T* value, const uint8_t* input, size_t input_length, StreamSink* output) { - // If the old value was requested, call the corresponding little endian serialization function - if (output) { - // TODO: make buffer size dependent on the type - uint8_t buffer[sizeof(T)]; - size_t cnt = write_le(*value, buffer); - if (cnt <= output->get_free_space()) - output->process_bytes(buffer, cnt, nullptr); - } - return false; // We don't ever write to const types -} - -// @brief Default endpoint handler for non-const types -template -std::enable_if_t::value && !std::is_const::value, bool> -default_readwrite_endpoint_handler(T* value, const uint8_t* input, size_t input_length, StreamSink* output) { - // Read the endpoint value into output - default_readwrite_endpoint_handler(const_cast(value), input, input_length, output); - - // If a new value was passed, call the corresponding little endian deserialization function - uint8_t buffer[sizeof(T)] = { 0 }; // TODO: make buffer size dependent on the type - if (input_length >= sizeof(buffer)) { - read_le(value, input); - return true; - } else { - return false; - } -} - -// @brief Default endpoint handler for endpoint_ref_t types -template -bool default_readwrite_endpoint_handler(endpoint_ref_t* value, const uint8_t* input, size_t input_length, StreamSink* output) { - constexpr size_t size = sizeof(value->endpoint_id) + sizeof(value->json_crc); - if (output) { - // TODO: make buffer size dependent on the type - uint8_t buffer[size]; - size_t cnt = write_leendpoint_id)>(value->endpoint_id, buffer); - cnt += write_lejson_crc)>(value->json_crc, buffer + cnt); - if (cnt <= output->get_free_space()) - output->process_bytes(buffer, cnt, nullptr); - } - - // If a new value was passed, call the corresponding little endian deserialization function - if (input_length >= size) { - read_leendpoint_id)>(&value->endpoint_id, input); - read_lejson_crc)>(&value->json_crc, input + 2); - return true; - } else { - return false; - } -} - -template -static constexpr inline const char* get_default_json_modifier(); - -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"float\",\"access\":\"r\""; -} -template<> -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\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint64\",\"access\":\"rw\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"int32\",\"access\":\"r\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"int32\",\"access\":\"rw\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint32\",\"access\":\"r\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint32\",\"access\":\"rw\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint32\",\"access\":\"r\""; // TODO: automatically detect size -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint32\",\"access\":\"rw\""; // TODO: automatically detect size -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint16\",\"access\":\"r\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint16\",\"access\":\"rw\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint8\",\"access\":\"r\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint8\",\"access\":\"rw\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"bool\",\"access\":\"r\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"bool\",\"access\":\"rw\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"endpoint_ref\",\"access\":\"rw\""; -} - -class Endpoint { -public: - //const char* const name_; - virtual void handle(const uint8_t* input, size_t input_length, StreamSink* output) = 0; - virtual bool get_string(char * output, size_t length) { return false; } - virtual bool set_string(char * buffer, size_t length) { return false; } - virtual bool set_from_float(float value) { return false; } +namespace fibre { +template +struct Codec { + static std::optional decode(cbufptr_t* buffer) { return std::nullopt; } }; -static inline int write_string(const char* str, StreamSink* output) { - return output->process_bytes(reinterpret_cast(str), strlen(str), nullptr); +template<> struct Codec { + static std::optional decode(cbufptr_t* buffer) { return (buffer->begin() == buffer->end()) ? std::nullopt : std::make_optional((bool)*(buffer->begin()++)); } + static bool encode(bool value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(int8_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(uint8_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(int16_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(uint16_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(int32_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(uint32_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(int64_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(cbufptr_t* buffer) { return SimpleSerializer::read(&(buffer->begin()), buffer->end()); } + static bool encode(uint64_t value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; +template<> struct Codec { + static std::optional decode(cbufptr_t* buffer) { + std::optional int_val = Codec::decode(buffer); + return int_val.has_value() ? std::optional(*reinterpret_cast(&int_val.value())) : std::nullopt; + } + static bool encode(float value, bufptr_t* buffer) { + return Codec::encode(*reinterpret_cast(&value), buffer); + } +}; +template +struct Codec::value>> { + static std::optional decode(cbufptr_t* buffer) { + std::optional int_val = SimpleSerializer::read(&(buffer->begin()), buffer->end()); + return int_val.has_value() ? std::make_optional(static_cast(int_val.value())) : std::nullopt; + } + static bool encode(T value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } +}; } @@ -612,112 +521,6 @@ static bool from_string(const char * buffer, size_t length, T* property, ...) { } -/* Object tree ---------------------------------------------------------------*/ - -template -struct MemberList; - -template<> -struct MemberList<> { -public: - static constexpr size_t endpoint_count = 0; - static constexpr bool is_empty = true; - void write_json(size_t id, StreamSink* output) { - // no action - } - void register_endpoints(Endpoint** list, size_t id, size_t length) { - // no action - } - Endpoint* get_by_name(const char * name, size_t length) { - return nullptr; - } - std::tuple<> get_names_as_tuple() const { return std::tuple<>(); } -}; - -template -struct MemberList { -public: - static constexpr size_t endpoint_count = TMember::endpoint_count + MemberList::endpoint_count; - static constexpr bool is_empty = false; - - MemberList(TMember&& this_member, TMembers&&... subsequent_members) : - this_member_(std::forward(this_member)), - subsequent_members_(std::forward(subsequent_members)...) {} - - MemberList(TMember&& this_member, MemberList&& subsequent_members) : - this_member_(std::forward(this_member)), - subsequent_members_(std::forward>(subsequent_members)) {} - - // @brief Move constructor -/* MemberList(MemberList&& other) : - this_member_(std::move(other.this_member_)), - subsequent_members_(std::move(other.subsequent_members_)) {}*/ - - void write_json(size_t id, StreamSink* output) /*final*/ { - this_member_.write_json(id, output); - if (!MemberList::is_empty) - write_string(",", output); - subsequent_members_.write_json(id + TMember::endpoint_count, output); - } - - Endpoint* get_by_name(const char * name, size_t length) { - Endpoint* result = this_member_.get_by_name(name, length); - if (result) return result; - else return subsequent_members_.get_by_name(name, length); - } - - void register_endpoints(Endpoint** list, size_t id, size_t length) /*final*/ { - this_member_.register_endpoints(list, id, length); - subsequent_members_.register_endpoints(list, id + TMember::endpoint_count, length); - } - - TMember this_member_; - MemberList subsequent_members_; -}; - -template -MemberList make_protocol_member_list(TMembers&&... member_list) { - return MemberList(std::forward(member_list)...); -} - -template -class ProtocolObject { -public: - ProtocolObject(const char * name, TMembers&&... member_list) : - name_(name), - member_list_(std::forward(member_list)...) {} - - static constexpr size_t endpoint_count = MemberList::endpoint_count; - - void write_json(size_t id, StreamSink* output) { - write_string("{\"name\":\"", output); - write_string(name_, output); - write_string("\",\"type\":\"object\",\"members\":[", output); - member_list_.write_json(id, output), - write_string("]}", output); - } - - Endpoint* get_by_name(const char * name, size_t length) { - size_t segment_length = strlen(name); - if (!strncmp(name, name_, length)) - return member_list_.get_by_name(name + segment_length + 1, length - segment_length - 1); - else - return nullptr; - } - - void register_endpoints(Endpoint** list, size_t id, size_t length) { - member_list_.register_endpoints(list, id, length); - } - - const char * name_; - MemberList member_list_; -}; - -template -ProtocolObject make_protocol_object(const char * name, TMembers&&... member_list) { - return ProtocolObject(name, std::forward(member_list)...); -} - //template //bool set_from_float_ex(float value, T* property) { // return false; @@ -747,396 +550,39 @@ bool set_from_float(float value, T* property) { } } -//template -//bool set_from_float_ex<>(float value, T* property) { -// return false; -//} - -template -class ProtocolProperty : public Endpoint { -public: - static constexpr const char * json_modifier = get_default_json_modifier(); - static constexpr size_t endpoint_count = 1; - - ProtocolProperty(const char * name, TProperty* property, - void (*written_hook)(void*), void* ctx) - : name_(name), property_(property), written_hook_(written_hook), ctx_(ctx) - {} - -/* TODO: find out why the move constructor is not used when it could be - ProtocolProperty(const ProtocolProperty&) = delete; - // @brief Move constructor - ProtocolProperty(ProtocolProperty&& other) : - Endpoint(std::move(other)), - name_(std::move(other.name_)), - property_(other.property_) - {} - constexpr ProtocolProperty& operator=(const ProtocolProperty& other) = delete; - constexpr ProtocolProperty& operator=(const ProtocolProperty& other) { - //Endpoint(std::move(other)), - //name_(std::move(other.name_)), - //property_(other.property_) - name_ = other.name_; - property_ = other.property_; - return *this; - } - ProtocolProperty& operator=(ProtocolProperty&& other) - : name_(other.name_), property_(other.property_) - {} - ProtocolProperty& operator=(const ProtocolProperty& other) - : name_(other.name_), property_(other.property_) - {}*/ - - void write_json(size_t id, StreamSink* output) { - // write name - write_string("{\"name\":\"", output); - LOG_FIBRE("json: this at %x, name at %x is s\r\n", (uintptr_t)this, (uintptr_t)name_); - //LOG_FIBRE("json\r\n"); - write_string(name_, output); - - // write endpoint ID - write_string("\",\"id\":", output); - char id_buf[10]; - snprintf(id_buf, sizeof(id_buf), "%u", (unsigned)id); // TODO: get rid of printf - write_string(id_buf, output); - - // write additional JSON data - if (json_modifier && json_modifier[0]) { - write_string(",", output); - write_string(json_modifier, output); - } - - write_string("}", output); - } - - // special-purpose function - to be moved - Endpoint* get_by_name(const char * name, size_t length) { - if (!strncmp(name, name_, length)) - return this; - else - return nullptr; - } - - // special-purpose function - to be moved - bool get_string(char * buffer, size_t length) final { - return to_string(*property_, buffer, length, 0); - } - - // special-purpose function - to be moved - bool set_string(char * buffer, size_t length) final { - return from_string(buffer, length, property_, 0); - } - - bool set_from_float(float value) final { - return conversion::set_from_float(value, property_); - } - - void register_endpoints(Endpoint** list, size_t id, size_t length) { - if (id < length) - list[id] = this; - } - void handle(const uint8_t* input, size_t input_length, StreamSink* output) final { - bool wrote = default_readwrite_endpoint_handler(property_, input, input_length, output); - if (wrote && written_hook_ != nullptr) { - written_hook_(ctx_); - } - } - /*void handle(const uint8_t* input, size_t input_length, StreamSink* output) { - handle(input, input_length, output); - }*/ - - const char* name_; - TProperty* property_; - void (*written_hook_)(void*); +template +struct Property { + Property(void* ctx, T(*getter)(void*), void(*setter)(void*, T)) + : ctx_(ctx), getter_(getter), setter_(setter) {} + Property* operator->() { return this; } + void* ctx_; -}; + T(*getter_)(void*); + void(*setter_)(void*, T); -// Non-const non-enum types -template::value)> -ProtocolProperty make_protocol_property(const char * name, TProperty* property, - void (*written_hook)(void*) = nullptr, void* ctx = nullptr) { - return ProtocolProperty(name, property, written_hook, ctx); -}; - -// Const non-enum types -template::value)> -ProtocolProperty make_protocol_ro_property(const char * name, TProperty* property, - void (*written_hook)(void*) = nullptr, void* ctx = nullptr) { - return ProtocolProperty(name, property, written_hook, ctx); -}; - -// Non-const enum types -template::value)> -ProtocolProperty> make_protocol_property(const char * name, TProperty* property, - void (*written_hook)(void*) = nullptr, void* ctx = nullptr) { - return ProtocolProperty>( - name, reinterpret_cast*>(property), written_hook, ctx); -}; - -// Const enum types -template::value)> -ProtocolProperty> make_protocol_ro_property(const char * name, TProperty* property, - void (*written_hook)(void*) = nullptr, void* ctx = nullptr) { - return ProtocolProperty>( - name, reinterpret_cast*>(property), written_hook, ctx); -}; - - -template -struct PropertyListFactory; - -template<> -struct PropertyListFactory<> { - template - static MemberList<> make_property_list(std::array names, std::tuple& values) { - return MemberList<>(); - } -}; - -template -struct PropertyListFactory { - template - static MemberList, ProtocolProperty...> - make_property_list(std::array names, std::tuple& values) { - return MemberList, ProtocolProperty...>( - make_protocol_property(std::get(names), &std::get(values)), - PropertyListFactory::template make_property_list(names, values) - ); - } -}; - -/* @brief return_type::type represents the true return type -* of a function returning 0 or more arguments. -* -* For an empty TypeList, the return type is void. For a list with -* one type, the return type is equal to that type. For a list with -* more than one items, the return type is a tuple. -*/ -template -struct return_type; - -template<> -struct return_type<> { typedef void type; }; -template -struct return_type { typedef T type; }; -template -struct return_type { typedef std::tuple type; }; - - -template -class ProtocolFunction; - -template -class ProtocolFunction, std::tuple> : Endpoint { -public: - // @brief The return type of the function as written by a C++ programmer - using TRet = typename return_type::type; - - static constexpr size_t endpoint_count = 1 + MemberList...>::endpoint_count + MemberList...>::endpoint_count; - - ProtocolFunction(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TInputs...), - std::array input_names, - std::array output_names) : - name_(name), obj_(&obj), func_ptr_(func_ptr), - input_names_{input_names}, output_names_{output_names}, - input_properties_(PropertyListFactory::template make_property_list<0>(input_names_, in_args_)), - output_properties_(PropertyListFactory::template make_property_list<0>(output_names_, out_args_)) - { - LOG_FIBRE("my tuple is at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); - } - - // The custom copy constructor is needed because otherwise the - // input_properties_ and output_properties_ would point to memory - // locations of the old object. - ProtocolFunction(const ProtocolFunction& other) : - name_(other.name_), obj_(other.obj_), func_ptr_(other.func_ptr_), - input_names_{other.input_names_}, output_names_{other.output_names_}, - input_properties_(PropertyListFactory::template make_property_list<0>(input_names_, in_args_)), - output_properties_(PropertyListFactory::template make_property_list<0>(output_names_, out_args_)) - { - LOG_FIBRE("COPIED! my tuple is at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); - } - - void write_json(size_t id, StreamSink* output) { - // write name - write_string("{\"name\":\"", output); - write_string(name_, output); - - // write endpoint ID - write_string("\",\"id\":", output); - char id_buf[10]; - snprintf(id_buf, sizeof(id_buf), "%u", (unsigned)id); // TODO: get rid of printf - write_string(id_buf, output); - - // write arguments - write_string(",\"type\":\"function\",\"inputs\":[", output); - input_properties_.write_json(id + 1, output), - write_string("],\"outputs\":[", output); - output_properties_.write_json(id + 1 + decltype(input_properties_)::endpoint_count, output), - write_string("]}", output); - } - - // special-purpose function - to be moved - Endpoint* get_by_name(const char * name, size_t length) { - return nullptr; // can't address functions by name - } - - void register_endpoints(Endpoint** list, size_t id, size_t length) { - if (id < length) - list[id] = this; - input_properties_.register_endpoints(list, id + 1, length); - output_properties_.register_endpoints(list, id + 1 + decltype(input_properties_)::endpoint_count, length); - } - - template std::enable_if_t - handle_ex() { - invoke_function_with_tuple(*obj_, func_ptr_, in_args_); - } - - template std::enable_if_t - handle_ex() { - std::get<0>(out_args_) = invoke_function_with_tuple(*obj_, func_ptr_, in_args_); - } - - template std::enable_if_t= 2> - handle_ex() { - out_args_ = invoke_function_with_tuple(*obj_, func_ptr_, in_args_); - } - - void handle(const uint8_t* input, size_t input_length, StreamSink* output) final { - (void) input; - (void) input_length; - (void) output; - LOG_FIBRE("tuple still at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); - LOG_FIBRE("invoke function using %d and %.3f\r\n", std::get<0>(in_args_), std::get<1>(in_args_)); - handle_ex(); - } - - const char * name_; - TObj* obj_; - TRet(TObj::*func_ptr_)(TInputs...); - std::array input_names_; // TODO: remove - std::array output_names_; // TODO: remove - std::tuple in_args_; - std::tuple out_args_; - MemberList...> input_properties_; - MemberList...> output_properties_; -}; - -template> -ProtocolFunction, std::tuple<>> make_protocol_function(const char * name, TObj& obj, void(TObj::*func_ptr)(TArgs...), TNames ... names) { - return ProtocolFunction, std::tuple<>>(name, obj, func_ptr, {names...}, {}); -} - -template::value>> -ProtocolFunction, std::tuple> make_protocol_function(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) { - return ProtocolFunction, std::tuple>(name, obj, func_ptr, {names...}, {"result"}); -} - - -#define FIBRE_EXPORTS(CLASS, ...) \ - struct fibre_export_t { \ - static CLASS* obj; \ - using type = decltype(make_protocol_member_list(__VA_ARGS__)); \ - }; \ - fibre_export_t::type make_fibre_definitions() { \ - CLASS* obj = this; \ - return make_protocol_member_list(__VA_ARGS__); \ - } \ - fibre_export_t::type fibre_definitions = make_fibre_definitions() - - - - - -class EndpointProvider { -public: - virtual size_t get_endpoint_count() = 0; - virtual void write_json(size_t id, StreamSink* output) = 0; - virtual Endpoint* get_by_name(char * name, size_t length) = 0; - virtual void register_endpoints(Endpoint** list, size_t id, size_t length) = 0; -}; - -template -class EndpointProvider_from_MemberList : public EndpointProvider { -public: - EndpointProvider_from_MemberList(T& member_list) : member_list_(member_list) {} - size_t get_endpoint_count() final { - return T::endpoint_count; - } - void write_json(size_t id, StreamSink* output) final { - return member_list_.write_json(id, output); - } - void register_endpoints(Endpoint** list, size_t id, size_t length) final { - return member_list_.register_endpoints(list, id, length); - } - Endpoint* get_by_name(char * name, size_t length) final { - for (size_t i = 0; i < length; i++) { - if (name[i] == '.') - name[i] = 0; + T exchange(std::optional value) { + T old_value = (*getter_)(ctx_); + if (value.has_value()) { + (*setter_)(ctx_, value.value()); } - name[length-1] = 0; - return member_list_.get_by_name(name, length); + return old_value; } - T& member_list_; }; - - -class JSONDescriptorEndpoint : Endpoint { -public: - static constexpr size_t endpoint_count = 1; - void write_json(size_t id, StreamSink* output); - void register_endpoints(Endpoint** list, size_t id, size_t length); - void handle(const uint8_t* input, size_t input_length, StreamSink* output); -}; - -// defined in protocol.cpp -extern Endpoint** endpoint_list_; -extern size_t n_endpoints_; -extern uint16_t json_crc_; -extern uint32_t json_version_id_; // exposed to hosts to facilitate cache lookup -extern JSONDescriptorEndpoint json_file_endpoint_; -extern EndpointProvider* application_endpoints_; - -bool is_endpoint_ref_valid(endpoint_ref_t endpoint_ref); -Endpoint* get_endpoint(endpoint_ref_t endpoint_ref); - -// @brief Registers the specified application object list using the provided endpoint table. -// This function should only be called once during the lifetime of the application. TODO: fix this. -// @param application_objects The application objects to be registred. template -int fibre_publish(T& application_objects) { - static constexpr size_t endpoint_list_size = 1 + T::endpoint_count; - static Endpoint* endpoint_list[endpoint_list_size]; - static auto endpoint_provider = EndpointProvider_from_MemberList(application_objects); - - json_file_endpoint_.register_endpoints(endpoint_list, 0, endpoint_list_size); - application_objects.register_endpoints(endpoint_list, 1, endpoint_list_size); - - // Update the global endpoint table - endpoint_list_ = endpoint_list; - n_endpoints_ = endpoint_list_size; - application_endpoints_ = &endpoint_provider; +struct Property { + Property(void* ctx, T(*getter)(void*)) + : ctx_(ctx), getter_(getter) {} + Property* operator->() { return this; } - // Calculate the CRC16 of the JSON file. - // The init value is the protocol version. - CRC16Calculator crc16_calculator(PROTOCOL_VERSION); + void* ctx_; + T(*getter_)(void*); - uint8_t offset[4] = { 0 }; - json_file_endpoint_.handle(offset, sizeof(offset), &crc16_calculator); - json_crc_ = crc16_calculator.get_crc16(); - - // Add entropy for fibre cache - json_file_endpoint_.handle(offset, sizeof(offset), &crc16_calculator); - json_version_id_ = (uint32_t) crc16_calculator.get_crc16(); - json_version_id_ += json_crc_ << 16; - - return 0; -} + T read() { + return (*getter_)(ctx_); + } +}; #endif diff --git a/Firmware/fibre/cpp/include/fibre/simple_serdes.hpp b/Firmware/fibre/cpp/include/fibre/simple_serdes.hpp new file mode 100644 index 00000000..32c09f27 --- /dev/null +++ b/Firmware/fibre/cpp/include/fibre/simple_serdes.hpp @@ -0,0 +1,77 @@ +#ifndef __FIBRE_SIMPLE_SERDES +#define __FIBRE_SIMPLE_SERDES + +//#include "stream.hpp" + + +template +struct SimpleSerializer; +template +using LittleEndianSerializer = SimpleSerializer; +template +using BigEndianSerializer = SimpleSerializer; + + +/* @brief Serializer/deserializer for arbitrary integral number types */ +// TODO: allow reading an arbitrary number of bits +template +struct SimpleSerializer::value>> { + static constexpr size_t BIT_WIDTH = std::numeric_limits::digits; + static constexpr size_t BYTE_WIDTH = (BIT_WIDTH + 7) / 8; + + template + static std::optional read(TIterator* begin, TIterator end = nullptr) { + T result = 0; + if (BigEndian) { + for (size_t i = BYTE_WIDTH; i > 0; (i++, (*begin)++)) { + if (end && !(*begin < end)) + return std::nullopt; + uint8_t byte = **begin; + result |= static_cast(byte) << ((i - 1) << 3); + } + } else { + for (size_t i = 0; i < BYTE_WIDTH; (i++, (*begin)++)) { + if (end && !(*begin < end)) + return std::nullopt; + uint8_t byte = **begin; + result |= static_cast(byte) << (i << 3); + } + } + return result; + } + + template + static bool write(T value, TIterator* begin, TIterator end = nullptr) { + if (BigEndian) { + for (size_t i = BYTE_WIDTH; i > 0; (i--, (*begin)++)) { + if (end && !(*begin < end)) + return false; + uint8_t byte = static_cast((value >> ((i - 1) << 3)) & 0xff); + **begin = byte; + } + } else { + for (size_t i = 0; i < BYTE_WIDTH; (i++, (*begin)++)) { + if (end && !(*begin < end)) + return false; + uint8_t byte = static_cast((value >> (i << 3)) & 0xff); + **begin = byte; + } + } + return true; + } +}; + +template +inline std::optional read_le(fibre::cbufptr_t* buffer) { + static_assert(is_complete>(), "no LittleEndianSerializer is defined for type T"); + return LittleEndianSerializer::read(&buffer->begin(), buffer->end()); +} + +template +inline bool write_le(T value, fibre::bufptr_t* buffer) { + static_assert(is_complete>(), "no LittleEndianSerializer is defined for type T"); + return LittleEndianSerializer::write(value, &buffer->begin(), buffer->end()); +} + + +#endif \ No newline at end of file diff --git a/Firmware/fibre/cpp/interfaces_template.j2 b/Firmware/fibre/cpp/interfaces_template.j2 new file mode 100644 index 00000000..76e0c90d --- /dev/null +++ b/Firmware/fibre/cpp/interfaces_template.j2 @@ -0,0 +1,67 @@ +/*[# This is the original template, thus the warning below does not apply to this file #] + * ============================ WARNING ============================ + * ==== This is an autogenerated file. ==== + * ==== Any changes to this file will be lost when recompiling. ==== + * ================================================================= + * + * This file contains base classes that correspond to the interfaces defined in + * your interface file. The objects you publish should inherit from these + * interfaces. + * + */ + +[%- macro rettype(func) %] +[%- if not func.out -%] +void +[%- elif func.out | length == 1 -%] +[[(func.out.values() | first).type.c_type]] +[%- else -%] +[% for arg in func.out.values() %][[arg.type]][[', ' if not loop.last]][% endfor %] +[%- endif -%] +[%- endmacro %] + +[%- macro render_interface(intf) %] +class [[intf.name | to_pascal_case]]Intf { +public: +[%- for intf in intf.interfaces -%] +[[render_interface(intf) | indent(4)]] +[%- endfor %] +[%- for enum in intf.enums %] + enum [[enum.name | to_pascal_case]] { +[%- for k, value in enum['values'].items() %] + [[((enum.name + k) | to_macro_case).ljust(32)]] = [% if enum.is_flags %]0x[['%08x' | format(value.value)]][% else %][[value.value]][% endif %], +[%- endfor %] + }; +[%- endfor %] +[%- for func in intf.functions.values() %] + virtual [[rettype(func)]] [[func.name | to_snake_case]]([% for in in func.in.values() %][% if loop.index0 %][[in.type.c_type]] [[in.name]][[', ' if not loop.last]][% endif %][% endfor %]) = 0; +[%- endfor %] +[%- for func in intf.functions.values() %] +[%- for k, arg in func.in.items() | skip_first %] + [[arg.type.c_type]] [[func.name | to_snake_case]]_in_[[arg.name]]_; // for internal use by Fibre +[%- endfor %] +[%- for k, arg in func.out.items() %] + [[arg.type.c_type]] [[func.name | to_snake_case]]_out_[[arg.name]]_; // for internal use by Fibre +[%- endfor %] +[%- endfor %] +}; +[%- endmacro %] + +[% for intf in toplevel_interfaces %] +[[render_interface(intf)]] +[% endfor %] + +[%- for _, enum in value_types.items() %] +[%- if enum.is_flags %] +// this is technically not thread-safe but practically it might be +inline [[enum.c_type]] operator | ([[enum.c_type]] a, [[enum.c_type]] b) { return static_cast<[[enum.c_type]]>(static_cast>(a) | static_cast>(b)); } +inline [[enum.c_type]] operator & ([[enum.c_type]] a, [[enum.c_type]] b) { return static_cast<[[enum.c_type]]>(static_cast>(a) & static_cast>(b)); } +inline [[enum.c_type]] operator ^ ([[enum.c_type]] a, [[enum.c_type]] b) { return static_cast<[[enum.c_type]]>(static_cast>(a) ^ static_cast>(b)); } +inline [[enum.c_type]]& operator |= ([[enum.c_type]] &a, [[enum.c_type]] b) { return reinterpret_cast<[[enum.c_type]]&>(reinterpret_cast&>(a) |= static_cast>(b)); } +inline [[enum.c_type]]& operator &= ([[enum.c_type]] &a, [[enum.c_type]] b) { return reinterpret_cast<[[enum.c_type]]&>(reinterpret_cast&>(a) &= static_cast>(b)); } +inline [[enum.c_type]]& operator ^= ([[enum.c_type]] &a, [[enum.c_type]] b) { return reinterpret_cast<[[enum.c_type]]&>(reinterpret_cast&>(a) ^= static_cast>(b)); } +inline [[enum.c_type]] operator ~ ([[enum.c_type]] a) { return static_cast<[[enum.c_type]]>(~static_cast>(a)); } +[%- endif %] +[%- endfor %] + + diff --git a/Firmware/fibre/cpp/protocol.cpp b/Firmware/fibre/cpp/protocol.cpp index d5af8a0b..e8285c87 100644 --- a/Firmware/fibre/cpp/protocol.cpp +++ b/Firmware/fibre/cpp/protocol.cpp @@ -13,19 +13,11 @@ /* Global constant data ------------------------------------------------------*/ /* Global variables ----------------------------------------------------------*/ -Endpoint** endpoint_list_ = nullptr; // initialized by calling fibre_publish -size_t n_endpoints_ = 0; // initialized by calling fibre_publish -uint16_t json_crc_; // initialized by calling fibre_publish -uint32_t json_version_id_; // initialized by calling fibre_publish -JSONDescriptorEndpoint json_file_endpoint_ = JSONDescriptorEndpoint(); -EndpointProvider* application_endpoints_; - /* Private constant data -----------------------------------------------------*/ /* Private variables ---------------------------------------------------------*/ /* Private function prototypes -----------------------------------------------*/ static void hexdump(const uint8_t* buf, size_t len); -static inline int write_string(const char* str, StreamSink* output); /* Function implementations --------------------------------------------------*/ @@ -116,45 +108,26 @@ int StreamBasedPacketSink::process_packet(const uint8_t *buffer, size_t length) } - -void JSONDescriptorEndpoint::write_json(size_t id, StreamSink* output) { - write_string("{\"name\":\"\",", output); - - // write endpoint ID - write_string("\"id\":", output); - char id_buf[10]; - snprintf(id_buf, sizeof(id_buf), "%u", (unsigned)id); // TODO: get rid of printf - write_string(id_buf, output); - - write_string(",\"type\":\"json\",\"access\":\"r\"}", output); -} - -void JSONDescriptorEndpoint::register_endpoints(Endpoint** list, size_t id, size_t length) { - if (id < length) - list[id] = this; -} - // Returns part of the JSON interface definition. -void JSONDescriptorEndpoint::handle(const uint8_t* input, size_t input_length, StreamSink* output) { +bool fibre::endpoint0_handler(fibre::cbufptr_t* input_buffer, fibre::bufptr_t* output_buffer) { // The request must contain a 32 bit integer to specify an offset - if (input_length < 4) - return; - uint32_t offset = 0; - read_le(&offset, input); - - // If the offset is special value 0xFFFFFFFF, send back the JSON version ID instead - if (offset == 0xffffffff) { - default_readwrite_endpoint_handler(&json_version_id_, nullptr, 0, output); + std::optional offset = read_le(input_buffer); + + if (!offset.has_value()) { + // Didn't receive any offset + return false; + } else if (offset.value() == 0xffffffff) { + // If the offset is special value 0xFFFFFFFF, send back the JSON version ID instead + return write_le(json_version_id_, output_buffer); + } else if (offset.value() >= embedded_json_length) { + // Attempt to read beyond the buffer end - return empty response + return true; } else { - NullStreamSink output_with_offset = NullStreamSink(offset, *output); - - size_t id = 0; - write_string("[", &output_with_offset); - json_file_endpoint_.write_json(id, &output_with_offset); - id += decltype(json_file_endpoint_)::endpoint_count; - write_string(",", &output_with_offset); - application_endpoints_->write_json(id, &output_with_offset); - write_string("]", &output_with_offset); + // Return part of the json file + size_t n_copy = std::min(output_buffer->size(), embedded_json_length - (size_t)offset.value()); + memcpy(output_buffer->begin(), embedded_json + offset.value(), n_copy); + *output_buffer = output_buffer->skip(n_copy); + return true; } } @@ -176,19 +149,10 @@ int BidirectionalPacketBasedChannel::process_packet(const uint8_t* buffer, size_ bool expect_response = endpoint_id & 0x8000; endpoint_id &= 0x7fff; - if (endpoint_id >= n_endpoints_) - return -1; - - Endpoint* endpoint = endpoint_list_[endpoint_id]; - if (!endpoint) { - LOG_FIBRE("critical: no endpoint at %d", endpoint_id); - return -1; - } - // Verify packet trailer. The expected trailer value depends on the selected endpoint. // For endpoint 0 this is just the protocol version, for all other endpoints it's a // CRC over the entire JSON descriptor tree (this may change in future versions). - uint16_t expected_trailer = endpoint_id ? json_crc_ : PROTOCOL_VERSION; + uint16_t expected_trailer = endpoint_id ? fibre::json_crc_ : PROTOCOL_VERSION; uint16_t actual_trailer = buffer[length - 2] | (buffer[length - 1] << 8); if (expected_trailer != actual_trailer) { LOG_FIBRE("trailer mismatch for endpoint %d: expected %04x, got %04x\r\n", endpoint_id, expected_trailer, actual_trailer); @@ -204,12 +168,13 @@ int BidirectionalPacketBasedChannel::process_packet(const uint8_t* buffer, size_ if (expected_response_length > sizeof(tx_buf_) - 2) expected_response_length = sizeof(tx_buf_) - 2; - MemoryStreamSink output(tx_buf_ + 2, expected_response_length); - endpoint->handle(buffer, length - 2, &output); + fibre::cbufptr_t input_buffer{buffer, length - 2}; + fibre::bufptr_t output_buffer{tx_buf_ + 2, expected_response_length}; + fibre::endpoint_handler(endpoint_id, &input_buffer, &output_buffer); // Send response if (expect_response) { - size_t actual_response_length = expected_response_length - output.get_free_space() + 2; + size_t actual_response_length = expected_response_length - output_buffer.size() + 2; write_le(seq_no | 0x8000, tx_buf_); LOG_FIBRE("send packet:\r\n"); @@ -220,15 +185,3 @@ int BidirectionalPacketBasedChannel::process_packet(const uint8_t* buffer, size_ return 0; } - -bool is_endpoint_ref_valid(endpoint_ref_t endpoint_ref) { - return (endpoint_ref.json_crc == json_crc_) - && (endpoint_ref.endpoint_id < n_endpoints_); -} - -Endpoint* get_endpoint(endpoint_ref_t endpoint_ref) { - if (is_endpoint_ref_valid(endpoint_ref)) - return endpoint_list_[endpoint_ref.endpoint_id]; - else - return nullptr; -} diff --git a/Firmware/interface_generator.py b/Firmware/interface_generator.py new file mode 100644 index 00000000..38468036 --- /dev/null +++ b/Firmware/interface_generator.py @@ -0,0 +1,589 @@ +#!/bin/python3 + +import yaml +import json +import jinja2 +import jsonschema +import re +import argparse +import sys + +# This schema describes what we expect interface definition files to look like +validator = jsonschema.Draft7Validator(yaml.safe_load(""" +definitions: + interface: + type: object + properties: + c_is_class: {type: boolean} + c_name: {type: string} + functions: + type: object + additionalProperties: {"$ref": "#/definitions/function"} + attributes: + type: object + additionalProperties: {"$ref": "#/definitions/attribute"} + __line__: {type: object} + __column__: {type: object} + additionalProperties: false + + valuetype: + type: object + properties: + mode: {type: string} # this shouldn't be here + c_name: {type: string} + values: {type: object} + flags: {type: object} + nullflag: {type: string} + __line__: {type: object} + __column__: {type: object} + additionalProperties: false + + intf_or_val_type: + anyOf: + - {"$ref": "#/definitions/interface"} + - {"$ref": "#/definitions/valuetype"} + - {"type": "string"} + + attribute: + anyOf: # this is probably not being used correctly + - {"$ref": "#/definitions/intf_or_val_type"} + - type: object + - type: object + properties: + type: {"$ref": "#/definitions/intf_or_val_type"} + c_name: {"type": string} + unit: {"type": string} + doc: {"type": string} + additionalProperties: false + + function: + anyOf: + - type: 'null' + - type: object + properties: + in: {type: object} + out: {type: object} + doc: {type: string} + __line__: {type: object} + __column__: {type: object} + additionalProperties: false + + +type: object +properties: + ns: {type: string} + version: {type: string} + summary: {type: string} + interfaces: + type: object + additionalProperties: { "$ref": "#/definitions/interface" } + valuetypes: + type: object + additionalProperties: { "$ref": "#/definitions/valuetype" } + __line__: {type: object} + __column__: {type: object} +additionalProperties: false +""")) + +# Source: https://stackoverflow.com/a/53647080/3621512 +class SafeLineLoader(yaml.SafeLoader): + pass +# def compose_node(self, parent, index): +# # the line number where the previous token has ended (plus empty lines) +# line = self.line +# node = super(SafeLineLoader, self).compose_node(parent, index) +# node.__line__ = line + 1 +# return node +# +# def construct_mapping(self, node, deep=False): +# mapping = super(SafeLineLoader, self).construct_mapping(node, deep=deep) +# mapping['__line__'] = node.__line__ +# #mapping['__column__'] = node.start_mark.column + 1 +# return mapping + + + +def get_words(string): + """ + Splits a string in PascalCase into a list of lower case words + """ + return [w.lower() for w in re.findall('[a-z0-9]+|[A-Z][a-z0-9]*', string)] + +def join_name(*names, delimiter: str = '.'): + """ + Joins two name components. + e.g. 'io.helloworld' + 'sayhello' => 'io.helloworld.sayhello' + """ + return delimiter.join(y for x in names for y in x.split(delimiter) if y != '') + +def split_name(name, delimiter: str = '.'): + def replace_delimiter_in_parentheses(): + parenthesis_depth = 0 + for c in name: + parenthesis_depth += 1 if c == '<' else -1 if c == '>' else 0 + yield c if (parenthesis_depth == 0) or (c != delimiter) else ':' + return [part.replace(':', '.') for part in ''.join(replace_delimiter_in_parentheses()).split('.')] + +def to_pascal_case(s): return ''.join([w.title() for w in get_words(s)]) +def to_camel_case(s): return ''.join([(c.lower() if i == 0 else c) for i, c in enumerate(''.join([w.title() for w in get_words(s)]))]) +def to_macro_case(s): return '_'.join(get_words(s)).upper() +def to_snake_case(s): return '_'.join(get_words(s)).lower() +def to_kebab_case(s): return '-'.join(get_words(s)).lower() + +value_types = { + 'bool': {'builtin': True, 'fullname': 'bool', 'name': 'bool', 'c_type': 'bool'}, + 'float32': {'builtin': True, 'fullname': 'float32', 'name': 'float32', 'c_type': 'float'}, + 'uint8': {'builtin': True, 'fullname': 'uint8', 'name': 'uint8', 'c_type': 'uint8_t'}, + 'uint16': {'builtin': True, 'fullname': 'uint16', 'name': 'uint16', 'c_type': 'uint16_t'}, + 'uint32': {'builtin': True, 'fullname': 'uint32', 'name': 'uint32', 'c_type': 'uint32_t'}, + 'uint64': {'builtin': True, 'fullname': 'uint64', 'name': 'uint64', 'c_type': 'uint64_t'}, + 'int8': {'builtin': True, 'fullname': 'int8', 'name': 'int8', 'c_type': 'int8_t'}, + 'int16': {'builtin': True, 'fullname': 'int16', 'name': 'int16', 'c_type': 'int16_t'}, + 'int32': {'builtin': True, 'fullname': 'int32', 'name': 'int32', 'c_type': 'int32_t'}, + 'int64': {'builtin': True, 'fullname': 'int64', 'name': 'int64', 'c_type': 'int64_t'}, +} + +enums = {} + +interfaces = {} + +def make_property_type(typeargs): + value_type = resolve_valuetype('', typeargs['fibre.Property.type']) + mode = typeargs.get('fibre.Property.mode', 'readwrite') + name = 'Property<' + value_type['fullname'] + ', ' + mode + '>' + fullname = join_name('fibre', name) + if fullname in interfaces: + return interfaces[fullname] + + c_type = 'Property<' + ('const ' if mode == 'readonly' else '') + value_type['c_type'] + '>' + prop_type = { + 'name': name, + 'fullname': fullname, + 'c_type': c_type, + 'value_type': value_type, # TODO: should be a metaarg + 'mode': mode, # TODO: should be a metaarg + 'attributes': {}, + 'functions': {} + } + if mode != 'readonly': + prop_type['functions']['exchange'] = { + 'name': 'exchange', + 'fullname': join_name(fullname, 'exchange'), + 'in': {'obj': {'name': 'obj', 'type': {'c_type': c_type}}, 'value': {'name': 'value', 'type': value_type, 'optional': True}}, + 'out': {'value': {'name': 'value', 'type': value_type}}, + #'implementation': 'fibre_property_exchange<' + value_type['c_type'] + '>' + } + else: + prop_type['functions']['read'] = { + 'name': 'read', + 'fullname': join_name(fullname, 'read'), + 'in': {'obj': {'name': 'obj', 'type': {'c_type': c_type}}}, + 'out': {'value': {'name': 'value', 'type': value_type}}, + #'implementation': 'fibre_property_read<' + value_type['c_type'] + '>' + } + + interfaces[fullname] = prop_type + return prop_type + +generics = { + 'fibre.Property': make_property_type # TODO: improve generic support +} + + +def make_ref_type(interface): + name = 'Ref<' + interface['fullname'] + '>' + fullname = join_name('fibre', name) + if fullname in interfaces: + return interfaces[fullname] + + ref_type = { + 'builtin': True, + 'name': name, + 'fullname': fullname, + 'c_type': interface['fullname'].replace('.', 'Intf::') + 'Intf*' + } + value_types[fullname] = ref_type + + return ref_type + +def get_dict(elem, key): + return elem.get(key, None) or {} + +def regularize_arg(path, name, elem): + if elem is None: + elem = {} + elif isinstance(elem, str): + elem = {'type': elem} + elem['name'] = name + elem['fullname'] = path = join_name(path, name) + elem['type'] = regularize_valuetype(path, name, elem['type']) + return elem + +def regularize_func(path, name, elem, prepend_args): + if elem is None: + elem = {} + elem['name'] = name + elem['fullname'] = path = join_name(path, name) + elem['in'] = {n: regularize_arg(path, n, arg) + for n, arg in {**prepend_args, **get_dict(elem, 'in')}.items()} + elem['out'] = {n: regularize_arg(path, n, arg) + for n, arg in get_dict(elem, 'out').items()} + return elem + +def regularize_attribute(path, name, elem, c_is_class): + if elem is None: + elem = {} + if isinstance(elem, str): + elem = {'type': elem} + elif not 'type' in elem: + elem['type'] = {} + if 'attributes' in elem: elem['type']['attributes'] = elem.pop('attributes') + if 'functions' in elem: elem['type']['functions'] = elem.pop('functions') + if 'values' in elem: elem['type']['values'] = elem.pop('values') + if 'flags' in elem: elem['type']['flags'] = elem.pop('flags') + if 'nullflag' in elem: elem['type']['nullflag'] = elem.pop('nullflag') + + elem['name'] = name + elem['fullname'] = join_name(path, name) + elem['typeargs'] = elem.get('typeargs', {}) + elem['c_name'] = elem.get('c_name', None) or (elem['name'] + ('_' if c_is_class else '')) + + if isinstance(elem['type'], str) and elem['type'].startswith('readonly '): + elem['typeargs']['fibre.Property.mode'] = 'readonly' + elem['typeargs']['fibre.Property.type'] = elem['type'][len('readonly '):] + elem['type'] = 'fibre.Property' + elif ('flags' in elem['type']) or ('values' in elem['type']): + elem['typeargs']['fibre.Property.mode'] = elem['typeargs'].get('fibre.Property.mode', None) or 'readwrite' + elem['typeargs']['fibre.Property.type'] = regularize_valuetype(path, to_pascal_case(name), elem['type']) + elem['type'] = 'fibre.Property' + else: + elem['type'] = regularize_interface(path, to_pascal_case(name), elem['type']) + return elem + + +def regularize_interface(path, name, elem): + if elem is None: + elem = {} + if isinstance(elem, str): + return elem # will be resolved during type resolution + #if path is None: + # max_anonymous_type = max([int((re.findall('^' + join_name(path, 'AnonymousType') + '([1-9]+)$', x) + ['0'])[0]) for x in interfaces.keys()]) + # path = 'AnonymousType' + str(max_anonymous_type + 1) + elem['name'] = split_name(name)[-1] + elem['fullname'] = path = join_name(path, name) + elem['c_type'] = elem.get('c_type', elem['fullname'].replace('.', 'Intf::')) + 'Intf' + interfaces[path] = elem + elem['functions'] = {name: regularize_func(path, name, func, {'obj': {'type': make_ref_type(elem)}}) + for name, func in get_dict(elem, 'functions').items()} + treat_as_class = elem.get('c_is_class', None) or (len(elem['functions']) > 0) + elem['attributes'] = {name: regularize_attribute(path, name, prop, treat_as_class) + for name, prop in get_dict(elem, 'attributes').items()} + elem['interfaces'] = [] + elem['enums'] = [] + return elem + +def regularize_valuetype(path, name, elem): + if elem is None: + elem = {} + if isinstance(elem, str): + return elem # will be resolved during type resolution + elem['name'] = split_name(name)[-1] + elem['fullname'] = path = join_name(path, name) + elem['c_type'] = elem.get('c_type', elem['fullname'].replace('.', 'Intf::')) + value_types[path] = elem + + if 'flags' in elem: # treat as flags + bit = 0 + for k, v in elem['flags'].items(): + elem['flags'][k] = elem['flags'][k] or {} + current_bit = elem['flags'][k].get('bit', bit) + elem['flags'][k]['bit'] = current_bit + elem['flags'][k]['value'] = 0 if current_bit is None else (1 << current_bit) + bit = bit if current_bit is None else current_bit + 1 + if 'nullflag' in elem: + elem['flags'] = {elem['nullflag']: {'value': 0, 'bit': None}, **elem['flags']} + elem['values'] = elem['flags'] + elem['is_flags'] = True + elem['is_enum'] = True + enums[path] = elem + + elif 'values' in elem: # treat as enum + val = 0 + for k, v in elem['values'].items(): + elem['values'][k] = elem['values'][k] or {} + val = elem['values'][k].get('value', val) + elem['values'][k]['value'] = val + val += 1 + enums[path] = elem + elem['is_enum'] = True + + return elem + +def resolve_interface(scope, name, typeargs): + """ + Resolves a type name (i.e. interface name or value type name) given as a + string to an interface object. The innermost scope is searched first. + At every scope level, if no matching interface is found, it is checked if a + matching value type exists. If so, the interface type fibre.Property + is returned. + """ + if not isinstance(name, str): + return name + + if 'fibre.Property.type' in typeargs: + typeargs['fibre.Property.type'] = resolve_valuetype(scope, typeargs['fibre.Property.type']) + + scope = scope.split('.') + for probe_scope in [join_name(*scope[:(len(scope)-i)]) for i in range(len(scope)+1)]: + probe_name = join_name(probe_scope, name) + #print('probing ' + probe_name) + if probe_name in interfaces: + return interfaces[probe_name] + elif probe_name in value_types: + typeargs['fibre.Property.type'] = value_types[probe_name] + return make_property_type(typeargs) + elif probe_name in generics: + return generics[probe_name](typeargs) + + raise Exception(f'could not resolve type {name} in {join_name(*scope)}. Known interfaces are: {list(interfaces.keys())}. Known value types are: {list(value_types.keys())}') + +def resolve_valuetype(scope, name): + """ + Resolves a type name given as a string to the type object. + The innermost scope is searched first. + """ + if not isinstance(name, str): + return name + + scope = scope.split('.') + for probe_scope in [join_name(*scope[:(len(scope)-i)]) for i in range(len(scope)+1)]: + probe_name = join_name(probe_scope, name) + if probe_name in value_types: + return value_types[probe_name] + + raise Exception(f'could not resolve type {name} in {join_name(*scope)}. Known value types are: {list(value_types.keys())}') + + +def map_to_fibre01_type(t): + if t.get('is_enum', False): + return 'int32' + elif t['fullname'] == 'float32': + return 'float' + return t['fullname'] + +def generate_endpoint_for_property(prop, bindto, idx): + c_value_type = prop['type']['value_type']['c_type'] + if prop.get('c_setter', None) is None: + c_setter = '[](void* ctx, ' + c_value_type + ' val){ ((decltype(&' + bindto + '))ctx)->' + prop['c_name'] + ' = val; }' + else: + c_setter = '[](void* ctx, ' + c_value_type + ' val){ ((decltype(&' + bindto + '))ctx)->' + prop['c_setter'] + '(val); }' + c_getter = '[](void* ctx) { return (const ' + c_value_type + '&)((decltype(&' + bindto + '))ctx)->' + prop['c_name'] + '; }' + + prop_intf = interfaces[prop['type']['fullname']] + if prop['type']['mode'] == 'readonly': + attr_bindto = prop_intf['c_type'] + '{(void*)&' + bindto + ', ' + c_getter + '}' + else: + attr_bindto = prop_intf['c_type'] + '{(void*)&' + bindto + ', ' + c_getter + ', ' + c_setter + '}' + + endpoint = { + 'id': idx, + 'function': prop_intf['functions']['read' if prop['type']['mode'] == 'readonly' else 'exchange'], + 'in_bindings': {'obj': attr_bindto}, + 'out_bindings': [] + } + endpoint_definition = { + 'name': prop['name'], + 'id': idx, + 'type': map_to_fibre01_type(prop['type']['value_type']), + 'access': 'r' if prop['type']['mode'] == 'readonly' else 'rw', + } + return endpoint, endpoint_definition + +def generate_endpoint_table(intf, bindto, idx): + """ + Generates a Fibre v0.1 endpoint table for a given interface. + This will probably be deprecated in the future. + The object must have no circular property types (i.e. A.b has type B and B.a has type A). + """ + endpoints = [] + endpoint_definitions = [] + cnt = 0 + + for k, prop in intf['attributes'].items(): + property_value_type = re.findall('^fibre\.Property<([^>]*), (readonly|readwrite)>$', prop['type']['fullname']) + #attr_bindto = join_name(bindto, bindings_map.get(join_name(intf['fullname'], k), k + ('_' if len(intf['functions']) or (intf['fullname'] in treat_as_classes) else ''))) + if len(property_value_type): + # Special handling for Property<...> attributes: they resolve to one single endpoint + endpoint, endpoint_definition = generate_endpoint_for_property(prop, bindto, idx + cnt) + endpoints.append(endpoint) + endpoint_definitions.append(endpoint_definition) + cnt += 1 + else: + attr_bindto = join_name(bindto, prop['c_name']) + inner_endpoints, inner_endpoint_definitions, inner_cnt = generate_endpoint_table(prop['type'], attr_bindto, idx + cnt) + endpoints += inner_endpoints + endpoint_definitions.append({ + 'name': k, + 'type': 'object', + 'members': inner_endpoint_definitions + }) + cnt += inner_cnt + + for k, func in intf['functions'].items(): + endpoints.append({ + 'id': idx + cnt, + 'function': func, + 'in_bindings': {**{'obj': '&' + bindto}, **{k_arg: bindto + '.' + func['name'] + '_in_' + k_arg + '_' for k_arg in list(func['in'].keys())[1:]}}, + 'out_bindings': {k_arg: '&' + bindto + '.' + func['name'] + '_out_' + k_arg + '_' for k_arg in func['out'].keys()}, + }) + in_def = [] + out_def = [] + for i, (k_arg, arg) in enumerate(list(func['in'].items())[1:]): + endpoint, endpoint_definition = generate_endpoint_for_property({ + 'name': arg['name'], + 'c_name': func['name'] + '_in_' + k_arg + '_', + 'type': make_property_type({'fibre.Property.type': arg['type'], 'fibre.Property.mode': 'readwrite'}) + }, bindto, idx + cnt + 1 + i) + endpoints.append(endpoint) + in_def.append(endpoint_definition) + for i, (k_arg, arg) in enumerate(func['out'].items()): + endpoint, endpoint_definition = generate_endpoint_for_property({ + 'name': arg['name'], + 'c_name': func['name'] + '_out_' + k_arg + '_', + 'type': make_property_type({'fibre.Property.type': arg['type'], 'fibre.Property.mode': 'readwrite'}) + }, bindto, idx + cnt + len(func['in']) + i) + endpoints.append(endpoint) + out_def.append(endpoint_definition) + + endpoint_definitions.append({ + 'name': k, + 'id': idx + cnt, + 'type': 'function', + 'inputs': in_def, + 'outputs': out_def + }) + cnt += len(func['in']) + len(func['out']) + + return endpoints, endpoint_definitions, cnt + + +# Parse arguments + +parser = argparse.ArgumentParser(description="Gernerate code from YAML interface definitions") +parser.add_argument("--version", action="store_true", + help="print version information") +parser.add_argument("-v", "--verbose", action="store_true", + help="print debug information (on stderr)") +parser.add_argument("-d", "--definitions", type=argparse.FileType('r'), nargs='+', + help="the YAML interface definition file(s) used to generate the code") +parser.add_argument("-t", "--template", type=argparse.FileType('r'), + help="the code template") +parser.add_argument("-o", "--output", type=argparse.FileType('w'), default='-', + help="path of the generated output") +args = parser.parse_args() + +if args.version: + print("0.0.1") + sys.exit(0) + + +definition_files = args.definitions +template_file = args.template +output_file = args.output + + +# Load definition files + +for definition_file in definition_files: + try: + file_content = yaml.load(definition_file, Loader=SafeLineLoader) + except yaml.scanner.ScannerError as ex: + print("YAML parsing error: " + str(ex), file=sys.stderr) + sys.exit(1) + for err in validator.iter_errors(file_content): + if '__line__' in err.absolute_path: + continue + if '__column__' in err.absolute_path: + continue + #instance = err.instance.get(re.findall("([^']*)' (?:was|were) unexpected\)", err.message)[0], err.instance) + # TODO: print line number + raise Exception(err.message + '\nat ' + str(list(err.absolute_path))) + interfaces = {**interfaces, **get_dict(file_content, 'interfaces')} + value_types = {**value_types, **get_dict(file_content, 'valuetypes')} + + +# Preprocess definitions + +# Regularize everything into a wellknown form +for k, item in list(interfaces.items()): + regularize_interface('', k, item) +for k, item in list(value_types.items()): + regularize_valuetype('', k, item) + +if args.verbose: + print('Known interfaces: ' + ''.join([('\n ' + k) for k in interfaces.keys()])) + print('Known value types: ' + ''.join([('\n ' + k) for k in value_types.keys()])) + +# Resolve all types into references +for _, item in list(interfaces.items()): + for _, prop in item['attributes'].items(): + prop['type'] = resolve_interface(item['fullname'], prop['type'], prop['typeargs']) + for _, func in item['functions'].items(): + for _, arg in func['in'].items(): + arg['type'] = resolve_valuetype(item['fullname'], arg['type']) + for _, arg in func['out'].items(): + arg['type'] = resolve_valuetype(item['fullname'], arg['type']) + +# Attach interfaces to their parents +toplevel_interfaces = [] +for k, item in list(interfaces.items()): + k = split_name(k) + if len(k) == 1: + toplevel_interfaces.append(item) + else: + if k[:-1] != ['fibre']: # TODO: remove special handling + interfaces[join_name(*k[:-1])]['interfaces'].append(item) +toplevel_enums = [] +for k, item in list(enums.items()): + k = split_name(k) + if len(k) == 1: + toplevel_enums.append(item) + else: + if k[:-1] != ['fibre']: # TODO: remove special handling + interfaces[join_name(*k[:-1])]['enums'].append(item) + + + +endpoints, embedded_endpoint_definitions, _ = generate_endpoint_table(interfaces['Odrive'], 'odrv', 1) # TODO: make user-configurable +embedded_endpoint_definitions = [{'name': '', 'id': 0, 'type': 'json', 'access': 'r'}] + embedded_endpoint_definitions +endpoints = [{'id': 0, 'function': {'fullname': 'endpoint0_handler', 'in': {}, 'out': {}}, 'bindings': {}}] + endpoints + + +# Render template + +env = jinja2.Environment( + comment_start_string='[#', comment_end_string='#]', + block_start_string='[%', block_end_string='%]', + variable_start_string='[[', variable_end_string=']]' +) + +env.filters['to_pascal_case'] = to_pascal_case +env.filters['to_camel_case'] = to_camel_case +env.filters['to_macro_case'] = to_macro_case +env.filters['to_snake_case'] = to_snake_case +env.filters['to_kebab_case'] = to_kebab_case +env.filters['first'] = lambda x: next(iter(x)) +env.filters['skip_first'] = lambda x: list(x)[1:] +env.filters['to_c_string'] = lambda x: '\n'.join(('"' + line.replace('"', '\\"') + '"') for line in json.dumps(x, separators=(',', ':')).replace('{"name"', '\n{"name"').split('\n')) + +template = env.from_string(template_file.read()) + +output = template.render( + interfaces = interfaces, + value_types = value_types, + toplevel_interfaces = toplevel_interfaces, + endpoints = endpoints, + embedded_endpoint_definitions = embedded_endpoint_definitions +) + +output_file.write(output) diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml new file mode 100644 index 00000000..ebf50db9 --- /dev/null +++ b/Firmware/odrive-interface.yaml @@ -0,0 +1,662 @@ +--- +version: 0.0.1 +ns: com.odriverobotics +summary: ODrive Interface Definitions + +interfaces: + Odrive: + attributes: + vbus_voltage: readonly float32 + ibus: readonly float32 + serial_number: readonly uint64 + hw_version_major: readonly uint8 + hw_version_minor: readonly uint8 + hw_version_variant: readonly uint8 + fw_version_major: readonly uint8 + fw_version_minor: readonly uint8 + fw_version_revision: readonly uint8 + fw_version_unreleased: + type: readonly uint8 + doc: 0 for official releases, 1 otherwise + brake_resistor_armed: readonly bool + brake_resistor_saturated: bool + system_stats: + attributes: + uptime: readonly uint32 + min_heap_space: readonly uint32 + min_stack_space_axis0: readonly uint32 + min_stack_space_axis1: readonly uint32 + min_stack_space_comms: readonly uint32 + min_stack_space_usb: readonly uint32 + min_stack_space_uart: readonly uint32 + min_stack_space_can: readonly uint32 + min_stack_space_usb_irq: readonly uint32 + min_stack_space_startup: readonly uint32 + stack_usage_axis0: readonly uint32 + stack_usage_axis1: readonly uint32 + stack_usage_comms: readonly uint32 + stack_usage_usb: readonly uint32 + stack_usage_uart: readonly uint32 + stack_usage_usb_irq: readonly uint32 + stack_usage_startup: readonly uint32 + stack_usage_can: readonly uint32 + usb: + attributes: + rx_cnt: readonly uint32 + tx_cnt: readonly uint32 + tx_overrun_cnt: readonly uint32 + i2c: + attributes: + addr: readonly uint8 + addr_match_cnt: readonly uint32 + rx_cnt: readonly uint32 + error_cnt: readonly uint32 + config: + attributes: + enable_uart: + type: bool + doc: 'TODO: changing this currently requires a reboot - fix this' + uart_baudrate: + type: uint32 + doc: "Defines the baudrate used on the UART interface. + Some baudrates will have a small timing error due to hardware limitations. + + Here's an (incomplete) list of baudrates for ODrive v3.x: + + Configured | Actual | Error [%] + -------------|---------------|----------- + 1.2 KBps | 1.2 KBps | 0 + 2.4 KBps | 2.4 KBps | 0 + 9.6 KBps | 9.6 KBps | 0 + 19.2 KBps | 19.195 KBps | 0.02 + 38.4 KBps | 38.391 KBps | 0.02 + 57.6 KBps | 57.613 KBps | 0.02 + 115.2 KBps | 115.068 KBps | 0.11 + 230.4 KBps | 230.769 KBps | 0.16 + 460.8 KBps | 461.538 KBps | 0.16 + 921.6 KBps | 913.043 KBps | 0.93 + 1.792 MBps | 1.826 MBps | 1.9 + 1.8432 MBps | 1.826 MBps | 0.93 + + For more information refer to Section 30.3.4 and Table 142 (the column with f_PCLK = 42 MHz) in the STM datasheet: + https://www.st.com/content/ccc/resource/technical/document/reference_manual/3d/6d/5a/66/b4/99/40/d4/DM00031020.pdf/files/DM00031020.pdf/jcr:content/translations/en.DM00031020.pdf" + enable_i2c_instead_of_can: + type: bool + doc: 'Changing this requires a reboot' + enable_ascii_protocol_on_usb: bool + max_regen_current: float32 + brake_resistance: + type: float32 + unit: Ohm + doc: Value of the brake resistor connected to the ODrive. Set to 0 to disable. + + dc_bus_undervoltage_trip_level: + type: float32 + unit: V + doc: Minimum voltage below which the motor stops operating. + dc_bus_overvoltage_trip_level: + type: float32 + unit: V + doc: 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. + + enable_dc_bus_overvoltage_ramp: + type: bool + doc: 'If enabled, if the measured DC voltage exceeds `dc_bus_overvoltage_ramp_start`, + the ODrive will sink more power than usual into the the brake resistor + in an attempt to bring the voltage down again. + + The brake duty cycle is increased by the following amount: + vbus_voltage == dc_bus_overvoltage_ramp_start => brake_duty_cycle += 0% + vbus_voltage == dc_bus_overvoltage_ramp_end => brake_duty_cycle += 100% + + Remarks: + - This feature is active even when all motors are disarmed. + - This feature is disabled if `brake_resistance` is non-positive.' + dc_bus_overvoltage_ramp_start: + type: float32 + doc: See `enable_dc_bus_overvoltage_ramp`. + Do not set this lower than your usual vbus_voltage, + unless you like fried brake resistors. + dc_bus_overvoltage_ramp_end: + type: float32 + doc: See `enable_dc_bus_overvoltage_ramp`. + Must be larger than `dc_bus_overvoltage_ramp_start`, + otherwise the ramp feature is disabled. + + dc_max_positive_current: + type: float32 + unit: A + doc: Max current the power supply can source. + dc_max_negative_current: + type: float32 + unit: A + doc: Max current the power supply can sink. You most likely want a non-positive value here. Set to -INFINITY to disable. + + #gpio1_pwm_mapping: Endpoint # TODO: disable for ODrive v3.2 and older + #gpio2_pwm_mapping: Endpoint # TODO: disable for ODrive v3.2 and older + #gpio3_pwm_mapping: Endpoint # TODO: disable for ODrive v3.2 and older + #gpio4_pwm_mapping: Endpoint + #gpio3_analog_mapping: Endpoint + #gpio4_analog_mapping: Endpoint + user_config_loaded: readonly bool + + axis0: {type: Axis, c_name: get_axis(0)} + axis1: {type: Axis, c_name: get_axis(1)} + can: {type: Can, c_name: get_can()} + test_property: uint32 + + functions: + test_function: {in: {delta: int32}, out: {cnt: int32}} + get_oscilloscope_val: {in: {index: uint32}, out: {val: float32}} + get_adc_voltage: {in: {gpio: uint32}, out: {voltage: float32}} + save_configuration: + erase_configuration: + reboot: + enter_dfu_mode: + + Odrive.Can: + attributes: + error: + nullflag: None + flags: {DuplicateCanIds: } + config: + attributes: + baud_rate: readonly uint32 + protocol: + values: {Simple: } + functions: + set_baud_rate: {in: {baudRate: uint32}} + + Axis: + attributes: + error: + typeargs: {fibre.Property.mode: readonly} + nullflag: 'None' + flags: + InvalidState: + doc: An invalid state was requested. + DcBusUnderVoltage: + DcBusOverVoltage: + CurrentMeasurementTimeout: + BrakeResistorDisarmed: + doc: The brake resistor was unexpectedly disarmed. + MotorDisarmed: + doc: The motor was unexpectedly disarmed. + MotorFailed: + doc: Check `motor.error` for more information. + SensorlessEstimatorFailed: + EncoderFailed: + doc: Check `encoder.error` for more information. + ControllerFailed: + PosCtrlDuringSensorless: + doc: DEPRECATED + WatchdogTimerExpired: + MinEndstopPressed: + MaxEndstopPressed: + EstopRequested: + HomingWithoutEndstop: + doc: the min endstop was not enabled during homing + step_dir_active: readonly bool + current_state: readonly AxisState + requested_state: AxisState + loop_counter: readonly uint32 + lockin_state: + typeargs: {fibre.Property.mode: readonly} + values: + Inactive: + Ramp: + Accelerate: + ConstVel: + is_homed: {type: bool, c_name: homing_.is_homed} + config: + attributes: + startup_motor_calibration: + type: bool + doc: run motor calibration at startup, skip otherwise + startup_encoder_index_search: + type: bool + doc: run encoder index search after startup, skip otherwise this only has an effect if encoder.config.use_index is also true + startup_encoder_offset_calibration: + type: bool + doc: run encoder offset calibration after startup, skip otherwise + startup_closed_loop_control: + type: bool + doc: enable closed loop control after calibration/startup + startup_sensorless_control: + type: bool + doc: enable sensorless control after calibration/startup + startup_homing: + type: bool + doc: enable homing after calibration/startup + enable_step_dir: + type: bool + doc: Enable step/dir input after calibration. + For M0 this has no effect if `enable_uart` is true. + step_dir_always_on: + type: bool + doc: Keep step/dir enabled while the motor is disabled. + This is ignored if enable_step_dir is false. + This setting only takes effect on a state transition + into idle or out of closed loop control. + counts_per_step: float32 + watchdog_timeout: + type: float32 + unit: s + doc: 0 disables watchdog + enable_watchdog: bool + step_gpio_pin: {type: uint16, c_setter: 'set_step_gpio_pin'} + dir_gpio_pin: {type: uint16, c_setter: 'set_dir_gpio_pin'} + calibration_lockin: # TODO: this is a subset of lockin state + attributes: + current: float32 + ramp_time: float32 + ramp_distance: float32 + accel: float32 + vel: float32 + sensorless_ramp: LockinState + general_lockin: LockinState + can_node_id: + type: uint8 + doc: Both axes will have the same id to start + can_heartbeat_rate_ms: uint32 + motor: Motor + controller: Controller + encoder: Encoder + sensorless_estimator: SensorlessEstimator + trap_traj: TrapezoidalTrajectory + min_endstop: Endstop + max_endstop: Endstop + functions: + watchdog_feed: + doc: Feed the watchdog to prevent watchdog timeouts. + clear_errors: + doc: Check the watchdog timer for expiration. Also sets the watchdog error bit if expired. + + Axis.LockinState: + attributes: + current: + type: float32 + unit: A + ramp_time: + type: float32 + unit: s + ramp_distance: + type: float32 + unit: rad + accel: + type: float32 + unit: rad/s^2 + vel: + type: float32 + unit: rad/s + finish_distance: + type: float32 + unit: rad + finish_on_vel: bool + finish_on_distance: bool + finish_on_enc_idx: bool + + + Motor: + c_is_class: True + attributes: + error: + nullflag: None + flags: + PhaseResistanceOutOfRange: + PhaseInductanceOutOfRange: + AdcFailed: + DrvFault: + ControlDeadlineMissed: + NotImplementedMotorType: + BrakeCurrentOutOfRange: + ModulationMagnitude: + BrakeDeadtimeViolation: + UnexpectedTimerCallback: + CurrentSenseSaturation: + InverterOverTemp: + CurrentLimitViolation: + BrakeDutyCycleNan: + DcBusOverRegenCurrent: {doc: too much current pushed into the power supply} + DcBusOverCurrent: {doc: too much current pulled out of the power supply} + armed_state: + typeargs: {fibre.Property.mode: readonly} + values: + Disarmed: + WaitingForTimings: + WaitingForUpdate: + Armed: + is_calibrated: readonly bool + current_meas_phB: {type: readonly float32, c_name: current_meas_.phB} + current_meas_phC: {type: readonly float32, c_name: current_meas_.phC} + DC_calib_phB: {type: float32, c_name: DC_calib_.phB} + DC_calib_phC: {type: float32, c_name: DC_calib_.phC} + phase_current_rev_gain: float32 + thermal_current_lim: readonly float32 + inverter_temp: readonly float32 + current_control: + attributes: + p_gain: float32 + i_gain: float32 + v_current_control_integral_d: float32 + v_current_control_integral_q: float32 + Ibus: float32 + final_v_alpha: float32 + final_v_beta: float32 + Id_setpoint: float32 + Iq_setpoint: readonly float32 + Iq_measured: float32 + Id_measured: float32 + I_measured_report_filter_k: float32 + max_allowed_current: readonly float32 + overcurrent_trip_level: readonly float32 + acim_rotor_flux: float32 + async_phase_vel: readonly float32 + async_phase_offset: float32 + gate_driver: + c_name: gate_driver_exported_ + attributes: + drv_fault: + typeargs: {fibre.Property.mode: readonly} + nullflag: NoFault + flags: + FetLowCOvercurrent: {bit: 0, doc: FET Low side, Phase C Over Current fault} + FetHighCOvercurrent: {bit: 1, doc: FET High side, Phase C Over Current fault} + FetLowBOvercurrent: {bit: 2, doc: FET Low side, Phase B Over Current fault} + FetHighBOvercurrent: {bit: 3, doc: FET High side, Phase B Over Current fault} + FetLowAOvercurrent: {bit: 4, doc: FET Low side, Phase A Over Current fault} + FetHighAOvercurrent: {bit: 5, doc: FET High side, Phase A Over Current fault} + OvertemperatureWarning: {bit: 6, doc: Over Temperature Warning fault} + OvertemperatureShutdown: {bit: 7, doc: Over Temperature Shut Down fault} + PVddUndervoltage: {bit: 8, doc: Power supply Vdd Under Voltage fault} + GVddUndervoltage: {bit: 9, doc: DRV8301 Vdd Under Voltage fault} + GVddOvervoltage: {bit: 10, doc: DRV8301 Vdd Over Voltage fault} + # status_reg_1: readonly uint32 + # status_reg_2: readonly uint32 + # ctrl_reg_1: readonly uint32 + # ctrl_reg_2: readonly uint32 + timing_log: + attributes: + general: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_GENERAL)'} + adc_cb_i: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_ADC_CB_I)'} + adc_cb_dc: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_ADC_CB_DC)'} + meas_r: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_MEAS_R)'} + meas_l: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_MEAS_L)'} + enc_calib: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_ENC_CALIB)'} + idx_search: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_IDX_SEARCH)'} + foc_voltage: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_FOC_VOLTAGE)'} + foc_current: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_FOC_CURRENT)'} + spi_start: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_SPI_START)'} + sample_now: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_SAMPLE_NOW)'} + spi_end: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_SPI_END)'} + config: + attributes: + pre_calibrated: {type: bool, c_setter: set_pre_calibrated} + pole_pairs: int32 + calibration_current: float32 + resistance_calib_max_voltage: float32 + phase_inductance: {type: float32, c_setter: set_phase_inductance} + phase_resistance: {type: float32, c_setter: set_phase_resistance} + direction: int32 + motor_type: MotorType + current_lim: float32 + current_lim_margin: float32 + inverter_temp_limit_lower: float32 + inverter_temp_limit_upper: float32 + requested_current_range: float32 + current_control_bandwidth: {type: float32, c_setter: set_current_control_bandwidth} + acim_slip_velocity: float32 + acim_gain_min_flux: float32 + acim_autoflux_min_Id: float32 + acim_autoflux_enable: bool + acim_autoflux_attack_gain: float32 + acim_autoflux_decay_gain: float32 + + + Controller: + attributes: + error: + nullflag: None + flags: + Overspeed: + InvalidInputMode: + UnstableGain: + InvalidMirrorAxis: + InvalidLoadEncoder: + InvalidEstimate: + input_pos: {type: float32, c_setter: set_input_pos} + input_vel: float32 + input_current: float32 + pos_setpoint: readonly float32 + vel_setpoint: readonly float32 + current_setpoint: readonly float32 + trajectory_done: readonly bool + vel_integrator_current: float32 + anticogging_valid: bool + config: + attributes: + gain_scheduling_width: float32 + enable_vel_limit: bool + enable_current_mode_vel_limit: + type: bool + doc: Enable velocity limit in current control mode (requires a valid velocity estimator). + enable_gain_scheduling: bool + enable_overspeed_error: bool + control_mode: ControlMode + input_mode: InputMode + pos_gain: + type: float32 + unit: (counts/s) / counts + vel_gain: + type: float32 + unit: 'A/(counts/s) (or A/(rad/s) in sensorless mode' + vel_integrator_gain: + type: float32 + unit: A/(counts/s * s) + vel_limit: + type: float32 + unit: counts/s + doc: Infinity to disable. + vel_limit_tolerance: + type: float32 + doc: Ratio to `vel_limit`. Infinity to disable. + vel_ramp_rate: float32 + current_ramp_rate: + type: float32 + unit: A / sec + homing_speed: + type: float32 + unit: counts/s + inertia: + type: float32 + unit: A/(count/s^2) + axis_to_mirror: uint8 + mirror_ratio: float32 + load_encoder_axis: + type: uint8 + # TODO: this is meaningless for a user. Should there be a separate developer note? + doc: Default depends on Axis number and is set in load_configuration() + input_filter_bandwidth: + type: float32 + unit: 1/s + c_setter: set_input_filter_bandwidth + anticogging: + attributes: + index: readonly uint32 + pre_calibrated: bool + calib_anticogging: readonly bool + calib_pos_threshold: float32 + calib_vel_threshold: float32 + cogging_ratio: readonly float32 + anticogging_enabled: bool + functions: + move_incremental: {in: {displacement: float32, from_input_pos: bool}} + start_anticogging_calibration: + + + Encoder: + attributes: + error: + nullflag: None + flags: + UnstableGain: + CprPolepairsMismatch: + NoResponse: + UnsupportedEncoderMode: + IllegalHallState: + IndexNotFoundYet: + AbsSpiTimeout: + AbsSpiComFail: + AbsSpiNotReady: + is_ready: readonly bool + index_found: readonly bool + shadow_count: readonly int32 + count_in_cpr: readonly int32 + interpolation: readonly float32 + phase: readonly float32 + pos_estimate: readonly float32 + pos_cpr: readonly float32 + hall_state: readonly uint8 + vel_estimate: readonly float32 + calib_scan_response: readonly float32 + pos_abs: int32 + spi_error_rate: readonly float32 + config: + attributes: + mode: Mode + use_index: {type: bool, c_setter: set_use_index} + find_idx_on_lockin_only: {type: bool, c_setter: set_find_idx_on_lockin_only} + abs_spi_cs_gpio_pin: {type: uint16, c_setter: set_abs_spi_cs_gpio_pin} + zero_count_on_find_idx: bool + cpr: int32 + offset: int32 + pre_calibrated: {type: bool, c_setter: set_pre_calibrated} + offset_float: float32 + enable_phase_interpolation: bool + bandwidth: {type: float32, c_setter: set_bandwidth} + calib_range: float32 + calib_scan_distance: float32 + calib_scan_omega: float32 + idx_search_unidirectional: bool + ignore_illegal_hall_state: bool + sincos_gpio_pin_sin: uint16 + sincos_gpio_pin_cos: uint16 + functions: + set_linear_count: {in: {count: int32}} + + + SensorlessEstimator: + c_is_class: True + attributes: + error: + nullflag: None + flags: + UnstableGain: + phase: float32 + pll_pos: float32 + vel_estimate: float32 + # pll_kp: float32 + # pll_ki: float32 + config: + attributes: + observer_gain: float32 + pll_bandwidth: float32 + pm_flux_linkage: float32 + + + TrapezoidalTrajectory: + c_is_class: True + attributes: + config: + attributes: + vel_limit: float32 + accel_limit: float32 + decel_limit: float32 + + + Endstop: + c_is_class: True + attributes: + endstop_state: readonly bool + config: + attributes: + gpio_num: {type: uint16, c_setter: set_gpio_num} + enabled: {type: bool, c_setter: set_enabled} + offset: float32 + is_active_high: bool + pullup: bool + debounce_ms: {type: uint32, c_setter: set_debounce_ms} + + +valuetypes: + Axis.AxisState: # TODO: remove redundant "Axis" in name + values: + Undefined: + doc: will fall through to idle + Idle: + doc: disable PWM and do nothing + StartupSequence: + doc: the actual sequence is defined by the config.startup... flags + FullCalibrationSequence: + doc: run all calibration procedures, then idle + MotorCalibration: + doc: run motor calibration + SensorlessControl: + doc: run sensorless control + EncoderIndexSearch: + doc: run encoder index search + EncoderOffsetCalibration: + doc: run encoder offset calibration + ClosedLoopControl: + doc: run closed loop control + LockinSpin: + doc: run lockin spin + EncoderDirFind: + Homing: + doc: run axis homing function + + Encoder.Mode: + values: + Incremental: + Hall: + Sincos: + SpiAbsCui: + value: 0x100 + doc: compatible with CUI AMT23xx + SpiAbsAms: + value: 0x101 + doc: compatible with AMS AS5047P, AS5048A/AS5048B (no daisy chain support) + SpiAbsAeat: + value: 0x102 + doc: not yet implemented + + Controller.ControlMode: + values: + # Note: these should be sorted from lowest level of control to + # highest level of control, to allow "<" style comparisons. + VoltageControl: + CurrentControl: + VelocityControl: + PositionControl: + + Controller.InputMode: + values: + Inactive: + Passthrough: + VelRamp: + PosFilter: + MixChannels: + TrapTraj: + CurrentRamp: + Mirror: + + + Motor.MotorType: + values: + HighCurrent: + LowCurrent: # TODO: hide this in code + Gimbal: + Acim: \ No newline at end of file diff --git a/docs/interface-definition-file.md b/docs/interface-definition-file.md new file mode 100644 index 00000000..9e4628e1 --- /dev/null +++ b/docs/interface-definition-file.md @@ -0,0 +1,133 @@ +# Interface Definition File + +This document describes the rules on which the ODrive Interface Definition file is built. It is intended for ODrive contributors who wish to modify it or ODrive users who want to autogenerate their own code from this file to interface with the ODrive. + +## Terms and Concepts + +*Value types* are a way of saying how values of this type are serialized/deserialized to/from raw bytes. +Value types can be: + - one of the well-known types `bool`, `int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `int32`, `uint32`, `int64`, `uint64`, `float32`, `float64`, `fibre.Ref` + - An enumeration (that is, a mapping between serialized numbers and well known value names) + - A set of flags (in many programming languages this is the same as normal enums) + +An *interface* is a collection of features (attributes and functions) that can be implemented by an object or used by a client as a filter for object discovery. + +A *function* is something that takes zero or more inputs from the client, does something, and then returns zero or more outputs to the client. Since these input and output arguments are transmitted as raw bytes, they each have a value type. + +An *attribute* is a reference to a subobject which again implements some interface. + +Many languages don't make clear distinctions between interfaces and value types so let's be clear on this: attributes _always_ have an interface type and function input/output arguments _always_ have a value type. If you see something that looks like an attribute with a value type (let's say `uint32`), it's actually an attribute with the interface type `fibre.Property`. If you see a function argument that looks like an interface type (let's say `MyIntf`) it's actually of the value type `fibre.Ref`. + + +## File Structure + +The top level contains a dictionary of interfaces and a dictionary of value types. +Interfaces as well as value types can be subordinate to other interfaces. Nested names are specified using dots in between the subnames. + +Example: + +```yaml +interfaces: + MyFirstInterface: ... + MyFirstInterface.SubInterface: ... + +valuetypes: + MyFirstEnum: ... + MyFirstInterface.SubEnum: ... +``` + +## Interfaces + +Interfaces consist of an `attributes` dictionary and a `functions` dictionary. + +**Attributes** have a type which is either given by name as a string or directly in place. +Even though attributes conceptually and internally are always resolved to an interface type, for your convenience you can also give a value type which is then implicitly resolved to `fibre.Property`. + +If the type is given as a string, it is resolved based on the scope in which it occurs. The search precedence is as follows: The innermost scope is searched first for an interface with that name and then for a value type with that name. If both names don't exist, the next outer scope is checked. Note that the order in which types are defined does not matter. The whole file is read before any type resolution occurs. + +**Functions** have an `in` and `out` dictionary specifying one or more argument names with their corresponding value types. Like with attributes, the types can be specified in place or as a name. Type resolution also works the same except that only value types are checked for. + +Example: +```yaml +interfaces: + Car: + attributes: + velocity: float + door_front_left: Door + door_front_right: Door + steering_wheel: + attributes: + angle: float + functions: + turn: {in: {delta_angle: float32}, out: {final_angle: float32}} + Car.Door: + attributes: + is_open: bool + part_of: Car + functions: + open: + close: +``` + +Let's see how the type resolution of the attibute `Car.Door.part_of: Car` would work here: + + 1. Interface `Car.Door.Car` => not found, proceed + 2. Value type `Car.Door.Car` => not found, proceed + 3. Interface `Car.Car` => not found, proceed + 4. Value type `Car.Car` => not found, proceed + 5. Interface `Car` => found. Link to this interface type. + + +## Enums + +Enums are values which are associated with a name. They are serialized as 32-bit numbers. + +Enumerators without an explicitly stated numerical value are guaranteed to have an underlying value one larger than that of the preceding enumerator. + +Each enumerator must have a unique value. + +Example: + +```yaml +valuetypes: + ModeOfTransport: + values: + Walking: + Bicycle: + Car: {value: 5} + Train: +``` + +This would be serialized as: + - Walking <=> `0x00000000` <=> `0x00 0x00 0x00 0x00` + - Bicycle <=> `0x00000001` <=> `0x01 0x00 0x00 0x00` + - Car <=> `0x00000005` <=> `0x05 0x00 0x00 0x00` + - Train <=> `0x00000006` <=> `0x06 0x00 0x00 0x00` + +## Flagfields + +Flagfields are serialized as 32-bit low endian values where each bit has a named meaning. + +A flag without an explicit bit number is guaranteed to have the bit number of the preceding flag plus one or bit 0 it it's the first in the list. + +Each flag must have a unique bit number. + +Example: + +```yaml +valuetypes: + Anchor: + nullflag: Nowhere + flags: + Top: + Left: + Bottom: {bit: 8} + Right: +``` + +This would be serialized as: + - Nowhere <=> `0x00000000` <=> `0x00 0x00 0x00 0x00` + - Top <=> `0x00000001` <=> `0x01 0x00 0x00 0x00` + - Top and Left <=> `0x00000003` <=> `0x03 0x00 0x00 0x00` + - Bottom <=> `0x00000100` <=> `0x00 0x01 0x00 0x00` + - Top and Bottom and Right <=> `0x00000301` <=> `0x01 0x03 0x00 0x00` From a5fd9dbeff8c595d184efed1534c4b13ffa5740d Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 15 May 2020 16:32:27 +0200 Subject: [PATCH 400/549] make explicit c_is_class a requirement remove inheritance from config --- Firmware/MotorControl/controller.hpp | 2 +- Firmware/MotorControl/encoder.hpp | 2 +- Firmware/MotorControl/endstop.hpp | 2 +- Firmware/MotorControl/motor.hpp | 2 +- Firmware/communication/interface_can.cpp | 4 +-- Firmware/communication/interface_can.hpp | 2 +- Firmware/interface_generator.py | 6 ++++- Firmware/odrive-interface.yaml | 33 +++++++++++++++++++++--- 8 files changed, 41 insertions(+), 12 deletions(-) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index f1529d96..ce3cf53f 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -18,7 +18,7 @@ public: bool anticogging_enabled = true; } Anticogging_t; - struct Config_t : ConfigIntf { + struct Config_t { ControlMode control_mode = CONTROL_MODE_POSITION_CONTROL; //see: ControlMode InputMode input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode float pos_gain = 20.0f; // [(counts/s) / counts] diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 04471e62..77f40068 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -9,7 +9,7 @@ class Encoder : public EncoderIntf { public: const uint32_t MODE_FLAG_ABS = 0x100; - struct Config_t : EncoderIntf::ConfigIntf { + struct Config_t { Mode mode = MODE_INCREMENTAL; bool use_index = false; bool pre_calibrated = false; // If true, this means the offset stored in diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index e9412f89..a87af4f6 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -13,7 +13,7 @@ class Endstop { bool pullup = true; // custom setters - Endstop* parent = nullptr + Endstop* parent = nullptr; void set_gpio_num(uint16_t value) { gpio_num = value; parent->update_config(); } void set_enabled(uint32_t value) { enabled = value; parent->update_config(); } void set_debounce_ms(uint32_t value) { debounce_ms = value; parent->update_config(); } diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 88a735e3..6043d7be 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -38,7 +38,7 @@ public: // NOTE: for gimbal motors, all units of A are instead V. // example: vel_gain is [V/(count/s)] instead of [A/(count/s)] // example: current_lim and calibration_current will instead determine the maximum voltage applied to the motor. - struct Config_t : public ConfigIntf { + struct Config_t { bool pre_calibrated = false; // can be set to true to indicate that all values here are valid int32_t pole_pairs = 7; float calibration_current = 10.0f; // [A] diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 659958c7..20b8ea6a 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -32,7 +32,7 @@ void ODriveCAN::can_server_thread() { while (available()) { read(rxmsg); switch (config_.protocol) { - case Config_t::PROTOCOL_SIMPLE: + case PROTOCOL_SIMPLE: CANSimple::handle_can_message(rxmsg); break; } @@ -183,7 +183,7 @@ void ODriveCAN::send_heartbeat(Axis *axis) { uint32_t now = osKernelSysTick(); if ((now - axis->last_heartbeat_) >= axis->config_.can_heartbeat_rate_ms) { switch (config_.protocol) { - case Config_t::PROTOCOL_SIMPLE: + case PROTOCOL_SIMPLE: CANSimple::send_heartbeat(axis); break; } diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 44791a33..b4864611 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -21,7 +21,7 @@ enum { class ODriveCAN : public OdriveIntf::CanIntf { public: - struct Config_t : ConfigIntf { + struct Config_t { uint32_t baud_rate = CAN_BAUD_250K; Protocol protocol = PROTOCOL_SIMPLE; }; diff --git a/Firmware/interface_generator.py b/Firmware/interface_generator.py index 38468036..a5c939f7 100644 --- a/Firmware/interface_generator.py +++ b/Firmware/interface_generator.py @@ -24,6 +24,7 @@ definitions: additionalProperties: {"$ref": "#/definitions/attribute"} __line__: {type: object} __column__: {type: object} + required: [c_is_class] additionalProperties: false valuetype: @@ -239,6 +240,7 @@ def regularize_attribute(path, name, elem, c_is_class): elem['type'] = {} if 'attributes' in elem: elem['type']['attributes'] = elem.pop('attributes') if 'functions' in elem: elem['type']['functions'] = elem.pop('functions') + if 'c_is_class' in elem: elem['type']['c_is_class'] = elem.pop('c_is_class') if 'values' in elem: elem['type']['values'] = elem.pop('values') if 'flags' in elem: elem['type']['flags'] = elem.pop('flags') if 'nullflag' in elem: elem['type']['nullflag'] = elem.pop('nullflag') @@ -275,7 +277,9 @@ def regularize_interface(path, name, elem): interfaces[path] = elem elem['functions'] = {name: regularize_func(path, name, func, {'obj': {'type': make_ref_type(elem)}}) for name, func in get_dict(elem, 'functions').items()} - treat_as_class = elem.get('c_is_class', None) or (len(elem['functions']) > 0) + if not 'c_is_class' in elem: + raise Exception(elem) + treat_as_class = elem['c_is_class'] # TODO: add command line arg to make this selectively optional elem['attributes'] = {name: regularize_attribute(path, name, prop, treat_as_class) for name, prop in get_dict(elem, 'attributes').items()} elem['interfaces'] = [] diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index ebf50db9..174d0822 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -5,6 +5,7 @@ summary: ODrive Interface Definitions interfaces: Odrive: + c_is_class: True attributes: vbus_voltage: readonly float32 ibus: readonly float32 @@ -21,6 +22,7 @@ interfaces: brake_resistor_armed: readonly bool brake_resistor_saturated: bool system_stats: + c_is_class: False attributes: uptime: readonly uint32 min_heap_space: readonly uint32 @@ -41,17 +43,20 @@ interfaces: stack_usage_startup: readonly uint32 stack_usage_can: readonly uint32 usb: + c_is_class: False attributes: rx_cnt: readonly uint32 tx_cnt: readonly uint32 tx_overrun_cnt: readonly uint32 i2c: + c_is_class: False attributes: addr: readonly uint8 addr_match_cnt: readonly uint32 rx_cnt: readonly uint32 error_cnt: readonly uint32 config: + c_is_class: False attributes: enable_uart: type: bool @@ -158,19 +163,21 @@ interfaces: enter_dfu_mode: Odrive.Can: + c_is_class: True attributes: error: nullflag: None flags: {DuplicateCanIds: } config: + c_is_class: False attributes: baud_rate: readonly uint32 - protocol: - values: {Simple: } + protocol: Protocol functions: set_baud_rate: {in: {baudRate: uint32}} Axis: + c_is_class: True attributes: error: typeargs: {fibre.Property.mode: readonly} @@ -212,6 +219,7 @@ interfaces: ConstVel: is_homed: {type: bool, c_name: homing_.is_homed} config: + c_is_class: False attributes: startup_motor_calibration: type: bool @@ -250,6 +258,7 @@ interfaces: step_gpio_pin: {type: uint16, c_setter: 'set_step_gpio_pin'} dir_gpio_pin: {type: uint16, c_setter: 'set_dir_gpio_pin'} calibration_lockin: # TODO: this is a subset of lockin state + c_is_class: False attributes: current: float32 ramp_time: float32 @@ -276,6 +285,7 @@ interfaces: doc: Check the watchdog timer for expiration. Also sets the watchdog error bit if expired. Axis.LockinState: + c_is_class: False attributes: current: type: float32 @@ -338,6 +348,7 @@ interfaces: thermal_current_lim: readonly float32 inverter_temp: readonly float32 current_control: + c_is_class: False attributes: p_gain: float32 i_gain: float32 @@ -358,6 +369,7 @@ interfaces: async_phase_offset: float32 gate_driver: c_name: gate_driver_exported_ + c_is_class: False attributes: drv_fault: typeargs: {fibre.Property.mode: readonly} @@ -379,6 +391,7 @@ interfaces: # ctrl_reg_1: readonly uint32 # ctrl_reg_2: readonly uint32 timing_log: + c_is_class: False attributes: general: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_GENERAL)'} adc_cb_i: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_ADC_CB_I)'} @@ -393,6 +406,7 @@ interfaces: sample_now: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_SAMPLE_NOW)'} spi_end: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_SPI_END)'} config: + c_is_class: False attributes: pre_calibrated: {type: bool, c_setter: set_pre_calibrated} pole_pairs: int32 @@ -417,6 +431,7 @@ interfaces: Controller: + c_is_class: True attributes: error: nullflag: None @@ -437,6 +452,7 @@ interfaces: vel_integrator_current: float32 anticogging_valid: bool config: + c_is_class: False attributes: gain_scheduling_width: float32 enable_vel_limit: bool @@ -484,6 +500,7 @@ interfaces: unit: 1/s c_setter: set_input_filter_bandwidth anticogging: + c_is_class: False attributes: index: readonly uint32 pre_calibrated: bool @@ -498,6 +515,7 @@ interfaces: Encoder: + c_is_class: True attributes: error: nullflag: None @@ -525,6 +543,7 @@ interfaces: pos_abs: int32 spi_error_rate: readonly float32 config: + c_is_class: False attributes: mode: Mode use_index: {type: bool, c_setter: set_use_index} @@ -561,6 +580,7 @@ interfaces: # pll_kp: float32 # pll_ki: float32 config: + c_is_class: False attributes: observer_gain: float32 pll_bandwidth: float32 @@ -571,6 +591,7 @@ interfaces: c_is_class: True attributes: config: + c_is_class: False attributes: vel_limit: float32 accel_limit: float32 @@ -582,6 +603,7 @@ interfaces: attributes: endstop_state: readonly bool config: + c_is_class: False attributes: gpio_num: {type: uint16, c_setter: set_gpio_num} enabled: {type: bool, c_setter: set_enabled} @@ -592,6 +614,9 @@ interfaces: valuetypes: + Odrive.Can.Protocol: + values: {Simple: } + Axis.AxisState: # TODO: remove redundant "Axis" in name values: Undefined: @@ -657,6 +682,6 @@ valuetypes: Motor.MotorType: values: HighCurrent: - LowCurrent: # TODO: hide this in code - Gimbal: + #LowCurrent: # not implemented + Gimbal: {value: 2} Acim: \ No newline at end of file From 149dcf737786a1dae555cccf7bb0a8b4167c7a38 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 18 May 2020 14:44:37 +0200 Subject: [PATCH 401/549] reenable ascii protocol --- Firmware/MotorControl/encoder.cpp | 12 +- Firmware/MotorControl/low_level.cpp | 4 +- Firmware/MotorControl/motor.hpp | 16 -- Firmware/MotorControl/odrive_main.h | 18 ++ Firmware/Tupfile.lua | 2 +- Firmware/ascii_type_info_template.j2 | 90 -------- Firmware/build.lua | 2 +- Firmware/communication/ascii_protocol.cpp | 21 +- .../fibre/cpp/include/fibre/introspection.hpp | 202 ++++++++++++++++++ Firmware/fibre/cpp/include/fibre/protocol.hpp | 24 ++- Firmware/fibre/cpp/interfaces_template.j2 | 15 ++ Firmware/fibre/cpp/type_info_template.j2 | 34 +++ Firmware/interface_generator.py | 37 ++-- Firmware/odrive-interface.yaml | 25 ++- 14 files changed, 332 insertions(+), 170 deletions(-) delete mode 100644 Firmware/ascii_type_info_template.j2 create mode 100644 Firmware/fibre/cpp/include/fibre/introspection.hpp create mode 100644 Firmware/fibre/cpp/type_info_template.j2 diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index c21683e7..743ae591 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -207,7 +207,7 @@ bool Encoder::run_offset_calibration() { axis_->run_control_loop([&](){ if (!axis_->motor_.enqueue_voltage_timings(voltage_magnitude, 0.0f)) return false; // error set inside enqueue_voltage_timings - axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); + axis_->motor_.log_timing(TIMING_LOG_ENC_CALIB); return ++i < start_lock_duration * current_meas_hz; }); if (axis_->error_ != Axis::ERROR_NONE) @@ -224,7 +224,7 @@ bool Encoder::run_offset_calibration() { float v_beta = voltage_magnitude * our_arm_sin_f32(phase); if (!axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta)) return false; // error set inside enqueue_voltage_timings - axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); + axis_->motor_.log_timing(TIMING_LOG_ENC_CALIB); encvaluesum += shadow_count_; @@ -264,7 +264,7 @@ bool Encoder::run_offset_calibration() { float v_beta = voltage_magnitude * our_arm_sin_f32(phase); if (!axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta)) return false; // error set inside enqueue_voltage_timings - axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); + axis_->motor_.log_timing(TIMING_LOG_ENC_CALIB); encvaluesum += shadow_count_; @@ -312,7 +312,7 @@ void Encoder::sample_now() { case MODE_SPI_ABS_CUI: case MODE_SPI_ABS_AEAT: { - axis_->motor_.log_timing(Motor::TIMING_LOG_SAMPLE_NOW); + axis_->motor_.log_timing(TIMING_LOG_SAMPLE_NOW); // Do nothing } break; @@ -348,7 +348,7 @@ bool Encoder::abs_spi_init(){ bool Encoder::abs_spi_start_transaction(){ if (mode_ & MODE_FLAG_ABS){ - axis_->motor_.log_timing(Motor::TIMING_LOG_SPI_START); + axis_->motor_.log_timing(TIMING_LOG_SPI_START); if(hw_config_.spi->State != HAL_SPI_STATE_READY){ set_error(ERROR_ABS_SPI_NOT_READY); return false; @@ -377,7 +377,7 @@ uint8_t cui_parity(uint16_t v) { void Encoder::abs_spi_cb(){ HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); - axis_->motor_.log_timing(Motor::TIMING_LOG_SPI_END); + axis_->motor_.log_timing(TIMING_LOG_SPI_END); uint16_t pos; diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 5c2886cf..ccfe6aa5 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -498,9 +498,9 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { // Check the timing of the sequencing if (current_meas_not_DC_CAL) - axis.motor_.log_timing(Motor::TIMING_LOG_ADC_CB_I); + axis.motor_.log_timing(TIMING_LOG_ADC_CB_I); else - axis.motor_.log_timing(Motor::TIMING_LOG_ADC_CB_DC); + axis.motor_.log_timing(TIMING_LOG_ADC_CB_DC); bool update_timings = false; if (hadc == &hadc2) { diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 6043d7be..9b6632cb 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -74,22 +74,6 @@ public: void set_current_control_bandwidth(float value) { current_control_bandwidth = value; parent->update_current_controller_gains(); } }; - enum TimingLog_t { - TIMING_LOG_GENERAL, - TIMING_LOG_ADC_CB_I, - TIMING_LOG_ADC_CB_DC, - TIMING_LOG_MEAS_R, - TIMING_LOG_MEAS_L, - TIMING_LOG_ENC_CALIB, - TIMING_LOG_IDX_SEARCH, - TIMING_LOG_FOC_VOLTAGE, - TIMING_LOG_FOC_CURRENT, - TIMING_LOG_SPI_START, - TIMING_LOG_SAMPLE_NOW, - TIMING_LOG_SPI_END, - TIMING_LOG_NUM_SLOTS - }; - Motor(const MotorHardwareConfig_t& hw_config, const GateDriverHardwareConfig_t& gate_driver_config, Config_t& config); diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index a3eb4447..20b1124a 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -177,6 +177,24 @@ inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b) { return reinterpret_cast inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b) { return reinterpret_cast(reinterpret_cast&>(a) ^= static_cast>(b)); } \ inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast(~static_cast>(a)); } + +enum TimingLog_t { + TIMING_LOG_GENERAL, + TIMING_LOG_ADC_CB_I, + TIMING_LOG_ADC_CB_DC, + TIMING_LOG_MEAS_R, + TIMING_LOG_MEAS_L, + TIMING_LOG_ENC_CALIB, + TIMING_LOG_IDX_SEARCH, + TIMING_LOG_FOC_VOLTAGE, + TIMING_LOG_FOC_CURRENT, + TIMING_LOG_SPI_START, + TIMING_LOG_SAMPLE_NOW, + TIMING_LOG_SPI_END, + TIMING_LOG_NUM_SLOTS +}; + + #include "autogen/interfaces.hpp" // ODrive specific includes diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index dec3547d..94e5ab53 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -4,7 +4,7 @@ tup.include('build.lua') tup.frule{inputs={'fibre/cpp/interfaces_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/interfaces.hpp'} tup.frule{inputs={'fibre/cpp/function_stubs_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/function_stubs.hpp'} tup.frule{inputs={'fibre/cpp/endpoints_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/endpoints.hpp'} -tup.frule{inputs={'ascii_type_info_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/ascii_type_info.hpp'} +tup.frule{inputs={'fibre/cpp/type_info_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/type_info.hpp'} tup.frule{ command='python ../tools/odrive/version.py --output %o', diff --git a/Firmware/ascii_type_info_template.j2 b/Firmware/ascii_type_info_template.j2 deleted file mode 100644 index 200eb1ba..00000000 --- a/Firmware/ascii_type_info_template.j2 +++ /dev/null @@ -1,90 +0,0 @@ -/*[# This is the original template, thus the warning below does not apply to this file #] - * ============================ WARNING ============================ - * ==== This is an autogenerated file. ==== - * ==== Any changes to this file will be lost when recompiling. ==== - * ================================================================= - * - * This file contains support functions for the ODrive ASCII protocol. - * - * TODO: might generalize this as an approach to runtime introspection. - */ - - -class TypeInfo; - -struct PropertyInfo { - const char * name; - void*(*getter)(void*); - TypeInfo* type_info; -}; - -class TypeInfo { -public: - TypeInfo(const PropertyInfo* property_table, size_t property_table_length) - : property_table_(property_table), property_table_length_(property_table_length) {} - - //virtual bool read_string(void* ctx) { return false; }; - //virtual bool write_string(void* ctx) { return false; }; - - const PropertyInfo* get_property_info(const char * name, size_t length) { - for (const PropertyInfo* prop = property_table_; prop < (property_table_ + property_table_length_); ++prop) { - if (!strncmp(name, prop->name, length)) { - return prop; - } - } - return nullptr; - } - -private: - const PropertyInfo* property_table_; - size_t property_table_length_; -}; - - -class Introspectable { -public: - Introspectable(void* obj, TypeInfo* type_info) : obj_(obj), type_info_(type_info) {} - - Introspectable get_child(const char * path, size_t length) { - Introspectable current = *this; - - const char * begin = path; - const char * end = path + length; - - while ((begin < end) && current.obj_ && current.type_info_) { - const char * end_of_token = std::find(begin, end, '.'); - const PropertyInfo* prop_info = current.type_info_->get_property_info(begin, end_of_token - begin); - if (prop_info) { - current = Introspectable{(*prop_info->getter)(obj_), prop_info->type_info}; - } else { - current = Introspectable{nullptr, nullptr}; - } - begin = std::min(end, end_of_token + 1); - } - - return current; - }; - -private: - void* obj_; - TypeInfo* type_info_; -}; - -[% for intf in interfaces.values() %] - -template -struct [[intf.fullname | to_pascal_case]]TypeInfo : TypeInfo { - static const PropertyInfo property_table[]; - static const TypeInfo singleton; -}; - -template -const PropertyInfo [[intf.name | to_pascal_case]]TypeInfo::property_table[] = { -[%- for property in intf.attributes.values() %] - {"[[property.name]]", [](void* obj){ return (void*)static_cast<[[property.type.c_type]]*>(&((T*)obj)->[[property.name | to_snake_case]]); }, [[property.type.fullname | to_pascal_case]]TypeInfo().[[property.name | to_snake_case]])>::singleton}, -[%- endfor %] -}; -template -const TypeInfo [[intf.fullname | to_pascal_case]]TypeInfo::singleton{[[intf.fullname | to_pascal_case]]TypeInfo::property_table, sizeof([[intf.fullname | to_pascal_case]]TypeInfo::property_table) / sizeof([[intf.fullname | to_pascal_case]]TypeInfo::property_table[0])}; - -[% endfor %] diff --git a/Firmware/build.lua b/Firmware/build.lua index 0f91697f..1204b5fe 100644 --- a/Firmware/build.lua +++ b/Firmware/build.lua @@ -72,7 +72,7 @@ function GCCToolchain(prefix, builddir, compiler_flags, linker_flags) else extra_outputs = {} end - extra_inputs = {'autogen/version.h', 'autogen/interfaces.hpp', 'autogen/function_stubs.hpp', 'autogen/endpoints.hpp'} -- TODO: fix hack + extra_inputs = {'autogen/version.h', 'autogen/interfaces.hpp', 'autogen/function_stubs.hpp', 'autogen/endpoints.hpp', 'autogen/type_info.hpp'} -- TODO: fix hack tup.frule{ inputs= { src, extra_inputs=extra_inputs }, command=compiler..' -c %f '.. diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 4eaaf611..1f8b1acf 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -14,8 +14,8 @@ #include #include -//#include "autogen/interfaces.hpp" -//#include "autogen/ascii_type_info.hpp" +#include "autogen/type_info.hpp" +#include "communication/interface_can.hpp" /* Private macros ------------------------------------------------------------*/ /* Private typedef -----------------------------------------------------------*/ @@ -28,6 +28,9 @@ #define TO_STR(s) TO_STR_INNER(s) /* Private variables ---------------------------------------------------------*/ + +static Introspectable root_obj = OdriveTypeInfo::make_introspectable(odrv); + /* Private function prototypes -----------------------------------------------*/ /* Function implementations --------------------------------------------------*/ @@ -224,19 +227,18 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& odrv.reboot(); } -#if 0 } else if (cmd[0] == 'r') { // read property char name[MAX_LINE_LENGTH]; int numscan = sscanf(cmd, "r %255s", name); if (numscan < 1) { respond(response_channel, use_checksum, "invalid command format"); } else { - Endpoint* endpoint = application_endpoints_->get_by_name(name, sizeof(name)); - if (!endpoint) { + Introspectable property = root_obj.get_child(name, sizeof(name)); + if (!property.is_valid()) { respond(response_channel, use_checksum, "invalid property"); } else { char response[10]; - bool success = endpoint->get_string(response, sizeof(response)); + bool success = property.get_string(response, sizeof(response)); if (!success) respond(response_channel, use_checksum, "not implemented"); else @@ -251,16 +253,15 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& if (numscan < 1) { respond(response_channel, use_checksum, "invalid command format"); } else { - Endpoint* endpoint = application_endpoints_->get_by_name(name, sizeof(name)); - if (!endpoint) { + Introspectable property = root_obj.get_child(name, sizeof(name)); + if (!property.is_valid()) { respond(response_channel, use_checksum, "invalid property"); } else { - bool success = endpoint->set_string(value, sizeof(value)); + bool success = property.set_string(value, sizeof(value)); if (!success) respond(response_channel, use_checksum, "not implemented"); } } -#endif } else if (cmd[0] == 'u') { // Update axis watchdog. unsigned motor_number; diff --git a/Firmware/fibre/cpp/include/fibre/introspection.hpp b/Firmware/fibre/cpp/include/fibre/introspection.hpp new file mode 100644 index 00000000..ac5885f2 --- /dev/null +++ b/Firmware/fibre/cpp/include/fibre/introspection.hpp @@ -0,0 +1,202 @@ +#ifndef __FIBRE_INTROSPECTION_HPP +#define __FIBRE_INTROSPECTION_HPP + +#include +#include +#include + +class TypeInfo; +class Introspectable; + +struct PropertyInfo { + const char * name; + void(*getter)(Introspectable&); + const TypeInfo* type_info; +}; + +/** + * @brief Contains runtime accessible type information. + * + * Specifically, this information consists of a list of PropertyInfo items which + * enable accessing attributes of an object by a runtime string. + * + * Typically, for each combination of C++ type and Fibre interface implemented + * by this type, one (static constant) TypeInfo object will exist. + */ +class TypeInfo { + friend class Introspectable; +public: + TypeInfo(const PropertyInfo* property_table, size_t property_table_length) + : property_table_(property_table), property_table_length_(property_table_length) {} + + const PropertyInfo* get_property_info(const char * name, size_t length) const { + for (const PropertyInfo* prop = property_table_; prop < (property_table_ + property_table_length_); ++prop) { + if (!strncmp(name, prop->name, length)) { + return prop; + } + } + return nullptr; + } + +protected: + template static T& as(Introspectable& obj); + template static const T& as(const Introspectable& obj); + template static Introspectable make_introspectable(T obj, const TypeInfo* type_info); + +private: + virtual bool get_string(const Introspectable& obj, char* buffer, size_t length) const { return false; } + virtual bool set_string(const Introspectable& obj, char* buffer, size_t length) const { return false; } + + const PropertyInfo* property_table_; + size_t property_table_length_; +}; + +/** + * @brief Wraps a reference to an application object by attaching runtime + * accessible type information. + * + * The reference that is wrapped is typically a pointer but can also be a small + * temporary, on-demand constructed object such as a fibre::Property<...> which + * contains multiple pointers. + */ +class Introspectable { + friend class TypeInfo; +public: + /** + * @brief Returns an Introspectable object for the attribute referenced by + * the specified attribute name. + * + * The name can consist of multiple parts separated by dots. + * + * If the attribute does not exist, an invalid Introspectable is returned. + * + * @param path: The name or path of the attribute. + * @param length: The maximum length of the name. + */ + Introspectable get_child(const char * path, size_t length) { + Introspectable current = *this; + + const char * begin = path; + const char * end = std::find(begin, path + length, '\0'); + + while ((begin < end) && current.type_info_) { + const char * end_of_token = std::find(begin, end, '.'); + const PropertyInfo* prop_info = current.type_info_->get_property_info(begin, end_of_token - begin); + if (prop_info) { + (*prop_info->getter)(current); + current.type_info_ = prop_info->type_info; + } else { + current.type_info_ = nullptr; + } + begin = std::min(end, end_of_token + 1); + } + + return current; + }; + + bool is_valid() { + return type_info_; + } + + /** + * @brief Returns the underlying value as a string. This will only succeed + * if this Introspectable contains a Property<...> object. + */ + bool get_string(char* buffer, size_t length) { + return type_info_ && type_info_->get_string(*this, buffer, length); + } + + /** + * @brief Sets the underlying value from a string. This will only succeed + * if this Introspectable contains a Property<...> object. + */ + bool set_string(char* buffer, size_t length) { + return type_info_ && type_info_->set_string(*this, buffer, length); + } + +private: + Introspectable() {} + + // We use this storage to hold generic small objects. Usually that's a pointer + // but sometimes it's an on-demand constructed Property<...>. + // Caution: only put objects in here which are trivially copyable, movable + // and destructible as any custom operation wouldn't be called. + unsigned char storage_[12]; + const TypeInfo* type_info_ = nullptr; +}; + + + +template T& TypeInfo::as(Introspectable& obj) { + static_assert(sizeof(T) <= sizeof(obj.storage_)); + return *(T*)obj.storage_; +} +template const T& TypeInfo::as(const Introspectable& obj) { + static_assert(sizeof(T) <= sizeof(obj.storage_)); + return *(const T*)obj.storage_; +} +template Introspectable TypeInfo::make_introspectable(T obj, const TypeInfo* type_info) { + Introspectable introspectable; + as(introspectable) = obj; + introspectable.type_info_ = type_info; + return introspectable; +} + + +// maybe_underlying_type_t resolves to the underlying type of T if T is an enum type or otherwise to T itself. +template::value> struct maybe_underlying_type; +template struct maybe_underlying_type { typedef std::underlying_type_t type; }; +template struct maybe_underlying_type { typedef T type; }; +template using maybe_underlying_type_t = typename maybe_underlying_type::type; + + + +/* Built-in type infos ********************************************************/ + +template +struct FibrePropertyTypeInfo; + +// readonly property +template +struct FibrePropertyTypeInfo> : TypeInfo { + using TypeInfo::TypeInfo; + static const PropertyInfo property_table[]; + static const FibrePropertyTypeInfo> singleton; + + bool get_string(const Introspectable& obj, char* buffer, size_t length) const override { + return to_string(static_cast>(as>(obj).read()), buffer, length, 0); + } +}; + +template +const PropertyInfo FibrePropertyTypeInfo>::property_table[] = {}; +template +const FibrePropertyTypeInfo> FibrePropertyTypeInfo>::singleton{FibrePropertyTypeInfo>::property_table, sizeof(FibrePropertyTypeInfo>::property_table) / sizeof(FibrePropertyTypeInfo>::property_table[0])}; + +// readwrite property +template +struct FibrePropertyTypeInfo> : TypeInfo { + using TypeInfo::TypeInfo; + static const PropertyInfo property_table[]; + static const FibrePropertyTypeInfo> singleton; + + bool get_string(const Introspectable& obj, char* buffer, size_t length) const override { + return to_string(static_cast>(as>(obj).read()), buffer, length, 0); + } + + bool set_string(const Introspectable& obj, char* buffer, size_t length) const override { + maybe_underlying_type_t value; + if (!from_string(buffer, length, &value, 0)) { + return false; + } + as>(obj).exchange(static_cast(value)); + return true; + } +}; + +template +const PropertyInfo FibrePropertyTypeInfo>::property_table[] = {}; +template +const FibrePropertyTypeInfo> FibrePropertyTypeInfo>::singleton{FibrePropertyTypeInfo>::property_table, sizeof(FibrePropertyTypeInfo>::property_table) / sizeof(FibrePropertyTypeInfo>::property_table[0])}; + +#endif // __FIBRE_INTROSPECTION_HPP \ No newline at end of file diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index 30a0b8a8..62cb384f 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -555,33 +555,39 @@ template struct Property { Property(void* ctx, T(*getter)(void*), void(*setter)(void*, T)) : ctx_(ctx), getter_(getter), setter_(setter) {} + Property& operator*() { return *this; } Property* operator->() { return this; } - - void* ctx_; - T(*getter_)(void*); - void(*setter_)(void*, T); - T exchange(std::optional value) { + T read() const { + return (*getter_)(ctx_); + } + + T exchange(std::optional value) const { T old_value = (*getter_)(ctx_); if (value.has_value()) { (*setter_)(ctx_, value.value()); } return old_value; } + + void* ctx_; + T(*getter_)(void*); + void(*setter_)(void*, T); }; template struct Property { Property(void* ctx, T(*getter)(void*)) : ctx_(ctx), getter_(getter) {} + Property& operator*() { return *this; } Property* operator->() { return this; } - - void* ctx_; - T(*getter_)(void*); - T read() { + T read() const { return (*getter_)(ctx_); } + + void* ctx_; + T(*getter_)(void*); }; diff --git a/Firmware/fibre/cpp/interfaces_template.j2 b/Firmware/fibre/cpp/interfaces_template.j2 index 76e0c90d..44a8293d 100644 --- a/Firmware/fibre/cpp/interfaces_template.j2 +++ b/Firmware/fibre/cpp/interfaces_template.j2 @@ -33,15 +33,30 @@ public: [%- endfor %] }; [%- endfor %] + +[%- for property in intf.attributes.values() %] +[%- if property.type.fullname.startswith("fibre.Property") %] +[%- if not property.c_setter %] + template static auto get_[[property.name]](T* obj) { return [[property.type.c_type]]{obj, [](void* ctx){ return ([[property.type.value_type.c_type]])((T*)ctx)->[[property.c_getter]]; }}; } +[%- else %] + template static auto get_[[property.name]](T* obj) { return [[property.type.c_type]]{obj, [](void* ctx){ return ([[property.type.value_type.c_type]])((T*)ctx)->[[property.c_getter]]; }, [](void* ctx, [[property.type.value_type.c_type]] value){ ((T*)ctx)->[[property.c_setter]](value); }}; } +[%- endif %] +[%- else %] + template static auto get_[[property.name]](T* obj) { return &obj->[[property.c_name]]; } +[%- endif %] +[%- endfor %] + [%- for func in intf.functions.values() %] virtual [[rettype(func)]] [[func.name | to_snake_case]]([% for in in func.in.values() %][% if loop.index0 %][[in.type.c_type]] [[in.name]][[', ' if not loop.last]][% endif %][% endfor %]) = 0; [%- endfor %] [%- for func in intf.functions.values() %] [%- for k, arg in func.in.items() | skip_first %] [[arg.type.c_type]] [[func.name | to_snake_case]]_in_[[arg.name]]_; // for internal use by Fibre + template static auto get_[[func.name | to_snake_case]]_in_[[arg.name]]_(T* obj) { return Property<[[arg.type.c_type]]>{obj, [](void* ctx){ return ([[arg.type.c_type]])((T*)ctx)->[[func.name | to_snake_case]]_in_[[arg.name]]_; }, [](void* ctx, [[arg.type.c_type]] value){ ((T*)ctx)->[[func.name | to_snake_case]]_in_[[arg.name]]_ = value; }}; } [%- endfor %] [%- for k, arg in func.out.items() %] [[arg.type.c_type]] [[func.name | to_snake_case]]_out_[[arg.name]]_; // for internal use by Fibre + template static auto get_[[func.name | to_snake_case]]_out_[[arg.name]]_(T* obj) { return Property{obj, [](void* ctx){ return ([[arg.type.c_type]])((T*)ctx)->[[func.name | to_snake_case]]_out_[[arg.name]]_; }}; } [%- endfor %] [%- endfor %] }; diff --git a/Firmware/fibre/cpp/type_info_template.j2 b/Firmware/fibre/cpp/type_info_template.j2 new file mode 100644 index 00000000..eb2a0b2c --- /dev/null +++ b/Firmware/fibre/cpp/type_info_template.j2 @@ -0,0 +1,34 @@ +/*[# This is the original template, thus the warning below does not apply to this file #] + * ============================ WARNING ============================ + * ==== This is an autogenerated file. ==== + * ==== Any changes to this file will be lost when recompiling. ==== + * ================================================================= + * + * This file contains support functions for the ODrive ASCII protocol. + * + * TODO: might generalize this as an approach to runtime introspection. + */ + +#include + +[% for intf in interfaces.values() %][% if not intf.builtin %] +template +struct [[intf.fullname | to_pascal_case]]TypeInfo : TypeInfo { + using TypeInfo::TypeInfo; + static const PropertyInfo property_table[]; + static const [[intf.fullname | to_pascal_case]]TypeInfo singleton; + static Introspectable make_introspectable(T& obj) { return TypeInfo::make_introspectable(&obj, &singleton); } +}; +[% endif %][% endfor %] + +[% for intf in interfaces.values() %][% if not intf.builtin %] +template +const PropertyInfo [[intf.fullname | to_pascal_case]]TypeInfo::property_table[] = { +[%- for property in intf.attributes.values() %] + {"[[property.name]]", [](Introspectable& obj){ as()))>>(obj) = [[intf.c_type]]::get_[[property.name]](as(obj)); }, &[[(property.type.purename or property.type.fullname) | to_pascal_case]]TypeInfo()))>>::singleton}, +[%- endfor %] +}; +template +const [[intf.fullname | to_pascal_case]]TypeInfo [[intf.fullname | to_pascal_case]]TypeInfo::singleton{[[intf.fullname | to_pascal_case]]TypeInfo::property_table, sizeof([[intf.fullname | to_pascal_case]]TypeInfo::property_table) / sizeof([[intf.fullname | to_pascal_case]]TypeInfo::property_table[0])}; + +[% endif %][% endfor %] diff --git a/Firmware/interface_generator.py b/Firmware/interface_generator.py index a5c939f7..c0b2148e 100644 --- a/Firmware/interface_generator.py +++ b/Firmware/interface_generator.py @@ -160,9 +160,11 @@ def make_property_type(typeargs): prop_type = { 'name': name, 'fullname': fullname, + 'purename': 'fibre.Property', 'c_type': c_type, 'value_type': value_type, # TODO: should be a metaarg 'mode': mode, # TODO: should be a metaarg + 'builtin': True, 'attributes': {}, 'functions': {} } @@ -249,15 +251,19 @@ def regularize_attribute(path, name, elem, c_is_class): elem['fullname'] = join_name(path, name) elem['typeargs'] = elem.get('typeargs', {}) elem['c_name'] = elem.get('c_name', None) or (elem['name'] + ('_' if c_is_class else '')) + elem['c_getter'] = elem.get('c_getter', elem['c_name']) + elem['c_setter'] = elem.get('c_setter', elem['c_name'] + ' = ') if isinstance(elem['type'], str) and elem['type'].startswith('readonly '): elem['typeargs']['fibre.Property.mode'] = 'readonly' elem['typeargs']['fibre.Property.type'] = elem['type'][len('readonly '):] elem['type'] = 'fibre.Property' + if elem['typeargs']['fibre.Property.mode'] == 'readonly': elem.pop('c_setter') elif ('flags' in elem['type']) or ('values' in elem['type']): elem['typeargs']['fibre.Property.mode'] = elem['typeargs'].get('fibre.Property.mode', None) or 'readwrite' elem['typeargs']['fibre.Property.type'] = regularize_valuetype(path, to_pascal_case(name), elem['type']) elem['type'] = 'fibre.Property' + if elem['typeargs']['fibre.Property.mode'] == 'readonly': elem.pop('c_setter') else: elem['type'] = regularize_interface(path, to_pascal_case(name), elem['type']) return elem @@ -375,19 +381,8 @@ def map_to_fibre01_type(t): return 'float' return t['fullname'] -def generate_endpoint_for_property(prop, bindto, idx): - c_value_type = prop['type']['value_type']['c_type'] - if prop.get('c_setter', None) is None: - c_setter = '[](void* ctx, ' + c_value_type + ' val){ ((decltype(&' + bindto + '))ctx)->' + prop['c_name'] + ' = val; }' - else: - c_setter = '[](void* ctx, ' + c_value_type + ' val){ ((decltype(&' + bindto + '))ctx)->' + prop['c_setter'] + '(val); }' - c_getter = '[](void* ctx) { return (const ' + c_value_type + '&)((decltype(&' + bindto + '))ctx)->' + prop['c_name'] + '; }' - +def generate_endpoint_for_property(prop, attr_bindto, idx): prop_intf = interfaces[prop['type']['fullname']] - if prop['type']['mode'] == 'readonly': - attr_bindto = prop_intf['c_type'] + '{(void*)&' + bindto + ', ' + c_getter + '}' - else: - attr_bindto = prop_intf['c_type'] + '{(void*)&' + bindto + ', ' + c_getter + ', ' + c_setter + '}' endpoint = { 'id': idx, @@ -416,14 +411,14 @@ def generate_endpoint_table(intf, bindto, idx): for k, prop in intf['attributes'].items(): property_value_type = re.findall('^fibre\.Property<([^>]*), (readonly|readwrite)>$', prop['type']['fullname']) #attr_bindto = join_name(bindto, bindings_map.get(join_name(intf['fullname'], k), k + ('_' if len(intf['functions']) or (intf['fullname'] in treat_as_classes) else ''))) + attr_bindto = intf['c_type'] + '::get_' + prop['name'] + '(' + bindto + ')' if len(property_value_type): # Special handling for Property<...> attributes: they resolve to one single endpoint - endpoint, endpoint_definition = generate_endpoint_for_property(prop, bindto, idx + cnt) + endpoint, endpoint_definition = generate_endpoint_for_property(prop, attr_bindto, idx + cnt) endpoints.append(endpoint) endpoint_definitions.append(endpoint_definition) cnt += 1 else: - attr_bindto = join_name(bindto, prop['c_name']) inner_endpoints, inner_endpoint_definitions, inner_cnt = generate_endpoint_table(prop['type'], attr_bindto, idx + cnt) endpoints += inner_endpoints endpoint_definitions.append({ @@ -437,25 +432,23 @@ def generate_endpoint_table(intf, bindto, idx): endpoints.append({ 'id': idx + cnt, 'function': func, - 'in_bindings': {**{'obj': '&' + bindto}, **{k_arg: bindto + '.' + func['name'] + '_in_' + k_arg + '_' for k_arg in list(func['in'].keys())[1:]}}, - 'out_bindings': {k_arg: '&' + bindto + '.' + func['name'] + '_out_' + k_arg + '_' for k_arg in func['out'].keys()}, + 'in_bindings': {**{'obj': bindto}, **{k_arg: '(' + bindto + ')->' + func['name'] + '_in_' + k_arg + '_' for k_arg in list(func['in'].keys())[1:]}}, + 'out_bindings': {k_arg: '&(' + bindto + ')->' + func['name'] + '_out_' + k_arg + '_' for k_arg in func['out'].keys()}, }) in_def = [] out_def = [] for i, (k_arg, arg) in enumerate(list(func['in'].items())[1:]): endpoint, endpoint_definition = generate_endpoint_for_property({ 'name': arg['name'], - 'c_name': func['name'] + '_in_' + k_arg + '_', 'type': make_property_type({'fibre.Property.type': arg['type'], 'fibre.Property.mode': 'readwrite'}) - }, bindto, idx + cnt + 1 + i) + }, intf['c_type'] + '::get_' + func['name'] + '_in_' + k_arg + '_' + '(' + bindto + ')', idx + cnt + 1 + i) endpoints.append(endpoint) in_def.append(endpoint_definition) for i, (k_arg, arg) in enumerate(func['out'].items()): endpoint, endpoint_definition = generate_endpoint_for_property({ 'name': arg['name'], - 'c_name': func['name'] + '_out_' + k_arg + '_', - 'type': make_property_type({'fibre.Property.type': arg['type'], 'fibre.Property.mode': 'readwrite'}) - }, bindto, idx + cnt + len(func['in']) + i) + 'type': make_property_type({'fibre.Property.type': arg['type'], 'fibre.Property.mode': 'readonly'}) + }, intf['c_type'] + '::get_' + func['name'] + '_out_' + k_arg + '_' + '(' + bindto + ')', idx + cnt + len(func['in']) + i) endpoints.append(endpoint) out_def.append(endpoint_definition) @@ -558,7 +551,7 @@ for k, item in list(enums.items()): -endpoints, embedded_endpoint_definitions, _ = generate_endpoint_table(interfaces['Odrive'], 'odrv', 1) # TODO: make user-configurable +endpoints, embedded_endpoint_definitions, _ = generate_endpoint_table(interfaces['Odrive'], '&odrv', 1) # TODO: make user-configurable embedded_endpoint_definitions = [{'name': '', 'id': 0, 'type': 'json', 'access': 'r'}] + embedded_endpoint_definitions endpoints = [{'id': 0, 'function': {'fullname': 'endpoint0_handler', 'in': {}, 'out': {}}, 'bindings': {}}] + endpoints diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 174d0822..3123e886 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -180,7 +180,6 @@ interfaces: c_is_class: True attributes: error: - typeargs: {fibre.Property.mode: readonly} nullflag: 'None' flags: InvalidState: @@ -393,18 +392,18 @@ interfaces: timing_log: c_is_class: False attributes: - general: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_GENERAL)'} - adc_cb_i: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_ADC_CB_I)'} - adc_cb_dc: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_ADC_CB_DC)'} - meas_r: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_MEAS_R)'} - meas_l: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_MEAS_L)'} - enc_calib: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_ENC_CALIB)'} - idx_search: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_IDX_SEARCH)'} - foc_voltage: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_FOC_VOLTAGE)'} - foc_current: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_FOC_CURRENT)'} - spi_start: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_SPI_START)'} - sample_now: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_SAMPLE_NOW)'} - spi_end: {type: readonly uint16, c_name: 'get(Motor::TIMING_LOG_SPI_END)'} + general: {type: readonly uint16, c_name: 'get(TIMING_LOG_GENERAL)'} + adc_cb_i: {type: readonly uint16, c_name: 'get(TIMING_LOG_ADC_CB_I)'} + adc_cb_dc: {type: readonly uint16, c_name: 'get(TIMING_LOG_ADC_CB_DC)'} + meas_r: {type: readonly uint16, c_name: 'get(TIMING_LOG_MEAS_R)'} + meas_l: {type: readonly uint16, c_name: 'get(TIMING_LOG_MEAS_L)'} + enc_calib: {type: readonly uint16, c_name: 'get(TIMING_LOG_ENC_CALIB)'} + idx_search: {type: readonly uint16, c_name: 'get(TIMING_LOG_IDX_SEARCH)'} + foc_voltage: {type: readonly uint16, c_name: 'get(TIMING_LOG_FOC_VOLTAGE)'} + foc_current: {type: readonly uint16, c_name: 'get(TIMING_LOG_FOC_CURRENT)'} + spi_start: {type: readonly uint16, c_name: 'get(TIMING_LOG_SPI_START)'} + sample_now: {type: readonly uint16, c_name: 'get(TIMING_LOG_SAMPLE_NOW)'} + spi_end: {type: readonly uint16, c_name: 'get(TIMING_LOG_SPI_END)'} config: c_is_class: False attributes: From 15b8f72f77dff77e28d777e0dae1e7698898cb54 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 18 May 2020 16:23:42 +0200 Subject: [PATCH 402/549] autogen enums --- Firmware/odrive-interface.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 3123e886..59f901b0 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -204,6 +204,7 @@ interfaces: MaxEndstopPressed: EstopRequested: HomingWithoutEndstop: + bit: 17 doc: the min endstop was not enabled during homing step_dir_active: readonly bool current_state: readonly AxisState From b0e7dcc6fd9b85b1524fbf2ee0d47447922bac63 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 18 May 2020 18:49:13 +0200 Subject: [PATCH 403/549] Add support for extended CAN IDs Previously, coexistence with devices that use extended CAN IDs was not possible since the ODrive would simply clip the 18 upper bits of an extended message ID. This adds proper support for extended CAN IDs by changing `can_node_id` from 8 to 32 bit and introducing the new option `can_node_id_extended`. --- Firmware/MotorControl/axis.hpp | 4 +++- Firmware/communication/can_simple.cpp | 26 +++++++++++++------------- Firmware/communication/can_simple.hpp | 2 +- docs/can-protocol.md | 6 +++--- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 5b489a93..5f397df9 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -86,7 +86,8 @@ public: LockinConfig_t calibration_lockin = default_calibration(); LockinConfig_t sensorless_ramp = default_sensorless(); LockinConfig_t lockin; - uint8_t can_node_id = 0; // Both axes will have the same id to start + uint32_t can_node_id = 0; // Both axes will have the same id to start + bool can_node_id_extended = false; uint32_t can_heartbeat_rate_ms = 100; }; @@ -314,6 +315,7 @@ public: make_protocol_property("finish_on_distance", &config_.lockin.finish_on_distance), make_protocol_property("finish_on_enc_idx", &config_.lockin.finish_on_enc_idx)), make_protocol_property("can_node_id", &config_.can_node_id), + make_protocol_property("can_node_id_extended", &config_.can_node_id_extended), make_protocol_property("can_heartbeat_rate_ms", &config_.can_heartbeat_rate_ms)), make_protocol_object("motor", motor_.make_protocol_definitions()), make_protocol_object("controller", controller_.make_protocol_definitions()), diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index da22bb70..7182d27a 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -26,7 +26,7 @@ void CANSimple::handle_can_message(can_Message_t& msg) { bool validAxis = false; for (uint8_t i = 0; i < AXIS_COUNT; i++) { - if (axes[i]->config_.can_node_id == nodeID) { + if ((axes[i]->config_.can_node_id == nodeID) && (axes[i]->config_.can_node_id_extended == msg.isExt)) { axis = axes[i]; if (!validAxis) { validAxis = true; @@ -137,7 +137,7 @@ void CANSimple::get_motor_error_callback(Axis* axis, can_Message_t& msg) { can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_MOTOR_ERROR; // heartbeat ID - txmsg.isExt = false; + txmsg.isExt = axis->config_.can_node_id_extended; txmsg.len = 8; txmsg.buf[0] = axis->motor_.error_; @@ -154,7 +154,7 @@ void CANSimple::get_encoder_error_callback(Axis* axis, can_Message_t& msg) { can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_ENCODER_ERROR; // heartbeat ID - txmsg.isExt = false; + txmsg.isExt = axis->config_.can_node_id_extended; txmsg.len = 8; txmsg.buf[0] = axis->encoder_.error_; @@ -171,7 +171,7 @@ void CANSimple::get_sensorless_error_callback(Axis* axis, can_Message_t& msg) { can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_SENSORLESS_ERROR; // heartbeat ID - txmsg.isExt = false; + txmsg.isExt = axis->config_.can_node_id_extended; txmsg.len = 8; txmsg.buf[0] = axis->sensorless_estimator_.error_; @@ -184,7 +184,7 @@ void CANSimple::get_sensorless_error_callback(Axis* axis, can_Message_t& msg) { } void CANSimple::set_axis_nodeid_callback(Axis* axis, can_Message_t& msg) { - axis->config_.can_node_id = msg.buf[0] & 0x3F; // Node ID bitmask + axis->config_.can_node_id = can_getSignal(msg, 0, 32, true); } void CANSimple::set_axis_requested_state_callback(Axis* axis, can_Message_t& msg) { @@ -199,7 +199,7 @@ void CANSimple::get_encoder_estimates_callback(Axis* axis, can_Message_t& msg) { can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_ENCODER_ESTIMATES; // heartbeat ID - txmsg.isExt = false; + txmsg.isExt = axis->config_.can_node_id_extended; txmsg.len = 8; // Undefined behaviour! @@ -230,7 +230,7 @@ void CANSimple::get_sensorless_estimates_callback(Axis* axis, can_Message_t& msg can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_SENSORLESS_ESTIMATES; // heartbeat ID - txmsg.isExt = false; + txmsg.isExt = axis->config_.can_node_id_extended; txmsg.len = 8; // Undefined behaviour! @@ -261,7 +261,7 @@ void CANSimple::get_encoder_count_callback(Axis* axis, can_Message_t& msg) { can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_ENCODER_COUNT; - txmsg.isExt = false; + txmsg.isExt = axis->config_.can_node_id_extended; txmsg.len = 8; txmsg.buf[0] = axis->encoder_.shadow_count_; @@ -325,7 +325,7 @@ void CANSimple::get_iq_callback(Axis* axis, can_Message_t& msg) { can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_IQ; - txmsg.isExt = false; + txmsg.isExt = axis->config_.can_node_id_extended; txmsg.len = 8; uint32_t floatBytes; @@ -354,7 +354,7 @@ void CANSimple::get_vbus_voltage_callback(Axis* axis, can_Message_t& msg) { txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_GET_VBUS_VOLTAGE; - txmsg.isExt = false; + txmsg.isExt = axis->config_.can_node_id_extended; txmsg.len = 8; uint32_t floatBytes; @@ -386,7 +386,7 @@ void CANSimple::send_heartbeat(Axis* axis) { can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; txmsg.id += MSG_ODRIVE_HEARTBEAT; // heartbeat ID - txmsg.isExt = false; + txmsg.isExt = axis->config_.can_node_id_extended; txmsg.len = 8; // Axis errors in 1st 32-bit value @@ -403,8 +403,8 @@ void CANSimple::send_heartbeat(Axis* axis) { odCAN->write(txmsg); } -uint8_t CANSimple::get_node_id(uint32_t msgID) { - return ((msgID >> NUM_CMD_ID_BITS) & 0x03F); // Upper 6 bits +uint32_t CANSimple::get_node_id(uint32_t msgID) { + return (msgID >> NUM_CMD_ID_BITS); // Upper 6 or more bits } uint8_t CANSimple::get_cmd_id(uint32_t msgID) { diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp index 4f98f0a8..c4b6d6ed 100644 --- a/Firmware/communication/can_simple.hpp +++ b/Firmware/communication/can_simple.hpp @@ -64,7 +64,7 @@ class CANSimple { static void clear_errors_callback(Axis* axis, can_Message_t& msg); // Utility functions - static uint8_t get_node_id(uint32_t msgID); + static uint32_t get_node_id(uint32_t msgID); static uint8_t get_cmd_id(uint32_t msgID); // Fetch a specific signal from the message diff --git a/docs/can-protocol.md b/docs/can-protocol.md index f316e73e..4fc06275 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -16,7 +16,7 @@ We've implemented a very basic CAN protocol that we call "CAN Simple" to get use ### CAN Frame At its most basic, the CAN Simple frame looks like this: -* Upper 6 bits - Node ID - max 0x3F +* Upper 6 bits - Node ID - max 0x3F (or 0xFFFFFF when using extended CAN IDs) * Lower 5 bits - Command ID - max 0x1F To understand how the Node ID and Command ID interact, let's look at an example @@ -40,7 +40,7 @@ CMD ID | Name | Sender | Signals | Start byte | Signal Type | Bits | Factor | Of 0x003 | Get Motor Error\* | Axis | Motor Error | 0 | Unsigned Int | 32 | 1 | 0 | Intel 0x004 | Get Encoder Error\* | Axis | Encoder Error | 0 | Unsigned Int | 32 | 1 | 0 | Intel 0x005 | Get Sensorless Error\* | Axis | Sensorless Error | 0 | Unsigned Int | 32 | 1 | 0 | Intel -0x006 | Set Axis Node ID | Master | Axis CAN Node ID | 0 | Unsigned Int | 16 | 1 | 0 | Intel +0x006 | Set Axis Node ID | Master | Axis CAN Node ID | 0 | Unsigned Int | 32 | 1 | 0 | Intel 0x007 | Set Axis Requested State | Master | Axis Requested State | 0 | Unsigned Int | 32 | 1 | 0 | Intel 0x008 | Set Axis Startup Config | Master | - Not yet implemented - | - | - | - | - | - | - 0x009 | Get Encoder Estimates\* | Master | Encoder Pos Estimate
Encoder Vel Estimate | 0
4 | IEEE 754 Float
IEEE 754 Float | 32
32 | 1
1 | 0
0 | Intel
Intel @@ -72,7 +72,7 @@ Configuration of the CAN parameters should be done via USB before putting the de To set the desired baud rate, use `.can.set_baud_rate()`. The baud rate can be done without rebooting the device. If you'd like to keep the baud rate, simply call `.save_configuration()` before rebooting. -Each axis looks like a separate node on the bus. Thus, they've inherited a new configuration property: `can_node_id`. This ID can be from 0 to 63 (0x3F) inclusive. +Each axis looks like a separate node on the bus. Thus, they both have the two properties `can_node_id` and `can_node_id_extended`. The node ID can be from 0 to 63 (0x3F) inclusive, or, if extended CAN IDs are used, from 0 to 16777215 (0xFFFFFF). ### Example Configuration From 16059a9e9b37de612dfa44d38499a8fce4d7209c Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 18 May 2020 18:52:24 +0200 Subject: [PATCH 404/549] add test for extended CAN IDs --- tools/odrive/tests/can_test.py | 45 ++++++++++++++++++------------- tools/odrive/tests/test_runner.py | 4 +-- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index e69a8bd9..e3535f54 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -17,8 +17,8 @@ command_set = { 'estop': (0x002, []), # tested 'get_motor_error': (0x003, [('motor_error', 'I', 1)]), # untested 'get_encoder_error': (0x004, [('encoder_error', 'I', 1)]), # untested - 'get_sensorless_error': (0x004, [('sensorless_error', 'I', 1)]), # untested - 'set_node_id': (0x006, [('node_id', 'H', 1)]), # tested + 'get_sensorless_error': (0x005, [('sensorless_error', 'I', 1)]), # untested + 'set_node_id': (0x006, [('node_id', 'I', 1)]), # tested 'set_requested_state': (0x007, [('requested_state', 'I', 1)]), # tested # 0x008 not yet implemented 'get_encoder_estimates': (0x009, [('encoder_pos_estimate', 'f', 1), ('encoder_vel_estimate', 'f', 1)]), # partially tested @@ -39,7 +39,7 @@ command_set = { 'clear_errors': (0x018, []), # partially tested } -def command(bus, node_id_, cmd_name, **kwargs): +def command(bus, node_id_, extended_id, cmd_name, **kwargs): cmd_spec = command_set[cmd_name] cmd_id = cmd_spec[0] fmt = '<' + ''.join([f for (n, f, s) in cmd_spec[1]]) # all little endian @@ -49,10 +49,10 @@ def command(bus, node_id_, cmd_name, **kwargs): fields = [((kwargs[n] / s) if f == 'f' else int(kwargs[n] / s)) for (n, f, s) in cmd_spec[1]] data = struct.pack(fmt, *fields) - msg = can.Message(arbitration_id=((node_id_ << 5) | cmd_id), data=data) + msg = can.Message(arbitration_id=((node_id_ << 5) | cmd_id), extended_id=extended_id, data=data) bus.send(msg) -async def record_messages(bus, node_id, cmd_name, timeout = 5.0): +async def record_messages(bus, node_id, extended_id, cmd_name, timeout = 5.0): """ Returns an async generator that yields a dictionary for each CAN message that is received, provided that the CAN ID matches the expected value. @@ -71,7 +71,7 @@ async def record_messages(bus, node_id, cmd_name, timeout = 5.0): start = time.monotonic() while True: msg = await reader.get_message() - if ((msg.arbitration_id == ((node_id << 5) | cmd_id)) and not msg.is_remote_frame): + if ((msg.arbitration_id == ((node_id << 5) | cmd_id)) and (msg.is_extended_id == extended_id) and not msg.is_remote_frame): fields = struct.unpack(fmt, msg.data[:(struct.calcsize(fmt))]) res = {n: (fields[i] * s) for (i, (n, f, s)) in enumerate(cmd_spec[1])} res['t'] = time.monotonic() @@ -81,13 +81,13 @@ async def record_messages(bus, node_id, cmd_name, timeout = 5.0): finally: notifier.stop() -async def request(bus, node_id, cmd_name, timeout = 1.0): +async def request(bus, node_id, extended_id, cmd_name, timeout = 1.0): cmd_spec = command_set[cmd_name] cmd_id = cmd_spec[0] - msg_generator = record_messages(bus, node_id, cmd_name, timeout) + msg_generator = record_messages(bus, node_id, extended_id, cmd_name, timeout) - msg = can.Message(arbitration_id=((node_id << 5) | cmd_id), data=[], is_remote_frame=True) + msg = can.Message(arbitration_id=((node_id << 5) | cmd_id), extended_id=extended_id, data=[], is_remote_frame=True) bus.send(msg) async for msg in msg_generator: @@ -102,34 +102,43 @@ async def get_all(async_iterator): class TestSimpleCAN(): def get_test_cases(self, testrig: TestRig): for odrive in testrig.get_components(ODriveComponent): - can_interfaces = testrig.get_connected_components(odrive.can, CanInterfaceComponent) - yield (odrive, list(can_interfaces)) + can_interfaces = list(testrig.get_connected_components(odrive.can, CanInterfaceComponent)) + yield (odrive, can_interfaces, 0, False) # standard ID + yield (odrive, can_interfaces, 0xfedcba, True) # extended ID - def run_test(self, odrive: ODriveComponent, canbus: CanInterfaceComponent, logger: Logger): + def run_test(self, odrive: ODriveComponent, canbus: CanInterfaceComponent, node_id: int, extended_id: bool, logger: Logger): # make sure no gpio input is overwriting our values odrive.unuse_gpios() - node_id = 0 axis = odrive.handle.axis0 + axis.clear_errors() axis.config.can_node_id = node_id + axis.config.can_node_id_extended = extended_id time.sleep(0.1) - def my_cmd(cmd_name, **kwargs): command(canbus.handle, node_id, cmd_name, **kwargs) - def my_req(cmd_name, **kwargs): return asyncio.run(request(canbus.handle, node_id, cmd_name, **kwargs)) + def my_cmd(cmd_name, **kwargs): command(canbus.handle, node_id, extended_id, cmd_name, **kwargs) + def my_req(cmd_name, **kwargs): return asyncio.run(request(canbus.handle, node_id, extended_id, cmd_name, **kwargs)) def fence(): my_req('get_vbus_voltage') # fence to ensure the CAN command was sent test_assert_eq(my_req('get_vbus_voltage')['vbus_voltage'], odrive.handle.vbus_voltage, accuracy=0.01) my_cmd('set_node_id', node_id=node_id+20) - asyncio.run(request(canbus.handle, node_id+20, 'get_vbus_voltage')) + asyncio.run(request(canbus.handle, node_id+20, extended_id, 'get_vbus_voltage')) test_assert_eq(axis.config.can_node_id, node_id+20) # Reset node ID to default value - command(canbus.handle, node_id+20, 'set_node_id', node_id=node_id) + command(canbus.handle, node_id+20, extended_id, 'set_node_id', node_id=node_id) fence() test_assert_eq(axis.config.can_node_id, node_id) + # Check that extended node IDs are not carelessly projected to 6-bit IDs + extended_id = not extended_id + my_cmd('estop') # should not be accepted + extended_id = not extended_id + fence() + test_assert_eq(axis.error, errors.axis.ERROR_NONE) + axis.encoder.set_linear_count(123) test_assert_eq(my_req('get_encoder_estimates')['encoder_pos_estimate'], 123.0, accuracy=0.01) test_assert_eq(my_req('get_encoder_count')['encoder_shadow_count'], 123.0, accuracy=0.01) @@ -205,7 +214,7 @@ class TestSimpleCAN(): logger.debug('testing heartbeat...') # note that this will include the heartbeats that were received during the # watchdog test (which takes 4.8s). - heartbeats = asyncio.run(get_all(record_messages(canbus.handle, node_id, 'heartbeat', timeout = 1.0))) + heartbeats = asyncio.run(get_all(record_messages(canbus.handle, node_id, extended_id, 'heartbeat', timeout = 1.0))) test_assert_eq(len(heartbeats), 5.8 / 0.1, accuracy=0.05) test_assert_eq([msg['error'] for msg in heartbeats[0:35]], [0] * 35) # before watchdog expiry test_assert_eq([msg['error'] for msg in heartbeats[-10:]], [errors.axis.ERROR_WATCHDOG_TIMER_EXPIRED] * 10) # after watchdog expiry diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index 80bb363d..a6dbffc0 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -679,7 +679,7 @@ def select_params(param_options): # Select parameters from the resource list # (this could be arbitrarily complex to improve parallelization of the tests) for combination in get_combinations(param_options): - if all_unique(combination): + if all_unique([x for x in combination if isinstance(x, Component)]): return list(combination) return None @@ -708,7 +708,7 @@ def run(tests): test_cases = list(test.get_test_cases(testrig)) if len(test_cases) == 0: - logger.warn('no resources are available to conduct the test {}'.format(type(test).__name__)) + logger.warn('no test cases are available to conduct the test {}'.format(type(test).__name__)) continue for test_case in test_cases: From 43a167f9fba81489e2dadc432facd10cdb686eb2 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 19 May 2020 15:50:43 +0200 Subject: [PATCH 405/549] make PWM/analog input work --- Firmware/fibre/cpp/endpoints_template.j2 | 27 +++++++++++-------- .../fibre/cpp/include/fibre/introspection.hpp | 24 ++++++++++++++--- Firmware/fibre/cpp/include/fibre/protocol.hpp | 11 ++++++++ Firmware/interface_generator.py | 1 + Firmware/odrive-interface.yaml | 19 ++++++++----- 5 files changed, 62 insertions(+), 20 deletions(-) diff --git a/Firmware/fibre/cpp/endpoints_template.j2 b/Firmware/fibre/cpp/endpoints_template.j2 index 98c5eb1d..8e2bc9e1 100644 --- a/Firmware/fibre/cpp/endpoints_template.j2 +++ b/Firmware/fibre/cpp/endpoints_template.j2 @@ -13,6 +13,8 @@ #ifndef __FIBRE_INTERFACES_HPP #define __FIBRE_INTERFACES_HPP +#include + namespace fibre { const unsigned char embedded_json[] = [[embedded_endpoint_definitions | to_c_string]]; @@ -48,22 +50,25 @@ bool is_endpoint_ref_valid(endpoint_ref_t endpoint_ref) { } } +Introspectable get_property(size_t idx) { + switch (idx) { +[%- for endpoint in endpoints %] +[%- if endpoint.function.name == 'exchange' and endpoint.in_bindings | list == ['obj'] %][# //case [[endpoint.id]]: *(decltype([[endpoint.in_bindings['obj']]])*)buf = [[endpoint.in_bindings['obj']]]; break;#] + case [[endpoint.id]]: return FibrePropertyTypeInfo<[[endpoint.function.in['obj'].type.c_type]]>::make_introspectable([[endpoint.in_bindings['obj']]]); +[%- endif %] +[%- endfor %] + default: return {}; + } +} + bool set_endpoint_from_float(endpoint_ref_t endpoint_ref, float value) { if (endpoint_ref.json_crc != json_crc_) { return false; } - return false; - // TODO: implement - /*cbufptr_t input_buffer{}; - bufptr_t output_buffer{}; - - switch (idx) { -[%- for endpoint in endpoints %] - case [[endpoint.id]]: return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_type]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %]&input_buffer, &output_buffer); -[%- endfor %] - default: return false; - }*/ + Introspectable property = get_property(endpoint_ref.endpoint_id); + const FloatSettableTypeInfo* type_info = dynamic_cast(property.get_type_info()); + return type_info && type_info->set_float(property, value); } } diff --git a/Firmware/fibre/cpp/include/fibre/introspection.hpp b/Firmware/fibre/cpp/include/fibre/introspection.hpp index ac5885f2..6792d9be 100644 --- a/Firmware/fibre/cpp/include/fibre/introspection.hpp +++ b/Firmware/fibre/cpp/include/fibre/introspection.hpp @@ -62,6 +62,8 @@ private: class Introspectable { friend class TypeInfo; public: + Introspectable() {} + /** * @brief Returns an Introspectable object for the attribute referenced by * the specified attribute name. @@ -114,9 +116,11 @@ public: return type_info_ && type_info_->set_string(*this, buffer, length); } -private: - Introspectable() {} + const TypeInfo* get_type_info() { + return type_info_; + } +private: // We use this storage to hold generic small objects. Usually that's a pointer // but sometimes it's an on-demand constructed Property<...>. // Caution: only put objects in here which are trivially copyable, movable @@ -151,6 +155,10 @@ template using maybe_underlying_type_t = typename maybe_underlying_t +struct FloatSettableTypeInfo { + virtual bool set_float(const Introspectable& obj, float val) const { return false; } +}; + /* Built-in type infos ********************************************************/ template @@ -175,10 +183,11 @@ const FibrePropertyTypeInfo> FibrePropertyTypeInfo -struct FibrePropertyTypeInfo> : TypeInfo { +struct FibrePropertyTypeInfo> : FloatSettableTypeInfo, TypeInfo { using TypeInfo::TypeInfo; static const PropertyInfo property_table[]; static const FibrePropertyTypeInfo> singleton; + static const Introspectable make_introspectable(Property obj) { return TypeInfo::make_introspectable(obj, &singleton); } bool get_string(const Introspectable& obj, char* buffer, size_t length) const override { return to_string(static_cast>(as>(obj).read()), buffer, length, 0); @@ -192,6 +201,15 @@ struct FibrePropertyTypeInfo> : TypeInfo { as>(obj).exchange(static_cast(value)); return true; } + + bool set_float(const Introspectable& obj, float val) const override { + maybe_underlying_type_t value; + if (!conversion::set_from_float(val, &value)) { + return false; + } + as>(obj).exchange(static_cast(value)); + return true; + } }; template diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index 62cb384f..7f9733aa 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -397,6 +397,17 @@ struct Codec::value>> { } static bool encode(T value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } }; +template<> struct Codec { + static std::optional decode(cbufptr_t* buffer) { + std::optional val0 = SimpleSerializer::read(&(buffer->begin()), buffer->end()); + std::optional val1 = SimpleSerializer::read(&(buffer->begin()), buffer->end()); + return (val0.has_value() && val1.has_value()) ? std::make_optional(endpoint_ref_t{val1.value(), val0.value()}) : std::nullopt; + } + static bool encode(endpoint_ref_t value, bufptr_t* buffer) { + return SimpleSerializer::write(value.endpoint_id, &(buffer->begin()), buffer->end()) + && SimpleSerializer::write(value.json_crc, &(buffer->begin()), buffer->end()); + } +}; } diff --git a/Firmware/interface_generator.py b/Firmware/interface_generator.py index c0b2148e..7e84afff 100644 --- a/Firmware/interface_generator.py +++ b/Firmware/interface_generator.py @@ -142,6 +142,7 @@ value_types = { 'int16': {'builtin': True, 'fullname': 'int16', 'name': 'int16', 'c_type': 'int16_t'}, 'int32': {'builtin': True, 'fullname': 'int32', 'name': 'int32', 'c_type': 'int32_t'}, 'int64': {'builtin': True, 'fullname': 'int64', 'name': 'int64', 'c_type': 'int64_t'}, + 'endpoint_ref': {'builtin': True, 'fullname': 'endpoint_ref', 'name': 'endpoint_ref', 'c_type': 'endpoint_ref_t'}, } enums = {} diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 59f901b0..854f9588 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -140,12 +140,12 @@ interfaces: unit: A doc: Max current the power supply can sink. You most likely want a non-positive value here. Set to -INFINITY to disable. - #gpio1_pwm_mapping: Endpoint # TODO: disable for ODrive v3.2 and older - #gpio2_pwm_mapping: Endpoint # TODO: disable for ODrive v3.2 and older - #gpio3_pwm_mapping: Endpoint # TODO: disable for ODrive v3.2 and older - #gpio4_pwm_mapping: Endpoint - #gpio3_analog_mapping: Endpoint - #gpio4_analog_mapping: Endpoint + gpio1_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[0]'} # TODO: disable for ODrive v3.2 and older + gpio2_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[1]'} # TODO: disable for ODrive v3.2 and older + gpio3_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[2]'} # TODO: disable for ODrive v3.2 and older + gpio4_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[3]'} + gpio3_analog_mapping: {type: Endpoint, c_name: 'analog_mappings[0]'} + gpio4_analog_mapping: {type: Endpoint, c_name: 'analog_mappings[1]'} user_config_loaded: readonly bool axis0: {type: Axis, c_name: get_axis(0)} @@ -176,6 +176,13 @@ interfaces: functions: set_baud_rate: {in: {baudRate: uint32}} + Endpoint: + c_is_class: False + attributes: + endpoint: endpoint_ref + min: float32 + max: float32 + Axis: c_is_class: True attributes: From fbf5b296066cd1e14de87ec9d4d528dc7e1020df Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 19 May 2020 20:15:27 +0200 Subject: [PATCH 406/549] attempt at optimizing (535'000 -> 522'268 B) --- Firmware/communication/ascii_protocol.cpp | 10 ++- Firmware/fibre/cpp/endpoints_template.j2 | 38 ++++++--- .../fibre/cpp/include/fibre/introspection.hpp | 82 +++++++++---------- Firmware/fibre/cpp/include/fibre/protocol.hpp | 4 + Firmware/fibre/cpp/interfaces_template.j2 | 15 +++- Firmware/fibre/cpp/type_info_template.j2 | 18 +++- Firmware/interface_generator.py | 9 +- 7 files changed, 109 insertions(+), 67 deletions(-) diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 1f8b1acf..632ceea3 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -234,11 +234,12 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "invalid command format"); } else { Introspectable property = root_obj.get_child(name, sizeof(name)); - if (!property.is_valid()) { + const StringConvertibleTypeInfo* type_info = dynamic_cast(property.get_type_info()); + if (!type_info) { respond(response_channel, use_checksum, "invalid property"); } else { char response[10]; - bool success = property.get_string(response, sizeof(response)); + bool success = type_info->get_string(property, response, sizeof(response)); if (!success) respond(response_channel, use_checksum, "not implemented"); else @@ -254,10 +255,11 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "invalid command format"); } else { Introspectable property = root_obj.get_child(name, sizeof(name)); - if (!property.is_valid()) { + const StringConvertibleTypeInfo* type_info = dynamic_cast(property.get_type_info()); + if (!type_info) { respond(response_channel, use_checksum, "invalid property"); } else { - bool success = property.set_string(value, sizeof(value)); + bool success = type_info->set_string(property, value, sizeof(value)); if (!success) respond(response_channel, use_checksum, "not implemented"); } diff --git a/Firmware/fibre/cpp/endpoints_template.j2 b/Firmware/fibre/cpp/endpoints_template.j2 index 8e2bc9e1..0d880997 100644 --- a/Firmware/fibre/cpp/endpoints_template.j2 +++ b/Firmware/fibre/cpp/endpoints_template.j2 @@ -15,6 +15,9 @@ #include +#pragma GCC push_options +#pragma GCC optimize ("s") + namespace fibre { const unsigned char embedded_json[] = [[embedded_endpoint_definitions | to_c_string]]; @@ -22,16 +25,36 @@ const size_t embedded_json_length = sizeof(embedded_json) - 1; const uint16_t json_crc_ = calc_crc16(PROTOCOL_VERSION, embedded_json, embedded_json_length); const uint32_t json_version_id_ = (json_crc_ << 16) | calc_crc16(json_crc_, embedded_json, embedded_json_length); +Introspectable get_property(size_t idx) { + switch (idx) { +[%- for endpoint in endpoints %] +[%- if endpoint.function.name == 'exchange' and endpoint.in_bindings | list == ['obj'] %] + case [[endpoint.id]]: return FibrePropertyTypeInfo<[[endpoint.function.in['obj'].type.c_type]]>::make_introspectable([[endpoint.in_bindings['obj']]]); +[%- endif %] +[%- endfor %] + default: return {}; + } +} + + // Note: with -Og this function reserves a huge amount of stack space because it // reserves separate space for the stack frame of each of the inlined functions. // The minimum known set of flags to prevent this is `-O1 -fipa-sra`. // `-O2` is a superset of this so that's what we use here. -bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) __attribute__ ((optimize(2))); +//bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) __attribute__ ((optimize(2))); bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) { + //Introspectable property = get_property(idx); + //if property.is_valid() + switch (idx) { [%- for endpoint in endpoints %] +[%- if (endpoint.function.name == 'exchange' or endpoint.function.name == 'read') and endpoint.in_bindings | list == ['obj'] %] + //case [[endpoint.id]]: return FibrePropertyTypeInfo<[[endpoint.function.in['obj'].type.c_type]]>::make_introspectable([[endpoint.in_bindings['obj']]]); case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_type]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_type]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break; +[%- else %] + case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_type]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_type]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break; +[%- endif %] [%- endfor %] default: return false; } @@ -50,17 +73,6 @@ bool is_endpoint_ref_valid(endpoint_ref_t endpoint_ref) { } } -Introspectable get_property(size_t idx) { - switch (idx) { -[%- for endpoint in endpoints %] -[%- if endpoint.function.name == 'exchange' and endpoint.in_bindings | list == ['obj'] %][# //case [[endpoint.id]]: *(decltype([[endpoint.in_bindings['obj']]])*)buf = [[endpoint.in_bindings['obj']]]; break;#] - case [[endpoint.id]]: return FibrePropertyTypeInfo<[[endpoint.function.in['obj'].type.c_type]]>::make_introspectable([[endpoint.in_bindings['obj']]]); -[%- endif %] -[%- endfor %] - default: return {}; - } -} - bool set_endpoint_from_float(endpoint_ref_t endpoint_ref, float value) { if (endpoint_ref.json_crc != json_crc_) { return false; @@ -73,4 +85,6 @@ bool set_endpoint_from_float(endpoint_ref_t endpoint_ref, float value) { } +#pragma GCC pop_options + #endif // __FIBRE_INTERFACES_HPP \ No newline at end of file diff --git a/Firmware/fibre/cpp/include/fibre/introspection.hpp b/Firmware/fibre/cpp/include/fibre/introspection.hpp index 6792d9be..d490ff71 100644 --- a/Firmware/fibre/cpp/include/fibre/introspection.hpp +++ b/Firmware/fibre/cpp/include/fibre/introspection.hpp @@ -5,12 +5,15 @@ #include #include +#pragma GCC push_options +#pragma GCC optimize ("s") + class TypeInfo; class Introspectable; +using introspectable_storage_t = std::aligned_storage<16, 4>::type; struct PropertyInfo { const char * name; - void(*getter)(Introspectable&); const TypeInfo* type_info; }; @@ -29,24 +32,16 @@ public: TypeInfo(const PropertyInfo* property_table, size_t property_table_length) : property_table_(property_table), property_table_length_(property_table_length) {} - const PropertyInfo* get_property_info(const char * name, size_t length) const { - for (const PropertyInfo* prop = property_table_; prop < (property_table_ + property_table_length_); ++prop) { - if (!strncmp(name, prop->name, length)) { - return prop; - } - } - return nullptr; - } + virtual introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const = 0; + Introspectable get_child(const Introspectable& obj, const char * name, size_t length) const; protected: + template static T& as(Introspectable& obj); template static const T& as(const Introspectable& obj); template static Introspectable make_introspectable(T obj, const TypeInfo* type_info); private: - virtual bool get_string(const Introspectable& obj, char* buffer, size_t length) const { return false; } - virtual bool set_string(const Introspectable& obj, char* buffer, size_t length) const { return false; } - const PropertyInfo* property_table_; size_t property_table_length_; }; @@ -83,13 +78,7 @@ public: while ((begin < end) && current.type_info_) { const char * end_of_token = std::find(begin, end, '.'); - const PropertyInfo* prop_info = current.type_info_->get_property_info(begin, end_of_token - begin); - if (prop_info) { - (*prop_info->getter)(current); - current.type_info_ = prop_info->type_info; - } else { - current.type_info_ = nullptr; - } + current = current.get_direct_child(begin, end_of_token - begin); begin = std::min(end, end_of_token + 1); } @@ -100,44 +89,38 @@ public: return type_info_; } - /** - * @brief Returns the underlying value as a string. This will only succeed - * if this Introspectable contains a Property<...> object. - */ - bool get_string(char* buffer, size_t length) { - return type_info_ && type_info_->get_string(*this, buffer, length); - } - - /** - * @brief Sets the underlying value from a string. This will only succeed - * if this Introspectable contains a Property<...> object. - */ - bool set_string(char* buffer, size_t length) { - return type_info_ && type_info_->set_string(*this, buffer, length); - } - const TypeInfo* get_type_info() { return type_info_; } private: + Introspectable get_direct_child(const char * name, size_t length) const { + for (size_t i = 0; i < type_info_->property_table_length_; ++i) { + if (!strncmp(name, type_info_->property_table_[i].name, length)) { + Introspectable result; + result.storage_ = type_info_->get_child(storage_, i); + result.type_info_ = type_info_->property_table_[i].type_info; + return result; + } + } + return {}; + } + // We use this storage to hold generic small objects. Usually that's a pointer // but sometimes it's an on-demand constructed Property<...>. // Caution: only put objects in here which are trivially copyable, movable // and destructible as any custom operation wouldn't be called. - unsigned char storage_[12]; + introspectable_storage_t storage_; const TypeInfo* type_info_ = nullptr; }; - - template T& TypeInfo::as(Introspectable& obj) { static_assert(sizeof(T) <= sizeof(obj.storage_)); - return *(T*)obj.storage_; + return *(T*)&obj.storage_; } template const T& TypeInfo::as(const Introspectable& obj) { static_assert(sizeof(T) <= sizeof(obj.storage_)); - return *(const T*)obj.storage_; + return *(const T*)&obj.storage_; } template Introspectable TypeInfo::make_introspectable(T obj, const TypeInfo* type_info) { Introspectable introspectable; @@ -154,8 +137,13 @@ template struct maybe_underlying_type { typedef T type; }; template using maybe_underlying_type_t = typename maybe_underlying_type::type; +struct StringConvertibleTypeInfo { + virtual bool get_string(const Introspectable& obj, char* buffer, size_t length) const { return false; } + virtual bool set_string(const Introspectable& obj, char* buffer, size_t length) const { return false; } +}; struct FloatSettableTypeInfo { + //virtual bool get_float(const Introspectable& obj, float* val) const { return false; } virtual bool set_float(const Introspectable& obj, float val) const { return false; } }; @@ -166,11 +154,15 @@ struct FibrePropertyTypeInfo; // readonly property template -struct FibrePropertyTypeInfo> : TypeInfo { +struct FibrePropertyTypeInfo> : StringConvertibleTypeInfo, TypeInfo { using TypeInfo::TypeInfo; static const PropertyInfo property_table[]; static const FibrePropertyTypeInfo> singleton; + introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const override { + return {}; + } + bool get_string(const Introspectable& obj, char* buffer, size_t length) const override { return to_string(static_cast>(as>(obj).read()), buffer, length, 0); } @@ -183,12 +175,16 @@ const FibrePropertyTypeInfo> FibrePropertyTypeInfo -struct FibrePropertyTypeInfo> : FloatSettableTypeInfo, TypeInfo { +struct FibrePropertyTypeInfo> : FloatSettableTypeInfo, StringConvertibleTypeInfo, TypeInfo { using TypeInfo::TypeInfo; static const PropertyInfo property_table[]; static const FibrePropertyTypeInfo> singleton; static const Introspectable make_introspectable(Property obj) { return TypeInfo::make_introspectable(obj, &singleton); } + introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const override { + return {}; + } + bool get_string(const Introspectable& obj, char* buffer, size_t length) const override { return to_string(static_cast>(as>(obj).read()), buffer, length, 0); } @@ -217,4 +213,6 @@ const PropertyInfo FibrePropertyTypeInfo>::property_table[] = {}; template const FibrePropertyTypeInfo> FibrePropertyTypeInfo>::singleton{FibrePropertyTypeInfo>::property_table, sizeof(FibrePropertyTypeInfo>::property_table) / sizeof(FibrePropertyTypeInfo>::property_table[0])}; +#pragma GCC pop_options + #endif // __FIBRE_INTROSPECTION_HPP \ No newline at end of file diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index 7f9733aa..a03a596e 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -566,6 +566,8 @@ template struct Property { Property(void* ctx, T(*getter)(void*), void(*setter)(void*, T)) : ctx_(ctx), getter_(getter), setter_(setter) {} + Property(T* ctx) + : ctx_(ctx), getter_([](void* ctx){ return *(T*)ctx; }), setter_([](void* ctx, T val){ *(T*)ctx = val; }) {} Property& operator*() { return *this; } Property* operator->() { return this; } @@ -590,6 +592,8 @@ template struct Property { Property(void* ctx, T(*getter)(void*)) : ctx_(ctx), getter_(getter) {} + Property(const T* ctx) + : ctx_(const_cast(ctx)), getter_([](void* ctx){ return *(const T*)ctx; }) {} Property& operator*() { return *this; } Property* operator->() { return this; } diff --git a/Firmware/fibre/cpp/interfaces_template.j2 b/Firmware/fibre/cpp/interfaces_template.j2 index 44a8293d..b8feeafa 100644 --- a/Firmware/fibre/cpp/interfaces_template.j2 +++ b/Firmware/fibre/cpp/interfaces_template.j2 @@ -10,6 +10,9 @@ * */ +#pragma GCC push_options +#pragma GCC optimize ("s") + [%- macro rettype(func) %] [%- if not func.out -%] void @@ -36,13 +39,15 @@ public: [%- for property in intf.attributes.values() %] [%- if property.type.fullname.startswith("fibre.Property") %] -[%- if not property.c_setter %] - template static auto get_[[property.name]](T* obj) { return [[property.type.c_type]]{obj, [](void* ctx){ return ([[property.type.value_type.c_type]])((T*)ctx)->[[property.c_getter]]; }}; } +[%- if not property.c_getter and not property.c_setter %] + template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_type]]{&obj->[[property.c_name]]}; } +[%- elif not property.c_setter %] + template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_type]]{obj, [](void* ctx){ return ([[property.type.value_type.c_type]])((T*)ctx)->[[property.c_getter]]; }}; } [%- else %] - template static auto get_[[property.name]](T* obj) { return [[property.type.c_type]]{obj, [](void* ctx){ return ([[property.type.value_type.c_type]])((T*)ctx)->[[property.c_getter]]; }, [](void* ctx, [[property.type.value_type.c_type]] value){ ((T*)ctx)->[[property.c_setter]](value); }}; } + template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_type]]{obj, [](void* ctx){ return ([[property.type.value_type.c_type]])((T*)ctx)->[[property.c_getter]]; }, [](void* ctx, [[property.type.value_type.c_type]] value){ ((T*)ctx)->[[property.c_setter]](value); }}; } [%- endif %] [%- else %] - template static auto get_[[property.name]](T* obj) { return &obj->[[property.c_name]]; } + template static inline auto get_[[property.name]](T* obj) { return &obj->[[property.c_name]]; } [%- endif %] [%- endfor %] @@ -80,3 +85,5 @@ inline [[enum.c_type]] operator ~ ([[enum.c_type]] a) { return static_cast<[[enu [%- endfor %] + +#pragma GCC pop_options diff --git a/Firmware/fibre/cpp/type_info_template.j2 b/Firmware/fibre/cpp/type_info_template.j2 index eb2a0b2c..70cfae2a 100644 --- a/Firmware/fibre/cpp/type_info_template.j2 +++ b/Firmware/fibre/cpp/type_info_template.j2 @@ -11,6 +11,9 @@ #include +#pragma GCC push_options +#pragma GCC optimize ("s") + [% for intf in interfaces.values() %][% if not intf.builtin %] template struct [[intf.fullname | to_pascal_case]]TypeInfo : TypeInfo { @@ -18,6 +21,17 @@ struct [[intf.fullname | to_pascal_case]]TypeInfo : TypeInfo { static const PropertyInfo property_table[]; static const [[intf.fullname | to_pascal_case]]TypeInfo singleton; static Introspectable make_introspectable(T& obj) { return TypeInfo::make_introspectable(&obj, &singleton); } + + introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const override { + T* ptr = *(T**)&obj; + introspectable_storage_t res; + switch (idx) { +[%- for property in intf.attributes.values() %] + case [[loop.index0]]: *(decltype([[intf.c_type]]::get_[[property.name]](std::declval()))*)(&res) = [[intf.c_type]]::get_[[property.name]](ptr); break; +[%- endfor %] + } + return res; + } }; [% endif %][% endfor %] @@ -25,10 +39,12 @@ struct [[intf.fullname | to_pascal_case]]TypeInfo : TypeInfo { template const PropertyInfo [[intf.fullname | to_pascal_case]]TypeInfo::property_table[] = { [%- for property in intf.attributes.values() %] - {"[[property.name]]", [](Introspectable& obj){ as()))>>(obj) = [[intf.c_type]]::get_[[property.name]](as(obj)); }, &[[(property.type.purename or property.type.fullname) | to_pascal_case]]TypeInfo()))>>::singleton}, + {"[[property.name]]", &[[(property.type.purename or property.type.fullname) | to_pascal_case]]TypeInfo()))>>::singleton}, [%- endfor %] }; template const [[intf.fullname | to_pascal_case]]TypeInfo [[intf.fullname | to_pascal_case]]TypeInfo::singleton{[[intf.fullname | to_pascal_case]]TypeInfo::property_table, sizeof([[intf.fullname | to_pascal_case]]TypeInfo::property_table) / sizeof([[intf.fullname | to_pascal_case]]TypeInfo::property_table[0])}; [% endif %][% endfor %] + +#pragma GCC pop_options diff --git a/Firmware/interface_generator.py b/Firmware/interface_generator.py index 7e84afff..369a4a1c 100644 --- a/Firmware/interface_generator.py +++ b/Firmware/interface_generator.py @@ -252,19 +252,20 @@ def regularize_attribute(path, name, elem, c_is_class): elem['fullname'] = join_name(path, name) elem['typeargs'] = elem.get('typeargs', {}) elem['c_name'] = elem.get('c_name', None) or (elem['name'] + ('_' if c_is_class else '')) - elem['c_getter'] = elem.get('c_getter', elem['c_name']) - elem['c_setter'] = elem.get('c_setter', elem['c_name'] + ' = ') + if ('c_getter' in elem) or ('c_setter' in elem): + elem['c_getter'] = elem.get('c_getter', elem['c_name']) + elem['c_setter'] = elem.get('c_setter', elem['c_name'] + ' = ') if isinstance(elem['type'], str) and elem['type'].startswith('readonly '): elem['typeargs']['fibre.Property.mode'] = 'readonly' elem['typeargs']['fibre.Property.type'] = elem['type'][len('readonly '):] elem['type'] = 'fibre.Property' - if elem['typeargs']['fibre.Property.mode'] == 'readonly': elem.pop('c_setter') + if elem['typeargs']['fibre.Property.mode'] == 'readonly' and 'c_setter' in elem: elem.pop('c_setter') elif ('flags' in elem['type']) or ('values' in elem['type']): elem['typeargs']['fibre.Property.mode'] = elem['typeargs'].get('fibre.Property.mode', None) or 'readwrite' elem['typeargs']['fibre.Property.type'] = regularize_valuetype(path, to_pascal_case(name), elem['type']) elem['type'] = 'fibre.Property' - if elem['typeargs']['fibre.Property.mode'] == 'readonly': elem.pop('c_setter') + if elem['typeargs']['fibre.Property.mode'] == 'readonly' and 'c_setter' in elem: elem.pop('c_setter') else: elem['type'] = regularize_interface(path, to_pascal_case(name), elem['type']) return elem From f3c883f86f2bd051ab8d65752e62610a2aca5dfa Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 19 May 2020 21:55:41 +0200 Subject: [PATCH 407/549] strip iostream from binary (~520'000 B -> ~380'000 B) --- Firmware/fibre/cpp/endpoints_template.j2 | 1 - .../fibre/cpp/include/fibre/cpp_utils.hpp | 66 ------------------- Firmware/fibre/cpp/interfaces_template.j2 | 4 +- 3 files changed, 2 insertions(+), 69 deletions(-) diff --git a/Firmware/fibre/cpp/endpoints_template.j2 b/Firmware/fibre/cpp/endpoints_template.j2 index 0d880997..72b9afe0 100644 --- a/Firmware/fibre/cpp/endpoints_template.j2 +++ b/Firmware/fibre/cpp/endpoints_template.j2 @@ -50,7 +50,6 @@ bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) switch (idx) { [%- for endpoint in endpoints %] [%- if (endpoint.function.name == 'exchange' or endpoint.function.name == 'read') and endpoint.in_bindings | list == ['obj'] %] - //case [[endpoint.id]]: return FibrePropertyTypeInfo<[[endpoint.function.in['obj'].type.c_type]]>::make_introspectable([[endpoint.in_bindings['obj']]]); case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_type]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_type]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break; [%- else %] case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_type]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_type]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break; diff --git a/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp b/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp index b2d84256..af28a07f 100644 --- a/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp +++ b/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp @@ -82,9 +82,6 @@ public: #include #include #include -//#include -#include -#include /* Backport features from C++14 and C++17 ------------------------------------*/ @@ -950,69 +947,6 @@ bool hex_string_to_int_arr(const char * str, TInt (&output)[ICount]) { return hex_string_to_int_arr(str, hex_digits() * ICount, output); } -namespace fibre { - -// TODO: move to print_utils.hpp -template -class HexPrinter { -public: - HexPrinter(T val, bool prefix) : val_(val) /*, prefix_(prefix)*/ { - const char digits[] = "0123456789abcdef"; - size_t prefix_length = prefix ? 2 : 0; - if (prefix) { - str[0] = '0'; - str[1] = 'x'; - } - str[prefix_length + hex_digits()] = '\0'; - - for (size_t i = 0; i < hex_digits(); ++i) { - str[prefix_length + hex_digits() - i - 1] = digits[val & 0xf]; - val >>= 4; - } - } - std::string to_string() const { return str; } - void to_string(char* buf) const { - for (size_t i = 0; (i < sizeof(str)) && str[i]; ++i) - buf[i] = str[i]; - } - - T val_; - //bool prefix_; - char str[hex_digits() + 3]; // 3 additional characters 0x and \0 -}; - -template -std::ostream& operator<<(std::ostream& stream, const HexPrinter& printer) { - // TODO: specialize for char - return stream << printer.to_string(); -} - -template -HexPrinter as_hex(T val, bool prefix = true) { return HexPrinter(val, prefix); } - -template -class HexArrayPrinter { -public: - HexArrayPrinter(T* ptr, size_t length) : ptr_(ptr), length_(length) {} - T* ptr_; - size_t length_; -}; - -template -std::ostream& operator<<(std::ostream& stream, const HexArrayPrinter& printer) { - for (size_t pos = 0; pos < printer.length_; ++pos) { - stream << " " << as_hex(printer.ptr_[pos]); - if (((pos + 1) % 16) == 0) - stream << std::endl; - } - return stream; -} - -template -HexArrayPrinter as_hex(T (&val)[ILength]) { return HexArrayPrinter(val, ILength); } - -} - template class simple_iterator : std::iterator { diff --git a/Firmware/fibre/cpp/interfaces_template.j2 b/Firmware/fibre/cpp/interfaces_template.j2 index b8feeafa..eb5167a7 100644 --- a/Firmware/fibre/cpp/interfaces_template.j2 +++ b/Firmware/fibre/cpp/interfaces_template.j2 @@ -57,11 +57,11 @@ public: [%- for func in intf.functions.values() %] [%- for k, arg in func.in.items() | skip_first %] [[arg.type.c_type]] [[func.name | to_snake_case]]_in_[[arg.name]]_; // for internal use by Fibre - template static auto get_[[func.name | to_snake_case]]_in_[[arg.name]]_(T* obj) { return Property<[[arg.type.c_type]]>{obj, [](void* ctx){ return ([[arg.type.c_type]])((T*)ctx)->[[func.name | to_snake_case]]_in_[[arg.name]]_; }, [](void* ctx, [[arg.type.c_type]] value){ ((T*)ctx)->[[func.name | to_snake_case]]_in_[[arg.name]]_ = value; }}; } + template static auto get_[[func.name | to_snake_case]]_in_[[arg.name]]_(T* obj) { return Property<[[arg.type.c_type]]>{&obj->[[func.name | to_snake_case]]_in_[[arg.name]]_}; } [%- endfor %] [%- for k, arg in func.out.items() %] [[arg.type.c_type]] [[func.name | to_snake_case]]_out_[[arg.name]]_; // for internal use by Fibre - template static auto get_[[func.name | to_snake_case]]_out_[[arg.name]]_(T* obj) { return Property{obj, [](void* ctx){ return ([[arg.type.c_type]])((T*)ctx)->[[func.name | to_snake_case]]_out_[[arg.name]]_; }}; } + template static auto get_[[func.name | to_snake_case]]_out_[[arg.name]]_(T* obj) { return Property{&obj->[[func.name | to_snake_case]]_out_[[arg.name]]_}; } [%- endfor %] [%- endfor %] }; From 3d710a538509c7bbcffb700f4c5c27ca6ecdd35e Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 20 May 2020 11:57:04 +0200 Subject: [PATCH 408/549] don't treat watchdog_timeout == 0 as special case this special treatment is now redundant with enable_watchdog --- Firmware/MotorControl/axis.cpp | 2 -- Firmware/MotorControl/axis.hpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index b68ef01b..1d416e68 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -202,9 +202,7 @@ void Axis::watchdog_feed() { // @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 (!config_.enable_watchdog) return true; - if (get_watchdog_reset() == 0) return true; // explicit check here to ensure that we don't underflow back to UINT32_MAX if (watchdog_current_value_ > 0) { diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 5f397df9..020cd12a 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -76,7 +76,7 @@ public: float counts_per_step = 2.0f; - float watchdog_timeout = 0.0f; // [s] (0 disables watchdog) + float watchdog_timeout = 0.0f; // [s] bool enable_watchdog = false; // Defaults loaded from hw_config in load_configuration in main.cpp From 3d073250222fafb28a7f12a3096846f362b38c03 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 20 May 2020 12:27:28 +0200 Subject: [PATCH 409/549] remove aligned formatting of assignments --- Firmware/MotorControl/axis.cpp | 2 +- Firmware/MotorControl/axis.hpp | 6 ++-- Firmware/MotorControl/controller.cpp | 4 +-- Firmware/MotorControl/controller.hpp | 44 +++++++++++++------------- Firmware/MotorControl/encoder.cpp | 38 +++++++++++----------- Firmware/MotorControl/encoder.hpp | 8 ++--- Firmware/MotorControl/endstop.hpp | 4 +-- Firmware/MotorControl/motor.cpp | 4 +-- Firmware/communication/can_helpers.hpp | 4 +-- Firmware/communication/can_simple.cpp | 6 ++-- 10 files changed, 60 insertions(+), 60 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 1d416e68..cb704404 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -87,7 +87,7 @@ static void run_state_machine_loop_wrapper(void* ctx) { // @brief Starts run_state_machine_loop in a new thread void Axis::start_thread() { osThreadDef(thread_def, run_state_machine_loop_wrapper, hw_config_.thread_priority, 0, stack_size_ / sizeof(StackType_t)); - thread_id_ = osThreadCreate(osThread(thread_def), this); + thread_id_ = osThreadCreate(osThread(thread_def), this); thread_id_valid_ = true; } diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 020cd12a..6be3c60c 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -139,10 +139,10 @@ public: bool watchdog_check(); void clear_errors() { - motor_.error_ = Motor::ERROR_NONE; - controller_.error_ = Controller::ERROR_NONE; + motor_.error_ = Motor::ERROR_NONE; + controller_.error_ = Controller::ERROR_NONE; sensorless_estimator_.error_ = SensorlessEstimator::ERROR_NONE; - encoder_.error_ = Encoder::ERROR_NONE; + encoder_.error_ = Encoder::ERROR_NONE; error_ = Axis::ERROR_NONE; } diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index beb93528..ed9163f7 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -165,8 +165,8 @@ bool Controller::update(float* current_setpoint_output) { } break; case INPUT_MODE_CURRENT_RAMP: { float max_step_size = std::abs(current_meas_period * config_.current_ramp_rate); - float full_step = input_current_ - current_setpoint_; - float step = std::clamp(full_step, -max_step_size, max_step_size); + float full_step = input_current_ - current_setpoint_; + float step = std::clamp(full_step, -max_step_size, max_step_size); current_setpoint_ += step; } break; diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 56a60020..4d207a83 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -44,34 +44,34 @@ public: bool calib_anticogging = false; float calib_pos_threshold = 1.0f; float calib_vel_threshold = 1.0f; - float cogging_ratio = 1.0f; - bool enable = true; + float cogging_ratio = 1.0f; + bool enable = true; } Anticogging_t; struct Config_t { ControlMode_t control_mode = CTRL_MODE_POSITION_CONTROL; //see: ControlMode_t InputMode_t input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t - float pos_gain = 20.0f; // [(counts/s) / counts] - float vel_gain = 5.0f / 10000.0f; // [A/(counts/s)] - // float vel_gain = 5.0f / 200.0f, // [A/(rad/s)] - float vel_integrator_gain = 10.0f / 10000.0f; // [A/(counts/s * s)] - float vel_limit = 20000.0f; // [counts/s] Infinity to disable. - float vel_limit_tolerance = 1.2f; // ratio to vel_lim. Infinity to disable. - float vel_ramp_rate = 10000.0f; // [(counts/s) / s] - float current_ramp_rate = 1.0f; // A / sec - bool setpoints_in_cpr = false; - float inertia = 0.0f; // [A/(count/s^2)] - float input_filter_bandwidth = 2.0f; // [1/s] - float homing_speed = 2000.0f; // [counts/s] + float pos_gain = 20.0f; // [(counts/s) / counts] + float vel_gain = 5.0f / 10000.0f; // [A/(counts/s)] + // float vel_gain = 5.0f / 200.0f, // [A/(rad/s)] + float vel_integrator_gain = 10.0f / 10000.0f; // [A/(counts/s * s)] + float vel_limit = 20000.0f; // [counts/s] Infinity to disable. + float vel_limit_tolerance = 1.2f; // ratio to vel_lim. Infinity to disable. + float vel_ramp_rate = 10000.0f; // [(counts/s) / s] + float current_ramp_rate = 1.0f; // A / sec + bool setpoints_in_cpr = false; + float inertia = 0.0f; // [A/(count/s^2)] + float input_filter_bandwidth = 2.0f; // [1/s] + float homing_speed = 2000.0f; // [counts/s] Anticogging_t anticogging; - float gain_scheduling_width = 10.0f; - bool enable_gain_scheduling = false; - bool enable_vel_limit = true; - bool enable_overspeed_error = true; - bool enable_current_vel_limit = true; // enable velocity limit in current control mode (requires a valid velocity estimator) - uint8_t axis_to_mirror = -1; - float mirror_ratio = 1.0f; - uint8_t load_encoder_axis = -1; // default depends on Axis number and is set in load_configuration() + float gain_scheduling_width = 10.0f; + bool enable_gain_scheduling = false; + bool enable_vel_limit = true; + bool enable_overspeed_error = true; + bool enable_current_vel_limit = true; // enable velocity limit in current control mode (requires a valid velocity estimator) + uint8_t axis_to_mirror = -1; + float mirror_ratio = 1.0f; + uint8_t load_encoder_axis = -1; // default depends on Axis number and is set in load_configuration() }; explicit Controller(Config_t& config); diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 77b809aa..e0e29a0b 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -109,8 +109,8 @@ void Encoder::set_linear_count(int32_t count) { uint32_t prim = cpu_enter_critical(); // Update states - shadow_count_ = count; - pos_estimate_ = static_cast(count); + shadow_count_ = count; + pos_estimate_ = static_cast(count); tim_cnt_sample_ = count; //Write hardware last @@ -132,7 +132,7 @@ void Encoder::set_circular_count(int32_t count, bool update_offset) { // Update states count_in_cpr_ = mod(count, config_.cpr); - pos_cpr_ = static_cast(count_in_cpr_); + pos_cpr_ = static_cast(count_in_cpr_); cpu_exit_critical(prim); } @@ -182,7 +182,7 @@ bool Encoder::run_direction_find() { // TODO: Do the scan with current, not voltage! bool Encoder::run_offset_calibration() { static const float start_lock_duration = 1.0f; - static const int num_steps = (int)(config_.calib_scan_distance / config_.calib_scan_omega * static_cast(current_meas_hz)); + static const int num_steps = (int)(config_.calib_scan_distance / config_.calib_scan_omega * static_cast(current_meas_hz)); // Require index found if enabled if (config_.use_index && !index_found_) { @@ -219,7 +219,7 @@ bool Encoder::run_offset_calibration() { // scan forward i = 0; axis_->run_control_loop([&]() { - float phase = wrap_pm_pi(config_.calib_scan_distance * static_cast(i) / static_cast(num_steps) - config_.calib_scan_distance / 2.0f); + float phase = wrap_pm_pi(config_.calib_scan_distance * static_cast(i) / static_cast(num_steps) - config_.calib_scan_distance / 2.0f); float v_alpha = voltage_magnitude * our_arm_cos_f32(phase); float v_beta = voltage_magnitude * our_arm_sin_f32(phase); if (!axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta)) @@ -248,9 +248,9 @@ bool Encoder::run_offset_calibration() { //TODO avoid recomputing elec_rad_per_enc every time // Check CPR - float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / static_cast(config_.cpr)); + float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / static_cast(config_.cpr)); float expected_encoder_delta = config_.calib_scan_distance / elec_rad_per_enc; - calib_scan_response_ = std::abs(shadow_count_ - init_enc_val); + calib_scan_response_ = std::abs(shadow_count_ - init_enc_val); if (std::abs(calib_scan_response_ - expected_encoder_delta) / expected_encoder_delta > config_.calib_range) { set_error(ERROR_CPR_POLEPAIRS_MISMATCH); return false; @@ -259,7 +259,7 @@ bool Encoder::run_offset_calibration() { // scan backwards i = 0; axis_->run_control_loop([&]() { - float phase = wrap_pm_pi(-config_.calib_scan_distance * static_cast(i) / static_cast(num_steps) + config_.calib_scan_distance / 2.0f); + float phase = wrap_pm_pi(-config_.calib_scan_distance * static_cast(i) / static_cast(num_steps) + config_.calib_scan_distance / 2.0f); float v_alpha = voltage_magnitude * our_arm_cos_f32(phase); float v_beta = voltage_magnitude * our_arm_sin_f32(phase); if (!axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta)) @@ -273,8 +273,8 @@ bool Encoder::run_offset_calibration() { if (axis_->error_ != Axis::ERROR_NONE) return false; - config_.offset = encvaluesum / (num_steps * 2); - int32_t residual = encvaluesum - ((int64_t)config_.offset * (int64_t)(num_steps * 2)); + config_.offset = encvaluesum / (num_steps * 2); + int32_t residual = encvaluesum - ((int64_t)config_.offset * (int64_t)(num_steps * 2)); config_.offset_float = static_cast(residual) / static_cast(num_steps * 2) + 0.5f; // add 0.5 to center-align state to phase is_ready_ = true; @@ -334,12 +334,12 @@ bool Encoder::abs_spi_init(){ spi->Init.CLKPhase = SPI_PHASE_2EDGE; spi->Init.NSS = SPI_NSS_SOFT; spi->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; - spi->Init.FirstBit = SPI_FIRSTBIT_MSB; - spi->Init.TIMode = SPI_TIMODE_DISABLE; - spi->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; - spi->Init.CRCPolynomial = 10; + spi->Init.FirstBit = SPI_FIRSTBIT_MSB; + spi->Init.TIMode = SPI_TIMODE_DISABLE; + spi->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; + spi->Init.CRCPolynomial = 10; if (mode_ == MODE_SPI_ABS_AEAT) { - spi->Init.CLKPolarity = SPI_POLARITY_HIGH; + spi->Init.CLKPolarity = SPI_POLARITY_HIGH; } HAL_SPI_DeInit(spi); HAL_SPI_Init(spi); @@ -508,9 +508,9 @@ bool Encoder::update() { pos_estimate_ += current_meas_period * vel_estimate_; pos_cpr_ += current_meas_period * vel_estimate_; // discrete phase detector - float delta_pos = static_cast(shadow_count_) - static_cast(std::floor(pos_estimate_)); + float delta_pos = static_cast(shadow_count_) - static_cast(std::floor(pos_estimate_)); float delta_pos_cpr = static_cast(count_in_cpr_) - static_cast(std::floor(pos_cpr_)); - delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * static_cast(config_.cpr)); + delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * static_cast(config_.cpr)); // pll feedback pos_estimate_ += current_meas_period * pll_kp_ * delta_pos; pos_cpr_ += current_meas_period * pll_kp_ * delta_pos_cpr; @@ -518,7 +518,7 @@ bool Encoder::update() { vel_estimate_ += current_meas_period * pll_ki_ * delta_pos_cpr; bool snap_to_zero_vel = false; if (std::abs(vel_estimate_) < 0.5f * current_meas_period * pll_ki_) { - vel_estimate_ = 0.0f; //align delta-sigma on zero to prevent jitter + vel_estimate_ = 0.0f; //align delta-sigma on zero to prevent jitter snap_to_zero_vel = true; } @@ -544,7 +544,7 @@ bool Encoder::update() { //// compute electrical phase //TODO avoid recomputing elec_rad_per_enc every time float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / static_cast(config_.cpr)); - float ph = elec_rad_per_enc * (interpolated_enc - config_.offset_float); + float ph = elec_rad_per_enc * (interpolated_enc - config_.offset_float); // ph = fmodf(ph, 2*M_PI); phase_ = wrap_pm_pi(ph); diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 28830c38..fcf94101 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -8,10 +8,10 @@ class Encoder { public: enum Error_t { - ERROR_NONE = 0, - ERROR_UNSTABLE_GAIN = 0x01, - ERROR_CPR_POLEPAIRS_MISMATCH = 0x02, - ERROR_NO_RESPONSE = 0x04, + ERROR_NONE = 0, + ERROR_UNSTABLE_GAIN = 0x01, + ERROR_CPR_POLEPAIRS_MISMATCH = 0x02, + ERROR_NO_RESPONSE = 0x04, ERROR_UNSUPPORTED_ENCODER_MODE = 0x08, ERROR_ILLEGAL_HALL_STATE = 0x10, ERROR_INDEX_NOT_FOUND_YET = 0x20, diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index 6983fa6c..e9cb23ab 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -42,8 +42,8 @@ class Endstop { } private: - bool pin_state_ = false; - float pos_when_pressed_ = 0.0f; + bool pin_state_ = false; + float pos_when_pressed_ = 0.0f; Timer debounceTimer_; }; #endif \ No newline at end of file diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 223aba1c..faafb7f5 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -329,7 +329,7 @@ bool Motor::FOC_voltage(float v_d, float v_q, float pwm_phase) { float c = our_arm_cos_f32(pwm_phase); float s = our_arm_sin_f32(pwm_phase); float v_alpha = c*v_d - s*v_q; - float v_beta = c*v_q + s*v_d; + float v_beta = c*v_q + s*v_d; return enqueue_voltage_timings(v_alpha, v_beta); } @@ -400,7 +400,7 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha float c_p = our_arm_cos_f32(pwm_phase); float s_p = our_arm_sin_f32(pwm_phase); float mod_alpha = c_p * mod_d - s_p * mod_q; - float mod_beta = c_p * mod_q + s_p * mod_d; + float mod_beta = c_p * mod_q + s_p * mod_d; // Report final applied voltage in stationary frame (for sensorles estimator) ictrl.final_v_alpha = mod_to_V * mod_alpha; diff --git a/Firmware/communication/can_helpers.hpp b/Firmware/communication/can_helpers.hpp index b5e40385..779b6a7a 100644 --- a/Firmware/communication/can_helpers.hpp +++ b/Firmware/communication/can_helpers.hpp @@ -26,7 +26,7 @@ struct can_Signal_t { template T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel) { uint64_t tempVal = 0; - uint64_t mask = (1ULL << length) - 1; + uint64_t mask = (1ULL << length) - 1; if (isIntel) { std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); @@ -50,7 +50,7 @@ float can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t len template void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { - T scaledVal = (val - offset) / factor; + T scaledVal = (val - offset) / factor; uint64_t valAsBits = 0; std::memcpy(&valAsBits, &scaledVal, sizeof(scaledVal)); diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 7182d27a..b00b13e0 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -279,14 +279,14 @@ void CANSimple::get_encoder_count_callback(Axis* axis, can_Message_t& msg) { } void CANSimple::set_input_pos_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.input_pos_ = can_getSignal(msg, 0, 32, true); - axis->controller_.input_vel_ = can_getSignal(msg, 32, 16, true, 0.1f, 0); + axis->controller_.input_pos_ = can_getSignal(msg, 0, 32, true); + axis->controller_.input_vel_ = can_getSignal(msg, 32, 16, true, 0.1f, 0); axis->controller_.input_current_ = can_getSignal(msg, 48, 16, true, 0.01f, 0); axis->controller_.input_pos_updated(); } void CANSimple::set_input_vel_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.input_vel_ = can_getSignal(msg, 0, 32, true, 0.01f, 0.0f); + axis->controller_.input_vel_ = can_getSignal(msg, 0, 32, true, 0.01f, 0.0f); axis->controller_.input_current_ = can_getSignal(msg, 32, 16, true, 0.01f, 0.0f); } From b3912d58c2e62880f0b542ef38c09cf8244b5dcc Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 20 May 2020 13:11:05 +0200 Subject: [PATCH 410/549] revert numerical static_cast to C style casts As per https://github.com/madcowswe/ODrive/pull/410#discussion_r427725994 --- Firmware/MotorControl/axis.cpp | 2 +- Firmware/MotorControl/controller.cpp | 2 +- Firmware/MotorControl/encoder.cpp | 24 ++++++++++++------------ Firmware/MotorControl/low_level.cpp | 2 +- Firmware/MotorControl/motor.cpp | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index cb704404..d7659d35 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -377,7 +377,7 @@ bool Axis::run_homing() { controller_.vel_setpoint_ = 0.0f; // Change directions without decelerating // Set our current position in encoder counts to make control more logical - encoder_.set_linear_count(static_cast(controller_.pos_setpoint_)); + encoder_.set_linear_count((int32_t)controller_.pos_setpoint_); controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ; diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index ed9163f7..60af0ee0 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -293,7 +293,7 @@ bool Controller::update(float* current_setpoint_output) { // We get the current position and apply a current feed-forward // ensuring that we handle negative encoder positions properly (-1 == motor->encoder.encoder_cpr - 1) if (anticogging_valid_ && config_.anticogging.enable) { - Iq += config_.anticogging.cogging_map[std::clamp(mod(static_cast(anticogging_pos), 3600), 0, 3600)]; + Iq += config_.anticogging.cogging_map[std::clamp(mod((int)anticogging_pos, 3600), 0, 3600)]; } float v_err = 0.0f; diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index e0e29a0b..d1545fc1 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -110,7 +110,7 @@ void Encoder::set_linear_count(int32_t count) { // Update states shadow_count_ = count; - pos_estimate_ = static_cast(count); + pos_estimate_ = (float)count; tim_cnt_sample_ = count; //Write hardware last @@ -132,7 +132,7 @@ void Encoder::set_circular_count(int32_t count, bool update_offset) { // Update states count_in_cpr_ = mod(count, config_.cpr); - pos_cpr_ = static_cast(count_in_cpr_); + pos_cpr_ = (float)count_in_cpr_; cpu_exit_critical(prim); } @@ -182,7 +182,7 @@ bool Encoder::run_direction_find() { // TODO: Do the scan with current, not voltage! bool Encoder::run_offset_calibration() { static const float start_lock_duration = 1.0f; - static const int num_steps = (int)(config_.calib_scan_distance / config_.calib_scan_omega * static_cast(current_meas_hz)); + static const int num_steps = (int)(config_.calib_scan_distance / config_.calib_scan_omega * (float)current_meas_hz); // Require index found if enabled if (config_.use_index && !index_found_) { @@ -219,7 +219,7 @@ bool Encoder::run_offset_calibration() { // scan forward i = 0; axis_->run_control_loop([&]() { - float phase = wrap_pm_pi(config_.calib_scan_distance * static_cast(i) / static_cast(num_steps) - config_.calib_scan_distance / 2.0f); + float phase = wrap_pm_pi(config_.calib_scan_distance * (float)i / (float)num_steps - config_.calib_scan_distance / 2.0f); float v_alpha = voltage_magnitude * our_arm_cos_f32(phase); float v_beta = voltage_magnitude * our_arm_sin_f32(phase); if (!axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta)) @@ -248,7 +248,7 @@ bool Encoder::run_offset_calibration() { //TODO avoid recomputing elec_rad_per_enc every time // Check CPR - float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / static_cast(config_.cpr)); + float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / (float)(config_.cpr)); float expected_encoder_delta = config_.calib_scan_distance / elec_rad_per_enc; calib_scan_response_ = std::abs(shadow_count_ - init_enc_val); if (std::abs(calib_scan_response_ - expected_encoder_delta) / expected_encoder_delta > config_.calib_range) { @@ -259,7 +259,7 @@ bool Encoder::run_offset_calibration() { // scan backwards i = 0; axis_->run_control_loop([&]() { - float phase = wrap_pm_pi(-config_.calib_scan_distance * static_cast(i) / static_cast(num_steps) + config_.calib_scan_distance / 2.0f); + float phase = wrap_pm_pi(-config_.calib_scan_distance * (float)i / (float)num_steps + config_.calib_scan_distance / 2.0f); float v_alpha = voltage_magnitude * our_arm_cos_f32(phase); float v_beta = voltage_magnitude * our_arm_sin_f32(phase); if (!axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta)) @@ -275,7 +275,7 @@ bool Encoder::run_offset_calibration() { config_.offset = encvaluesum / (num_steps * 2); int32_t residual = encvaluesum - ((int64_t)config_.offset * (int64_t)(num_steps * 2)); - config_.offset_float = static_cast(residual) / static_cast(num_steps * 2) + 0.5f; // add 0.5 to center-align state to phase + config_.offset_float = (float)residual / (float)(num_steps * 2) + 0.5f; // add 0.5 to center-align state to phase is_ready_ = true; return true; @@ -508,13 +508,13 @@ bool Encoder::update() { pos_estimate_ += current_meas_period * vel_estimate_; pos_cpr_ += current_meas_period * vel_estimate_; // discrete phase detector - float delta_pos = static_cast(shadow_count_) - static_cast(std::floor(pos_estimate_)); - float delta_pos_cpr = static_cast(count_in_cpr_) - static_cast(std::floor(pos_cpr_)); - delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * static_cast(config_.cpr)); + float delta_pos = (float)(shadow_count_ - (int32_t)std::floor(pos_estimate_)); + float delta_pos_cpr = (float)(count_in_cpr_ - (int32_t)std::floor(pos_cpr_)); + delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * (float)(config_.cpr)); // pll feedback pos_estimate_ += current_meas_period * pll_kp_ * delta_pos; pos_cpr_ += current_meas_period * pll_kp_ * delta_pos_cpr; - pos_cpr_ = fmodf_pos(pos_cpr_, static_cast(config_.cpr)); + pos_cpr_ = fmodf_pos(pos_cpr_, (float)(config_.cpr)); vel_estimate_ += current_meas_period * pll_ki_ * delta_pos_cpr; bool snap_to_zero_vel = false; if (std::abs(vel_estimate_) < 0.5f * current_meas_period * pll_ki_) { @@ -543,7 +543,7 @@ bool Encoder::update() { //// compute electrical phase //TODO avoid recomputing elec_rad_per_enc every time - float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / static_cast(config_.cpr)); + float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / (float)(config_.cpr)); float ph = elec_rad_per_enc * (interpolated_enc - config_.offset_float); // ph = fmodf(ph, 2*M_PI); phase_ = wrap_pm_pi(ph); diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index fda04cf4..0dcc8754 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -647,7 +647,7 @@ void update_brake_current() { return; } - int high_on = static_cast(TIM_APB1_PERIOD_CLOCKS * (1.0f - brake_duty)); + int high_on = (int)(TIM_APB1_PERIOD_CLOCKS * (1.0f - brake_duty)); int low_off = high_on - TIM_APB1_DEADTIME_CLOCKS; if (low_off < 0) low_off = 0; safety_critical_apply_brake_resistor_timings(low_off, high_on); diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index faafb7f5..ac03e863 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -216,7 +216,7 @@ float Motor::phase_current_from_adcval(uint32_t ADCValue) { // TODO check Ibeta balance to verify good motor connection bool Motor::measure_phase_resistance(float test_current, float max_voltage) { static const float kI = 10.0f; // [(V/s)/A] - static const int num_test_cycles = static_cast(3.0f / CURRENT_MEAS_PERIOD); // Test runs for 3s + static const int num_test_cycles = (int)(3.0f / CURRENT_MEAS_PERIOD); // Test runs for 3s float test_voltage = 0.0f; size_t i = 0; From 5ca7ae1f3e804a4bb70b27393e7b870bde0df26d Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 20 May 2020 13:26:43 +0200 Subject: [PATCH 411/549] change inverter temp initial value to NaN --- Firmware/MotorControl/motor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 8c627aa7..58f27d83 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -193,7 +193,7 @@ public: DRV8301_FaultType_e drv_fault_ = DRV8301_FaultType_NoFault; DRV_SPI_8301_Vars_t gate_driver_regs_; //Local view of DRV registers (initialized by DRV8301_setup) float thermal_current_lim_ = 10.0f; //[A] - float inverter_temp_ = 20.0f; + float inverter_temp_ = NAN; // [°C] NaN while the ODrive is initializing. // Communication protocol definitions auto make_protocol_definitions() { From 8d7ddf203c9dd7aae3a710d7eda64f0b66bd7c97 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 20 May 2020 13:30:30 +0200 Subject: [PATCH 412/549] remove obsolete comment --- Firmware/MotorControl/utils.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Firmware/MotorControl/utils.hpp b/Firmware/MotorControl/utils.hpp index 224f3716..0c191dfe 100644 --- a/Firmware/MotorControl/utils.hpp +++ b/Firmware/MotorControl/utils.hpp @@ -86,7 +86,6 @@ static inline float wrap_pm(float x, float pm_range) { return fmodf_pos(x + pm_range, 2.0f * pm_range) - pm_range; } -//beware of inserting large angles! static inline float wrap_pm_pi(float theta) { return wrap_pm(theta, M_PI); } From 9446a28142882ade336df206ce1dfa0cd26b067a Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 20 May 2020 15:19:46 +0200 Subject: [PATCH 413/549] increase test tolerance --- tools/odrive/tests/pwm_input_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/odrive/tests/pwm_input_test.py b/tools/odrive/tests/pwm_input_test.py index a37cc155..f871b4dd 100644 --- a/tools/odrive/tests/pwm_input_test.py +++ b/tools/odrive/tests/pwm_input_test.py @@ -81,7 +81,7 @@ class TestPwmInput(): full_scale = max_val - min_val slope, offset, fitted_curve = fit_sawtooth(data, min_val, max_val) test_assert_eq(slope, full_scale / 1.0, accuracy=0.001) - test_curve_fit(data, fitted_curve, max_mean_err = full_scale * 0.02, inlier_range = full_scale * 0.03, max_outliers = len(data[:,0]) * 0.01) + test_curve_fit(data, fitted_curve, max_mean_err = full_scale * 0.02, inlier_range = full_scale * 0.05, max_outliers = len(data[:,0]) * 0.01) From 7f82ef46181cc68a2dd91298c0d370c27a547c81 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 6 Mar 2019 13:14:54 +0100 Subject: [PATCH 414/549] fix __packed attribute error for some compilers related: https://github.com/zephyrproject-rtos/zephyr/issues/13237 --- .../Src/stm32f4xx_ll_usb.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_ll_usb.c b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_ll_usb.c index 61253fca..b20415d2 100644 --- a/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_ll_usb.c +++ b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_ll_usb.c @@ -56,6 +56,20 @@ /* Includes ------------------------------------------------------------------*/ #include "stm32f4xx_hal.h" +// taken from https://github.com/ARMmbed/mbed-os/blob/master/cmsis/TARGET_CORTEX_M/cmsis_compiler.h +#ifndef __PACKED_STRUCT + #define __PACKED_STRUCT struct __attribute__((packed)) +#endif +#ifndef __UNALIGNED_UINT32_WRITE + __PACKED_STRUCT T_UINT32_WRITE { uint32_t v; }; + #define __UNALIGNED_UINT32_WRITE(addr, val) (void)((((struct T_UINT32_WRITE *)(void *)(addr))->v) = (val)) +#endif +#ifndef __UNALIGNED_UINT32_READ + __PACKED_STRUCT T_UINT32_READ { uint32_t v; }; + #define __UNALIGNED_UINT32_READ(addr) (((const struct T_UINT32_READ *)(const void *)(addr))->v) +#endif + + /** @addtogroup STM32F4xx_LL_USB_DRIVER * @{ */ @@ -883,7 +897,7 @@ HAL_StatusTypeDef USB_WritePacket(USB_OTG_GlobalTypeDef *USBx, uint8_t *src, uin count32b = (len + 3U) / 4U; for (i = 0U; i < count32b; i++, src += 4U) { - USBx_DFIFO(ch_ep_num) = *((__packed uint32_t *)src); + USBx_DFIFO(ch_ep_num) = __UNALIGNED_UINT32_READ(src); } } return HAL_OK; @@ -909,7 +923,7 @@ void *USB_ReadPacket(USB_OTG_GlobalTypeDef *USBx, uint8_t *dest, uint16_t len) for ( i = 0U; i < count32b; i++, dest += 4U ) { - *(__packed uint32_t *)dest = USBx_DFIFO(0U); + __UNALIGNED_UINT32_WRITE(dest, USBx_DFIFO(0U)); } return ((void *)dest); From e58e9ac2577b718268eb884807f367b2ac0bbf8e Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 20 May 2020 19:15:01 +0200 Subject: [PATCH 415/549] clarify documentation on CRC calculation --- Firmware/fibre/python/fibre/protocol.py | 5 ++--- docs/protocol.md | 28 ++++++++++++++++++++----- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Firmware/fibre/python/fibre/protocol.py b/Firmware/fibre/python/fibre/protocol.py index 851ad457..c9d65735 100644 --- a/Firmware/fibre/python/fibre/protocol.py +++ b/Firmware/fibre/python/fibre/protocol.py @@ -28,6 +28,8 @@ CRC16_DEFAULT = 0x3d65 # this must match the polynomial in the C++ implementatio MAX_PACKET_SIZE = 128 +# For more information on the CRC algorithm refer to protocol.md + def calc_crc(remainder, value, polynomial, bitwidth): topbit = (1 << (bitwidth - 1)) @@ -61,9 +63,6 @@ def calc_crc16(remainder, value): remainder = calc_crc(remainder, value, CRC16_DEFAULT, 16) return remainder -# Can be verified with http://www.sunshine2k.de/coding/javascript/crc/crc_js.html: -#print(hex(calc_crc8(0x12, [1, 2, 3, 4, 5, 0x10, 0x13, 0x37]))) -#print(hex(calc_crc16(0xfeef, [1, 2, 3, 4, 5, 0x10, 0x13, 0x37]))) class DeviceInitException(Exception): pass diff --git a/docs/protocol.md b/docs/protocol.md index 6e8809d6..fd244e65 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -45,7 +45,7 @@ __Request__ - The length of the payload is determined by the total packet size. The format of the payload depends on the endpoint type. The endpoint type can be obtained from the JSON definition. - __Bytes N-2, N-1__ - For endpoint 0: Protocol version (currently 1). A server shall ignore packets with other values. - - For all other endpoints: The CRC16 calculated over the JSON definition. The CRC16 init value is the protocol version (currently 1). A server shall ignore packets that set this field incorrectly. See protocol.hpp for CRC details. + - For all other endpoints: The CRC16 calculated over the JSON definition using the algorithm described below, except that the initial value is set to the protocol version (currently 1). A server shall ignore packets that set this field incorrectly. __Response__ @@ -61,8 +61,26 @@ The stream based format is just a wrapper for the packet format. - __Byte 0__ Sync byte `0xAA` - __Byte 1__ Packet length - Currently both parties shall only emit and accept values of 0 through 127. - - __Byte 2__ CRC8 of bytes 0 and 1 - - See protocol.hpp for CRC details. + - __Byte 2__ CRC8 of bytes 0 and 1 (see below for details) - __Bytes 3 to N-3__ Packet - - __Bytes N-2, N-1__ CRC16 - - See protocol.hpp for CRC details. + - __Bytes N-2, N-1__ CRC16 (see below for details) + +## CRC algorithms ## + +__CRC8__ + - Polynomial: `0x37` + - Initial value: `0x42` + - No input reflection, no result reflection, no final XOR operation + - Examples: + - `0x01, 0x02, 0x03, 0x04` => `0x61` + - `0x05, 0x04, 0x03, 0x02, 0x01` => `0x64` + +__CRC16__ + - Polynomial: `0x3d65` + - Initial value: `0x1337` (or `0x0001` for the JSON CRC) + - No input reflection, no result reflection, no final XOR operation + - Examples: + - `0x01, 0x02, 0x03, 0x04` => `0x672E` + - `0x05, 0x04, 0x03, 0x02, 0x01` => `0xE251` + +You can use the online calculator at http://www.sunshine2k.de/coding/javascript/crc/crc_js.html to verify your implementation. From 20c5276126e11235e9408ba3d505c7e04b4c2ee2 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 25 May 2020 16:32:45 +0200 Subject: [PATCH 416/549] fix analog input regression --- Firmware/odrive-interface.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index d545f412..93158017 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -144,8 +144,8 @@ interfaces: gpio2_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[1]'} # TODO: disable for ODrive v3.2 and older gpio3_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[2]'} # TODO: disable for ODrive v3.2 and older gpio4_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[3]'} - gpio3_analog_mapping: {type: Endpoint, c_name: 'analog_mappings[0]'} - gpio4_analog_mapping: {type: Endpoint, c_name: 'analog_mappings[1]'} + gpio3_analog_mapping: {type: Endpoint, c_name: 'analog_mappings[2]'} + gpio4_analog_mapping: {type: Endpoint, c_name: 'analog_mappings[3]'} user_config_loaded: readonly bool axis0: {type: Axis, c_name: get_axis(0)} From a255ffd121563cf0624b9d9936cd6708bbdf66e5 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 22 May 2020 17:18:48 +0200 Subject: [PATCH 417/549] autogenerate enums.py --- Firmware/Tupfile.lua | 1 + Firmware/enums_template.j2 | 13 ++ Firmware/interface_generator.py | 8 +- tools/odrive/enums.py | 218 ++++++++++++++---------- tools/odrive/tests/analog_input_test.py | 2 +- tools/odrive/tests/calibration_test.py | 4 +- tools/odrive/tests/can_test.py | 8 +- tools/odrive/tests/closed_loop_test.py | 4 +- tools/odrive/tests/encoder_test.py | 4 +- tools/odrive/tests/pwm_input_test.py | 2 +- tools/odrive/tests/test_runner.py | 2 +- tools/odrive/utils.py | 17 +- tools/setup_hall_as_index.py | 2 +- 13 files changed, 174 insertions(+), 111 deletions(-) create mode 100644 Firmware/enums_template.j2 diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 94e5ab53..0e636fd2 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -5,6 +5,7 @@ tup.frule{inputs={'fibre/cpp/interfaces_template.j2'}, command='python3 interfac tup.frule{inputs={'fibre/cpp/function_stubs_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/function_stubs.hpp'} tup.frule{inputs={'fibre/cpp/endpoints_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/endpoints.hpp'} tup.frule{inputs={'fibre/cpp/type_info_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/type_info.hpp'} +tup.frule{command='python3 interface_generator.py --definitions odrive-interface.yaml --template enums_template.j2 --output ../tools/odrive/enums.py'} tup.frule{ command='python ../tools/odrive/version.py --output %o', diff --git a/Firmware/enums_template.j2 b/Firmware/enums_template.j2 new file mode 100644 index 00000000..28105c3c --- /dev/null +++ b/Firmware/enums_template.j2 @@ -0,0 +1,13 @@ + +# TODO: This file is dangerous because the enums could potentially change between API versions. Should transmit as part of the JSON. + +[%- for _, enum in value_types.items() %] +[%- if enum.is_enum %] + +# [[enum.fullname]] +[%- for k, value in enum['values'].items() %] +[[(((enum.parent.name if enum.name in ['Error', 'Mode'] else '') + enum.name + k) | to_macro_case).ljust(40)]] = [% if enum.is_flags %]0x[['%08x' | format(value.value)]][% else %][[value.value]][% endif %] +[%- endfor %] +[%- endif %] +[%- endfor %] + diff --git a/Firmware/interface_generator.py b/Firmware/interface_generator.py index 369a4a1c..f98cee1e 100644 --- a/Firmware/interface_generator.py +++ b/Firmware/interface_generator.py @@ -541,7 +541,9 @@ for k, item in list(interfaces.items()): toplevel_interfaces.append(item) else: if k[:-1] != ['fibre']: # TODO: remove special handling - interfaces[join_name(*k[:-1])]['interfaces'].append(item) + parent = interfaces[join_name(*k[:-1])] + parent['interfaces'].append(item) + item['parent'] = parent toplevel_enums = [] for k, item in list(enums.items()): k = split_name(k) @@ -549,7 +551,9 @@ for k, item in list(enums.items()): toplevel_enums.append(item) else: if k[:-1] != ['fibre']: # TODO: remove special handling - interfaces[join_name(*k[:-1])]['enums'].append(item) + parent = interfaces[join_name(*k[:-1])] + parent['enums'].append(item) + item['parent'] = parent diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index e3d3ebd0..55bc64e3 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -1,97 +1,141 @@ -# TODO: This is dangerous. Transmit as part of the JSON +# TODO: This file is dangerous because the enums could potentially change between API versions. Should transmit as part of the JSON. -AXIS_STATE_UNDEFINED = 0 -AXIS_STATE_IDLE = 1 -AXIS_STATE_STARTUP_SEQUENCE = 2 -AXIS_STATE_FULL_CALIBRATION_SEQUENCE = 3 -AXIS_STATE_MOTOR_CALIBRATION = 4 -AXIS_STATE_SENSORLESS_CONTROL = 5 -AXIS_STATE_ENCODER_INDEX_SEARCH = 6 -AXIS_STATE_ENCODER_OFFSET_CALIBRATION = 7 -AXIS_STATE_CLOSED_LOOP_CONTROL = 8 -AXIS_STATE_LOCKIN_SPIN = 9 -AXIS_STATE_ENCODER_DIR_FIND = 10 -AXIS_STATE_HOMING = 11 +# Odrive.Can.Protocol +PROTOCOL_SIMPLE = 0 -class errors: - class axis: - ERROR_NONE = 0x00 - ERROR_INVALID_STATE = 0x01 # Date: Fri, 22 May 2020 20:50:42 +0200 Subject: [PATCH 418/549] [interface generator] rename c_type to c_name --- Firmware/fibre/cpp/endpoints_template.j2 | 6 +-- Firmware/fibre/cpp/function_stubs_template.j2 | 10 ++-- Firmware/fibre/cpp/interfaces_template.j2 | 32 ++++++------- Firmware/fibre/cpp/type_info_template.j2 | 4 +- Firmware/interface_generator.py | 46 +++++++++---------- 5 files changed, 49 insertions(+), 49 deletions(-) diff --git a/Firmware/fibre/cpp/endpoints_template.j2 b/Firmware/fibre/cpp/endpoints_template.j2 index 72b9afe0..975d9422 100644 --- a/Firmware/fibre/cpp/endpoints_template.j2 +++ b/Firmware/fibre/cpp/endpoints_template.j2 @@ -29,7 +29,7 @@ Introspectable get_property(size_t idx) { switch (idx) { [%- for endpoint in endpoints %] [%- if endpoint.function.name == 'exchange' and endpoint.in_bindings | list == ['obj'] %] - case [[endpoint.id]]: return FibrePropertyTypeInfo<[[endpoint.function.in['obj'].type.c_type]]>::make_introspectable([[endpoint.in_bindings['obj']]]); + case [[endpoint.id]]: return FibrePropertyTypeInfo<[[endpoint.function.in['obj'].type.c_name]]>::make_introspectable([[endpoint.in_bindings['obj']]]); [%- endif %] [%- endfor %] default: return {}; @@ -50,9 +50,9 @@ bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) switch (idx) { [%- for endpoint in endpoints %] [%- if (endpoint.function.name == 'exchange' or endpoint.function.name == 'read') and endpoint.in_bindings | list == ['obj'] %] - case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_type]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_type]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break; + case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_name]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_name]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break; [%- else %] - case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_type]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_type]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break; + case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_name]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_name]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break; [%- endif %] [%- endfor %] default: return false; diff --git a/Firmware/fibre/cpp/function_stubs_template.j2 b/Firmware/fibre/cpp/function_stubs_template.j2 index 759b49eb..fb44fdaa 100644 --- a/Firmware/fibre/cpp/function_stubs_template.j2 +++ b/Firmware/fibre/cpp/function_stubs_template.j2 @@ -13,9 +13,9 @@ [% for intf in interfaces.values() %] [% for func in intf.functions.values() %] -static inline bool [[func.fullname | to_snake_case]]([% for arg in func.in.values() %]std::optional<[[arg.type.c_type]]> in_[[arg.name]], [% endfor %][% for arg in func.out.values() %][[arg.type.c_type]]* out_[[arg.name]], [% endfor %]fibre::cbufptr_t* input_buffer, fibre::bufptr_t* output_buffer) { +static inline bool [[func.fullname | to_snake_case]]([% for arg in func.in.values() %]std::optional<[[arg.type.c_name]]> in_[[arg.name]], [% endfor %][% for arg in func.out.values() %][[arg.type.c_name]]* out_[[arg.name]], [% endfor %]fibre::cbufptr_t* input_buffer, fibre::bufptr_t* output_buffer) { [%- if func.in %] - bool success = [% for arg in func.in.values() %](in_[[arg.name]].has_value() || (in_[[arg.name]] = fibre::Codec<[[arg.type.c_type]]>::decode(input_buffer)).has_value()[% if arg.optional %] || true[% endif %])[% if not loop.last %] + bool success = [% for arg in func.in.values() %](in_[[arg.name]].has_value() || (in_[[arg.name]] = fibre::Codec<[[arg.type.c_name]]>::decode(input_buffer)).has_value()[% if arg.optional %] || true[% endif %])[% if not loop.last %] && [% endif %][% endfor %]; [%- else %] bool success = true; @@ -24,12 +24,12 @@ static inline bool [[func.fullname | to_snake_case]]([% for arg in func.in.value return false; } [%- if func.implementation %] - [% if func.out %]std::tuple<[% for arg in func.out.values() %][[arg.type.c_type]][[', ' if not loop.last]][% endfor %]> ret = [% endif %][[func.implementation]]([% for arg in func.in.values() %]in_[[arg.name]][% if not arg.optional %].value()[% endif %][[', ' if not loop.last]][% endfor %]); + [% if func.out %]std::tuple<[% for arg in func.out.values() %][[arg.type.c_name]][[', ' if not loop.last]][% endfor %]> ret = [% endif %][[func.implementation]]([% for arg in func.in.values() %]in_[[arg.name]][% if not arg.optional %].value()[% endif %][[', ' if not loop.last]][% endfor %]); [%- else %] - [% if func.out %]std::tuple<[% for arg in func.out.values() %][[arg.type.c_type]][[', ' if not loop.last]][% endfor %]> ret = [% endif %]in_[[(func.in.values() | first).name]].value()->[[func.name]]([% for arg in func.in.values() | skip_first %]in_[[arg.name]][% if not arg.optional %].value()[% endif %][[', ' if not loop.last]][% endfor %]); + [% if func.out %]std::tuple<[% for arg in func.out.values() %][[arg.type.c_name]][[', ' if not loop.last]][% endfor %]> ret = [% endif %]in_[[(func.in.values() | first).name]].value()->[[func.name]]([% for arg in func.in.values() | skip_first %]in_[[arg.name]][% if not arg.optional %].value()[% endif %][[', ' if not loop.last]][% endfor %]); [%- endif %] [%- if func.out %] - return [% for arg in func.out.values() %]((out_[[arg.name]] && ((*out_[[arg.name]] = std::get<[[loop.index0]]>(ret)), true)) || fibre::Codec<[[arg.type.c_type]]>::encode(std::get<[[loop.index0]]>(ret), output_buffer))[% if not loop.last %] + return [% for arg in func.out.values() %]((out_[[arg.name]] && ((*out_[[arg.name]] = std::get<[[loop.index0]]>(ret)), true)) || fibre::Codec<[[arg.type.c_name]]>::encode(std::get<[[loop.index0]]>(ret), output_buffer))[% if not loop.last %] && [% endif %][% endfor %]; [%- else %] return true; diff --git a/Firmware/fibre/cpp/interfaces_template.j2 b/Firmware/fibre/cpp/interfaces_template.j2 index eb5167a7..60c92b35 100644 --- a/Firmware/fibre/cpp/interfaces_template.j2 +++ b/Firmware/fibre/cpp/interfaces_template.j2 @@ -17,7 +17,7 @@ [%- if not func.out -%] void [%- elif func.out | length == 1 -%] -[[(func.out.values() | first).type.c_type]] +[[(func.out.values() | first).type.c_name]] [%- else -%] [% for arg in func.out.values() %][[arg.type]][[', ' if not loop.last]][% endfor %] [%- endif -%] @@ -40,11 +40,11 @@ public: [%- for property in intf.attributes.values() %] [%- if property.type.fullname.startswith("fibre.Property") %] [%- if not property.c_getter and not property.c_setter %] - template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_type]]{&obj->[[property.c_name]]}; } + template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_name]]{&obj->[[property.c_name]]}; } [%- elif not property.c_setter %] - template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_type]]{obj, [](void* ctx){ return ([[property.type.value_type.c_type]])((T*)ctx)->[[property.c_getter]]; }}; } + template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }}; } [%- else %] - template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_type]]{obj, [](void* ctx){ return ([[property.type.value_type.c_type]])((T*)ctx)->[[property.c_getter]]; }, [](void* ctx, [[property.type.value_type.c_type]] value){ ((T*)ctx)->[[property.c_setter]](value); }}; } + template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }, [](void* ctx, [[property.type.value_type.c_name]] value){ ((T*)ctx)->[[property.c_setter]](value); }}; } [%- endif %] [%- else %] template static inline auto get_[[property.name]](T* obj) { return &obj->[[property.c_name]]; } @@ -52,16 +52,16 @@ public: [%- endfor %] [%- for func in intf.functions.values() %] - virtual [[rettype(func)]] [[func.name | to_snake_case]]([% for in in func.in.values() %][% if loop.index0 %][[in.type.c_type]] [[in.name]][[', ' if not loop.last]][% endif %][% endfor %]) = 0; + virtual [[rettype(func)]] [[func.name | to_snake_case]]([% for in in func.in.values() %][% if loop.index0 %][[in.type.c_name]] [[in.name]][[', ' if not loop.last]][% endif %][% endfor %]) = 0; [%- endfor %] [%- for func in intf.functions.values() %] [%- for k, arg in func.in.items() | skip_first %] - [[arg.type.c_type]] [[func.name | to_snake_case]]_in_[[arg.name]]_; // for internal use by Fibre - template static auto get_[[func.name | to_snake_case]]_in_[[arg.name]]_(T* obj) { return Property<[[arg.type.c_type]]>{&obj->[[func.name | to_snake_case]]_in_[[arg.name]]_}; } + [[arg.type.c_name]] [[func.name | to_snake_case]]_in_[[arg.name]]_; // for internal use by Fibre + template static auto get_[[func.name | to_snake_case]]_in_[[arg.name]]_(T* obj) { return Property<[[arg.type.c_name]]>{&obj->[[func.name | to_snake_case]]_in_[[arg.name]]_}; } [%- endfor %] [%- for k, arg in func.out.items() %] - [[arg.type.c_type]] [[func.name | to_snake_case]]_out_[[arg.name]]_; // for internal use by Fibre - template static auto get_[[func.name | to_snake_case]]_out_[[arg.name]]_(T* obj) { return Property{&obj->[[func.name | to_snake_case]]_out_[[arg.name]]_}; } + [[arg.type.c_name]] [[func.name | to_snake_case]]_out_[[arg.name]]_; // for internal use by Fibre + template static auto get_[[func.name | to_snake_case]]_out_[[arg.name]]_(T* obj) { return Property{&obj->[[func.name | to_snake_case]]_out_[[arg.name]]_}; } [%- endfor %] [%- endfor %] }; @@ -74,13 +74,13 @@ public: [%- for _, enum in value_types.items() %] [%- if enum.is_flags %] // this is technically not thread-safe but practically it might be -inline [[enum.c_type]] operator | ([[enum.c_type]] a, [[enum.c_type]] b) { return static_cast<[[enum.c_type]]>(static_cast>(a) | static_cast>(b)); } -inline [[enum.c_type]] operator & ([[enum.c_type]] a, [[enum.c_type]] b) { return static_cast<[[enum.c_type]]>(static_cast>(a) & static_cast>(b)); } -inline [[enum.c_type]] operator ^ ([[enum.c_type]] a, [[enum.c_type]] b) { return static_cast<[[enum.c_type]]>(static_cast>(a) ^ static_cast>(b)); } -inline [[enum.c_type]]& operator |= ([[enum.c_type]] &a, [[enum.c_type]] b) { return reinterpret_cast<[[enum.c_type]]&>(reinterpret_cast&>(a) |= static_cast>(b)); } -inline [[enum.c_type]]& operator &= ([[enum.c_type]] &a, [[enum.c_type]] b) { return reinterpret_cast<[[enum.c_type]]&>(reinterpret_cast&>(a) &= static_cast>(b)); } -inline [[enum.c_type]]& operator ^= ([[enum.c_type]] &a, [[enum.c_type]] b) { return reinterpret_cast<[[enum.c_type]]&>(reinterpret_cast&>(a) ^= static_cast>(b)); } -inline [[enum.c_type]] operator ~ ([[enum.c_type]] a) { return static_cast<[[enum.c_type]]>(~static_cast>(a)); } +inline [[enum.c_name]] operator | ([[enum.c_name]] a, [[enum.c_name]] b) { return static_cast<[[enum.c_name]]>(static_cast>(a) | static_cast>(b)); } +inline [[enum.c_name]] operator & ([[enum.c_name]] a, [[enum.c_name]] b) { return static_cast<[[enum.c_name]]>(static_cast>(a) & static_cast>(b)); } +inline [[enum.c_name]] operator ^ ([[enum.c_name]] a, [[enum.c_name]] b) { return static_cast<[[enum.c_name]]>(static_cast>(a) ^ static_cast>(b)); } +inline [[enum.c_name]]& operator |= ([[enum.c_name]] &a, [[enum.c_name]] b) { return reinterpret_cast<[[enum.c_name]]&>(reinterpret_cast&>(a) |= static_cast>(b)); } +inline [[enum.c_name]]& operator &= ([[enum.c_name]] &a, [[enum.c_name]] b) { return reinterpret_cast<[[enum.c_name]]&>(reinterpret_cast&>(a) &= static_cast>(b)); } +inline [[enum.c_name]]& operator ^= ([[enum.c_name]] &a, [[enum.c_name]] b) { return reinterpret_cast<[[enum.c_name]]&>(reinterpret_cast&>(a) ^= static_cast>(b)); } +inline [[enum.c_name]] operator ~ ([[enum.c_name]] a) { return static_cast<[[enum.c_name]]>(~static_cast>(a)); } [%- endif %] [%- endfor %] diff --git a/Firmware/fibre/cpp/type_info_template.j2 b/Firmware/fibre/cpp/type_info_template.j2 index 70cfae2a..7e6cda57 100644 --- a/Firmware/fibre/cpp/type_info_template.j2 +++ b/Firmware/fibre/cpp/type_info_template.j2 @@ -27,7 +27,7 @@ struct [[intf.fullname | to_pascal_case]]TypeInfo : TypeInfo { introspectable_storage_t res; switch (idx) { [%- for property in intf.attributes.values() %] - case [[loop.index0]]: *(decltype([[intf.c_type]]::get_[[property.name]](std::declval()))*)(&res) = [[intf.c_type]]::get_[[property.name]](ptr); break; + case [[loop.index0]]: *(decltype([[intf.c_name]]::get_[[property.name]](std::declval()))*)(&res) = [[intf.c_name]]::get_[[property.name]](ptr); break; [%- endfor %] } return res; @@ -39,7 +39,7 @@ struct [[intf.fullname | to_pascal_case]]TypeInfo : TypeInfo { template const PropertyInfo [[intf.fullname | to_pascal_case]]TypeInfo::property_table[] = { [%- for property in intf.attributes.values() %] - {"[[property.name]]", &[[(property.type.purename or property.type.fullname) | to_pascal_case]]TypeInfo()))>>::singleton}, + {"[[property.name]]", &[[(property.type.purename or property.type.fullname) | to_pascal_case]]TypeInfo()))>>::singleton}, [%- endfor %] }; template diff --git a/Firmware/interface_generator.py b/Firmware/interface_generator.py index f98cee1e..ad770328 100644 --- a/Firmware/interface_generator.py +++ b/Firmware/interface_generator.py @@ -132,17 +132,17 @@ def to_snake_case(s): return '_'.join(get_words(s)).lower() def to_kebab_case(s): return '-'.join(get_words(s)).lower() value_types = { - 'bool': {'builtin': True, 'fullname': 'bool', 'name': 'bool', 'c_type': 'bool'}, - 'float32': {'builtin': True, 'fullname': 'float32', 'name': 'float32', 'c_type': 'float'}, - 'uint8': {'builtin': True, 'fullname': 'uint8', 'name': 'uint8', 'c_type': 'uint8_t'}, - 'uint16': {'builtin': True, 'fullname': 'uint16', 'name': 'uint16', 'c_type': 'uint16_t'}, - 'uint32': {'builtin': True, 'fullname': 'uint32', 'name': 'uint32', 'c_type': 'uint32_t'}, - 'uint64': {'builtin': True, 'fullname': 'uint64', 'name': 'uint64', 'c_type': 'uint64_t'}, - 'int8': {'builtin': True, 'fullname': 'int8', 'name': 'int8', 'c_type': 'int8_t'}, - 'int16': {'builtin': True, 'fullname': 'int16', 'name': 'int16', 'c_type': 'int16_t'}, - 'int32': {'builtin': True, 'fullname': 'int32', 'name': 'int32', 'c_type': 'int32_t'}, - 'int64': {'builtin': True, 'fullname': 'int64', 'name': 'int64', 'c_type': 'int64_t'}, - 'endpoint_ref': {'builtin': True, 'fullname': 'endpoint_ref', 'name': 'endpoint_ref', 'c_type': 'endpoint_ref_t'}, + 'bool': {'builtin': True, 'fullname': 'bool', 'name': 'bool', 'c_name': 'bool'}, + 'float32': {'builtin': True, 'fullname': 'float32', 'name': 'float32', 'c_name': 'float'}, + 'uint8': {'builtin': True, 'fullname': 'uint8', 'name': 'uint8', 'c_name': 'uint8_t'}, + 'uint16': {'builtin': True, 'fullname': 'uint16', 'name': 'uint16', 'c_name': 'uint16_t'}, + 'uint32': {'builtin': True, 'fullname': 'uint32', 'name': 'uint32', 'c_name': 'uint32_t'}, + 'uint64': {'builtin': True, 'fullname': 'uint64', 'name': 'uint64', 'c_name': 'uint64_t'}, + 'int8': {'builtin': True, 'fullname': 'int8', 'name': 'int8', 'c_name': 'int8_t'}, + 'int16': {'builtin': True, 'fullname': 'int16', 'name': 'int16', 'c_name': 'int16_t'}, + 'int32': {'builtin': True, 'fullname': 'int32', 'name': 'int32', 'c_name': 'int32_t'}, + 'int64': {'builtin': True, 'fullname': 'int64', 'name': 'int64', 'c_name': 'int64_t'}, + 'endpoint_ref': {'builtin': True, 'fullname': 'endpoint_ref', 'name': 'endpoint_ref', 'c_name': 'endpoint_ref_t'}, } enums = {} @@ -157,12 +157,12 @@ def make_property_type(typeargs): if fullname in interfaces: return interfaces[fullname] - c_type = 'Property<' + ('const ' if mode == 'readonly' else '') + value_type['c_type'] + '>' + c_name = 'Property<' + ('const ' if mode == 'readonly' else '') + value_type['c_name'] + '>' prop_type = { 'name': name, 'fullname': fullname, 'purename': 'fibre.Property', - 'c_type': c_type, + 'c_name': c_name, 'value_type': value_type, # TODO: should be a metaarg 'mode': mode, # TODO: should be a metaarg 'builtin': True, @@ -173,17 +173,17 @@ def make_property_type(typeargs): prop_type['functions']['exchange'] = { 'name': 'exchange', 'fullname': join_name(fullname, 'exchange'), - 'in': {'obj': {'name': 'obj', 'type': {'c_type': c_type}}, 'value': {'name': 'value', 'type': value_type, 'optional': True}}, + 'in': {'obj': {'name': 'obj', 'type': {'c_name': c_name}}, 'value': {'name': 'value', 'type': value_type, 'optional': True}}, 'out': {'value': {'name': 'value', 'type': value_type}}, - #'implementation': 'fibre_property_exchange<' + value_type['c_type'] + '>' + #'implementation': 'fibre_property_exchange<' + value_type['c_name'] + '>' } else: prop_type['functions']['read'] = { 'name': 'read', 'fullname': join_name(fullname, 'read'), - 'in': {'obj': {'name': 'obj', 'type': {'c_type': c_type}}}, + 'in': {'obj': {'name': 'obj', 'type': {'c_name': c_name}}}, 'out': {'value': {'name': 'value', 'type': value_type}}, - #'implementation': 'fibre_property_read<' + value_type['c_type'] + '>' + #'implementation': 'fibre_property_read<' + value_type['c_name'] + '>' } interfaces[fullname] = prop_type @@ -204,7 +204,7 @@ def make_ref_type(interface): 'builtin': True, 'name': name, 'fullname': fullname, - 'c_type': interface['fullname'].replace('.', 'Intf::') + 'Intf*' + 'c_name': interface['fullname'].replace('.', 'Intf::') + 'Intf*' } value_types[fullname] = ref_type @@ -281,7 +281,7 @@ def regularize_interface(path, name, elem): # path = 'AnonymousType' + str(max_anonymous_type + 1) elem['name'] = split_name(name)[-1] elem['fullname'] = path = join_name(path, name) - elem['c_type'] = elem.get('c_type', elem['fullname'].replace('.', 'Intf::')) + 'Intf' + elem['c_name'] = elem.get('c_name', elem['fullname'].replace('.', 'Intf::')) + 'Intf' interfaces[path] = elem elem['functions'] = {name: regularize_func(path, name, func, {'obj': {'type': make_ref_type(elem)}}) for name, func in get_dict(elem, 'functions').items()} @@ -301,7 +301,7 @@ def regularize_valuetype(path, name, elem): return elem # will be resolved during type resolution elem['name'] = split_name(name)[-1] elem['fullname'] = path = join_name(path, name) - elem['c_type'] = elem.get('c_type', elem['fullname'].replace('.', 'Intf::')) + elem['c_name'] = elem.get('c_name', elem['fullname'].replace('.', 'Intf::')) value_types[path] = elem if 'flags' in elem: # treat as flags @@ -413,7 +413,7 @@ def generate_endpoint_table(intf, bindto, idx): for k, prop in intf['attributes'].items(): property_value_type = re.findall('^fibre\.Property<([^>]*), (readonly|readwrite)>$', prop['type']['fullname']) #attr_bindto = join_name(bindto, bindings_map.get(join_name(intf['fullname'], k), k + ('_' if len(intf['functions']) or (intf['fullname'] in treat_as_classes) else ''))) - attr_bindto = intf['c_type'] + '::get_' + prop['name'] + '(' + bindto + ')' + attr_bindto = intf['c_name'] + '::get_' + prop['name'] + '(' + bindto + ')' if len(property_value_type): # Special handling for Property<...> attributes: they resolve to one single endpoint endpoint, endpoint_definition = generate_endpoint_for_property(prop, attr_bindto, idx + cnt) @@ -443,14 +443,14 @@ def generate_endpoint_table(intf, bindto, idx): endpoint, endpoint_definition = generate_endpoint_for_property({ 'name': arg['name'], 'type': make_property_type({'fibre.Property.type': arg['type'], 'fibre.Property.mode': 'readwrite'}) - }, intf['c_type'] + '::get_' + func['name'] + '_in_' + k_arg + '_' + '(' + bindto + ')', idx + cnt + 1 + i) + }, intf['c_name'] + '::get_' + func['name'] + '_in_' + k_arg + '_' + '(' + bindto + ')', idx + cnt + 1 + i) endpoints.append(endpoint) in_def.append(endpoint_definition) for i, (k_arg, arg) in enumerate(func['out'].items()): endpoint, endpoint_definition = generate_endpoint_for_property({ 'name': arg['name'], 'type': make_property_type({'fibre.Property.type': arg['type'], 'fibre.Property.mode': 'readonly'}) - }, intf['c_type'] + '::get_' + func['name'] + '_out_' + k_arg + '_' + '(' + bindto + ')', idx + cnt + len(func['in']) + i) + }, intf['c_name'] + '::get_' + func['name'] + '_out_' + k_arg + '_' + '(' + bindto + ')', idx + cnt + len(func['in']) + i) endpoints.append(endpoint) out_def.append(endpoint_definition) From 28f48e8fab6368e3428470c18a44a0b0c0a5d02a Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 22 May 2020 21:12:14 +0200 Subject: [PATCH 419/549] [interface autogen] remove hardcoded intf name --- Firmware/Tupfile.lua | 2 +- Firmware/communication/communication.cpp | 2 ++ Firmware/interface_generator.py | 13 +++++++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 0e636fd2..2d4b7341 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -3,7 +3,7 @@ tup.include('build.lua') tup.frule{inputs={'fibre/cpp/interfaces_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/interfaces.hpp'} tup.frule{inputs={'fibre/cpp/function_stubs_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/function_stubs.hpp'} -tup.frule{inputs={'fibre/cpp/endpoints_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/endpoints.hpp'} +tup.frule{inputs={'fibre/cpp/endpoints_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --generate-endpoints Odrive --template %f --output %o', outputs='autogen/endpoints.hpp'} tup.frule{inputs={'fibre/cpp/type_info_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/type_info.hpp'} tup.frule{command='python3 interface_generator.py --definitions odrive-interface.yaml --template enums_template.j2 --output ../tools/odrive/enums.py'} diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 4e732a9c..794debaf 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -95,4 +95,6 @@ int _write(int file, const char* data, int len) { #include "../autogen/function_stubs.hpp" + +ODrive& ep_root = odrv; #include "../autogen/endpoints.hpp" diff --git a/Firmware/interface_generator.py b/Firmware/interface_generator.py index ad770328..1acfc747 100644 --- a/Firmware/interface_generator.py +++ b/Firmware/interface_generator.py @@ -479,6 +479,8 @@ parser.add_argument("-t", "--template", type=argparse.FileType('r'), help="the code template") parser.add_argument("-o", "--output", type=argparse.FileType('w'), default='-', help="path of the generated output") +parser.add_argument("--generate-endpoints", type=str, nargs='?', + help="if specified, an endpoint table will be generated and passed to the template for the specified interface") args = parser.parse_args() if args.version: @@ -556,10 +558,13 @@ for k, item in list(enums.items()): item['parent'] = parent - -endpoints, embedded_endpoint_definitions, _ = generate_endpoint_table(interfaces['Odrive'], '&odrv', 1) # TODO: make user-configurable -embedded_endpoint_definitions = [{'name': '', 'id': 0, 'type': 'json', 'access': 'r'}] + embedded_endpoint_definitions -endpoints = [{'id': 0, 'function': {'fullname': 'endpoint0_handler', 'in': {}, 'out': {}}, 'bindings': {}}] + endpoints +if args.generate_endpoints: + endpoints, embedded_endpoint_definitions, _ = generate_endpoint_table(interfaces[args.generate_endpoints], '&ep_root', 1) # TODO: make user-configurable + embedded_endpoint_definitions = [{'name': '', 'id': 0, 'type': 'json', 'access': 'r'}] + embedded_endpoint_definitions + endpoints = [{'id': 0, 'function': {'fullname': 'endpoint0_handler', 'in': {}, 'out': {}}, 'bindings': {}}] + endpoints +else: + embedded_endpoint_definitions = None + endpoints = None # Render template From 49f789148c2c025efb2a3bec135517c5ecb49881 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 22 May 2020 23:51:17 +0200 Subject: [PATCH 420/549] [interface autogen] fix stack overflow --- Firmware/fibre/cpp/endpoints_template.j2 | 21 ++++++++++--------- .../fibre/cpp/include/fibre/introspection.hpp | 1 + Firmware/fibre/cpp/interfaces_template.j2 | 5 +++++ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Firmware/fibre/cpp/endpoints_template.j2 b/Firmware/fibre/cpp/endpoints_template.j2 index 975d9422..db9b8dbd 100644 --- a/Firmware/fibre/cpp/endpoints_template.j2 +++ b/Firmware/fibre/cpp/endpoints_template.j2 @@ -15,6 +15,12 @@ #include +// Note: with -Og the functions with large switch statements reserves a huge amount +// of stack space because they reserves separate space for the stack frame of each +// of the inlined functions. +// The minimum known set of flags to prevent this is `-O1 -fipa-sra`. +// `-O2`, `-O3` and `-Os` are supersets of this. + #pragma GCC push_options #pragma GCC optimize ("s") @@ -25,24 +31,18 @@ const size_t embedded_json_length = sizeof(embedded_json) - 1; const uint16_t json_crc_ = calc_crc16(PROTOCOL_VERSION, embedded_json, embedded_json_length); const uint32_t json_version_id_ = (json_crc_ << 16) | calc_crc16(json_crc_, embedded_json, embedded_json_length); -Introspectable get_property(size_t idx) { +static void get_property(Introspectable& result, size_t idx) { switch (idx) { [%- for endpoint in endpoints %] [%- if endpoint.function.name == 'exchange' and endpoint.in_bindings | list == ['obj'] %] - case [[endpoint.id]]: return FibrePropertyTypeInfo<[[endpoint.function.in['obj'].type.c_name]]>::make_introspectable([[endpoint.in_bindings['obj']]]); + case [[endpoint.id]]: { [[(endpoint.in_bindings['obj'] + '$') | replace(')$', ', &result.storage_)')]]; result.type_info_ = &FibrePropertyTypeInfo<[[endpoint.function.in['obj'].type.c_name]]>::singleton; } break; [%- endif %] [%- endfor %] - default: return {}; + default: break; } } -// Note: with -Og this function reserves a huge amount of stack space because it -// reserves separate space for the stack frame of each of the inlined functions. -// The minimum known set of flags to prevent this is `-O1 -fipa-sra`. -// `-O2` is a superset of this so that's what we use here. -//bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) __attribute__ ((optimize(2))); - bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) { //Introspectable property = get_property(idx); //if property.is_valid() @@ -77,7 +77,8 @@ bool set_endpoint_from_float(endpoint_ref_t endpoint_ref, float value) { return false; } - Introspectable property = get_property(endpoint_ref.endpoint_id); + Introspectable property{}; + get_property(property, endpoint_ref.endpoint_id); const FloatSettableTypeInfo* type_info = dynamic_cast(property.get_type_info()); return type_info && type_info->set_float(property, value); } diff --git a/Firmware/fibre/cpp/include/fibre/introspection.hpp b/Firmware/fibre/cpp/include/fibre/introspection.hpp index d490ff71..f52b73a8 100644 --- a/Firmware/fibre/cpp/include/fibre/introspection.hpp +++ b/Firmware/fibre/cpp/include/fibre/introspection.hpp @@ -106,6 +106,7 @@ private: return {}; } +public: // these should technically be protected but are public for optimization reasons // We use this storage to hold generic small objects. Usually that's a pointer // but sometimes it's an on-demand constructed Property<...>. // Caution: only put objects in here which are trivially copyable, movable diff --git a/Firmware/fibre/cpp/interfaces_template.j2 b/Firmware/fibre/cpp/interfaces_template.j2 index 60c92b35..f4eb3163 100644 --- a/Firmware/fibre/cpp/interfaces_template.j2 +++ b/Firmware/fibre/cpp/interfaces_template.j2 @@ -41,10 +41,13 @@ public: [%- if property.type.fullname.startswith("fibre.Property") %] [%- if not property.c_getter and not property.c_setter %] template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_name]]{&obj->[[property.c_name]]}; } + template static inline void get_[[property.name]](T* obj, void* ptr) { new (ptr) [[property.type.c_name]]{&obj->[[property.c_name]]}; }[# these are for the set_endpoint_from_float function. This is unmaintainable and should go away #] [%- elif not property.c_setter %] template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }}; } + template static inline void get_[[property.name]](T* obj, void* ptr) { new (ptr) [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }}; } [%- else %] template static inline auto get_[[property.name]](T* obj) { return [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }, [](void* ctx, [[property.type.value_type.c_name]] value){ ((T*)ctx)->[[property.c_setter]](value); }}; } + template static inline void get_[[property.name]](T* obj, void* ptr) { new (ptr) [[property.type.c_name]]{obj, [](void* ctx){ return ([[property.type.value_type.c_name]])((T*)ctx)->[[property.c_getter]]; }, [](void* ctx, [[property.type.value_type.c_name]] value){ ((T*)ctx)->[[property.c_setter]](value); }}; } [%- endif %] [%- else %] template static inline auto get_[[property.name]](T* obj) { return &obj->[[property.c_name]]; } @@ -58,10 +61,12 @@ public: [%- for k, arg in func.in.items() | skip_first %] [[arg.type.c_name]] [[func.name | to_snake_case]]_in_[[arg.name]]_; // for internal use by Fibre template static auto get_[[func.name | to_snake_case]]_in_[[arg.name]]_(T* obj) { return Property<[[arg.type.c_name]]>{&obj->[[func.name | to_snake_case]]_in_[[arg.name]]_}; } + template static void get_[[func.name | to_snake_case]]_in_[[arg.name]]_(T* obj, void* ptr) { new (ptr) Property<[[arg.type.c_name]]>{&obj->[[func.name | to_snake_case]]_in_[[arg.name]]_}; } [%- endfor %] [%- for k, arg in func.out.items() %] [[arg.type.c_name]] [[func.name | to_snake_case]]_out_[[arg.name]]_; // for internal use by Fibre template static auto get_[[func.name | to_snake_case]]_out_[[arg.name]]_(T* obj) { return Property{&obj->[[func.name | to_snake_case]]_out_[[arg.name]]_}; } + template static void get_[[func.name | to_snake_case]]_out_[[arg.name]]_(T* obj, void* ptr) { new (ptr) Property{&obj->[[func.name | to_snake_case]]_out_[[arg.name]]_}; } [%- endfor %] [%- endfor %] }; From 8a3213cb881e9148b68c8c6a770893d0234fa8d0 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 22 May 2020 23:53:53 +0200 Subject: [PATCH 421/549] increase test tolerance (yes that's gonna be a thing now) --- tools/odrive/tests/analog_input_test.py | 3 ++- tools/odrive/tests/pwm_input_test.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/odrive/tests/analog_input_test.py b/tools/odrive/tests/analog_input_test.py index e21553a3..8932e5e7 100644 --- a/tools/odrive/tests/analog_input_test.py +++ b/tools/odrive/tests/analog_input_test.py @@ -103,7 +103,8 @@ class TestAnalogInput(): # Expect mean error to be at most 2% (of the full scale). # Expect there to be less than 2% outliers, where an outlier is anything that is more than 5% (of full scale) away from the expected value. full_range = abs(max_val - min_val) - slope, offset, fitted_curve = fit_sawtooth(data, min_val, max_val) + slope, offset, fitted_curve = fit_sawtooth(data, min_val, max_val, sigma=20) + save_log(np.concatenate([data, np.array([fitted_curve]).transpose()], 1)) test_assert_eq(slope, (max_val - min_val) / period, accuracy=0.005) test_curve_fit(data, fitted_curve, max_mean_err = full_range * 0.02, inlier_range = full_range * 0.05, max_outliers = len(data[:,0]) * 0.02) diff --git a/tools/odrive/tests/pwm_input_test.py b/tools/odrive/tests/pwm_input_test.py index 0adc9243..a1945997 100644 --- a/tools/odrive/tests/pwm_input_test.py +++ b/tools/odrive/tests/pwm_input_test.py @@ -81,7 +81,7 @@ class TestPwmInput(): full_scale = max_val - min_val slope, offset, fitted_curve = fit_sawtooth(data, min_val, max_val) test_assert_eq(slope, full_scale / 1.0, accuracy=0.001) - test_curve_fit(data, fitted_curve, max_mean_err = full_scale * 0.02, inlier_range = full_scale * 0.05, max_outliers = len(data[:,0]) * 0.01) + test_curve_fit(data, fitted_curve, max_mean_err = full_scale * 0.05, inlier_range = full_scale * 0.05, max_outliers = len(data[:,0]) * 0.01) From 0d45c3ff2266e5fec405aac4c891e65956227f32 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 25 May 2020 16:25:24 -0700 Subject: [PATCH 422/549] Fix default data_rate in liveplotter from 10 to 100 --- tools/odrive/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index f5ce0c7f..24a355a2 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -60,7 +60,7 @@ def dump_errors(odrv, clear=False): else: print(prefix + _VT100Colors['green'] + "no error" + _VT100Colors['default']) -data_rate = 10 +data_rate = 100 plot_rate = 10 num_samples = 1000 def start_liveplotter(get_var_callback): From 41b3a0ec5205b7f8b04e70db10e55601e7ad54ee Mon Sep 17 00:00:00 2001 From: Kyle Bartholomew Date: Mon, 25 May 2020 18:36:20 -0700 Subject: [PATCH 423/549] New functions for plotting, and fixed typo of data_rate --- tools/odrive/utils.py | 94 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 7 deletions(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index f5ce0c7f..8f8acb81 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -60,7 +60,7 @@ def dump_errors(odrv, clear=False): else: print(prefix + _VT100Colors['green'] + "no error" + _VT100Colors['default']) -data_rate = 10 +data_rate = 100 plot_rate = 10 num_samples = 1000 def start_liveplotter(get_var_callback): @@ -115,14 +115,94 @@ def start_liveplotter(get_var_callback): fetch_t.daemon = True fetch_t.start() - plot_t = threading.Thread(target=plot_data) - plot_t.daemon = True - plot_t.start() - + #plot_t = threading.Thread(target=plot_data) + #plot_t.daemon = True + #plot_t.start() + plot_data() return cancellation_token; #plot_data() +def start_bulk_capture(get_var_callback, + sleep_time=1.0/1000.0, + samples=2000): + ''' + Synchronous function to capture data and return as a pandas Dataframe + ''' + import pandas as pd + vals = [] + start_time = time.monotonic() + last_time = 0 + too_slow_counter = 0 + #total_samples = length_seconds * data_rate + for i in range(samples): + try: + data = get_var_callback() + except Exception as ex: + print(str(ex)) + print("Waiting 1 second before next data point") + time.sleep(1) + continue + relative_time = time.monotonic() - start_time + vals.append([relative_time] + data) + time.sleep(sleep_time) + # delta_t = (relative_time - last_time) + # period = 1.0 / data_rate + # if delta_t < period: + # time.sleep(period - delta_t) + # elif delta_t > period: + # too_slow_counter += 1 + # last_time = relative_time + # if too_slow_counter > 0: + # print("Slower than requested data rate for {} samples out of {} total samples" + # .format(too_slow_counter, total_samples)) + return pd.DataFrame(vals) + +def start_bulk_capture2(get_var_callback, + data_rate=1000.0, + length_seconds=2): + ''' + Synchronous function to capture data and return as a pandas Dataframe + ''' + import pandas as pd + vals = [] + start_time = time.monotonic() + last_time = 0 + too_slow_counter = 0 + total_samples = int(length_seconds * data_rate) + for i in range(total_samples): + try: + data = get_var_callback() + except Exception as ex: + print(str(ex)) + print("Waiting 1 second before next data point") + time.sleep(1) + continue + relative_time = time.monotonic() - start_time + vals.append([relative_time] + data) + + delta_t = (relative_time - last_time) + period = 1.0 / data_rate + if delta_t < period: + time.sleep(period - delta_t) + elif delta_t > period: + too_slow_counter += 1 + last_time = relative_time + if too_slow_counter > 0: + print("Slower than requested data rate for {} samples out of {} total samples" + .format(too_slow_counter, total_samples)) + return pd.DataFrame(vals) + +def capture_and_plot(get_var_callback, + sleep_time=1.0/1000.0, + samples=2000): + import matplotlib.pyplot as plt + data = start_bulk_capture(get_var_callback, + sleep_time, + samples) + plt.plot(data[0], data.drop(0, axis=1)) + plt.show() + def print_drv_regs(name, motor): """ Dumps the current gate driver regisers for the specified motor @@ -157,14 +237,14 @@ def rate_test(device): # import matplotlib.pyplot as plt # plt.ion() - print("reading 10000 values...") + print("reading 10000 values... new value") numFrames = 10000 vals = [] for _ in range(numFrames): vals.append(device.axis0.loop_counter) loopsPerFrame = (vals[-1] - vals[0])/numFrames - loopsPerSec = (168000000/(2*10192)) + loopsPerSec = (168000000/(6*3500)) FramePerSec = loopsPerSec/loopsPerFrame print("Frames per second: " + str(FramePerSec)) From 392781c33d8368eaf7a8e8e4f90312aabf688290 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 26 May 2020 12:13:16 +0200 Subject: [PATCH 424/549] fix return value of ODriveArduino::run_state() Previously, the function would return false in most cases even on success. --- Arduino/ODriveArduino/ODriveArduino.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Arduino/ODriveArduino/ODriveArduino.cpp b/Arduino/ODriveArduino/ODriveArduino.cpp index 3f4eab5a..465ba582 100644 --- a/Arduino/ODriveArduino/ODriveArduino.cpp +++ b/Arduino/ODriveArduino/ODriveArduino.cpp @@ -66,7 +66,7 @@ bool ODriveArduino::run_state(int axis, int requested_state, bool wait) { do { delay(100); serial_ << "r axis" << axis << ".current_state\n"; - } while (readInt() != AXIS_STATE_IDLE && --timeout_ctr > 0); + } while (readInt() != requested_state && --timeout_ctr > 0); } return timeout_ctr > 0; From ef89d4cfb0b3617917c47c09f63d9eee1698ea89 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 26 May 2020 14:34:22 +0200 Subject: [PATCH 425/549] clarify note on ~/.local/bin (it's a Debian bug) --- docs/getting-started.md | 42 ++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 05fd2f94..5a5d8ff3 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -9,16 +9,36 @@ permalink: / ### Table of contents -- [Hardware Requirements](#hardware-requirements) -- [Wiring up the ODrive](#wiring-up-the-odrive) -- [Downloading and Installing Tools](#downloading-and-installing-tools) -- [Firmware](#firmware) -- [Start `odrivetool`](#start-odrivetool) -- [Configure M0](#configure-m0) -- [Position control of M0](#position-control-of-m0) -- [Other control modes](#other-control-modes) -- [Watchdog Timer](#watchdog-timer) -- [What's next?](#whats-next) +- [Getting Started](#getting-started) + - [Table of contents](#table-of-contents) + - [Hardware Requirements](#hardware-requirements) + - [You will need:](#you-will-need) + - [Wiring up the ODrive](#wiring-up-the-odrive) + - [Wiring up the motors](#wiring-up-the-motors) + - [Wiring up the encoders](#wiring-up-the-encoders) + - [Safety & Power UP](#safety--power-up) + - [Downloading and Installing Tools](#downloading-and-installing-tools) + - [Windows](#windows) + - [OSX](#osx) + - [Linux](#linux) + - [Firmware](#firmware) + - [Start `odrivetool`](#start-odrivetool) + - [Configure M0](#configure-m0) + - [1. Set the limits:](#1-set-the-limits) + - [2. Set other hardware parameters](#2-set-other-hardware-parameters) + - [3. Save configuration](#3-save-configuration) + - [Position control of M0](#position-control-of-m0) + - [Other control modes](#other-control-modes) + - [Filtered position control](#filtered-position-control) + - [Trajectory control](#trajectory-control) + - [Parameters](#parameters) + - [Usage](#usage) + - [Circular position control](#circular-position-control) + - [Velocity control](#velocity-control) + - [Ramped velocity control](#ramped-velocity-control) + - [Current control](#current-control) + - [Watchdog Timer](#watchdog-timer) + - [What's next?](#whats-next) @@ -133,7 +153,7 @@ Try step 5 again sudo udevadm control --reload-rules sudo udevadm trigger ``` -3. (needed on Ubuntu, maybe other distros too) Add odrivetool into the path, by adding `~/.local/bin/` into `~/.bash_profile`, for example by running `nano ~/.bashrc`, scrolling to the bottom, pasting `PATH=$PATH:~/.local/bin/`, and then saving and closing, and close and reopen the terminal window. +3. **Ubuntu**, **Raspbian**: If you can't invoke `odrivetool` at this point, try adding `~/.local/bin` to your `$PATH` ([see related bug](https://unix.stackexchange.com/a/392710/176715)). This is done for example by running `nano ~/.bashrc`, scrolling to the bottom, pasting `PATH=$PATH:~/.local/bin`, and then saving and closing, and close and reopen the terminal window. ## Firmware **ODrive v3.5 and later**
From 128b7d7cb748eeaa0e49399db72707b8dbda965a Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 26 May 2020 17:10:04 +0200 Subject: [PATCH 426/549] revert erroneous table-of-contents update --- docs/getting-started.md | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 5a5d8ff3..07b298d8 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -9,36 +9,16 @@ permalink: / ### Table of contents -- [Getting Started](#getting-started) - - [Table of contents](#table-of-contents) - - [Hardware Requirements](#hardware-requirements) - - [You will need:](#you-will-need) - - [Wiring up the ODrive](#wiring-up-the-odrive) - - [Wiring up the motors](#wiring-up-the-motors) - - [Wiring up the encoders](#wiring-up-the-encoders) - - [Safety & Power UP](#safety--power-up) - - [Downloading and Installing Tools](#downloading-and-installing-tools) - - [Windows](#windows) - - [OSX](#osx) - - [Linux](#linux) - - [Firmware](#firmware) - - [Start `odrivetool`](#start-odrivetool) - - [Configure M0](#configure-m0) - - [1. Set the limits:](#1-set-the-limits) - - [2. Set other hardware parameters](#2-set-other-hardware-parameters) - - [3. Save configuration](#3-save-configuration) - - [Position control of M0](#position-control-of-m0) - - [Other control modes](#other-control-modes) - - [Filtered position control](#filtered-position-control) - - [Trajectory control](#trajectory-control) - - [Parameters](#parameters) - - [Usage](#usage) - - [Circular position control](#circular-position-control) - - [Velocity control](#velocity-control) - - [Ramped velocity control](#ramped-velocity-control) - - [Current control](#current-control) - - [Watchdog Timer](#watchdog-timer) - - [What's next?](#whats-next) +- [Hardware Requirements](#hardware-requirements) +- [Wiring up the ODrive](#wiring-up-the-odrive) +- [Downloading and Installing Tools](#downloading-and-installing-tools) +- [Firmware](#firmware) +- [Start `odrivetool`](#start-odrivetool) +- [Configure M0](#configure-m0) +- [Position control of M0](#position-control-of-m0) +- [Other control modes](#other-control-modes) +- [Watchdog Timer](#watchdog-timer) +- [What's next?](#whats-next) From 81f8dd2fe94099c5adc6b234e2642c119e3aad9d Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 26 May 2020 20:03:24 +0200 Subject: [PATCH 427/549] Show udev hint when USB access is denied --- Firmware/fibre/python/fibre/usbbulk_transport.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Firmware/fibre/python/fibre/usbbulk_transport.py b/Firmware/fibre/python/fibre/usbbulk_transport.py index f8a8905a..6643c8b2 100644 --- a/Firmware/fibre/python/fibre/usbbulk_transport.py +++ b/Firmware/fibre/python/fibre/usbbulk_transport.py @@ -200,14 +200,15 @@ def discover_channels(path, serial_number, callback, cancellation_token, channel channel.usb_device = usb_device # for debugging only except usb.core.USBError as ex: if ex.errno == 13: - logger.debug("USB device access denied. Did you set up your udev rules correctly?") - continue + # TODO: this is an ODrive specific message and should live outside of the fibre library + logger.warn("I found a USB device that looks like an ODrive (bus {}, device {}) but I can't access it. Try running `sudo odrivetool udev-setup`, then unplug and replug the device.".format(usb_device.bus, usb_device.address)) + known_devices.append((usb_device.bus, usb_device.address)) elif ex.errno == 16: logger.debug("USB device busy. I'll reset it and try again.") usb_device.reset() continue else: - logger.debug("USB device init failed. Ignoring this device. More info: " + traceback.format_exc()) + logger.warn("USB device init failed (bus {}, device {}). Ignoring this device. More info: ".format(usb_device.bus, usb_device.address) + traceback.format_exc()) known_devices.append((usb_device.bus, usb_device.address)) else: known_devices.append((usb_device.bus, usb_device.address)) From 2b38ce75133847d5ff58e2b42a48eef050b6c99d Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 26 May 2020 20:12:42 +0200 Subject: [PATCH 428/549] fix python2 compatibility String literal interpolation is not supported by Python < 3.6 --- Firmware/fibre/python/fibre/discovery.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Firmware/fibre/python/fibre/discovery.py b/Firmware/fibre/python/fibre/discovery.py index 4407537b..bd4ce071 100644 --- a/Firmware/fibre/python/fibre/discovery.py +++ b/Firmware/fibre/python/fibre/discovery.py @@ -90,7 +90,7 @@ def find_all(path, serial_number, fp.seek(0) json_data = json.load(fp) except: - logger.debug(f"Failed load JSON cache file {cache_path}") + logger.debug("Failed load JSON cache file {}".format(cache_path)) # Fallback to loading JSON from device if json_data is None: @@ -108,11 +108,11 @@ def find_all(path, serial_number, # Save JSON to cache if not cache_path is None: - logger.debug(f"Creating new JSON cache file {cache_path}") + logger.debug("Creating new JSON cache file {}".format(cache_path)) os.makedirs(cache_dir, exist_ok=True) with open(cache_path, 'w+') as json_cache: json_cache.write(json_string) - logger.debug(f"Saved JSON to cache file {cache_path}") + logger.debug("Saved JSON to cache file {}".format(cache_path)) channel._interface_definition_crc = json_crc16 From 9f9f2ea91475437716c871097d0b4a3b7aa57ad2 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 26 May 2020 20:53:14 +0200 Subject: [PATCH 429/549] Fix installation and dependencies of odrivetool Previously the install would fail if monotonic and appdirs were not already installed before installing odrivetool. --- tools/odrive/version.py | 6 +++--- tools/setup.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/odrive/version.py b/tools/odrive/version.py index a96e9b4e..3b88a255 100644 --- a/tools/odrive/version.py +++ b/tools/odrive/version.py @@ -82,12 +82,12 @@ if __name__ == '__main__': def setup_udev_rules(logger): if platform.system() != 'Linux': - logger.error("This command only makes sense on Linux") + if logger: logger.error("This command only makes sense on Linux") return if os.getuid() != 0: - logger.warn("you should run this as root, otherwise it will probably not work") + if logger: logger.warn("you should run this as root, otherwise it will probably not work") with open('/etc/udev/rules.d/91-odrive.rules', 'w') as file: file.write('SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="0d3[0-9]", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1"\n') subprocess.check_call(["udevadm", "control", "--reload-rules"]) subprocess.check_call(["udevadm", "trigger"]) - logger.info('udev rules configured successfully') + if logger: logger.info('udev rules configured successfully') diff --git a/tools/setup.py b/tools/setup.py index 118f0ae5..83d5c1ed 100644 --- a/tools/setup.py +++ b/tools/setup.py @@ -91,9 +91,8 @@ if creating_package: if not creating_package: import platform if platform.system() == 'Linux': - from fibre.utils import Logger try: - odrive.version.setup_udev_rules(Logger()) + odrive.version.setup_udev_rules(None) except Exception: print("Warning: could not set up udev rules. Run `sudo odrivetool udev-setup` to try again.") @@ -117,6 +116,7 @@ try: 'IntelHex', # Used to by DFU to download firmware from github 'matplotlib', # Required to run the liveplotter 'monotonic', # For compatibility with older python versions + 'appdirs', # Used to find caching directory 'pywin32 >= 222; platform_system == "Windows"' # Required for fancy terminal features on Windows ], package_data={'': ['version.txt']}, From 224a2d91de170c553a490e65e379f6e5dcf2cea4 Mon Sep 17 00:00:00 2001 From: camrbuss Date: Wed, 27 May 2020 19:39:48 -0600 Subject: [PATCH 430/549] Endstop configuration example offset --- docs/endstops.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/endstops.md b/docs/endstops.md index 6826e020..6379fb0b 100644 --- a/docs/endstops.md +++ b/docs/endstops.md @@ -52,7 +52,7 @@ The debouncing time for this endstop. Most switches exhibit some sort of bounce ### is_active_high This is how you configure the endstop to be either "NPN" or "PNP". An "NPN" configuration would be `is_active_high = False` whereas a PNP configuration is `is_active_high = True`. Refer to the following table for more information: -3D printer endstops (like those that come with a RAMPS 1.4) are typically configuration **4**. Typically configuration **1** or **3** is preferred when using mechanical switches as the most common failure mode leaves the switch open. +Typically configuration **1** or **3** is preferred when using mechanical switches as the most common failure mode leaves the switch open. ### pullup Match the pullup value to the configuration. If `true`, it enables the GPIO pullup resistor. If `false`, it enables the GPIO pull*down* resistor. @@ -62,11 +62,12 @@ Match the pullup value to the configuration. If `true`, it enables the GPIO pul ### Example -If we want to configure a 3D printer-style minimum endstop for homing on GPIO 5 and we want our motor to move away from the endstop about a quarter turn with a 8192 cpr encoder, we would set: +If we want to configure a 3D printer-style (configuration 4) minimum endstop for homing on GPIO 5 and we want our motor to move away from the endstop about a quarter turn with a 8192 cpr encoder, we would set: ``` ..min_endstop.config.gpio_num = 5 ..min_endstop.config.is_active_high = False +..min_endstop.config.offset = -1.0*(8912/4) ..min_endstop.config.enabled = True ``` From 201604c510ca2ae23cd45085437cf3f960428c17 Mon Sep 17 00:00:00 2001 From: camrbuss Date: Thu, 28 May 2020 18:53:57 -0600 Subject: [PATCH 431/549] protocol documentation tweaks --- docs/ascii-protocol.md | 4 ++-- docs/can-protocol.md | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/ascii-protocol.md b/docs/ascii-protocol.md index b82863e0..93ba8a02 100644 --- a/docs/ascii-protocol.md +++ b/docs/ascii-protocol.md @@ -20,7 +20,7 @@ The ASCII protocol is human-readable and line-oriented, with each line having th command *42 ; comment [new line character] ``` - * `*42` stands for a GCode compatible checksum and can be omitted. If and only if a checksum is provided, the device will also include a checksum in the response, if any. The checksum is calculated as the bitwise xor of all characters before the asterisk (`*`).
Example of a valid checksum: `r vbus_voltage *93`. + * `*42` stands for a GCode compatible checksum and can be omitted. If and only if a checksum is provided, the device will also include a checksum in the response, if any. If the checksum is provided but is not valid, the line is ignored. The checksum is calculated as the bitwise xor of all characters before the asterisk (`*`).
Example of a valid checksum: `r vbus_voltage *93`. * comments are supported for GCode compatibility * the command is interpreted once the new-line character is encountered @@ -126,7 +126,7 @@ Not all parameters can be accessed via the ASCII protocol but at least all param ``` * `property` name of the property, as seen in ODrive Tool * response: text representation of the requested value - * Example: `r vbus_voltage` => response: `24.087744` + * Example: `r vbus_voltage` => response: `24.087744` <new line> * Writing: ``` w [property] [value] diff --git a/docs/can-protocol.md b/docs/can-protocol.md index b4522bb3..3360b4ce 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -1,7 +1,7 @@ # CAN Protocol ## Hardware Setup -ODrive assumes the CAN PHY is a standard differential twisted pair in a linear bus configuration with 120 ohm termination resistance at each end. ODrive uses 3.3v as the high output, but conforms to the CAN PHY requirement of achieving a differential voltage > 1.5V to represent a "0". As such, it is compatible with standard 5V bus architectures. +ODrive assumes the CAN PHY is a standard differential twisted pair in a linear bus configuration with 120 ohm termination resistance at each end. ODrive versions less than V3.5 include a soldered 120 ohm termination resistor, but ODrive versions greater than V3.5 implement a dip switch to toggle the termination. ODrive uses 3.3v as the high output, but conforms to the CAN PHY requirement of achieving a differential voltage > 1.5V to represent a "0". As such, it is compatible with standard 5V bus architectures. ODrive currently supports the following CAN baud rates: * 125 kbps @@ -32,6 +32,7 @@ Receive PDO 0x200 + nodeID = 0x223, which does not conflict with the range [0x20 Be careful that you don't assign too many nodeIDs per PDO group. Four CAN Simple nodes (32*4) is all of the available address space of a single PDO. If the bus is strictly ODrive CAN Simple nodes, a simple sequential Node ID assignment will work fine. ### Messages + CMD ID | Name | Sender | Signals | Start byte | Signal Type | Bits | Factor | Offset | Byte Order --: | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- 0x000 | CANOpen NMT Message\*\* | Master | - | - | - | - | - | - | - @@ -40,7 +41,7 @@ CMD ID | Name | Sender | Signals | Start byte | Signal Type | Bits | Factor | Of 0x003 | Get Motor Error\* | Axis | Motor Error | 0 | Unsigned Int | 32 | 1 | 0 | Intel 0x004 | Get Encoder Error\* | Axis | Encoder Error | 0 | Unsigned Int | 32 | 1 | 0 | Intel 0x005 | Get Sensorless Error\* | Axis | Sensorless Error | 0 | Unsigned Int | 32 | 1 | 0 | Intel -0x006 | Set Axis Node ID | Master | Axis CAN Node ID | 0 | Unsigned Int | 16 | 1 | 0 | Intel +0x006 | Set Axis Node ID | Master | Axis CAN Node ID | 0 | Unsigned Int | 32 | 1 | 0 | Intel 0x007 | Set Axis Requested State | Master | Axis Requested State | 0 | Unsigned Int | 32 | 1 | 0 | Intel 0x008 | Set Axis Startup Config | Master | - Not yet implemented - | - | - | - | - | - | - 0x009 | Get Encoder Estimates\* | Master | Encoder Pos Estimate
Encoder Vel Estimate | 0
4 | IEEE 754 Float
IEEE 754 Float | 32
32 | 1
1 | 0
0 | Intel
Intel From 6acf7f1bbde2248555632937f6c6730c8844fd39 Mon Sep 17 00:00:00 2001 From: camrbuss Date: Thu, 28 May 2020 18:56:16 -0600 Subject: [PATCH 432/549] protocol documentation tweaks --- docs/ascii-protocol.md | 4 ++-- docs/can-protocol.md | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/ascii-protocol.md b/docs/ascii-protocol.md index b82863e0..93ba8a02 100644 --- a/docs/ascii-protocol.md +++ b/docs/ascii-protocol.md @@ -20,7 +20,7 @@ The ASCII protocol is human-readable and line-oriented, with each line having th command *42 ; comment [new line character] ``` - * `*42` stands for a GCode compatible checksum and can be omitted. If and only if a checksum is provided, the device will also include a checksum in the response, if any. The checksum is calculated as the bitwise xor of all characters before the asterisk (`*`).
Example of a valid checksum: `r vbus_voltage *93`. + * `*42` stands for a GCode compatible checksum and can be omitted. If and only if a checksum is provided, the device will also include a checksum in the response, if any. If the checksum is provided but is not valid, the line is ignored. The checksum is calculated as the bitwise xor of all characters before the asterisk (`*`).
Example of a valid checksum: `r vbus_voltage *93`. * comments are supported for GCode compatibility * the command is interpreted once the new-line character is encountered @@ -126,7 +126,7 @@ Not all parameters can be accessed via the ASCII protocol but at least all param ``` * `property` name of the property, as seen in ODrive Tool * response: text representation of the requested value - * Example: `r vbus_voltage` => response: `24.087744` + * Example: `r vbus_voltage` => response: `24.087744` <new line> * Writing: ``` w [property] [value] diff --git a/docs/can-protocol.md b/docs/can-protocol.md index b4522bb3..e0f4c75e 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -1,7 +1,7 @@ # CAN Protocol ## Hardware Setup -ODrive assumes the CAN PHY is a standard differential twisted pair in a linear bus configuration with 120 ohm termination resistance at each end. ODrive uses 3.3v as the high output, but conforms to the CAN PHY requirement of achieving a differential voltage > 1.5V to represent a "0". As such, it is compatible with standard 5V bus architectures. +ODrive assumes the CAN PHY is a standard differential twisted pair in a linear bus configuration with 120 ohm termination resistance at each end. ODrive versions less than V3.5 include a soldered 120 ohm termination resistor, but ODrive versions V3.5 and greater implement a dip switch to toggle the termination. ODrive uses 3.3v as the high output, but conforms to the CAN PHY requirement of achieving a differential voltage > 1.5V to represent a "0". As such, it is compatible with standard 5V bus architectures. ODrive currently supports the following CAN baud rates: * 125 kbps @@ -32,6 +32,7 @@ Receive PDO 0x200 + nodeID = 0x223, which does not conflict with the range [0x20 Be careful that you don't assign too many nodeIDs per PDO group. Four CAN Simple nodes (32*4) is all of the available address space of a single PDO. If the bus is strictly ODrive CAN Simple nodes, a simple sequential Node ID assignment will work fine. ### Messages + CMD ID | Name | Sender | Signals | Start byte | Signal Type | Bits | Factor | Offset | Byte Order --: | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- 0x000 | CANOpen NMT Message\*\* | Master | - | - | - | - | - | - | - @@ -40,7 +41,7 @@ CMD ID | Name | Sender | Signals | Start byte | Signal Type | Bits | Factor | Of 0x003 | Get Motor Error\* | Axis | Motor Error | 0 | Unsigned Int | 32 | 1 | 0 | Intel 0x004 | Get Encoder Error\* | Axis | Encoder Error | 0 | Unsigned Int | 32 | 1 | 0 | Intel 0x005 | Get Sensorless Error\* | Axis | Sensorless Error | 0 | Unsigned Int | 32 | 1 | 0 | Intel -0x006 | Set Axis Node ID | Master | Axis CAN Node ID | 0 | Unsigned Int | 16 | 1 | 0 | Intel +0x006 | Set Axis Node ID | Master | Axis CAN Node ID | 0 | Unsigned Int | 32 | 1 | 0 | Intel 0x007 | Set Axis Requested State | Master | Axis Requested State | 0 | Unsigned Int | 32 | 1 | 0 | Intel 0x008 | Set Axis Startup Config | Master | - Not yet implemented - | - | - | - | - | - | - 0x009 | Get Encoder Estimates\* | Master | Encoder Pos Estimate
Encoder Vel Estimate | 0
4 | IEEE 754 Float
IEEE 754 Float | 32
32 | 1
1 | 0
0 | Intel
Intel From b4213a83e324a1e8e401ba9131bb36237cad7b5e Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 29 May 2020 09:54:42 +0200 Subject: [PATCH 433/549] upgrade interface_generator.py for doc autogen --- Firmware/interface_generator.py | 124 +++++++++++++++++++++++++------- Firmware/odrive-interface.yaml | 6 +- 2 files changed, 101 insertions(+), 29 deletions(-) diff --git a/Firmware/interface_generator.py b/Firmware/interface_generator.py index 1acfc747..e83c91d8 100644 --- a/Firmware/interface_generator.py +++ b/Firmware/interface_generator.py @@ -16,6 +16,8 @@ definitions: properties: c_is_class: {type: boolean} c_name: {type: string} + brief: {type: string} + doc: {type: string} functions: type: object additionalProperties: {"$ref": "#/definitions/function"} @@ -64,6 +66,7 @@ definitions: properties: in: {type: object} out: {type: object} + brief: {type: string} doc: {type: string} __line__: {type: object} __column__: {type: object} @@ -132,17 +135,17 @@ def to_snake_case(s): return '_'.join(get_words(s)).lower() def to_kebab_case(s): return '-'.join(get_words(s)).lower() value_types = { - 'bool': {'builtin': True, 'fullname': 'bool', 'name': 'bool', 'c_name': 'bool'}, - 'float32': {'builtin': True, 'fullname': 'float32', 'name': 'float32', 'c_name': 'float'}, - 'uint8': {'builtin': True, 'fullname': 'uint8', 'name': 'uint8', 'c_name': 'uint8_t'}, - 'uint16': {'builtin': True, 'fullname': 'uint16', 'name': 'uint16', 'c_name': 'uint16_t'}, - 'uint32': {'builtin': True, 'fullname': 'uint32', 'name': 'uint32', 'c_name': 'uint32_t'}, - 'uint64': {'builtin': True, 'fullname': 'uint64', 'name': 'uint64', 'c_name': 'uint64_t'}, - 'int8': {'builtin': True, 'fullname': 'int8', 'name': 'int8', 'c_name': 'int8_t'}, - 'int16': {'builtin': True, 'fullname': 'int16', 'name': 'int16', 'c_name': 'int16_t'}, - 'int32': {'builtin': True, 'fullname': 'int32', 'name': 'int32', 'c_name': 'int32_t'}, - 'int64': {'builtin': True, 'fullname': 'int64', 'name': 'int64', 'c_name': 'int64_t'}, - 'endpoint_ref': {'builtin': True, 'fullname': 'endpoint_ref', 'name': 'endpoint_ref', 'c_name': 'endpoint_ref_t'}, + 'bool': {'builtin': True, 'fullname': 'bool', 'name': 'bool', 'c_name': 'bool', 'py_type': 'bool'}, + 'float32': {'builtin': True, 'fullname': 'float32', 'name': 'float32', 'c_name': 'float', 'py_type': 'float'}, + 'uint8': {'builtin': True, 'fullname': 'uint8', 'name': 'uint8', 'c_name': 'uint8_t', 'py_type': 'int'}, + 'uint16': {'builtin': True, 'fullname': 'uint16', 'name': 'uint16', 'c_name': 'uint16_t', 'py_type': 'int'}, + 'uint32': {'builtin': True, 'fullname': 'uint32', 'name': 'uint32', 'c_name': 'uint32_t', 'py_type': 'int'}, + 'uint64': {'builtin': True, 'fullname': 'uint64', 'name': 'uint64', 'c_name': 'uint64_t', 'py_type': 'int'}, + 'int8': {'builtin': True, 'fullname': 'int8', 'name': 'int8', 'c_name': 'int8_t', 'py_type': 'int'}, + 'int16': {'builtin': True, 'fullname': 'int16', 'name': 'int16', 'c_name': 'int16_t', 'py_type': 'int'}, + 'int32': {'builtin': True, 'fullname': 'int32', 'name': 'int32', 'c_name': 'int32_t', 'py_type': 'int'}, + 'int64': {'builtin': True, 'fullname': 'int64', 'name': 'int64', 'c_name': 'int64_t', 'py_type': 'int'}, + 'endpoint_ref': {'builtin': True, 'fullname': 'endpoint_ref', 'name': 'endpoint_ref', 'c_name': 'endpoint_ref_t', 'py_type': '[not implemented]'}, } enums = {} @@ -234,7 +237,7 @@ def regularize_func(path, name, elem, prepend_args): for n, arg in get_dict(elem, 'out').items()} return elem -def regularize_attribute(path, name, elem, c_is_class): +def regularize_attribute(parent, name, elem, c_is_class): if elem is None: elem = {} if isinstance(elem, str): @@ -249,7 +252,8 @@ def regularize_attribute(path, name, elem, c_is_class): if 'nullflag' in elem: elem['type']['nullflag'] = elem.pop('nullflag') elem['name'] = name - elem['fullname'] = join_name(path, name) + elem['fullname'] = join_name(parent['fullname'], name) + elem['parent'] = parent elem['typeargs'] = elem.get('typeargs', {}) elem['c_name'] = elem.get('c_name', None) or (elem['name'] + ('_' if c_is_class else '')) if ('c_getter' in elem) or ('c_setter' in elem): @@ -263,11 +267,11 @@ def regularize_attribute(path, name, elem, c_is_class): if elem['typeargs']['fibre.Property.mode'] == 'readonly' and 'c_setter' in elem: elem.pop('c_setter') elif ('flags' in elem['type']) or ('values' in elem['type']): elem['typeargs']['fibre.Property.mode'] = elem['typeargs'].get('fibre.Property.mode', None) or 'readwrite' - elem['typeargs']['fibre.Property.type'] = regularize_valuetype(path, to_pascal_case(name), elem['type']) + elem['typeargs']['fibre.Property.type'] = regularize_valuetype(parent['fullname'], to_pascal_case(name), elem['type']) elem['type'] = 'fibre.Property' if elem['typeargs']['fibre.Property.mode'] == 'readonly' and 'c_setter' in elem: elem.pop('c_setter') else: - elem['type'] = regularize_interface(path, to_pascal_case(name), elem['type']) + elem['type'] = regularize_interface(parent['fullname'], to_pascal_case(name), elem['type']) return elem @@ -288,7 +292,7 @@ def regularize_interface(path, name, elem): if not 'c_is_class' in elem: raise Exception(elem) treat_as_class = elem['c_is_class'] # TODO: add command line arg to make this selectively optional - elem['attributes'] = {name: regularize_attribute(path, name, prop, treat_as_class) + elem['attributes'] = {name: regularize_attribute(elem, name, prop, treat_as_class) for name, prop in get_dict(elem, 'attributes').items()} elem['interfaces'] = [] elem['enums'] = [] @@ -308,6 +312,7 @@ def regularize_valuetype(path, name, elem): bit = 0 for k, v in elem['flags'].items(): elem['flags'][k] = elem['flags'][k] or {} + elem['flags'][k]['name'] = k current_bit = elem['flags'][k].get('bit', bit) elem['flags'][k]['bit'] = current_bit elem['flags'][k]['value'] = 0 if current_bit is None else (1 << current_bit) @@ -323,6 +328,7 @@ def regularize_valuetype(path, name, elem): val = 0 for k, v in elem['values'].items(): elem['values'][k] = elem['values'][k] or {} + elem['values'][k]['name'] = k val = elem['values'][k].get('value', val) elem['values'][k]['value'] = val val += 1 @@ -477,8 +483,11 @@ parser.add_argument("-d", "--definitions", type=argparse.FileType('r'), nargs='+ help="the YAML interface definition file(s) used to generate the code") parser.add_argument("-t", "--template", type=argparse.FileType('r'), help="the code template") -parser.add_argument("-o", "--output", type=argparse.FileType('w'), default='-', +group = parser.add_mutually_exclusive_group(required=True) +group.add_argument("-o", "--output", type=argparse.FileType('w'), help="path of the generated output") +group.add_argument("--outputs", type=str, + help="path pattern for the generated outputs. One output is generated for each interface. Use # as placeholder for the interface name.") parser.add_argument("--generate-endpoints", type=str, nargs='?', help="if specified, an endpoint table will be generated and passed to the template for the specified interface") args = parser.parse_args() @@ -490,7 +499,6 @@ if args.version: definition_files = args.definitions template_file = args.template -output_file = args.output # Load definition files @@ -525,6 +533,11 @@ if args.verbose: print('Known interfaces: ' + ''.join([('\n ' + k) for k in interfaces.keys()])) print('Known value types: ' + ''.join([('\n ' + k) for k in value_types.keys()])) +clashing_names = list(set(value_types.keys()).intersection(set(interfaces.keys()))) +if len(clashing_names): + print(f"**Error**: Found both an interface and a value type with the name {clashing_names[0]}. This is not allowed, interfaces and value types (such as enums) share the same namespace.", file=sys.stderr) + sys.exit(1) + # Resolve all types into references for _, item in list(interfaces.items()): for _, prop in item['attributes'].items(): @@ -575,6 +588,46 @@ env = jinja2.Environment( variable_start_string='[[', variable_end_string=']]' ) +def tokenize(text, interface, interface_transform, value_type_transform, attribute_transform): + """ + Looks for referencable tokens (interface names, value type names or + attribute names) in a documentation text and runs them through the provided + processing functions. + Tokens are detected by enclosing back-ticks (`). + + interface: The interface type object that defines the scope in which the + tokens should be detected. + interface_transform: A function that takes an interface object as an argument + and returns a string. + value_type_transform: A function that takes a value type object as an argument + and returns a string. + attribute_transform: A function that takes the token strin and an attribute + object as arguments and returns a string. + """ + if text is None or isinstance(text, jinja2.runtime.Undefined): + return text + + def token_transform(token): + token = token.groups()[0] + token_list = split_name(token) + + # Check if this is an attribute reference + attr_intf = interface + for name in token_list: + if not name in attr_intf['attributes']: + attr = None + break + attr = attr_intf['attributes'][name] + attr_intf = attr['type'] + + if not attr is None: + return attribute_transform(token, attr) + + print(f'Warning: cannot resolve "{token}" in ' + interface['fullname']) + return "`" + token + "`" + + return re.sub(r'`([A-Za-z\._]+)`', token_transform, text) + env.filters['to_pascal_case'] = to_pascal_case env.filters['to_camel_case'] = to_camel_case env.filters['to_macro_case'] = to_macro_case @@ -583,15 +636,34 @@ env.filters['to_kebab_case'] = to_kebab_case env.filters['first'] = lambda x: next(iter(x)) env.filters['skip_first'] = lambda x: list(x)[1:] env.filters['to_c_string'] = lambda x: '\n'.join(('"' + line.replace('"', '\\"') + '"') for line in json.dumps(x, separators=(',', ':')).replace('{"name"', '\n{"name"').split('\n')) +env.filters['tokenize'] = tokenize template = env.from_string(template_file.read()) -output = template.render( - interfaces = interfaces, - value_types = value_types, - toplevel_interfaces = toplevel_interfaces, - endpoints = endpoints, - embedded_endpoint_definitions = embedded_endpoint_definitions -) +template_args = { + 'interfaces': interfaces, + 'value_types': value_types, + 'toplevel_interfaces': toplevel_interfaces, + 'endpoints': endpoints, + 'embedded_endpoint_definitions': embedded_endpoint_definitions +} -output_file.write(output) +if not args.output is None: + output = template.render(**template_args) + args.output.write(output) +else: + assert('#' in args.outputs) + + for k, intf in interfaces.items(): + if split_name(k)[0] == 'fibre': + continue # TODO: remove special case + output = template.render(interface = intf, **template_args) + with open(args.outputs.replace('#', k.lower()), 'w') as output_file: + output_file.write(output) + + for k, enum in value_types.items(): + if enum.get('builtin', False) or not enum.get('is_enum', False): + continue + output = template.render(enum = enum, **template_args) + with open(args.outputs.replace('#', k.lower()), 'w') as output_file: + output_file.write(output) diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 93158017..8d319fe8 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -272,8 +272,8 @@ interfaces: ramp_distance: float32 accel: float32 vel: float32 - sensorless_ramp: LockinState - general_lockin: LockinState + sensorless_ramp: LockinConfig + general_lockin: LockinConfig can_node_id: type: uint32 doc: Both axes will have the same id to start @@ -292,7 +292,7 @@ interfaces: clear_errors: doc: Check the watchdog timer for expiration. Also sets the watchdog error bit if expired. - Axis.LockinState: + Axis.LockinConfig: c_is_class: False attributes: current: From 6902fd6b34ce0137b48dcf3ba26057c13163c875 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 29 May 2020 10:19:10 +0200 Subject: [PATCH 434/549] rename Odrive interface to ODrive --- Firmware/MotorControl/odrive_main.h | 2 +- Firmware/Tupfile.lua | 2 +- Firmware/communication/ascii_protocol.cpp | 2 +- Firmware/communication/interface_can.hpp | 2 +- Firmware/interface_generator.py | 9 ++++++--- Firmware/odrive-interface.yaml | 8 +++++--- tools/odrive/enums.py | 4 ++-- 7 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 20b1124a..62bd4f57 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -214,7 +214,7 @@ enum TimingLog_t { // general system functions defined in main.cpp -class ODrive : public OdriveIntf { +class ODrive : public ODriveIntf { public: void save_configuration() override; void erase_configuration() override; diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 2d4b7341..5f8843e9 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -3,7 +3,7 @@ tup.include('build.lua') tup.frule{inputs={'fibre/cpp/interfaces_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/interfaces.hpp'} tup.frule{inputs={'fibre/cpp/function_stubs_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/function_stubs.hpp'} -tup.frule{inputs={'fibre/cpp/endpoints_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --generate-endpoints Odrive --template %f --output %o', outputs='autogen/endpoints.hpp'} +tup.frule{inputs={'fibre/cpp/endpoints_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --generate-endpoints ODrive --template %f --output %o', outputs='autogen/endpoints.hpp'} tup.frule{inputs={'fibre/cpp/type_info_template.j2'}, command='python3 interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/type_info.hpp'} tup.frule{command='python3 interface_generator.py --definitions odrive-interface.yaml --template enums_template.j2 --output ../tools/odrive/enums.py'} diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 632ceea3..39f4d891 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -29,7 +29,7 @@ /* Private variables ---------------------------------------------------------*/ -static Introspectable root_obj = OdriveTypeInfo::make_introspectable(odrv); +static Introspectable root_obj = ODriveTypeInfo::make_introspectable(odrv); /* Private function prototypes -----------------------------------------------*/ /* Function implementations --------------------------------------------------*/ diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index b4864611..19855047 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -19,7 +19,7 @@ enum { CAN_BAUD_1M = 1000000 }; -class ODriveCAN : public OdriveIntf::CanIntf { +class ODriveCAN : public ODriveIntf::CanIntf { public: struct Config_t { uint32_t baud_rate = CAN_BAUD_250K; diff --git a/Firmware/interface_generator.py b/Firmware/interface_generator.py index e83c91d8..14184f1f 100644 --- a/Firmware/interface_generator.py +++ b/Firmware/interface_generator.py @@ -78,6 +78,7 @@ properties: ns: {type: string} version: {type: string} summary: {type: string} + dictionary: {type: array, items: {type: string}} interfaces: type: object additionalProperties: { "$ref": "#/definitions/interface" } @@ -105,13 +106,14 @@ class SafeLineLoader(yaml.SafeLoader): # #mapping['__column__'] = node.start_mark.column + 1 # return mapping - +dictionary = [] def get_words(string): """ Splits a string in PascalCase into a list of lower case words """ - return [w.lower() for w in re.findall('[a-z0-9]+|[A-Z][a-z0-9]*', string)] + regex = ''.join((re.escape(w) + '|') for w in dictionary) + '[a-z0-9]+|[A-Z][a-z0-9]*' + return [(w if w in dictionary else w.lower()) for w in re.findall(regex, string)] def join_name(*names, delimiter: str = '.'): """ @@ -128,7 +130,7 @@ def split_name(name, delimiter: str = '.'): yield c if (parenthesis_depth == 0) or (c != delimiter) else ':' return [part.replace(':', '.') for part in ''.join(replace_delimiter_in_parentheses()).split('.')] -def to_pascal_case(s): return ''.join([w.title() for w in get_words(s)]) +def to_pascal_case(s): return ''.join([(w.title() if not w in dictionary else w) for w in get_words(s)]) def to_camel_case(s): return ''.join([(c.lower() if i == 0 else c) for i, c in enumerate(''.join([w.title() for w in get_words(s)]))]) def to_macro_case(s): return '_'.join(get_words(s)).upper() def to_snake_case(s): return '_'.join(get_words(s)).lower() @@ -519,6 +521,7 @@ for definition_file in definition_files: raise Exception(err.message + '\nat ' + str(list(err.absolute_path))) interfaces = {**interfaces, **get_dict(file_content, 'interfaces')} value_types = {**value_types, **get_dict(file_content, 'valuetypes')} + dictionary += file_content.get('dictionary', None) or [] # Preprocess definitions diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 8d319fe8..36309b82 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -3,8 +3,10 @@ version: 0.0.1 ns: com.odriverobotics summary: ODrive Interface Definitions +dictionary: [ODrive] # Prevent the word 'ODrive' from being detected as two words 'O' and 'Drive' + interfaces: - Odrive: + ODrive: c_is_class: True attributes: vbus_voltage: readonly float32 @@ -162,7 +164,7 @@ interfaces: reboot: enter_dfu_mode: - Odrive.Can: + ODrive.Can: c_is_class: True attributes: error: @@ -625,7 +627,7 @@ interfaces: valuetypes: - Odrive.Can.Protocol: + ODrive.Can.Protocol: values: {Simple: } Axis.AxisState: # TODO: remove redundant "Axis" in name diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 55bc64e3..466f11b6 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -1,7 +1,7 @@ # TODO: This file is dangerous because the enums could potentially change between API versions. Should transmit as part of the JSON. -# Odrive.Can.Protocol +# ODrive.Can.Protocol PROTOCOL_SIMPLE = 0 # Axis.AxisState @@ -47,7 +47,7 @@ MOTOR_TYPE_HIGH_CURRENT = 0 MOTOR_TYPE_GIMBAL = 2 MOTOR_TYPE_ACIM = 3 -# Odrive.Can.Error +# ODrive.Can.Error CAN_ERROR_NONE = 0x00000000 CAN_ERROR_DUPLICATE_CAN_IDS = 0x00000001 From fb7cabef5b54c96deea5f479f9a8c09260a4816b Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 29 May 2020 10:24:36 +0200 Subject: [PATCH 435/549] make all other interfaces children of ODrive --- Firmware/MotorControl/axis.hpp | 2 +- Firmware/MotorControl/controller.hpp | 2 +- Firmware/MotorControl/encoder.hpp | 2 +- Firmware/MotorControl/motor.hpp | 2 +- .../MotorControl/sensorless_estimator.hpp | 2 +- Firmware/odrive-interface.yaml | 28 +++++++++---------- tools/odrive/enums.py | 26 ++++++++--------- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 598da2a8..480de844 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -5,7 +5,7 @@ #error "This file should not be included directly. Include odrive_main.h instead." #endif -class Axis : public AxisIntf { +class Axis : public ODriveIntf::AxisIntf { public: struct LockinConfig_t { float current = 10.0f; // [A] diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 66f2dbb1..be84d159 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -5,7 +5,7 @@ #error "This file should not be included directly. Include odrive_main.h instead." #endif -class Controller : public ControllerIntf { +class Controller : public ODriveIntf::ControllerIntf { public: typedef struct { uint32_t index = 0; diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 77f40068..cb97a45b 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -5,7 +5,7 @@ #error "This file should not be included directly. Include odrive_main.h instead." #endif -class Encoder : public EncoderIntf { +class Encoder : public ODriveIntf::EncoderIntf { public: const uint32_t MODE_FLAG_ABS = 0x100; diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index a7e87139..6e449111 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -7,7 +7,7 @@ #include "drv8301.h" -class Motor : public MotorIntf { +class Motor : public ODriveIntf::MotorIntf { public: struct Iph_BC_t { float phB; diff --git a/Firmware/MotorControl/sensorless_estimator.hpp b/Firmware/MotorControl/sensorless_estimator.hpp index 99c339be..95992ae0 100644 --- a/Firmware/MotorControl/sensorless_estimator.hpp +++ b/Firmware/MotorControl/sensorless_estimator.hpp @@ -1,7 +1,7 @@ #ifndef __SENSORLESS_ESTIMATOR_HPP #define __SENSORLESS_ESTIMATOR_HPP -class SensorlessEstimator : public SensorlessEstimatorIntf { +class SensorlessEstimator : public ODriveIntf::SensorlessEstimatorIntf { public: struct Config_t { float observer_gain = 1000.0f; // [rad/s] diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 36309b82..f17aca63 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -178,14 +178,14 @@ interfaces: functions: set_baud_rate: {in: {baudRate: uint32}} - Endpoint: + ODrive.Endpoint: c_is_class: False attributes: endpoint: endpoint_ref min: float32 max: float32 - Axis: + ODrive.Axis: c_is_class: True attributes: error: @@ -294,7 +294,7 @@ interfaces: clear_errors: doc: Check the watchdog timer for expiration. Also sets the watchdog error bit if expired. - Axis.LockinConfig: + ODrive.Axis.LockinConfig: c_is_class: False attributes: current: @@ -320,7 +320,7 @@ interfaces: finish_on_enc_idx: bool - Motor: + ODrive.Motor: c_is_class: True attributes: error: @@ -443,7 +443,7 @@ interfaces: acim_autoflux_decay_gain: float32 - Controller: + ODrive.Controller: c_is_class: True attributes: error: @@ -527,7 +527,7 @@ interfaces: start_anticogging_calibration: - Encoder: + ODrive.Encoder: c_is_class: True attributes: error: @@ -580,7 +580,7 @@ interfaces: set_linear_count: {in: {count: int32}} - SensorlessEstimator: + ODrive.SensorlessEstimator: c_is_class: True attributes: error: @@ -600,7 +600,7 @@ interfaces: pm_flux_linkage: float32 - TrapezoidalTrajectory: + ODrive.TrapezoidalTrajectory: c_is_class: True attributes: config: @@ -611,7 +611,7 @@ interfaces: decel_limit: float32 - Endstop: + ODrive.Endstop: c_is_class: True attributes: endstop_state: readonly bool @@ -630,7 +630,7 @@ valuetypes: ODrive.Can.Protocol: values: {Simple: } - Axis.AxisState: # TODO: remove redundant "Axis" in name + ODrive.Axis.AxisState: # TODO: remove redundant "Axis" in name values: Undefined: doc: will fall through to idle @@ -656,7 +656,7 @@ valuetypes: Homing: doc: run axis homing function - Encoder.Mode: + ODrive.Encoder.Mode: values: Incremental: Hall: @@ -671,7 +671,7 @@ valuetypes: value: 0x102 doc: not yet implemented - Controller.ControlMode: + ODrive.Controller.ControlMode: values: # Note: these should be sorted from lowest level of control to # highest level of control, to allow "<" style comparisons. @@ -680,7 +680,7 @@ valuetypes: VelocityControl: PositionControl: - Controller.InputMode: + ODrive.Controller.InputMode: values: Inactive: Passthrough: @@ -692,7 +692,7 @@ valuetypes: Mirror: - Motor.MotorType: + ODrive.Motor.MotorType: values: HighCurrent: #LowCurrent: # not implemented diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 466f11b6..93a5cfe2 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -4,7 +4,7 @@ # ODrive.Can.Protocol PROTOCOL_SIMPLE = 0 -# Axis.AxisState +# ODrive.Axis.AxisState AXIS_STATE_UNDEFINED = 0 AXIS_STATE_IDLE = 1 AXIS_STATE_STARTUP_SEQUENCE = 2 @@ -18,7 +18,7 @@ AXIS_STATE_LOCKIN_SPIN = 9 AXIS_STATE_ENCODER_DIR_FIND = 10 AXIS_STATE_HOMING = 11 -# Encoder.Mode +# ODrive.Encoder.Mode ENCODER_MODE_INCREMENTAL = 0 ENCODER_MODE_HALL = 1 ENCODER_MODE_SINCOS = 2 @@ -26,13 +26,13 @@ ENCODER_MODE_SPI_ABS_CUI = 256 ENCODER_MODE_SPI_ABS_AMS = 257 ENCODER_MODE_SPI_ABS_AEAT = 258 -# Controller.ControlMode +# ODrive.Controller.ControlMode CONTROL_MODE_VOLTAGE_CONTROL = 0 CONTROL_MODE_CURRENT_CONTROL = 1 CONTROL_MODE_VELOCITY_CONTROL = 2 CONTROL_MODE_POSITION_CONTROL = 3 -# Controller.InputMode +# ODrive.Controller.InputMode INPUT_MODE_INACTIVE = 0 INPUT_MODE_PASSTHROUGH = 1 INPUT_MODE_VEL_RAMP = 2 @@ -42,7 +42,7 @@ INPUT_MODE_TRAP_TRAJ = 5 INPUT_MODE_CURRENT_RAMP = 6 INPUT_MODE_MIRROR = 7 -# Motor.MotorType +# ODrive.Motor.MotorType MOTOR_TYPE_HIGH_CURRENT = 0 MOTOR_TYPE_GIMBAL = 2 MOTOR_TYPE_ACIM = 3 @@ -51,7 +51,7 @@ MOTOR_TYPE_ACIM = 3 CAN_ERROR_NONE = 0x00000000 CAN_ERROR_DUPLICATE_CAN_IDS = 0x00000001 -# Axis.Error +# ODrive.Axis.Error AXIS_ERROR_NONE = 0x00000000 AXIS_ERROR_INVALID_STATE = 0x00000001 AXIS_ERROR_DC_BUS_UNDER_VOLTAGE = 0x00000002 @@ -70,13 +70,13 @@ AXIS_ERROR_MAX_ENDSTOP_PRESSED = 0x00002000 AXIS_ERROR_ESTOP_REQUESTED = 0x00004000 AXIS_ERROR_HOMING_WITHOUT_ENDSTOP = 0x00020000 -# Axis.LockinState +# ODrive.Axis.LockinState LOCKIN_STATE_INACTIVE = 0 LOCKIN_STATE_RAMP = 1 LOCKIN_STATE_ACCELERATE = 2 LOCKIN_STATE_CONST_VEL = 3 -# Motor.Error +# ODrive.Motor.Error MOTOR_ERROR_NONE = 0x00000000 MOTOR_ERROR_PHASE_RESISTANCE_OUT_OF_RANGE = 0x00000001 MOTOR_ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE = 0x00000002 @@ -95,13 +95,13 @@ MOTOR_ERROR_BRAKE_DUTY_CYCLE_NAN = 0x00002000 MOTOR_ERROR_DC_BUS_OVER_REGEN_CURRENT = 0x00004000 MOTOR_ERROR_DC_BUS_OVER_CURRENT = 0x00008000 -# Motor.ArmedState +# ODrive.Motor.ArmedState ARMED_STATE_DISARMED = 0 ARMED_STATE_WAITING_FOR_TIMINGS = 1 ARMED_STATE_WAITING_FOR_UPDATE = 2 ARMED_STATE_ARMED = 3 -# Motor.GateDriver.DrvFault +# ODrive.Motor.GateDriver.DrvFault DRV_FAULT_NO_FAULT = 0x00000000 DRV_FAULT_FET_LOW_C_OVERCURRENT = 0x00000001 DRV_FAULT_FET_HIGH_C_OVERCURRENT = 0x00000002 @@ -115,7 +115,7 @@ DRV_FAULT_P_VDD_UNDERVOLTAGE = 0x00000100 DRV_FAULT_G_VDD_UNDERVOLTAGE = 0x00000200 DRV_FAULT_G_VDD_OVERVOLTAGE = 0x00000400 -# Controller.Error +# ODrive.Controller.Error CONTROLLER_ERROR_NONE = 0x00000000 CONTROLLER_ERROR_OVERSPEED = 0x00000001 CONTROLLER_ERROR_INVALID_INPUT_MODE = 0x00000002 @@ -124,7 +124,7 @@ CONTROLLER_ERROR_INVALID_MIRROR_AXIS = 0x00000008 CONTROLLER_ERROR_INVALID_LOAD_ENCODER = 0x00000010 CONTROLLER_ERROR_INVALID_ESTIMATE = 0x00000020 -# Encoder.Error +# ODrive.Encoder.Error ENCODER_ERROR_NONE = 0x00000000 ENCODER_ERROR_UNSTABLE_GAIN = 0x00000001 ENCODER_ERROR_CPR_POLEPAIRS_MISMATCH = 0x00000002 @@ -136,6 +136,6 @@ ENCODER_ERROR_ABS_SPI_TIMEOUT = 0x00000040 ENCODER_ERROR_ABS_SPI_COM_FAIL = 0x00000080 ENCODER_ERROR_ABS_SPI_NOT_READY = 0x00000100 -# SensorlessEstimator.Error +# ODrive.SensorlessEstimator.Error SENSORLESS_ESTIMATOR_ERROR_NONE = 0x00000000 SENSORLESS_ESTIMATOR_ERROR_UNSTABLE_GAIN = 0x00000001 From a755f2791770b460628a6d0924599b9c1221acb0 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 2 Jun 2020 16:54:28 -0700 Subject: [PATCH 436/549] Fix hang on compile on Windows Windows 10 includes since version 1903 a python stub which is supposed to open the MS Store if python is invoked but not installed. However when invoked from tup, the stub simply hangs without output. With this change we first check the python version (something that the Windows stub does not interer with) before we invoke it. --- Firmware/Tupfile.lua | 18 +++++++++++++++++- Firmware/build.lua | 8 ++++++++ docs/developer-guide.md | 3 ++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 4577bde1..1974adab 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -1,6 +1,22 @@ tup.include('build.lua') +-- If we simply invoke python or python3 on a pristine Windows 10, it will try +-- to open the Microsoft Store which will not work and hang tup instead. The +-- command "python --version" does not open the Microsoft Store. +-- On some systems this may return a python2 command if Python3 is not installed. +function find_python3() + success, python_version = run_now("python3 --version") + if success then return "python3" end + io.stderr:write("This should go to stderr\n") + success, python_version = run_now("python --version") + if success then return "python" end + error("Python 3 not found.") +end + +python_command = find_python3() +print('Using python command "'..python_command..'"') + -- Switch between board versions boardversion = tup.getconfig("BOARD_VERSION") if boardversion == "v3.1" then @@ -146,7 +162,7 @@ build{ } tup.frule{ - command='python ../tools/odrive/version.py --output %o', + command=python_command..' ../tools/odrive/version.py --output %o', outputs={'build/version.h'} } diff --git a/Firmware/build.lua b/Firmware/build.lua index 016f6d8e..90fb84ef 100644 --- a/Firmware/build.lua +++ b/Firmware/build.lua @@ -12,6 +12,14 @@ function string:split(sep) return fields end +function run_now(command) + local handle + handle = io.popen(command) + local output = handle:read("*a") + local rc = {handle:close()} + return rc[1], output +end + -- Very basic parser to retrieve variables from a Makefile function parse_makefile_vars(makefile) vars = {} diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 28ef7ee5..868c233d 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -35,7 +35,7 @@ The recommended tools for ODrive development are: * **ARM GNU Compiler**: For cross-compiling code * **ARM GDB**: For debugging the code and stepping through on the device * **OpenOCD**: For flashing the ODrive with the STLink/v2 programmer - * **Python**: For running the Python tools + * **Python**: For running the Python tools (`odrivetool`). Also required for compiling firmware. See below for specific installation instructions for your OS. @@ -92,6 +92,7 @@ Some instructions in this document may assume that you're using a bash command p * __Note 2__: 8-2018-q4-major seems to have a bug on Windows. Please use 7-2018-q2-update. * [Tup](http://gittup.org/tup/index.html) * [GNU MCU Eclipse's Windows Build Tools](https://github.com/gnu-mcu-eclipse/windows-build-tools/releases) +* [Python 3](https://www.python.org/downloads/) * [OpenOCD](https://github.com/xpack-dev-tools/openocd-xpack/releases/). * [ST-Link/V2 Drivers](http://www.st.com/web/en/catalog/tools/FM147/SC1887/PF260219) From 1d29893e8bf8da94312e7ed7762c796a8130b2ba Mon Sep 17 00:00:00 2001 From: samuelsadok Date: Tue, 2 Jun 2020 10:28:24 +0200 Subject: [PATCH 437/549] Merge pull request #10 from RoCkaZ/master Only set new timeout if it is different from the old one --- Firmware/fibre/python/fibre/serial_transport.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Firmware/fibre/python/fibre/serial_transport.py b/Firmware/fibre/python/fibre/serial_transport.py index 931a9415..e737dfca 100644 --- a/Firmware/fibre/python/fibre/serial_transport.py +++ b/Firmware/fibre/python/fibre/serial_transport.py @@ -17,7 +17,8 @@ DEFAULT_BAUDRATE = 115200 class SerialStreamTransport(fibre.protocol.StreamSource, fibre.protocol.StreamSink): def __init__(self, port, baud): - self._dev = serial.Serial(port, baud, timeout=1) + self._timeout = 1 + self._dev = serial.Serial(port, baud, timeout=self._timeout) def process_bytes(self, bytes): self._dev.write(bytes) @@ -29,10 +30,16 @@ class SerialStreamTransport(fibre.protocol.StreamSource, fibre.protocol.StreamSi function blocks forever. A deadline before the current time corresponds to non-blocking mode. """ - if deadline is None: + # Only set new timeout value if it is reasonably different from the old one (e.g. 20% as below) + # Otherwise it adds significant overhead (at least under Win10) as the port is reset with every reconfiguration + if deadline is None and self._timeout is not None: + self._timeout = None self._dev.timeout = None - else: - self._dev.timeout = max(deadline - time.monotonic(), 0) + elif deadline is not None: + new_timeout = max(deadline - time.monotonic(), 0) + if abs(new_timeout - self._timeout) > self._timeout * 0.2: + self._timeout = new_timeout + self._dev.timeout = new_timeout return self._dev.read(n_bytes) def get_bytes_or_fail(self, n_bytes, deadline): From 6047c72014ffdf1523cfc3d2b829af96ad08cdf7 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 2 Jun 2020 19:21:32 +0200 Subject: [PATCH 438/549] remove stray line --- Firmware/Tupfile.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 1974adab..c6d2aca3 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -8,7 +8,6 @@ tup.include('build.lua') function find_python3() success, python_version = run_now("python3 --version") if success then return "python3" end - io.stderr:write("This should go to stderr\n") success, python_version = run_now("python --version") if success then return "python" end error("Python 3 not found.") From 592b5f48c775c2ba47a1a27170a1fe3e2c325682 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 3 Jun 2020 17:53:23 +0200 Subject: [PATCH 439/549] fix compilation improve Python3 install check, remove python string interpolation which is not supported by older python versions remove string interpolation --- Firmware/Tupfile.lua | 11 ++++++++--- Firmware/interface_generator.py | 8 ++++---- docs/developer-guide.md | 9 ++++++--- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 8f707d7e..147cbb30 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -7,9 +7,9 @@ tup.include('build.lua') -- On some systems this may return a python2 command if Python3 is not installed. function find_python3() success, python_version = run_now("python3 --version") - if success then return "python3" end + if success and string.match(python_version, "Python 3") then return "python3" end success, python_version = run_now("python --version") - if success then return "python" end + if success and string.match(python_version, "Python 3") then return "python" end error("Python 3 not found.") end @@ -20,7 +20,12 @@ tup.frule{inputs={'fibre/cpp/interfaces_template.j2'}, command=python_command..' tup.frule{inputs={'fibre/cpp/function_stubs_template.j2'}, command=python_command..' interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/function_stubs.hpp'} tup.frule{inputs={'fibre/cpp/endpoints_template.j2'}, command=python_command..' interface_generator.py --definitions odrive-interface.yaml --generate-endpoints ODrive --template %f --output %o', outputs='autogen/endpoints.hpp'} tup.frule{inputs={'fibre/cpp/type_info_template.j2'}, command=python_command..' interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/type_info.hpp'} -tup.frule{command=python_command..' interface_generator.py --definitions odrive-interface.yaml --template enums_template.j2 --output ../tools/odrive/enums.py'} + +-- Note: we currently check this file into source control for two reasons: +-- - Don't require tup to run in order to use odrivetool from the repo +-- - On Windows, tup is unhappy with writing outside of the tup directory +-- TODO: use CI to verify that on PRs the enums.py file is consistent with the YAML. +--tup.frule{command=python_command..' interface_generator.py --definitions odrive-interface.yaml --template enums_template.j2 --output ../tools/odrive/enums.py'} tup.frule{ command=python_command..' ../tools/odrive/version.py --output %o', diff --git a/Firmware/interface_generator.py b/Firmware/interface_generator.py index 14184f1f..4dc89906 100644 --- a/Firmware/interface_generator.py +++ b/Firmware/interface_generator.py @@ -365,7 +365,7 @@ def resolve_interface(scope, name, typeargs): elif probe_name in generics: return generics[probe_name](typeargs) - raise Exception(f'could not resolve type {name} in {join_name(*scope)}. Known interfaces are: {list(interfaces.keys())}. Known value types are: {list(value_types.keys())}') + raise Exception('could not resolve type {} in {}. Known interfaces are: {}. Known value types are: {}'.format(name, join_name(*scope), list(interfaces.keys()), list(value_types.keys()))) def resolve_valuetype(scope, name): """ @@ -381,7 +381,7 @@ def resolve_valuetype(scope, name): if probe_name in value_types: return value_types[probe_name] - raise Exception(f'could not resolve type {name} in {join_name(*scope)}. Known value types are: {list(value_types.keys())}') + raise Exception('could not resolve type {} in {}. Known value types are: {}'.format(name, join_name(*scope), list(value_types.keys()))) def map_to_fibre01_type(t): @@ -538,7 +538,7 @@ if args.verbose: clashing_names = list(set(value_types.keys()).intersection(set(interfaces.keys()))) if len(clashing_names): - print(f"**Error**: Found both an interface and a value type with the name {clashing_names[0]}. This is not allowed, interfaces and value types (such as enums) share the same namespace.", file=sys.stderr) + print("**Error**: Found both an interface and a value type with the name {}. This is not allowed, interfaces and value types (such as enums) share the same namespace.".format(clashing_names[0]), file=sys.stderr) sys.exit(1) # Resolve all types into references @@ -626,7 +626,7 @@ def tokenize(text, interface, interface_transform, value_type_transform, attribu if not attr is None: return attribute_transform(token, attr) - print(f'Warning: cannot resolve "{token}" in ' + interface['fullname']) + print('Warning: cannot resolve "{}" in {}'.format(token, interface['fullname'])) return "`" + token + "`" return re.sub(r'`([A-Za-z\._]+)`', token_transform, text) diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 868c233d..93d306bb 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -35,7 +35,7 @@ The recommended tools for ODrive development are: * **ARM GNU Compiler**: For cross-compiling code * **ARM GDB**: For debugging the code and stepping through on the device * **OpenOCD**: For flashing the ODrive with the STLink/v2 programmer - * **Python**: For running the Python tools (`odrivetool`). Also required for compiling firmware. + * **Python 3**, along with the packages `PyYAML`, `Jinja2` and `jsonschema`: For running the Python tools (`odrivetool`). Also required for compiling firmware. See below for specific installation instructions for your OS. @@ -57,6 +57,7 @@ sudo apt-get update sudo apt-get install gcc-arm-embedded sudo apt-get install openocd sudo add-apt-repository ppa:jonathonf/tup && sudo apt-get update && sudo apt-get install tup +sudo apt-get install python3 python3-yaml python3-jinja2 python3-jsonschema ``` #### Linux (Ubuntu >= 20.04) @@ -64,6 +65,7 @@ sudo add-apt-repository ppa:jonathonf/tup && sudo apt-get update && sudo apt-get sudo apt install gcc-arm-embedded sudo apt install openocd sudo apt install tup +sudo apt install python3 python3-yaml python3-jinja2 python3-jsonschema ``` #### Arch Linux @@ -71,6 +73,7 @@ sudo apt install tup sudo pacman -S arm-none-eabi-gcc arm-none-eabi-binutils sudo pacman -S arm-none-eabi-gdb sudo pacman -S tup +sudo pacman -S python python-yaml python-jinja python-jsonschema ``` * [OpenOCD AUR package](https://aur.archlinux.org/packages/openocd/) @@ -80,6 +83,7 @@ First install [Homebrew](https://brew.sh/). Then you can run these commands in T brew install armmbed/formulae/arm-none-eabi-gcc brew cask install osxfuse && brew install tup brew install openocd +pip install PyYAML Jinja2 jsonschema ``` #### Windows @@ -93,6 +97,7 @@ Some instructions in this document may assume that you're using a bash command p * [Tup](http://gittup.org/tup/index.html) * [GNU MCU Eclipse's Windows Build Tools](https://github.com/gnu-mcu-eclipse/windows-build-tools/releases) * [Python 3](https://www.python.org/downloads/) + * Install Python packages: `pip install PyYAML Jinja2 jsonschema` * [OpenOCD](https://github.com/xpack-dev-tools/openocd-xpack/releases/). * [ST-Link/V2 Drivers](http://www.st.com/web/en/catalog/tools/FM147/SC1887/PF260219) @@ -127,8 +132,6 @@ You can also modify the compile-time defaults for all `.config` parameters. You 2. Connect the ODrive via USB and power it up. 3. Flash the firmware using [odrivetool dfu](odrivetool#device-firmware-update). -If you get `/bin/sh: 1: python: not found` while running `make`, change the tup file command to use `python3` - ### Flashing using an STLink/v2 programmer * Connect `GND`, `SWD`, and `SWC` on connector J2 to the programmer. Note: Always plug in `GND` first! From 0541cb2518dcf7f3fe1e9ef3bd46df69b36700df Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 3 Jun 2020 17:55:54 +0200 Subject: [PATCH 440/549] amend changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86db8832..d480c4aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * Using an STM32F405 .svd file allows CortexDebug to view registers during debugging * Added scripts for building via docker. * Added ability to change uart baudrate via fibre +* Introduced `odrive-interface.yaml` as a root source for the ODrive's API. `odrivetool` connects much faster as a side effect. ### Changed * Changed ratiometric `motor.config.current_lim_tolerance` to absolute `motor.config.current_lim_margin` From b529bddd947d7b25699a846dc4cfc37027af7235 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 3 Jun 2020 18:19:04 +0200 Subject: [PATCH 441/549] add note to enums.py --- {Firmware => tools}/enums_template.j2 | 0 tools/odrive/enums.py | 2 ++ 2 files changed, 2 insertions(+) rename {Firmware => tools}/enums_template.j2 (100%) diff --git a/Firmware/enums_template.j2 b/tools/enums_template.j2 similarity index 100% rename from Firmware/enums_template.j2 rename to tools/enums_template.j2 diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 93a5cfe2..36040da9 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -1,5 +1,7 @@ # TODO: This file is dangerous because the enums could potentially change between API versions. Should transmit as part of the JSON. +# To regenerate this file, nagivate to the top level of the ODrive repository and run: +# python Firmware/interface_generator.py --definitions Firmware/odrive-interface.yaml --template tools/enums_template.j2 --output tools/odrive/enums.py # ODrive.Can.Protocol PROTOCOL_SIMPLE = 0 From 3aeb790e15780f2b9084e0ca25bdfe8d23531c88 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 3 Jun 2020 18:19:19 +0200 Subject: [PATCH 442/549] attempt to fix travis build --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6efeeeb9..2e86de28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,11 @@ sudo: false addons: apt: packages: - libc6-i386 + - libc6-i386 + - python3 + - python3-yaml + - python3-jinja2 + - python3-jsonschema cache: directories: From 23a993ac42f96392f7ceb47411fb20381f80c7d7 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 3 Jun 2020 18:24:54 +0200 Subject: [PATCH 443/549] switch to jsonschema draft 4 --- Firmware/interface_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/interface_generator.py b/Firmware/interface_generator.py index 4dc89906..2ad0b4a3 100644 --- a/Firmware/interface_generator.py +++ b/Firmware/interface_generator.py @@ -9,7 +9,7 @@ import argparse import sys # This schema describes what we expect interface definition files to look like -validator = jsonschema.Draft7Validator(yaml.safe_load(""" +validator = jsonschema.Draft4Validator(yaml.safe_load(""" definitions: interface: type: object From c53ec7e9a56786c8cb4d41d4dc1275d4f4e1e4e5 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 4 Jun 2020 10:12:09 +0200 Subject: [PATCH 444/549] resolve compile issues --- .../fibre/cpp/include/fibre/cpp_utils.hpp | 65 ------------------- Firmware/fibre/cpp/include/fibre/protocol.hpp | 3 +- 2 files changed, 2 insertions(+), 66 deletions(-) diff --git a/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp b/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp index af28a07f..6a81858d 100644 --- a/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp +++ b/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp @@ -882,71 +882,6 @@ TRet* dynamic_get(size_t i, const std::tuple& t) { return dynamic_get_impl, TRet, Ts...>::get(i, t); } -/* Hex to numbers ------------------------------------------------------------*/ - -template -constexpr size_t hex_digits() { - return (std::numeric_limits::digits + 3) / 4; -} - -/* @brief Converts a hexadecimal digit to a uint8_t. -* @param output If not null, the digit's value is stored in this output -* Returns true if the char is a valid hex digit, false otherwise -*/ -static bool hex_digit_to_byte(char ch, uint8_t* output) { - uint8_t nil_output = 0; - if (!output) - output = &nil_output; - if (ch >= '0' && ch <= '9') - return (*output) = ch - '0', true; - if (ch >= 'a' && ch <= 'f') - return (*output) = ch - 'a' + 10, true; - if (ch >= 'A' && ch <= 'F') - return (*output) = ch - 'A' + 10, true; - return false; -} - -/* @brief Converts a hex string to an integer -* @param output If not null, the result is stored in this output -* Returns true if the string represents a valid hex value, false otherwise. -*/ -template -bool hex_string_to_int(const char * str, size_t length, TInt* output) { - constexpr size_t N_DIGITS = hex_digits(); - TInt result = 0; - if (length > N_DIGITS) - length = N_DIGITS; - for (size_t i = 0; i < length && str[i]; i++) { - uint8_t digit = 0; - if (!hex_digit_to_byte(str[i], &digit)) - return false; - result <<= 4; - result += digit; - } - if (output) - *output = result; - return true; -} - -template -bool hex_string_to_int(const char * str, TInt* output) { - return hex_string_to_int(str, hex_digits(), output); -} - -template -bool hex_string_to_int_arr(const char * str, size_t length, TInt (&output)[ICount]) { - for (size_t i = 0; i < ICount; i++) { - if (!hex_string_to_int(&str[i * hex_digits()], &output[i])) - return false; - } - return true; -} - -template -bool hex_string_to_int_arr(const char * str, TInt (&output)[ICount]) { - return hex_string_to_int_arr(str, hex_digits() * ICount, output); -} - template class simple_iterator : std::iterator { diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index a03a596e..09bac8c4 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -386,7 +386,8 @@ template<> struct Codec { return int_val.has_value() ? std::optional(*reinterpret_cast(&int_val.value())) : std::nullopt; } static bool encode(float value, bufptr_t* buffer) { - return Codec::encode(*reinterpret_cast(&value), buffer); + void* ptr = &value; + return Codec::encode(*reinterpret_cast(ptr), buffer); } }; template From bcaa5cca9fa0b4a40edb7173f8fdaf836d454ce2 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 4 Jun 2020 13:02:39 +0200 Subject: [PATCH 445/549] make interface generator python 3.5 compatible. Python 3.5 is the default version of Ubuntu 16.04 which is what our Travis CI runs on. - Python <3.6 does not keep the order of normal dicts. - Python 3.5 does not use UTF-8 encoding for files by default. --- Firmware/interface_generator.py | 74 ++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/Firmware/interface_generator.py b/Firmware/interface_generator.py index 2ad0b4a3..cb8c6fa5 100644 --- a/Firmware/interface_generator.py +++ b/Firmware/interface_generator.py @@ -7,6 +7,7 @@ import jsonschema import re import argparse import sys +from collections import OrderedDict # This schema describes what we expect interface definition files to look like validator = jsonschema.Draft4Validator(yaml.safe_load(""" @@ -106,6 +107,13 @@ class SafeLineLoader(yaml.SafeLoader): # #mapping['__column__'] = node.start_mark.column + 1 # return mapping +# Ensure that dicts remain ordered, even in Python <3.6 +# source: https://stackoverflow.com/a/21912744/3621512 +def construct_mapping(loader, node): + loader.flatten_mapping(node) + return OrderedDict(loader.construct_pairs(node)) +SafeLineLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, construct_mapping) + dictionary = [] def get_words(string): @@ -136,7 +144,7 @@ def to_macro_case(s): return '_'.join(get_words(s)).upper() def to_snake_case(s): return '_'.join(get_words(s)).lower() def to_kebab_case(s): return '-'.join(get_words(s)).lower() -value_types = { +value_types = OrderedDict({ 'bool': {'builtin': True, 'fullname': 'bool', 'name': 'bool', 'c_name': 'bool', 'py_type': 'bool'}, 'float32': {'builtin': True, 'fullname': 'float32', 'name': 'float32', 'c_name': 'float', 'py_type': 'float'}, 'uint8': {'builtin': True, 'fullname': 'uint8', 'name': 'uint8', 'c_name': 'uint8_t', 'py_type': 'int'}, @@ -148,11 +156,11 @@ value_types = { 'int32': {'builtin': True, 'fullname': 'int32', 'name': 'int32', 'c_name': 'int32_t', 'py_type': 'int'}, 'int64': {'builtin': True, 'fullname': 'int64', 'name': 'int64', 'c_name': 'int64_t', 'py_type': 'int'}, 'endpoint_ref': {'builtin': True, 'fullname': 'endpoint_ref', 'name': 'endpoint_ref', 'c_name': 'endpoint_ref_t', 'py_type': '[not implemented]'}, -} +}) -enums = {} +enums = OrderedDict() -interfaces = {} +interfaces = OrderedDict() def make_property_type(typeargs): value_type = resolve_valuetype('', typeargs['fibre.Property.type']) @@ -171,23 +179,23 @@ def make_property_type(typeargs): 'value_type': value_type, # TODO: should be a metaarg 'mode': mode, # TODO: should be a metaarg 'builtin': True, - 'attributes': {}, - 'functions': {} + 'attributes': OrderedDict(), + 'functions': OrderedDict() } if mode != 'readonly': prop_type['functions']['exchange'] = { 'name': 'exchange', 'fullname': join_name(fullname, 'exchange'), - 'in': {'obj': {'name': 'obj', 'type': {'c_name': c_name}}, 'value': {'name': 'value', 'type': value_type, 'optional': True}}, - 'out': {'value': {'name': 'value', 'type': value_type}}, + 'in': OrderedDict([('obj', {'name': 'obj', 'type': {'c_name': c_name}}), ('value', {'name': 'value', 'type': value_type, 'optional': True})]), + 'out': OrderedDict([('value', {'name': 'value', 'type': value_type})]), #'implementation': 'fibre_property_exchange<' + value_type['c_name'] + '>' } else: prop_type['functions']['read'] = { 'name': 'read', 'fullname': join_name(fullname, 'read'), - 'in': {'obj': {'name': 'obj', 'type': {'c_name': c_name}}}, - 'out': {'value': {'name': 'value', 'type': value_type}}, + 'in': OrderedDict([('obj', {'name': 'obj', 'type': {'c_name': c_name}})]), + 'out': OrderedDict([('value', {'name': 'value', 'type': value_type})]), #'implementation': 'fibre_property_read<' + value_type['c_name'] + '>' } @@ -216,7 +224,7 @@ def make_ref_type(interface): return ref_type def get_dict(elem, key): - return elem.get(key, None) or {} + return elem.get(key, None) or OrderedDict() def regularize_arg(path, name, elem): if elem is None: @@ -233,10 +241,10 @@ def regularize_func(path, name, elem, prepend_args): elem = {} elem['name'] = name elem['fullname'] = path = join_name(path, name) - elem['in'] = {n: regularize_arg(path, n, arg) - for n, arg in {**prepend_args, **get_dict(elem, 'in')}.items()} - elem['out'] = {n: regularize_arg(path, n, arg) - for n, arg in get_dict(elem, 'out').items()} + elem['in'] = OrderedDict((n, regularize_arg(path, n, arg)) + for n, arg in (*prepend_args.items(), *get_dict(elem, 'in').items())) + elem['out'] = OrderedDict((n, regularize_arg(path, n, arg)) + for n, arg in get_dict(elem, 'out').items()) return elem def regularize_attribute(parent, name, elem, c_is_class): @@ -289,13 +297,13 @@ def regularize_interface(path, name, elem): elem['fullname'] = path = join_name(path, name) elem['c_name'] = elem.get('c_name', elem['fullname'].replace('.', 'Intf::')) + 'Intf' interfaces[path] = elem - elem['functions'] = {name: regularize_func(path, name, func, {'obj': {'type': make_ref_type(elem)}}) - for name, func in get_dict(elem, 'functions').items()} + elem['functions'] = OrderedDict((name, regularize_func(path, name, func, {'obj': {'type': make_ref_type(elem)}})) + for name, func in get_dict(elem, 'functions').items()) if not 'c_is_class' in elem: raise Exception(elem) treat_as_class = elem['c_is_class'] # TODO: add command line arg to make this selectively optional - elem['attributes'] = {name: regularize_attribute(elem, name, prop, treat_as_class) - for name, prop in get_dict(elem, 'attributes').items()} + elem['attributes'] = OrderedDict((name, regularize_attribute(elem, name, prop, treat_as_class)) + for name, prop in get_dict(elem, 'attributes').items()) elem['interfaces'] = [] elem['enums'] = [] return elem @@ -313,14 +321,14 @@ def regularize_valuetype(path, name, elem): if 'flags' in elem: # treat as flags bit = 0 for k, v in elem['flags'].items(): - elem['flags'][k] = elem['flags'][k] or {} + elem['flags'][k] = elem['flags'][k] or OrderedDict() elem['flags'][k]['name'] = k current_bit = elem['flags'][k].get('bit', bit) elem['flags'][k]['bit'] = current_bit elem['flags'][k]['value'] = 0 if current_bit is None else (1 << current_bit) bit = bit if current_bit is None else current_bit + 1 if 'nullflag' in elem: - elem['flags'] = {elem['nullflag']: {'value': 0, 'bit': None}, **elem['flags']} + elem['flags'] = OrderedDict([(elem['nullflag'], {'value': 0, 'bit': None}), *elem['flags'].items()]) elem['values'] = elem['flags'] elem['is_flags'] = True elem['is_enum'] = True @@ -329,7 +337,7 @@ def regularize_valuetype(path, name, elem): elif 'values' in elem: # treat as enum val = 0 for k, v in elem['values'].items(): - elem['values'][k] = elem['values'][k] or {} + elem['values'][k] = elem['values'][k] or OrderedDict() elem['values'][k]['name'] = k val = elem['values'][k].get('value', val) elem['values'][k]['value'] = val @@ -397,8 +405,8 @@ def generate_endpoint_for_property(prop, attr_bindto, idx): endpoint = { 'id': idx, 'function': prop_intf['functions']['read' if prop['type']['mode'] == 'readonly' else 'exchange'], - 'in_bindings': {'obj': attr_bindto}, - 'out_bindings': [] + 'in_bindings': OrderedDict([('obj', attr_bindto)]), + 'out_bindings': OrderedDict() } endpoint_definition = { 'name': prop['name'], @@ -442,8 +450,8 @@ def generate_endpoint_table(intf, bindto, idx): endpoints.append({ 'id': idx + cnt, 'function': func, - 'in_bindings': {**{'obj': bindto}, **{k_arg: '(' + bindto + ')->' + func['name'] + '_in_' + k_arg + '_' for k_arg in list(func['in'].keys())[1:]}}, - 'out_bindings': {k_arg: '&(' + bindto + ')->' + func['name'] + '_out_' + k_arg + '_' for k_arg in func['out'].keys()}, + 'in_bindings': OrderedDict([('obj', bindto), *[(k_arg, '(' + bindto + ')->' + func['name'] + '_in_' + k_arg + '_') for k_arg in list(func['in'].keys())[1:]]]), + 'out_bindings': OrderedDict((k_arg, '&(' + bindto + ')->' + func['name'] + '_out_' + k_arg + '_') for k_arg in func['out'].keys()), }) in_def = [] out_def = [] @@ -481,12 +489,12 @@ parser.add_argument("--version", action="store_true", help="print version information") parser.add_argument("-v", "--verbose", action="store_true", help="print debug information (on stderr)") -parser.add_argument("-d", "--definitions", type=argparse.FileType('r'), nargs='+', +parser.add_argument("-d", "--definitions", type=argparse.FileType('r', encoding='utf-8'), nargs='+', help="the YAML interface definition file(s) used to generate the code") -parser.add_argument("-t", "--template", type=argparse.FileType('r'), +parser.add_argument("-t", "--template", type=argparse.FileType('r', encoding='utf-8'), help="the code template") group = parser.add_mutually_exclusive_group(required=True) -group.add_argument("-o", "--output", type=argparse.FileType('w'), +group.add_argument("-o", "--output", type=argparse.FileType('w', encoding='utf-8'), help="path of the generated output") group.add_argument("--outputs", type=str, help="path pattern for the generated outputs. One output is generated for each interface. Use # as placeholder for the interface name.") @@ -519,8 +527,8 @@ for definition_file in definition_files: #instance = err.instance.get(re.findall("([^']*)' (?:was|were) unexpected\)", err.message)[0], err.instance) # TODO: print line number raise Exception(err.message + '\nat ' + str(list(err.absolute_path))) - interfaces = {**interfaces, **get_dict(file_content, 'interfaces')} - value_types = {**value_types, **get_dict(file_content, 'valuetypes')} + interfaces.update(get_dict(file_content, 'interfaces')) + value_types.update(get_dict(file_content, 'valuetypes')) dictionary += file_content.get('dictionary', None) or [] @@ -661,12 +669,12 @@ else: if split_name(k)[0] == 'fibre': continue # TODO: remove special case output = template.render(interface = intf, **template_args) - with open(args.outputs.replace('#', k.lower()), 'w') as output_file: + with open(args.outputs.replace('#', k.lower()), 'w', encoding='utf-8') as output_file: output_file.write(output) for k, enum in value_types.items(): if enum.get('builtin', False) or not enum.get('is_enum', False): continue output = template.render(enum = enum, **template_args) - with open(args.outputs.replace('#', k.lower()), 'w') as output_file: + with open(args.outputs.replace('#', k.lower()), 'w', encoding='utf-8') as output_file: output_file.write(output) From 623e49671938beafe87ee90b79b1a91bf6374e40 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 4 Jun 2020 13:05:02 +0200 Subject: [PATCH 446/549] fix various test suite issues --- tools/odrive/tests/analog_input_test.py | 2 +- tools/odrive/tests/can_test.py | 1 + tools/odrive/tests/encoder_test.py | 2 +- tools/odrive/tests/test_runner.py | 6 +++--- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/odrive/tests/analog_input_test.py b/tools/odrive/tests/analog_input_test.py index 2110d6f5..d1b435b3 100644 --- a/tools/odrive/tests/analog_input_test.py +++ b/tools/odrive/tests/analog_input_test.py @@ -103,7 +103,7 @@ class TestAnalogInput(): # Expect mean error to be at most 2% (of the full scale). # Expect there to be less than 2% outliers, where an outlier is anything that is more than 5% (of full scale) away from the expected value. full_range = abs(max_val - min_val) - slope, offset, fitted_curve = fit_sawtooth(data, min_val, max_val) + slope, offset, fitted_curve = fit_sawtooth(data, min_val, max_val, sigma=30) test_assert_eq(slope, (max_val - min_val) / period, accuracy=0.005) test_curve_fit(data, fitted_curve, max_mean_err = full_range * 0.02, inlier_range = full_range * 0.05, max_outliers = len(data[:,0]) * 0.02) diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index e3535f54..ccbaf365 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -112,6 +112,7 @@ class TestSimpleCAN(): odrive.unuse_gpios() axis = odrive.handle.axis0 + axis.config.enable_watchdog = False axis.clear_errors() axis.config.can_node_id = node_id axis.config.can_node_id_extended = extended_id diff --git a/tools/odrive/tests/encoder_test.py b/tools/odrive/tests/encoder_test.py index 59eac451..af7f624c 100644 --- a/tools/odrive/tests/encoder_test.py +++ b/tools/odrive/tests/encoder_test.py @@ -69,7 +69,7 @@ class TestEncoderBase(): slope, offset, fitted_curve = fit_line(data[:,(0,6)]) test_assert_eq(slope, 0.0, range = true_cpr * abs(true_rps) * 0.01) test_assert_eq(offset, true_cpr * true_rps, accuracy = 0.02) - test_curve_fit(data[:,(0,6)], fitted_curve, max_mean_err = true_cpr * 0.05, inlier_range = true_cpr * 0.05, max_outliers = len(data[:,0]) * 0.02) + test_curve_fit(data[:,(0,6)], fitted_curve, max_mean_err = true_cpr * 0.05, inlier_range = true_cpr * 0.05, max_outliers = len(data[:,0]) * 0.05) diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index a6dbffc0..6fc648d2 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -781,12 +781,12 @@ if args.setup_host: if not os.path.isdir("/sys/class/gpio/gpio{}".format(num)): with open("/sys/class/gpio/export", "w") as fp: fp.write(str(num)) - os.chmod("/sys/class/gpio/gpio{}/value".format(num), stat.S_IROTH | stat.S_IWOTH) - os.chmod("/sys/class/gpio/gpio{}/direction".format(num), stat.S_IROTH | stat.S_IWOTH) + os.chmod("/sys/class/gpio/gpio{}/value".format(num), stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + os.chmod("/sys/class/gpio/gpio{}/direction".format(num), stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) for port in testrig.get_components(SerialPortComponent): logger.debug('changing permissions on ' + port.yaml['port'] + '...') - os.chmod(port.yaml['port'], stat.S_IROTH | stat.S_IWOTH) + os.chmod(port.yaml['port'], stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) if len(list(testrig.get_components(TeensyComponent))): # This breaks the annoying teensy loader that shows up on every compile From abf4fa85d3a850989bbcb3fcb51661d5dd556dab Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 29 May 2020 16:19:04 +0200 Subject: [PATCH 447/549] add pip install to CI --- .github/workflows/nightly.yaml | 34 ++++++++++++++++++++++++++++++++++ README.md | 1 + docs/getting-started.md | 2 +- 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/nightly.yaml diff --git a/.github/workflows/nightly.yaml b/.github/workflows/nightly.yaml new file mode 100644 index 00000000..4b3e3294 --- /dev/null +++ b/.github/workflows/nightly.yaml @@ -0,0 +1,34 @@ +name: pip install odrive (nightly) + +on: + schedule: + - cron: '0 2 * * *' # run at 2 AM UTC + +jobs: + nightly: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + #pip: [pip2, pip3] + + runs-on: ${{ matrix.os }} + steps: + - name: Install odrivetool + run: | + pip install monotonic # TODO: this is dishonest. Must be removed as soon as v0.5.0 is published! + pip install odrive + + # This one currently fails because Github Actions runs pip as non-root + #- name: Check if udev rules were set up properly + # if: matrix.os == 'ubuntu-latest' + # run: test -f /etc/udev/rules.d/91-odrive.rules + + # This step is mentioned in the user guide + - name: Add ~/.local/bin to path + if: matrix.os == 'ubuntu-latest' + run: echo "::add-path::~/.local/bin" + + - name: Launch odrivetool + # This returns a non-zero exit code if the odrivetool throws an exception + run: echo 'quit()' | odrivetool shell diff --git a/README.md b/README.md index 915c1636..eb3cea15 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ This project is all about accurately driving brushless motors, for cheap. The ai | master | [![Build Status](https://travis-ci.org/madcowswe/ODrive.png?branch=master)](https://travis-ci.org/madcowswe/ODrive) | | devel | [![Build Status](https://travis-ci.org/madcowswe/ODrive.png?branch=devel)](https://travis-ci.org/madcowswe/ODrive) | +[![pip install odrive (nightly)](https://github.com/madcowswe/ODrive/workflows/pip%20install%20odrive%20(nightly)/badge.svg)](https://github.com/madcowswe/ODrive/actions?query=workflow%3A%22pip+install+odrive+%28nightly%29%22) Please refer to the [Developer Guide](https://docs.odriverobotics.com/developer-guide) to get started with ODrive firmware development. diff --git a/docs/getting-started.md b/docs/getting-started.md index 07b298d8..64251c57 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -133,7 +133,7 @@ Try step 5 again sudo udevadm control --reload-rules sudo udevadm trigger ``` -3. **Ubuntu**, **Raspbian**: If you can't invoke `odrivetool` at this point, try adding `~/.local/bin` to your `$PATH` ([see related bug](https://unix.stackexchange.com/a/392710/176715)). This is done for example by running `nano ~/.bashrc`, scrolling to the bottom, pasting `PATH=$PATH:~/.local/bin`, and then saving and closing, and close and reopen the terminal window. +3. **Ubuntu**, **Raspbian**: If you can't invoke `odrivetool` at this point, try adding `~/.local/bin` to your `$PATH` ([see related bug](https://unix.stackexchange.com/a/392710/176715)). This is done for example by running `nano ~/.bashrc`, scrolling to the bottom, pasting `export PATH=$PATH:~/.local/bin`, and then saving and closing, and close and reopen the terminal window. ## Firmware **ODrive v3.5 and later**
From a18cc28fdd2dcd7d3a9ad54329bd78443ab02bdb Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 4 Jun 2020 16:51:36 +0200 Subject: [PATCH 448/549] add compile workflow --- .github/workflows/compile.yaml | 114 +++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 .github/workflows/compile.yaml diff --git a/.github/workflows/compile.yaml b/.github/workflows/compile.yaml new file mode 100644 index 00000000..388d2688 --- /dev/null +++ b/.github/workflows/compile.yaml @@ -0,0 +1,114 @@ +name: Tests + +on: + pull_request: + branches: [master, devel] + tags: ['fw-v*'] + push: + branches: [master, devel] + tags: ['fw-v*'] + +jobs: + compile: + strategy: + fail-fast: false + matrix: + os: [ubuntu-16.04, ubuntu-latest, windows-latest, macOS-latest] + board_version: [v3.6-56V] + debug: [true] + + include: + - {os: ubuntu-latest, board_version: v3.2, debug: false} + - {os: ubuntu-latest, board_version: v3.3, debug: false} + - {os: ubuntu-latest, board_version: v3.4-24V, debug: false} + - {os: ubuntu-latest, board_version: v3.4-48V, debug: false} + - {os: ubuntu-latest, board_version: v3.5-24V, debug: false} + - {os: ubuntu-latest, board_version: v3.5-48V, debug: false} + - {os: ubuntu-latest, board_version: v3.6-24V, debug: false} + - {os: ubuntu-latest, board_version: v3.6-56V, debug: false} + + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + + - name: Install ARM GCC and tup (Debian) + if: startsWith(matrix.os, 'ubuntu-') + run: | + DEBIAN_VERSION="$(lsb_release --release --short)" + echo Debian version: $DEBIAN_VERSION + if [ "$DEBIAN_VERSION" -gt 9 ]; then + sudo apt-get install gcc-arm-none-eabi + else + # Ubuntu 16.04 (Debian 9) is on ARM GCC 4.9 which is too old for us + sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa + sudo apt-get update + sudo apt-get install gcc-arm-embedded + fi + + if ! (apt-cache search tup | grep "^tup - "); then + sudo add-apt-repository ppa:jonathonf/tup + sudo apt-get update + fi + + sudo apt-get install tup + + - name: Install ARM GCC and tup (macOS) + if: startsWith(matrix.os, 'macOS-') + run: | + brew install armmbed/formulae/arm-none-eabi-gcc + brew cask install osxfuse && brew install tup + + - name: Cache chocolatey + uses: actions/cache@v2 + if: startsWith(matrix.os, 'windows-') + with: + path: C:\Users\runneradmin\AppData\Local\Temp\chocolatey\gcc-arm-embedded + key: ${{ runner.os }}-gcc-arm-embedded + restore-keys: | + ${{ runner.os }}-gcc-arm-embedded + + - name: Install ARM GCC and tup (Windows) + if: startsWith(matrix.os, 'windows-') + run: | + Invoke-WebRequest -Uri "http://gittup.org/tup/win32/tup-latest.zip" -OutFile ".\tup-latest.zip" + dir + Expand-Archive ".\tup-latest.zip" -DestinationPath ".\tup-latest" -Force + echo "::add-path::$(Resolve-Path .)\tup-latest" + + choco install gcc-arm-embedded # downloads https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/gcc-arm-none-eabi-9-2019-q4-major-win32.zip + + - name: Dump path + if: startsWith(matrix.os, 'windows-') + run: | + $Env:Path + + - name: Prepare Compilation + run: | + arm-none-eabi-gcc --version # for debugging + cd ${{ github.workspace }}/Firmware + echo "CONFIG_BOARD_VERSION=${{ matrix.board_version }}" >> tup.config + echo "CONFIG_STRICT=true" >> tup.config + echo "CONFIG_DEBUG=${{ matrix.release }}" >> tup.config + tup init + tup generate ./tup_build.sh + + - name: Compile (Unix) + if: "!startsWith(matrix.os, 'windows-')" + run: | + cd ${{ github.workspace }}/Firmware + bash -xe ./tup_build.sh + + - name: Compile (Windows) + if: startsWith(matrix.os, 'windows-') + run: | + cd ${{ github.workspace }}/Firmware + mv tup_build.sh tup_build.bat # in reality this is a .bat script on windows + .\tup_build.bat + + #code-checks: + # runs-on: ubuntu-latest + # steps: + # TODO: + # - check if enums.py is consistent with yaml + # - clang-format check + # - check if interface_generator outputs the same thing with Python 3.5 and Python 3.8 From b6066eac01953c5f4318e5c5b5c20c4001d28ede Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 5 Jun 2020 12:13:15 +0200 Subject: [PATCH 449/549] fix windows compile --- .github/workflows/compile.yaml | 17 +++++++---------- Firmware/Tupfile.lua | 6 +++--- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/compile.yaml b/.github/workflows/compile.yaml index 388d2688..45847d91 100644 --- a/.github/workflows/compile.yaml +++ b/.github/workflows/compile.yaml @@ -4,9 +4,9 @@ on: pull_request: branches: [master, devel] tags: ['fw-v*'] - push: - branches: [master, devel] - tags: ['fw-v*'] + #push: + # branches: [master, devel] + # tags: ['fw-v*'] jobs: compile: @@ -71,20 +71,17 @@ jobs: if: startsWith(matrix.os, 'windows-') run: | Invoke-WebRequest -Uri "http://gittup.org/tup/win32/tup-latest.zip" -OutFile ".\tup-latest.zip" - dir Expand-Archive ".\tup-latest.zip" -DestinationPath ".\tup-latest" -Force echo "::add-path::$(Resolve-Path .)\tup-latest" choco install gcc-arm-embedded # downloads https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/gcc-arm-none-eabi-9-2019-q4-major-win32.zip - - name: Dump path - if: startsWith(matrix.os, 'windows-') - run: | - $Env:Path - - name: Prepare Compilation run: | - arm-none-eabi-gcc --version # for debugging + # for debugging + arm-none-eabi-gcc --version + python --version + cd ${{ github.workspace }}/Firmware echo "CONFIG_BOARD_VERSION=${{ matrix.board_version }}" >> tup.config echo "CONFIG_STRICT=true" >> tup.config diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index c6d2aca3..9710d26c 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -6,10 +6,10 @@ tup.include('build.lua') -- command "python --version" does not open the Microsoft Store. -- On some systems this may return a python2 command if Python3 is not installed. function find_python3() - success, python_version = run_now("python3 --version") - if success then return "python3" end success, python_version = run_now("python --version") - if success then return "python" end + if success and string.match(python_version, "Python 3") then return "python" end + success, python_version = run_now("python3 --version") + if success and string.match(python_version, "Python 3") then return "python3" end error("Python 3 not found.") end From f54ef3d31ae5ee01896232992f536b0d9ede49ab Mon Sep 17 00:00:00 2001 From: Kyle Bartholomew Date: Fri, 5 Jun 2020 21:29:10 -0700 Subject: [PATCH 450/549] New class and methods for bulk capture --- tools/odrive/utils.py | 130 +++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 77 deletions(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 8f8acb81..519612da 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -7,7 +7,7 @@ import platform import subprocess import os from fibre.utils import Event -from odrive.enums import errors +from odrive.enums import * try: if platform.system() == 'Windows': @@ -123,85 +123,61 @@ def start_liveplotter(get_var_callback): return cancellation_token; #plot_data() -def start_bulk_capture(get_var_callback, - sleep_time=1.0/1000.0, - samples=2000): - ''' - Synchronous function to capture data and return as a pandas Dataframe - ''' - import pandas as pd - vals = [] - start_time = time.monotonic() - last_time = 0 - too_slow_counter = 0 - #total_samples = length_seconds * data_rate - for i in range(samples): - try: - data = get_var_callback() - except Exception as ex: - print(str(ex)) - print("Waiting 1 second before next data point") - time.sleep(1) - continue - relative_time = time.monotonic() - start_time - vals.append([relative_time] + data) - time.sleep(sleep_time) - # delta_t = (relative_time - last_time) - # period = 1.0 / data_rate - # if delta_t < period: - # time.sleep(period - delta_t) - # elif delta_t > period: - # too_slow_counter += 1 - # last_time = relative_time - # if too_slow_counter > 0: - # print("Slower than requested data rate for {} samples out of {} total samples" - # .format(too_slow_counter, total_samples)) - return pd.DataFrame(vals) -def start_bulk_capture2(get_var_callback, - data_rate=1000.0, - length_seconds=2): - ''' - Synchronous function to capture data and return as a pandas Dataframe - ''' - import pandas as pd - vals = [] - start_time = time.monotonic() - last_time = 0 - too_slow_counter = 0 - total_samples = int(length_seconds * data_rate) - for i in range(total_samples): - try: - data = get_var_callback() - except Exception as ex: - print(str(ex)) - print("Waiting 1 second before next data point") - time.sleep(1) - continue - relative_time = time.monotonic() - start_time - vals.append([relative_time] + data) +class BulkCapture: + def __init__(self, + get_var_callback, + data_rate=500.0, + length=2.0): + from threading import Event, Thread + import pandas as pd - delta_t = (relative_time - last_time) - period = 1.0 / data_rate - if delta_t < period: - time.sleep(period - delta_t) - elif delta_t > period: - too_slow_counter += 1 - last_time = relative_time - if too_slow_counter > 0: - print("Slower than requested data rate for {} samples out of {} total samples" - .format(too_slow_counter, total_samples)) - return pd.DataFrame(vals) + self.event = Event() + def loop(): + vals = [] + start_time = time.monotonic() + total_samples = int(length * data_rate) + period = 1.0/data_rate + for i in range(total_samples): + try: + data = get_var_callback() + except Exception as ex: + print(str(ex)) + print("Waiting 1 second before next data point") + time.sleep(1) + continue + relative_time = time.monotonic() - start_time + vals.append([relative_time] + data) + time.sleep(period - (relative_time % period)) + self.data = pd.DataFrame(vals) # A lock is not really necessary due to the event + print("Achieved average data rate: {}Hz".format(total_samples / self.data.iloc[-1, 0])) + print("If this rate is significantly lower than what you specified, consider lowering it below the achieved value for more consistent sampling.") + self.event.set() + Thread(target=loop, daemon=True).start() + + def plot_data(self): + import matplotlib.pyplot as plt + plt.plot(self.data[0], self.data.drop(0, axis=1)) + plt.xlabel("Time (seconds)") + plt.ylabel("Counts") + plt.legend() + plt.show() + + +def step_and_plot(axis, step_size=100.0, settle_time=1.0, data_rate=500.0): + initial_settle_time = 0.5 + axis.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL + capture = BulkCapture(lambda :[axis.encoder.pos_estimate, axis.controller.pos_setpoint], + data_rate=data_rate, + length = settle_time + initial_settle_time) + initial_setpoint = axis.encoder.pos_estimate + axis.controller.pos_setpoint = initial_setpoint # set initial loc as current loc + time.sleep(initial_settle_time) + axis.controller.pos_setpoint = initial_setpoint + step_size + capture.event.wait() + axis.requested_state = AXIS_STATE_IDLE + capture.plot_data() -def capture_and_plot(get_var_callback, - sleep_time=1.0/1000.0, - samples=2000): - import matplotlib.pyplot as plt - data = start_bulk_capture(get_var_callback, - sleep_time, - samples) - plt.plot(data[0], data.drop(0, axis=1)) - plt.show() def print_drv_regs(name, motor): """ From 3a593b072a8210215be5ad52fcd634016bbb8c7c Mon Sep 17 00:00:00 2001 From: Kyle Bartholomew Date: Fri, 5 Jun 2020 21:45:16 -0700 Subject: [PATCH 451/549] Added comments --- tools/odrive/utils.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 519612da..1d88736e 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -125,6 +125,21 @@ def start_liveplotter(get_var_callback): class BulkCapture: + ''' + Asynchronously captures a bulk set of data when instance is created. + + get_var_callback: a function that returns the data you want to collect (see the example below) + data_rate: Rate in hz + length: Length of time to capture in seconds + + Example Usage: + capture = BulkCapture(lambda :[odrv0.axis0.encoder.pos_estimate, odrv0.axis0.controller.pos_setpoint]) + # Do stuff while capturing (like sending position commands) + capture.event.wait() # When you're done doing stuff, wait for the capture to be completed. + print(capture.data) # Do stuff with the data + capture.plot_data() # Helper method to plot the data + ''' + def __init__(self, get_var_callback, data_rate=500.0, @@ -137,7 +152,7 @@ class BulkCapture: vals = [] start_time = time.monotonic() total_samples = int(length * data_rate) - period = 1.0/data_rate + period = 1.0 / data_rate for i in range(total_samples): try: data = get_var_callback() @@ -148,11 +163,11 @@ class BulkCapture: continue relative_time = time.monotonic() - start_time vals.append([relative_time] + data) - time.sleep(period - (relative_time % period)) + time.sleep(period - (relative_time % period)) # this ensures consistently timed samples self.data = pd.DataFrame(vals) # A lock is not really necessary due to the event print("Achieved average data rate: {}Hz".format(total_samples / self.data.iloc[-1, 0])) print("If this rate is significantly lower than what you specified, consider lowering it below the achieved value for more consistent sampling.") - self.event.set() + self.event.set() # tell the main thread that the bulk capture is complete Thread(target=loop, daemon=True).start() def plot_data(self): @@ -160,7 +175,6 @@ class BulkCapture: plt.plot(self.data[0], self.data.drop(0, axis=1)) plt.xlabel("Time (seconds)") plt.ylabel("Counts") - plt.legend() plt.show() @@ -171,10 +185,10 @@ def step_and_plot(axis, step_size=100.0, settle_time=1.0, data_rate=500.0): data_rate=data_rate, length = settle_time + initial_settle_time) initial_setpoint = axis.encoder.pos_estimate - axis.controller.pos_setpoint = initial_setpoint # set initial loc as current loc + axis.controller.pos_setpoint = initial_setpoint # set current position as setpoint time.sleep(initial_settle_time) - axis.controller.pos_setpoint = initial_setpoint + step_size - capture.event.wait() + axis.controller.pos_setpoint = initial_setpoint + step_size # relative/incremental movement + capture.event.wait() # wait for Bulk Capture to be complete axis.requested_state = AXIS_STATE_IDLE capture.plot_data() From ebef9950cce4651d0fc604e04b7e7cd591cbfa32 Mon Sep 17 00:00:00 2001 From: Kyle Bartholomew Date: Fri, 5 Jun 2020 21:52:19 -0700 Subject: [PATCH 452/549] Remove Mac specific changes to prep for pull request --- tools/odrive/utils.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 1d88736e..fe93dafd 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -115,10 +115,9 @@ def start_liveplotter(get_var_callback): fetch_t.daemon = True fetch_t.start() - #plot_t = threading.Thread(target=plot_data) - #plot_t.daemon = True - #plot_t.start() - plot_data() + plot_t = threading.Thread(target=plot_data) + plot_t.daemon = True + plot_t.start() return cancellation_token; #plot_data() From 8c9ba4651c8d5f37c83473dc58530d6704b14b62 Mon Sep 17 00:00:00 2001 From: Kyle Bartholomew Date: Fri, 5 Jun 2020 21:53:58 -0700 Subject: [PATCH 453/549] Removed test print statement --- tools/odrive/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index fe93dafd..5d61eef4 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -226,7 +226,7 @@ def rate_test(device): # import matplotlib.pyplot as plt # plt.ion() - print("reading 10000 values... new value") + print("reading 10000 values...") numFrames = 10000 vals = [] for _ in range(numFrames): From 140a11a569ab2b6f252e5404d2f25d6ff9c65d0e Mon Sep 17 00:00:00 2001 From: Kyle Bartholomew Date: Fri, 5 Jun 2020 22:04:02 -0700 Subject: [PATCH 454/549] Added new helpers to shell --- tools/odrive/shell.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/odrive/shell.py b/tools/odrive/shell.py index c25278e3..a4ffc5f0 100644 --- a/tools/odrive/shell.py +++ b/tools/odrive/shell.py @@ -5,8 +5,7 @@ import threading import fibre import odrive import odrive.enums -from odrive.utils import start_liveplotter, dump_errors -#from odrive.enums import * # pylint: disable=W0614 +from odrive.utils import start_liveplotter, dump_errors, BulkCapture, step_and_plot def print_banner(): print('Please connect your ODrive.') @@ -77,7 +76,9 @@ def launch_shell(args, logger, app_shutdown_token): interactive_variables = { 'start_liveplotter': start_liveplotter, - 'dump_errors': dump_errors + 'dump_errors': dump_errors, + 'BulkCapture': BulkCapture, + 'step_and_plot': step_and_plot } # Expose all enums from odrive.enums From e3a314eb34cb61927b15fd4375dbad82f5ad954f Mon Sep 17 00:00:00 2001 From: Kyle Bartholomew Date: Sat, 6 Jun 2020 12:25:40 -0700 Subject: [PATCH 455/549] Added support for velocity step and made function modular for other modes --- tools/odrive/utils.py | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 5d61eef4..8f279eba 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -177,18 +177,44 @@ class BulkCapture: plt.show() -def step_and_plot(axis, step_size=100.0, settle_time=1.0, data_rate=500.0): +def step_and_plot( axis, + step_size=100.0, + settle_time=0.5, + data_rate=500.0, + ctrl_mode=CTRL_MODE_POSITION_CONTROL): + + if ctrl_mode is CTRL_MODE_POSITION_CONTROL: + get_var_callback = lambda :[axis.encoder.pos_estimate, axis.controller.pos_setpoint] + initial_setpoint = axis.encoder.pos_estimate + def set_setpoint(setpoint): + axis.controller.pos_setpoint = setpoint + elif ctrl_mode is CTRL_MODE_VELOCITY_CONTROL: + get_var_callback = lambda :[axis.encoder.vel_estimate, axis.controller.vel_setpoint] + initial_setpoint = 0 + def set_setpoint(setpoint): + axis.controller.vel_setpoint = setpoint + else: + print("Invalid control mode") + return + initial_settle_time = 0.5 + initial_control_mode = axis.controller.config.control_mode # Set it back afterwards + print(initial_control_mode) + axis.controller.config.control_mode = ctrl_mode axis.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL - capture = BulkCapture(lambda :[axis.encoder.pos_estimate, axis.controller.pos_setpoint], + + capture = BulkCapture(get_var_callback, data_rate=data_rate, - length = settle_time + initial_settle_time) - initial_setpoint = axis.encoder.pos_estimate - axis.controller.pos_setpoint = initial_setpoint # set current position as setpoint + length = initial_settle_time + settle_time) + + set_setpoint(initial_setpoint) time.sleep(initial_settle_time) - axis.controller.pos_setpoint = initial_setpoint + step_size # relative/incremental movement + set_setpoint(initial_setpoint + step_size) # relative/incremental movement + capture.event.wait() # wait for Bulk Capture to be complete + axis.requested_state = AXIS_STATE_IDLE + axis.controller.config.control_mode = initial_control_mode capture.plot_data() From c8f46bbb4ae1de0ddff32b1e5b82f1a2f4bcaa60 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 8 Jun 2020 10:16:39 +0200 Subject: [PATCH 456/549] Make compile work on GCC 10.1 --- Firmware/fibre/cpp/include/fibre/protocol.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index 89091f74..1fbecea0 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -12,6 +12,7 @@ see protocol.md for the protocol specification #include #include //#include +#include #include #include "crc.hpp" #include "cpp_utils.hpp" From 61bc1322d11e632a7bf4a099a4337cecdddd7696 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 10 Jun 2020 13:20:17 +0200 Subject: [PATCH 457/549] Add help text for missing dependencies --- Firmware/Tupfile.lua | 12 +++++++----- Firmware/{ => fibre/tools}/interface_generator.py | 0 Firmware/interface_generator_stub.py | 12 ++++++++++++ tools/enums_template.j2 | 2 ++ tools/odrive/enums.py | 2 +- 5 files changed, 22 insertions(+), 6 deletions(-) rename Firmware/{ => fibre/tools}/interface_generator.py (100%) create mode 100644 Firmware/interface_generator_stub.py diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 51a1e5c7..35725d80 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -16,16 +16,18 @@ end python_command = find_python3() print('Using python command "'..python_command..'"') -tup.frule{inputs={'fibre/cpp/interfaces_template.j2'}, command=python_command..' interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/interfaces.hpp'} -tup.frule{inputs={'fibre/cpp/function_stubs_template.j2'}, command=python_command..' interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/function_stubs.hpp'} -tup.frule{inputs={'fibre/cpp/endpoints_template.j2'}, command=python_command..' interface_generator.py --definitions odrive-interface.yaml --generate-endpoints ODrive --template %f --output %o', outputs='autogen/endpoints.hpp'} -tup.frule{inputs={'fibre/cpp/type_info_template.j2'}, command=python_command..' interface_generator.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/type_info.hpp'} +run_now("") + +tup.frule{inputs={'fibre/cpp/interfaces_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/interfaces.hpp'} +tup.frule{inputs={'fibre/cpp/function_stubs_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/function_stubs.hpp'} +tup.frule{inputs={'fibre/cpp/endpoints_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --generate-endpoints ODrive --template %f --output %o', outputs='autogen/endpoints.hpp'} +tup.frule{inputs={'fibre/cpp/type_info_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/type_info.hpp'} -- Note: we currently check this file into source control for two reasons: -- - Don't require tup to run in order to use odrivetool from the repo -- - On Windows, tup is unhappy with writing outside of the tup directory -- TODO: use CI to verify that on PRs the enums.py file is consistent with the YAML. ---tup.frule{command=python_command..' interface_generator.py --definitions odrive-interface.yaml --template enums_template.j2 --output ../tools/odrive/enums.py'} +--tup.frule{command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template enums_template.j2 --output ../tools/odrive/enums.py'} tup.frule{ command=python_command..' ../tools/odrive/version.py --output %o', diff --git a/Firmware/interface_generator.py b/Firmware/fibre/tools/interface_generator.py similarity index 100% rename from Firmware/interface_generator.py rename to Firmware/fibre/tools/interface_generator.py diff --git a/Firmware/interface_generator_stub.py b/Firmware/interface_generator_stub.py new file mode 100644 index 00000000..cdbc91a8 --- /dev/null +++ b/Firmware/interface_generator_stub.py @@ -0,0 +1,12 @@ +#!/bin/python3 + +import sys +import os + +try: + exec(open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'fibre', 'tools', 'interface_generator.py')).read()) +except ModuleNotFoundError as ex: + print(str(ex), file=sys.stderr) + print("Note that there are new compile-time dependencies since around v0.5.1.", file=sys.stderr) + print("Check out https://github.com/madcowswe/ODrive/blob/devel/docs/developer-guide.md#prerequisites for details.", file=sys.stderr) + exit(1) diff --git a/tools/enums_template.j2 b/tools/enums_template.j2 index 28105c3c..bb20ca37 100644 --- a/tools/enums_template.j2 +++ b/tools/enums_template.j2 @@ -1,5 +1,7 @@ # TODO: This file is dangerous because the enums could potentially change between API versions. Should transmit as part of the JSON. +# To regenerate this file, nagivate to the top level of the ODrive repository and run: +# python Firmware/interface_generator_stub.py --definitions Firmware/odrive-interface.yaml --template tools/enums_template.j2 --output tools/odrive/enums.py [%- for _, enum in value_types.items() %] [%- if enum.is_enum %] diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 36040da9..2fe32b24 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -1,7 +1,7 @@ # TODO: This file is dangerous because the enums could potentially change between API versions. Should transmit as part of the JSON. # To regenerate this file, nagivate to the top level of the ODrive repository and run: -# python Firmware/interface_generator.py --definitions Firmware/odrive-interface.yaml --template tools/enums_template.j2 --output tools/odrive/enums.py +# python Firmware/interface_generator_stub.py --definitions Firmware/odrive-interface.yaml --template tools/enums_template.j2 --output tools/odrive/enums.py # ODrive.Can.Protocol PROTOCOL_SIMPLE = 0 From 6380c2b2011d1464d85862536172548a733c76e3 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 10 Jun 2020 14:05:52 +0200 Subject: [PATCH 458/549] add fibre HWIL tests --- docs/testing.md | 1 + tools/odrive/tests/fibre_test.py | 56 +++++++++++++++++++++++++++ tools/odrive/tests/uart_ascii_test.py | 9 +++++ 3 files changed, 66 insertions(+) create mode 100644 tools/odrive/tests/fibre_test.py diff --git a/docs/testing.md b/docs/testing.md index 1c4e42c3..5ba1160b 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -17,6 +17,7 @@ The testing facility consists of the following components: - `can_test.py`: Partial coverage of the commands described in [CAN Protocol](can-protocol) - `closed_loop_test.py`: Velocity control, position control (TODO: sensorless control), brake regen current hard limit, current control with velocity limiting - `encoder_test.py`: Incremental encoder, hall effect encoder, sin/cos encoder, SPI encoders (AMS, CUI) + - `fibre_test.py`: General USB protocol tests - `nvm_test.py`: Configuration storage - `pwm_input_test.py`: PWM input - `step_dir_test.py`: Step/dir input diff --git a/tools/odrive/tests/fibre_test.py b/tools/odrive/tests/fibre_test.py new file mode 100644 index 00000000..75801a5b --- /dev/null +++ b/tools/odrive/tests/fibre_test.py @@ -0,0 +1,56 @@ + +import test_runner + +import time + +from fibre.utils import Logger +from odrive.enums import * +from test_runner import * + +class FibreFunctionalTest(): + """ + Tests basic protocol functionality. + """ + + def get_test_cases(self, testrig: TestRig): + return testrig.get_components(ODriveComponent) + + def run_test(self, odrive: ODriveComponent, logger: Logger): + # Test property read/write + odrive.handle.test_property = 42 + test_assert_eq(odrive.handle.test_property, 42) + odrive.handle.test_property = 0xffffffff + test_assert_eq(odrive.handle.test_property, 0xffffffff) + + # Test function call + val = odrive.handle.get_adc_voltage(0) + test_assert_within(val, 0.01, 3.29) + + # Test custom setter (aka property write hook) + odrive.handle.axis0.motor.config.phase_resistance = 1 + odrive.handle.axis0.motor.config.phase_inductance = 1 + odrive.handle.axis0.motor.config.current_control_bandwidth = 1000 + old_gain = odrive.handle.axis0.motor.current_control.p_gain + test_assert_eq(old_gain, 1000, accuracy=0.0001) # must be non-zero for subsequent check to work + odrive.handle.axis0.motor.config.current_control_bandwidth /= 2 + test_assert_eq(odrive.handle.axis0.motor.current_control.p_gain, old_gain / 2, accuracy=0.0001) + +class FibreBurnInTest(): + """ + Tests continuous usage of the protocol. + """ + + def get_test_cases(self, testrig: TestRig): + return testrig.get_components(ODriveComponent) + + def run_test(self, odrive: ODriveComponent, logger: Logger): + data = record_log(lambda: [odrive.handle.vbus_voltage], duration=10.0) + expected_data = np.mean(data[:,1]) * np.ones(data[:,1].size) + test_curve_fit(data, expected_data, max_mean_err = 0.1, inlier_range = 0.5, max_outliers = 0) + + +if __name__ == '__main__': + test_runner.run([ + FibreFunctionalTest(), + FibreBurnInTest(), + ]) diff --git a/tools/odrive/tests/uart_ascii_test.py b/tools/odrive/tests/uart_ascii_test.py index 17cf85f8..ee9a3fa4 100644 --- a/tools/odrive/tests/uart_ascii_test.py +++ b/tools/odrive/tests/uart_ascii_test.py @@ -89,6 +89,15 @@ class TestUartAscii(): response = int(ser.readline().strip()) test_assert_eq(response, 12345) + # Test custom setter (aka property write hook) + odrive.handle.axis0.motor.config.phase_resistance = 1 + odrive.handle.axis0.motor.config.phase_inductance = 1 + odrive.handle.axis0.motor.config.current_control_bandwidth = 1000 + old_gain = odrive.handle.axis0.motor.current_control.p_gain + test_assert_eq(old_gain, 1000, accuracy=0.0001) # must be non-zero for subsequent check to work + ser.write('w axis0.motor.config.current_control_bandwidth {}\n'.format(odrive.handle.axis0.motor.config.current_control_bandwidth / 2).encode('ascii')) + test_assert_eq(ser.readline(), b'') + test_assert_eq(odrive.handle.axis0.motor.current_control.p_gain, old_gain / 2, accuracy=0.0001) # Test 'c', 'v', 'p', 'q' and 'f' commands From 8c6ab4bd3084af8a77b49343939f11ebc5797071 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 10 Jun 2020 15:36:47 +0200 Subject: [PATCH 459/549] add compile prerequisites to CI --- .github/workflows/compile.yaml | 12 +++++++++--- Firmware/interface_generator_stub.py | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/compile.yaml b/.github/workflows/compile.yaml index 45847d91..eefd6d22 100644 --- a/.github/workflows/compile.yaml +++ b/.github/workflows/compile.yaml @@ -31,7 +31,7 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Install ARM GCC and tup (Debian) + - name: Install prerequisites (Debian) if: startsWith(matrix.os, 'ubuntu-') run: | DEBIAN_VERSION="$(lsb_release --release --short)" @@ -52,11 +52,15 @@ jobs: sudo apt-get install tup - - name: Install ARM GCC and tup (macOS) + sudo apt install python3 python3-yaml python3-jinja2 python3-jsonschema + + - name: Install prerequisites (macOS) if: startsWith(matrix.os, 'macOS-') run: | brew install armmbed/formulae/arm-none-eabi-gcc brew cask install osxfuse && brew install tup + pip3 install PyYAML Jinja2 jsonschema + - name: Cache chocolatey uses: actions/cache@v2 @@ -67,7 +71,7 @@ jobs: restore-keys: | ${{ runner.os }}-gcc-arm-embedded - - name: Install ARM GCC and tup (Windows) + - name: Install prerequisites (Windows) if: startsWith(matrix.os, 'windows-') run: | Invoke-WebRequest -Uri "http://gittup.org/tup/win32/tup-latest.zip" -OutFile ".\tup-latest.zip" @@ -75,6 +79,8 @@ jobs: echo "::add-path::$(Resolve-Path .)\tup-latest" choco install gcc-arm-embedded # downloads https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/gcc-arm-none-eabi-9-2019-q4-major-win32.zip + + pip install PyYAML Jinja2 jsonschema - name: Prepare Compilation run: | diff --git a/Firmware/interface_generator_stub.py b/Firmware/interface_generator_stub.py index cdbc91a8..d5b22093 100644 --- a/Firmware/interface_generator_stub.py +++ b/Firmware/interface_generator_stub.py @@ -5,7 +5,7 @@ import os try: exec(open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'fibre', 'tools', 'interface_generator.py')).read()) -except ModuleNotFoundError as ex: +except ImportError as ex: print(str(ex), file=sys.stderr) print("Note that there are new compile-time dependencies since around v0.5.1.", file=sys.stderr) print("Check out https://github.com/madcowswe/ODrive/blob/devel/docs/developer-guide.md#prerequisites for details.", file=sys.stderr) From b88f66ab99e514b2bb7d70373da3d725c1b7115c Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 10 Jun 2020 16:09:18 +0200 Subject: [PATCH 460/549] update macOS build instructions --- docs/developer-guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 93d306bb..b169b1f7 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -83,7 +83,7 @@ First install [Homebrew](https://brew.sh/). Then you can run these commands in T brew install armmbed/formulae/arm-none-eabi-gcc brew cask install osxfuse && brew install tup brew install openocd -pip install PyYAML Jinja2 jsonschema +pip3 install PyYAML Jinja2 jsonschema ``` #### Windows From 426236dff1dbe4693dd8fcaf5d50b0debf20ae36 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sat, 23 May 2020 01:31:00 +0200 Subject: [PATCH 461/549] implement HTML API reference autogeneration This adds a jinja template to generate markdown files (with lots of HTML mixed in) from YAML using the existing interface_generator.py script. The docs website layout files are modified to incorporate the new documentation. While previously Github Pages was automatically running Jekyll on the docs folder, this commit adds a custom Github workflow to compile and deploy the website to facilitate the custom markdown generation step before Jekyll runs. --- .github/workflows/documentation.yml | 67 ++++++ .gitignore | 19 +- Firmware/fibre/tools/interface_generator.py | 19 +- docs/Gemfile.lock | 230 ++++++++++---------- docs/_config.yaml | 5 +- docs/_data/index.yaml | 29 +-- docs/_layouts/api_documentation_template.j2 | 148 +++++++++++++ docs/_layouts/api_index_template.j2 | 29 +++ docs/_layouts/default.html | 60 +++-- docs/assets/css/style.scss | 108 +++++++-- docs/developer-guide.md | 11 +- 11 files changed, 540 insertions(+), 185 deletions(-) create mode 100644 .github/workflows/documentation.yml create mode 100644 docs/_layouts/api_documentation_template.j2 create mode 100644 docs/_layouts/api_index_template.j2 diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 00000000..18f8786e --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,67 @@ +name: Build and publish HTML documentation website + +on: + push: + branches: [ feature/doc_autogen ] + +jobs: + jekyll: + runs-on: ubuntu-16.04 + steps: + - uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + + # Use GitHub Actions' cache for ruby and python packages to shorten build times and decrease load on servers + - name: Cache gems + uses: actions/cache@v2 + with: + path: docs/vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('docs/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-PyYAML-Jinja2-jsonschema + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + + - name: Install Python dependencies + run: pip install PyYAML Jinja2 jsonschema + + # Autogenerate the API reference .md files in the python in the python/python3 container + - name: Autogenerate the API reference .md files in the python container + run: | + mkdir -p docs/_api docs/_includes + python Firmware/interface_generator_stub.py --definitions Firmware/odrive-interface.yaml --template docs/_layouts/api_documentation_template.j2 --outputs docs/_api/#.md + python Firmware/interface_generator_stub.py --definitions Firmware/odrive-interface.yaml --template docs/_layouts/api_index_template.j2 --output docs/_includes/apiindex.html + + - name: Build the site in the jekyll/builder container + run: | + docker run \ + -v ${{ github.workspace }}:/srv/jekyll -e PAGES_REPO_NWO=${GITHUB_REPOSITORY} \ + ruby:2.7-buster /bin/sh -c " + chmod 777 /srv/jekyll/docs && \ + cd /srv/jekyll/docs && \ + bundle config path vendor/bundle && \ + bundle install && \ + JEKYLL_ENV=production bundle exec jekyll build + " + touch .nojekyll + + - name: Push to documentation branch + run: | + git config user.name "${GITHUB_ACTOR}" + git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" + git add -f docs/_site + git commit -m "jekyll build from Action ${GITHUB_SHA}" + git push --force origin HEAD:${REMOTE_BRANCH} + env: + REMOTE_BRANCH: gh-pages diff --git a/.gitignore b/.gitignore index 44828929..66a8990f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,9 +24,6 @@ coverage.xml # Django stuff: *.log -# Sphinx documentation -docs/_build/ - # PyBuilder target/ @@ -37,10 +34,18 @@ target/ .tup tup.config -docs/ruby-bundle -docs/_site -docs/.bundle -docs/Gemfile.lock +# Sphinx documentation +/docs/_build/ + +# Autogenerated API reference +/docs/_api +/docs/_includes/apiindex.html + +# Jekyll HTML documention and artifacts +/docs/ruby-bundle +/docs/_site +/docs/.bundle/config +/docs/.jekyll-metadata *.exe diff --git a/Firmware/fibre/tools/interface_generator.py b/Firmware/fibre/tools/interface_generator.py index cb8c6fa5..659d97f6 100644 --- a/Firmware/fibre/tools/interface_generator.py +++ b/Firmware/fibre/tools/interface_generator.py @@ -623,13 +623,17 @@ def tokenize(text, interface, interface_transform, value_type_transform, attribu token_list = split_name(token) # Check if this is an attribute reference - attr_intf = interface - for name in token_list: - if not name in attr_intf['attributes']: - attr = None - break - attr = attr_intf['attributes'][name] - attr_intf = attr['type'] + scope = interface + attr = None + while attr is None and not scope is None: + attr_intf = scope + for name in token_list: + if not name in attr_intf['attributes']: + attr = None + break + attr = attr_intf['attributes'][name] + attr_intf = attr['type'] + scope = scope.get('parent', None) if not attr is None: return attribute_transform(token, attr) @@ -648,6 +652,7 @@ env.filters['first'] = lambda x: next(iter(x)) env.filters['skip_first'] = lambda x: list(x)[1:] env.filters['to_c_string'] = lambda x: '\n'.join(('"' + line.replace('"', '\\"') + '"') for line in json.dumps(x, separators=(',', ':')).replace('{"name"', '\n{"name"').split('\n')) env.filters['tokenize'] = tokenize +env.filters['diagonalize'] = lambda lst: [lst[:i + 1] for i in range(len(lst))] template = env.from_string(template_file.read()) diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 93af7d5e..1490c10c 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,55 +1,57 @@ GEM remote: https://rubygems.org/ specs: - activesupport (4.2.9) - i18n (~> 0.7) + activesupport (6.0.3.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) + zeitwerk (~> 2.2, >= 2.2.2) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.11.1) colorator (1.1.0) - commonmarker (0.17.9) + commonmarker (0.17.13) ruby-enum (~> 0.5) - concurrent-ruby (1.0.5) + concurrent-ruby (1.1.6) + dnsruby (1.61.3) + addressable (~> 2.5) em-websocket (0.5.1) eventmachine (>= 0.12.9) http_parser.rb (~> 0.6.0) - ethon (0.11.0) + ethon (0.12.0) ffi (>= 1.3.0) - eventmachine (1.2.5) + eventmachine (1.2.7) execjs (2.7.0) - faraday (0.14.0) + faraday (1.0.1) multipart-post (>= 1.2, < 3) - ffi (1.9.24) + ffi (1.12.2) forwardable-extended (2.6.0) - gemoji (3.0.0) - github-pages (181) - activesupport (= 4.2.9) - github-pages-health-check (= 1.4.0) - jekyll (= 3.7.4) - jekyll-avatar (= 0.5.0) + gemoji (3.0.1) + github-pages (206) + github-pages-health-check (= 1.16.1) + jekyll (= 3.8.7) + jekyll-avatar (= 0.7.0) jekyll-coffeescript (= 1.1.1) - jekyll-commonmark-ghpages (= 0.1.5) + jekyll-commonmark-ghpages (= 0.1.6) jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.9.3) + jekyll-feed (= 0.13.0) jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.9.4) - jekyll-mentions (= 1.3.0) - jekyll-optional-front-matter (= 0.3.0) + jekyll-github-metadata (= 2.13.0) + jekyll-mentions (= 1.5.1) + jekyll-optional-front-matter (= 0.3.2) jekyll-paginate (= 1.1.0) - jekyll-readme-index (= 0.2.0) - jekyll-redirect-from (= 0.13.0) - jekyll-relative-links (= 0.5.3) - jekyll-remote-theme (= 0.2.3) + jekyll-readme-index (= 0.3.0) + jekyll-redirect-from (= 0.15.0) + jekyll-relative-links (= 0.6.1) + jekyll-remote-theme (= 0.4.1) jekyll-sass-converter (= 1.5.2) - jekyll-seo-tag (= 2.4.0) - jekyll-sitemap (= 1.2.0) - jekyll-swiss (= 0.4.0) + jekyll-seo-tag (= 2.6.1) + jekyll-sitemap (= 1.4.0) + jekyll-swiss (= 1.0.0) jekyll-theme-architect (= 0.1.1) jekyll-theme-cayman (= 0.1.1) jekyll-theme-dinky (= 0.1.1) @@ -59,33 +61,32 @@ GEM jekyll-theme-midnight (= 0.1.1) jekyll-theme-minimal (= 0.1.1) jekyll-theme-modernist (= 0.1.1) - jekyll-theme-primer (= 0.5.3) + jekyll-theme-primer (= 0.5.4) jekyll-theme-slate (= 0.1.1) jekyll-theme-tactile (= 0.1.1) jekyll-theme-time-machine (= 0.1.1) - jekyll-titles-from-headings (= 0.5.1) - jemoji (= 0.9.0) - kramdown (= 1.16.2) - liquid (= 4.0.0) - listen (= 3.1.5) + jekyll-titles-from-headings (= 0.5.3) + jemoji (= 0.11.1) + kramdown (= 1.17.0) + liquid (= 4.0.3) mercenary (~> 0.3) - minima (= 2.4.0) - nokogiri (>= 1.8.5, < 2.0) - rouge (= 2.2.1) + minima (= 2.5.1) + nokogiri (>= 1.10.4, < 2.0) + rouge (= 3.19.0) terminal-table (~> 1.4) - github-pages-health-check (1.4.0) + github-pages-health-check (1.16.1) addressable (~> 2.3) - net-dns (~> 0.8) + dnsruby (~> 1.60) octokit (~> 4.0) - public_suffix (~> 2.0) + public_suffix (~> 3.0) typhoeus (~> 1.3) - html-pipeline (2.7.1) + html-pipeline (2.13.0) activesupport (>= 2) - nokogiri (>= 1.8.5) + nokogiri (>= 1.4) http_parser.rb (0.6.0) i18n (0.9.5) concurrent-ruby (~> 1.0) - jekyll (3.7.4) + jekyll (3.8.7) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) @@ -98,51 +99,50 @@ GEM pathutil (~> 0.9) rouge (>= 1.7, < 4) safe_yaml (~> 1.0) - jekyll-avatar (0.5.0) - jekyll (~> 3.0) + jekyll-avatar (0.7.0) + jekyll (>= 3.0, < 5.0) jekyll-coffeescript (1.1.1) coffee-script (~> 2.2) coffee-script-source (~> 1.11.1) - jekyll-commonmark (1.2.0) + jekyll-commonmark (1.3.1) commonmarker (~> 0.14) - jekyll (>= 3.0, < 4.0) - jekyll-commonmark-ghpages (0.1.5) + jekyll (>= 3.7, < 5.0) + jekyll-commonmark-ghpages (0.1.6) commonmarker (~> 0.17.6) - jekyll-commonmark (~> 1) - rouge (~> 2) + jekyll-commonmark (~> 1.2) + rouge (>= 2.0, < 4.0) jekyll-default-layout (0.1.4) jekyll (~> 3.0) - jekyll-feed (0.9.3) - jekyll (~> 3.3) + jekyll-feed (0.13.0) + jekyll (>= 3.7, < 5.0) jekyll-gist (1.5.0) octokit (~> 4.2) - jekyll-github-metadata (2.9.4) - jekyll (~> 3.1) + jekyll-github-metadata (2.13.0) + jekyll (>= 3.4, < 5.0) octokit (~> 4.0, != 4.4.0) - jekyll-mentions (1.3.0) - activesupport (~> 4.0) + jekyll-mentions (1.5.1) html-pipeline (~> 2.3) - jekyll (~> 3.0) - jekyll-optional-front-matter (0.3.0) - jekyll (~> 3.0) + jekyll (>= 3.7, < 5.0) + jekyll-optional-front-matter (0.3.2) + jekyll (>= 3.0, < 5.0) jekyll-paginate (1.1.0) - jekyll-readme-index (0.2.0) - jekyll (~> 3.0) - jekyll-redirect-from (0.13.0) - jekyll (~> 3.3) - jekyll-relative-links (0.5.3) - jekyll (~> 3.3) - jekyll-remote-theme (0.2.3) - jekyll (~> 3.5) - rubyzip (>= 1.3.0, < 3.0) - typhoeus (>= 0.7, < 2.0) + jekyll-readme-index (0.3.0) + jekyll (>= 3.0, < 5.0) + jekyll-redirect-from (0.15.0) + jekyll (>= 3.3, < 5.0) + jekyll-relative-links (0.6.1) + jekyll (>= 3.3, < 5.0) + jekyll-remote-theme (0.4.1) + addressable (~> 2.0) + jekyll (>= 3.5, < 5.0) + rubyzip (>= 1.3.0) jekyll-sass-converter (1.5.2) sass (~> 3.4) - jekyll-seo-tag (2.4.0) - jekyll (~> 3.3) - jekyll-sitemap (1.2.0) - jekyll (~> 3.3) - jekyll-swiss (0.4.0) + jekyll-seo-tag (2.6.1) + jekyll (>= 3.3, < 5.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-swiss (1.0.0) jekyll-theme-architect (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) @@ -170,8 +170,8 @@ GEM jekyll-theme-modernist (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-primer (0.5.3) - jekyll (~> 3.5) + jekyll-theme-primer (0.5.4) + jekyll (> 3.5, < 5.0) jekyll-github-metadata (~> 2.9) jekyll-seo-tag (~> 2.0) jekyll-theme-slate (0.1.1) @@ -183,62 +183,60 @@ GEM jekyll-theme-time-machine (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-titles-from-headings (0.5.1) - jekyll (~> 3.3) - jekyll-watch (2.0.0) + jekyll-titles-from-headings (0.5.3) + jekyll (>= 3.3, < 5.0) + jekyll-watch (2.2.1) listen (~> 3.0) - jemoji (0.9.0) - activesupport (~> 4.0, >= 4.2.9) + jemoji (0.11.1) gemoji (~> 3.0) html-pipeline (~> 2.2) - jekyll (~> 3.0) - kramdown (1.16.2) - liquid (4.0.0) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) + jekyll (>= 3.0, < 5.0) + kramdown (1.17.0) + liquid (4.0.3) + listen (3.2.1) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) - mini_portile2 (2.3.0) - minima (2.4.0) - jekyll (~> 3.5) + mini_portile2 (2.4.0) + minima (2.5.1) + jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.11.3) - multipart-post (2.0.0) - net-dns (0.8.0) - nokogiri (>= 1.8.5) - mini_portile2 (~> 2.3.0) - octokit (4.8.0) + minitest (5.14.1) + multipart-post (2.1.1) + nokogiri (1.10.9) + mini_portile2 (~> 2.4.0) + octokit (4.18.0) + faraday (>= 0.9) sawyer (~> 0.8.0, >= 0.5.3) - pathutil (0.16.1) + pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (2.0.5) - rb-fsevent (0.10.3) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) - rouge (2.2.1) - ruby-enum (0.7.2) + public_suffix (3.1.1) + rb-fsevent (0.10.4) + rb-inotify (0.10.1) + ffi (~> 1.0) + rouge (3.19.0) + ruby-enum (0.8.0) i18n - ruby_dep (1.5.0) - rubyzip (1.3.0) - safe_yaml (1.0.4) - sass (3.5.6) + rubyzip (2.3.0) + safe_yaml (1.0.5) + sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sawyer (0.8.1) - addressable (>= 2.3.5, < 2.6) - faraday (~> 0.8, < 1.0) + sawyer (0.8.2) + addressable (>= 2.3.5) + faraday (> 0.8, < 2.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) - typhoeus (1.3.0) + typhoeus (1.4.0) ethon (>= 0.9.0) - tzinfo (1.2.5) + tzinfo (1.2.7) thread_safe (~> 0.1) - unicode-display_width (1.3.0) + unicode-display_width (1.7.0) + zeitwerk (2.3.0) PLATFORMS ruby @@ -248,4 +246,4 @@ DEPENDENCIES jekyll-redirect-from BUNDLED WITH - 1.16.1 + 2.1.4 diff --git a/docs/_config.yaml b/docs/_config.yaml index d753141e..54752e15 100644 --- a/docs/_config.yaml +++ b/docs/_config.yaml @@ -1,5 +1,8 @@ theme: jekyll-theme-minimal -exclude: [ruby-bundle] +exclude: [ruby-bundle, vendor] plugins: - jekyll-redirect-from google_analytics: UA-93396600-3 +collections: + api: + output: true diff --git a/docs/_data/index.yaml b/docs/_data/index.yaml index 6d34a8d2..d14d3c73 100644 --- a/docs/_data/index.yaml +++ b/docs/_data/index.yaml @@ -3,34 +3,37 @@ # https://jekyllrb.com/tutorials/navigation/#scenario-8-retrieving-items-based-on-front-matter-properties sections: - - title: For Users + - title: General docs: - title: Getting Started url: / - title: ODrive Tool - url: odrivetool + url: /odrivetool - title: Parameters & Commands - url: commands + url: /commands - title: Interfaces - url: interfaces + url: /interfaces - title: Encoders - url: encoders + url: /encoders - title: Homing & Endstops - url: endstops + url: /endstops - title: Control & Tuning - url: control - - title: Hoverboard Guide - url: hoverboard + url: /control - title: Troubleshooting - url: troubleshooting + url: /troubleshooting + - title: Tutorials + docs: + - title: Hoverboard Guide + url: /hoverboard + - title: API Reference - title: For ODrive Developers docs: - title: Firmware Developer Guide - url: developer-guide + url: /developer-guide - title: Configuring Visual Studio Code - url: configuring-vscode + url: /configuring-vscode - title: Configuring Eclipse - url: configuring-eclipse + url: /configuring-eclipse - title: Component Guides docs: - title: Motor Guide diff --git a/docs/_layouts/api_documentation_template.j2 b/docs/_layouts/api_documentation_template.j2 new file mode 100644 index 00000000..7ac0d834 --- /dev/null +++ b/docs/_layouts/api_documentation_template.j2 @@ -0,0 +1,148 @@ +--- +title: '[% if interface %][[interface.fullname]][% else %][[enum.fullname]][% endif %]' +layout: default +edit_url: 'Firmware/odrive-interface.yaml' +download: + url: 'Firmware/odrive-interface.yaml' + text: 'download as YAML' +--- + +[%- macro interface_ref(type) -%] +**[['[']][[type.name]][[']']]([[type.fullname | lower]])** +[%- endmacro %] + +[%- macro value_type_ref(type) -%] +[%- if type.builtin %] +**[[type.name]]** +[%- else %] +**[['[']][[type.name]][[']']]([[type.fullname | lower]])** +[%- endif %] +[%- endmacro %] + +[% macro attr_ref(token, attr) -%] +**[['[']][[token]][[']']]([[attr.parent.fullname | lower]]#[[attr.name]])** +[%- endmacro %] + +[% if interface %] +[% set scope = interface %] +[% else %] +[% set scope = enum.parent %] +[% endif %] + +[%- macro doc_tokenize(text) %][[ text | tokenize(scope, interface_ref, value_type_ref, attr_ref) ]][% endmacro %] + +[%- macro status_badge(status) %] +[%- if status == 'experimental' %] +Experimental +[%- endif %] +[%- if status == 'deprecated' %] +Deprecated +[%- endif %] +[%- endmacro %] + +[%- macro breadcrumbs(title) %] +# [% for item in title.split('.') | diagonalize -%] +
[[item[-1]]] +[%- if not loop.last %] 〉[% endif %] +[%- endfor %] +[%- endmacro %] + +[% if interface %] + +[[breadcrumbs(interface.fullname)]] + +[%- if interface.doc or interface.brief %] +[[doc_tokenize(interface.brief)]][% if interface.brief and interface.doc %] + +[% endif %][[doc_tokenize(interface.doc)]] +[%- endif %] + +## Attributes + +[% if interface.attributes %] +[% for attr in interface.attributes.values() %] +[%- if attr.type.purename == 'fibre.Property' %] +[[attr.name]] - [[value_type_ref(attr.type.value_type)]]    _([[attr.type.mode]] property)_ +[%- else %] +[[attr.name]] - [[interface_ref(attr.type)]] +[%- endif %] +[[-status_badge(attr.status)]] + +
    +[% if attr.doc or attr.brief %] +[[doc_tokenize(attr.brief)]][% if attr.brief and attr.doc %] + +[% endif %][%- if attr.unit %] + +**Unit:** [[attr.unit]] + +[% endif %][[doc_tokenize(attr.doc)]] +[%- else %] +_No description_ +[%- endif %] +
+[% endfor %] +[% else %] +This interface has no attributes. +[% endif %] + +## Functions + +[% if interface.functions %] +[% for function in interface.functions.values() %] +[[function.name]]([% for arg in function.in.values() | skip_first %][[arg.name]]: [[value_type_ref(arg.type)]][[', ' if not loop.last]][% endfor %])[[' -> ' if function.out]][% for arg in function.out.values() %][[arg.name]]: [[value_type_ref(arg.type)]][[', ' if not loop.last]][% endfor %] + +
    +[% if function.doc or function.brief %] +[[doc_tokenize(function.brief)]][% if function.brief and function.doc %] + +[% endif %][[doc_tokenize(function.doc)]] +[%- else %] +_No description_ +[%- endif %] +[% if function.in.values() | skip_first %] +**Inputs:** +[%- for arg in function.in.values() | skip_first %] + - `[[arg.name]]`: [% if arg.doc %][[doc_tokenize(arg.doc)]][% else %] _No description_[% endif %] +[%- endfor %] +[%- endif %] +[% if function.out.values() %] +**Outputs:** +[%- for arg in function.out.values() %] + - `[[arg.name]]`: [% if arg.doc %][[doc_tokenize(arg.doc)]][% else %] _No description_[% endif %] +[%- endfor %] +[%- endif %] +
+[% endfor %] +[% else %] +This interface has no functions. +[% endif %] + +[% else %] + +[[breadcrumbs(enum.fullname)]] + +[%- if enum.doc or enum.brief %] +[[doc_tokenize(enum.brief)]][% if enum.brief and enum.doc %] + +[% endif %][[doc_tokenize(enum.doc)]] +[%- endif %] + +## [% if enum.is_flags %]Flags[% else %]Values[% endif %] + +[% for k, value in enum['values'].items() %] +[[(enum.name + value.name) | to_macro_case]] – [% if enum.is_flags %]0x[['%08x' | format(value.value)]][% else %][[value.value]][% endif %] +[[-status_badge(value.status)]] + +
    +[% if value.doc or value.brief %] +[[doc_tokenize(value.brief)]][% if value.brief and value.doc %] + +[% endif %][[doc_tokenize(value.doc)]] +[%- else %] +_No description_ +[%- endif %] +
+[% endfor %] + +[% endif %] diff --git a/docs/_layouts/api_index_template.j2 b/docs/_layouts/api_index_template.j2 new file mode 100644 index 00000000..271be252 --- /dev/null +++ b/docs/_layouts/api_index_template.j2 @@ -0,0 +1,29 @@ +[%- macro dump_interfaces(interfaces) %] +[%- for intf in interfaces %] +[%- if intf.interfaces or intf.value_types %] +
  • +{% assign myvar = (page.title + '.') | split: "[[intf.fullname + '.']]" %} + + +
      +[[dump_interfaces(intf.interfaces) | indent(4)]] +[[dump_value_types(intf.enums) | indent(4)]] +
    +
  • +[%- else %] +
  • + +
  • +[%- endif %] +[%- endfor %] +[%- endmacro %] + +[%- macro dump_value_types(value_types) %] +[%- for enum in value_types %] +
  • + +
  • +[%- endfor %] +[%- endmacro %] + +[[dump_interfaces(toplevel_interfaces)]] diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index 9be4b36e..5569b3a5 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -1,3 +1,6 @@ +{% assign pagename = page.url | replace_first: '/', '' | replace: '.html', '' %} +{% if pagename == '' %}{% assign pagename = 'getting-started' %}{% endif %} + @@ -23,20 +26,29 @@ {% endif %}

    {{ site.description | default: site.github.project_tagline }}

    - +
    +
    - +
    +
    {% if site.github.is_project_page %}

    View the Project on GitHub {{ site.github.repository_nwo }}

    {% endif %} @@ -59,15 +71,30 @@
    -
    - {% assign filename = page.url | replace_first: '/', '' | replace: '.html', '.md' %} - {% if filename == '' %}{% assign filename = 'getting-started.md' %}{% endif %} - - - edit on GitHub -
    +
    +
    + + + {% if page.edit_url %} + {% assign edit_url = "https://www.github.com/madcowswe/ODrive/edit/master/" | append: edit_url %} + {% else %} + {% assign edit_url = "https://www.github.com/madcowswe/ODrive/edit/master/docs/" | append: pagename | append: ".md" %} + {% endif %} + edit on GitHub +
    + + {% if page.download %} + + {% endif %} +
    + {{ content }} @@ -105,6 +132,11 @@ } } + diff --git a/docs/assets/css/style.scss b/docs/assets/css/style.scss index 1ddd4fb8..2c60c3c5 100644 --- a/docs/assets/css/style.scss +++ b/docs/assets/css/style.scss @@ -100,7 +100,7 @@ table { width:100%; border-collapse:collapse; display: block; - overflow-x: scroll; + overflow-x: auto; } th, td { @@ -128,10 +128,12 @@ header { float:left; position:fixed; -webkit-font-smoothing:subpixel-antialiased; - - overflow-y: auto; - top: 50px; + height: 100%; + display: flex; + flex-direction: column; + top: 0; bottom: 0; + padding: 10px 0; } @@ -261,7 +263,11 @@ a { } h1 a { - color: unset; + color: unset; +} + +.navitem a { + color: unset; } // a:hover, a:focus { @@ -270,6 +276,7 @@ h1 a { // } /*** Navigation bar ***/ + header > div { margin-right: 20px; } @@ -287,42 +294,57 @@ header li { #navbar { max-width: 250px; + flex: 1; + overflow: auto; + margin: 0; } -header ul p { - margin:0; +.navgroup { + background: #9c9c9c; + margin-top: 20px; +} +.navgroup:first-child { + margin-top: 0px; +} + +#navbar ul { + background-color: #ffffffa6; + margin: 0; +} + +.navheader { + margin:0px; padding-left:5px; display: block; // color: #d60000; color: #000; font-weight: bold; - background-color: #cbcbcb; } -header ul ul li a { - background: #f8f8f8; +.navitem { //border:1px solid #e0e0e0; - line-height:1; font-size:12px; font-weight: bold; color:#676767; display:block; text-align:left; - padding:12px 0px 5px 5px; - //margin:12px; - height:20px; + padding:0px 5px; + margin:0px; + height:37px; + line-height:37px; } -//// rounded edges (look bad) -//header ul p { -// border-radius:5px 5px 0 0; -//} -//header ul ul li:last-child a { -// border-radius:0 0 5px 5px; -//} +.navitem a { display: block; } + +.currentitem { + //-webkit-box-shadow: inset 0px 0px 5px 3px #aa0000a6; + //-moz-box-shadow: inset 0px 0px 5px 3px #aa0000a6; + //box-shadow: inset 0px 0px 5px 3px #aa0000a6; + color: #d60000; +} /*** Navbar Hover ***/ -header ul a:hover, header ul a:focus { +.navitem:hover, .navitem:focus { color: #d60000; // color:rgb(0, 0, 0); // background-color: rgba(0, 0, 0, 0.24); @@ -418,11 +440,14 @@ details > div > p:last-child { border-left-color: #5bc0de; } -/*** edit link ***/ -.edit { +/*** edit/download link ***/ +.pageactions { float: right; font-size: 12px; } +.pageactions > div { + text-align: right; +} /*** inline code ***/ :not(pre) > code { @@ -441,3 +466,38 @@ table th { table tr:nth-child(2n) { background-color: #f8f8f8; } + + +.expandable-list { + height: 100%; + margin: 0px; + //background-color: #ffbfbf61; + max-height: 0; + overflow: hidden; + -webkit-transition: max-height .5s ease-in-out; + transition: max-height .5s ease-in-out; +} + +#navbar input[type=checkbox]:checked ~ .expandable-list { /* reset the height when checkbox is checked */ + max-height: 1000px; +} + +.chevron:before { + text-align: left; + content: "\3009" +} + +.chevron { + float: left; + -webkit-transition: -webkit-transform .5s ease; + transition: transform .5s ease; + transform-origin: 40% 50%; + padding-left: 5px; + padding-right: 5px; +} + +#navbar input[type=checkbox]:checked ~ p .chevron { /* rotate down when checkbox is checked */ + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} diff --git a/docs/developer-guide.md b/docs/developer-guide.md index b169b1f7..f9fa7159 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -258,9 +258,14 @@ To run the docs server locally: ```bash cd docs -gem install bundler -bundle install --path ruby-bundle -bundle exec jekyll serve --host=0.0.0.0 +gem install bundler # The gem command typically comes with a Ruby installation +#export PATH="$PATH:~/.gem/ruby/2.7.0/bin" # or similar (depends on OS) +rm Gemfile.lock # only if below commands cause trouble +bundle config path ruby-bundle +bundle install +mkdir -p _api _includes +python ../Firmware/interface_generator_stub.py --definitions ../Firmware/odrive-interface.yaml --template _layouts/api_documentation_template.j2 --outputs _api/'#'.md && python ../Firmware/interface_generator_stub.py --definitions ../Firmware/odrive-interface.yaml --template _layouts/api_index_template.j2 --output _includes/apiindex.html +bundle exec jekyll serve --incremental --host=0.0.0.0 ``` ## Releases From d304d4e7eb8edb519173ca3dc8b442524104af10 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 10 Jun 2020 22:21:06 +0200 Subject: [PATCH 462/549] move part of documentation into the yaml file The following documentation is moved to odrive-interfaces.yaml: - Error flag documentation from troubleshooting.md - Axis state documentation from commands.md - Input mode documentation from input_modes.md --- Firmware/odrive-interface.yaml | 266 ++++++++++++++++++++++++++++++--- docs/commands.md | 62 +------- docs/input_modes.md | 113 -------------- docs/troubleshooting.md | 98 +----------- 4 files changed, 262 insertions(+), 277 deletions(-) delete mode 100644 docs/input_modes.md diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index f17aca63..39c6f27e 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -193,8 +193,45 @@ interfaces: flags: InvalidState: doc: An invalid state was requested. + doc: | + You tried to run a state before you are allowed to. Typically you + tried to run encoder calibration or closed loop control before the + motor was calibrated, or you tried to run closed loop control + before the encoder was calibrated. DcBusUnderVoltage: + doc: | + Confirm that your power leads are connected securely. For initial + testing a 12V PSU which can supply a couple of amps should be + sufficient while the use of low current ‘wall wart’ plug packs may + lead to inconsistent behaviour and is not recommended. + + You can monitor your PSU voltage using liveplotter in odrivetool + by entering `start_liveplotter(lambda: [odrv0.vbus_voltage])`. If + you see your votlage drop below `config.dc_bus_undervoltage_trip_level` + (default: ~ 8V) then you will trip this error. Even a relatively + small motor can draw multiple kW momentary and so unless you have + a very large PSU or are running of a battery you may encounter + this error when executing high speed movements with a high current + limit. To limit your PSU power draw you can limit your motor + current and/or velocity limit `controller.config.vel_limit` and + `motor.config.current_lim`. DcBusOverVoltage: + doc: | + Confirm that you have a brake resistor of the correct value + connected securely and that `config.brake_resistance` is set to + the value of your brake resistor. + + You can monitor your PSU voltage using liveplotter in odrivetool + 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. CurrentMeasurementTimeout: BrakeResistorDisarmed: doc: The brake resistor was unexpectedly disarmed. @@ -251,7 +288,7 @@ interfaces: enable_step_dir: type: bool doc: Enable step/dir input after calibration. - For M0 this has no effect if `enable_uart` is true. + For M0 this has no effect if `config.enable_uart` is true. step_dir_always_on: type: bool doc: Keep step/dir enabled while the motor is disabled. @@ -327,13 +364,68 @@ interfaces: nullflag: None flags: PhaseResistanceOutOfRange: + doc: | + During calibration the motor resistance and + [inductance](https://en.wikipedia.org/wiki/Inductance) is measured. + If the measured motor resistance or inductance falls outside a set + range this error will be returned. Check that all motor leads are + connected securely. + + The measured values can be viewed using odrivetool as is shown below: + ``` + In [2]: odrv0.axis0.motor.config.phase_inductance + Out[2]: 1.408751450071577e-05 + + In [3]: odrv0.axis0.motor.config.phase_resistance + Out[3]: 0.029788672924041748 + ``` + Some motors will have a considerably different phase resistance + and inductance than this. For example, gimbal motors, some small + motors (e.g. < 10A peak current). If you think this applies to you + try increasing `config.resistance_calib_max_voltage` from + its default value of 1 using odrivetool and repeat the motor + calibration process. If your motor has a small peak current draw + (e.g. < 20A) you can also try decreasing + `config.calibration_current` from its default value of 10A. + + In general, you need + ```text + resistance_calib_max_voltage > calibration_current * phase_resistance + resistance_calib_max_voltage < 0.5 * vbus_voltage + ``` PhaseInductanceOutOfRange: + doc: | + See `PhaseResistanceOutOfRange` for details. AdcFailed: DrvFault: + doc: | + The ODrive v3.4 is known to have a hardware issue whereby the + motors would stop operating when applying high currents to M0. The + reported error of both motors in this case is `ERROR_DRV_FAULT`. + + The conjecture is that the high switching current creates large + ripples in the power supply of the DRV8301 gate driver chips, thus + tripping its under-voltage fault detection. + + To resolve this issue you can limit the M0 current to 40A. The + lowest current at which the DRV fault was observed is 45A on one + test motor and 50A on another test motor. Refer to + [this post](https://discourse.odriverobotics.com/t/drv-fault-on-odrive-v3-4/558) + for instructions for a hardware fix. ControlDeadlineMissed: NotImplementedMotorType: BrakeCurrentOutOfRange: ModulationMagnitude: + doc: | + The bus voltage was insufficent to push the requested current + through the motor. + If you are getting this during motor calibration, make sure that + `config.resistance_calib_max_voltage` is no more than half + your bus voltage. + + For gimbal motors, it is recommended to set the + `config.calibration_current` and `config.current_lim` + to half your bus voltage, or less. BrakeDeadtimeViolation: UnexpectedTimerCallback: CurrentSenseSaturation: @@ -450,6 +542,18 @@ interfaces: nullflag: None flags: Overspeed: + doc: | + Try increasing `config.vel_limit`. The default of 20,000 encoder + counts per second gives a motor speed of only ~146 RPM with the + common CUI-AMT102 8192 count per rotation encoder. Note: Even if + you do not commanded your motor to exceed `config.vel_limit` + sudden changes in the load placed on a motor may cause this speed + to be temporarily exceeded, resulting in this error. + + You can also try increasing `config.vel_limit_tolerance`. The + default value of 1.2 means it will only allow a 20% violation of + the speed limit. You can set the `config.vel_limit_tolerance` to 0 + to disable the check altogether. InvalidInputMode: UnstableGain: InvalidMirrorAxis: @@ -535,10 +639,27 @@ interfaces: flags: UnstableGain: CprPolepairsMismatch: + doc: | + Confirm you have entered the correct count per rotation (CPR) for + [your encoder](https://docs.odriverobotics.com/encoders). The + ODrive uses your supplied value for the motor pole pairs to + measure the CPR. So you should also double check this value. + + Note that the AMT encoders are configurable using the micro- + switches on the encoder PCB and so you may need to check that + these are in the right positions. If your encoder lists its pulse + per rotation (PPR) multiply that number by four to get CPR. NoResponse: + doc: | + Confirm that your encoder is plugged into the right pins on the + ODrive board. UnsupportedEncoderMode: IllegalHallState: IndexNotFoundYet: + doc: | + Check that your encoder is a model that has an index pulse. If + your encoder does not have a wire connected to pin Z on your + ODrive then it does not output an index pulse. AbsSpiTimeout: AbsSpiComFail: AbsSpiNotReady: @@ -635,26 +756,54 @@ valuetypes: Undefined: doc: will fall through to idle Idle: - doc: disable PWM and do nothing + brief: Disable motor PWM and do nothing. StartupSequence: - doc: the actual sequence is defined by the config.startup... flags + brief: Run the startup procedure. + doc: the actual sequence is defined by the `config`.startup... flags FullCalibrationSequence: - doc: run all calibration procedures, then idle + doc: Run motor calibration and then encoder offset calibration (or encoder + index search if `.encoder.config.use_index` is `True`). MotorCalibration: - doc: run motor calibration + brief: Measure phase resistance and phase inductance of the motor. + doc: | + * To store the results set `motor.config.pre_calibrated` to `True` + and save the configuration (`save_configuration()`). After that you + don't have to run the motor calibration on the next start up. + * This modifies the variables `motor.config.phase_resistance` and + `motor.config.phase_inductance`. SensorlessControl: - doc: run sensorless control + brief: Run sensorless control. + doc: | + * The motor must be calibrated (`motor.is_calibrated`) + * `controller.config.control_mode` must be `True`. EncoderIndexSearch: - doc: run encoder index search + brief: Turn the motor in one direction until the encoder index is traversed. + doc: This state can only be entered if `encoder.config.use_index` is `True`. EncoderOffsetCalibration: - doc: run encoder offset calibration + brief: Turn the motor in one direction for a few seconds and then back to measure the offset between the encoder position and the electrical phase. + doc: | + * Can only be entered if the motor is calibrated (`motor.is_calibrated`). + * A successful encoder calibration will make the `encoder.is_ready` + go to true. ClosedLoopControl: - doc: run closed loop control + brief: Run closed loop control. + doc: | + * The action depends on the `controller.config.control_mode`. + * Can only be entered if the motor is calibrated + (`motor.is_calibrated`) and the encoder is ready (`encoder.is_ready`). LockinSpin: - doc: run lockin spin + brief: Run lockin spin. + doc: | + Can only be entered if the motor is calibrated (`motor.is_calibrated`) + or the motor direction is unspecified (`motor.config.direction` == 1) EncoderDirFind: + brief: Run encoder direction search. + doc: | + Can only be entered if the motor is calibrated (`motor.is_calibrated`). Homing: - doc: run axis homing function + brief: Run axis homing function. + doc: + Endstops must be enabled to use this feature. ODrive.Encoder.Mode: values: @@ -676,6 +825,7 @@ valuetypes: # Note: these should be sorted from lowest level of control to # highest level of control, to allow "<" style comparisons. VoltageControl: + doc: this one is not normally used CurrentControl: VelocityControl: PositionControl: @@ -683,14 +833,96 @@ valuetypes: ODrive.Controller.InputMode: values: Inactive: + brief: Disable inputs. Setpoints retain their last value. Passthrough: - VelRamp: - PosFilter: - MixChannels: - TrapTraj: - CurrentRamp: - Mirror: + brief: Pass `input_xxx` through to `xxx_setpoint` directly. + doc: | + ### Valid Inputs: + * `input_pos` + * `input_vel` + * `input_current` + ### Valid Control modes: + * `CONTROL_MODE_VOLTAGE_CONTROL` + * `CONTROL_MODE_CURRENT_CONTROL` + * `CONTROL_MODE_VELOCITY_CONTROL` + * `CONTROL_MODE_POSITION_CONTROL` + VelRamp: + brief: Ramps a velocity command from the current value to the target value. + doc: | + ### Configuration Values: + * `config.vel_ramp_rate` [cpr/sec] + * `config.inertia` [A/(count/s^2))] + + ### Valid inputs: + * `input_vel` + + ### Valid Control Modes: + * `CONTROL_MODE_VELOCITY_CONTROL` + PosFilter: + brief: Implements a 2nd order position tracking filter. + doc: | + Intended for use with step/dir interface, but can also be used with + position-only commands. + + ![POS Filter Response](../secondOrderResponse.PNG) + Result of a step command from 1000 to 0 + + ### Configuration Values: + * `config.input_filter_bandwidth` + * `config.inertia` + + ### Valid inputs: + * `input_pos` + + ### Valid Control modes: + * `CONTROL_MODE_POSITION_CONTROL` + MixChannels: + brief: Not Implemented. + TrapTraj: + brief: Implementes an online trapezoidal trajectory planner. + doc: | + ![Trapezoidal Planner Response](../TrapTrajPosVel.PNG) + + ### Configuration Values: + * `trap_traj.config.vel_limit` + * `trap_traj.config.accel_limit` + * `trap_traj.config.decel_limit` + * `config.inertia` + + ### Valid Inputs: + * `input_pos` + + ### Valid Control Modes: + * `CONTROL_MODE_POSITION_CONTROL` + CurrentRamp: + brief: Ramp a current command from the current value to the target value. + doc: | + ### Configuration Values: + * `config.current_ramp_rate` + + ### Valid Inputs: + * `input_current` + + ### Valid Control Modes: + * `CONTROL_MODE_CURRENT_CONTROL` + Mirror: + brief: Implements "electronic mirroring". + doc: | + This is like electronic camming, but you can only mirror exactly the + movements of the other motor, according to a fixed ratio. + + [![](http://img.youtube.com/vi/D4_vBtyVVzM/0.jpg)](http://www.youtube.com/watch?v=D4_vBtyVVzM "Example Mirroring Video") + + ### Configuration Values + * `config.axis_to_mirror` + * `config.mirror_ratio` + + ### Valid Inputs + * None. Inputs are taken directly from the other axis encoder estimates + + ### Valid Control modes + * `CONTROL_MODE_POSITION_CONTROL` ODrive.Motor.MotorType: values: diff --git a/docs/commands.md b/docs/commands.md index 8849c9dc..28a184a2 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -18,30 +18,7 @@ For the most part, both axes on the ODrive can be controlled independently. ### State Machine -The current state of an axis is indicated by `.current_state`. The user can request a new state by assigning a new value to `.requested_state`. The default state after startup is `AXIS_STATE_IDLE`. - - 1. `AXIS_STATE_IDLE` Disable motor PWM and do nothing. - 2. `AXIS_STATE_STARTUP_SEQUENCE` Run the [startup procedure](#startup-procedure). - 3. `AXIS_STATE_FULL_CALIBRATION_SEQUENCE` Run motor calibration and then encoder offset calibration (or encoder index search if `.encoder.config.use_index` is `True`). - 4. `AXIS_STATE_MOTOR_CALIBRATION` Measure phase resistance and phase inductance of the motor. - * To store the results set `.motor.config.pre_calibrated` to `True` and [save the configuration](#saving-the-configuration). After that you don't have to run the motor calibration on the next start up. - * This modifies the variables `.motor.config.phase_resistance` and `.motor.config.phase_inductance`. - 5. `AXIS_STATE_SENSORLESS_CONTROL` Run sensorless control. - * The motor must be calibrated (`.motor.is_calibrated`) - * [`.controller.control_mode`](#control-mode) must be `True`. - 6. `AXIS_STATE_ENCODER_INDEX_SEARCH` Turn the motor in one direction until the encoder index is traversed. This state can only be entered if `.encoder.config.use_index` is `True`. - 7. `AXIS_STATE_ENCODER_OFFSET_CALIBRATION` Turn the motor in one direction for a few seconds and then back to measure the offset between the encoder position and the electrical phase. - * Can only be entered if the motor is calibrated (`.motor.is_calibrated`). - * A successful encoder calibration will make the `.encoder.is_ready` go to true. - 8. `AXIS_STATE_CLOSED_LOOP_CONTROL` Run closed loop control. - * The action depends on the [control mode](#control-mode). - * Can only be entered if the motor is calibrated (`.motor.is_calibrated`) and the encoder is ready (`.encoder.is_ready`). - 9. `AXIS_STATE_LOCKIN_SPIN` Run lockin spin. - * Can only be entered if the motor is calibrated (`.motor.is_calibrated`) or the motor direction is unspecified (`.motor.config.direction == 1`) - 10. `AXIS_STATE_ENCODER_DIR_FIND` Run encoder direction search. - * Can only be entered if the motor is calibrated (`.motor.is_calibrated`). - 11. `AXIS_STATE_HOMING` Run axis homing function. - * Endstops must be enabled to use this feature. +The current state of an axis is indicated by [`.current_state`](api/odrive.axis#current_state). The user can request a new state by assigning a new value to [`.requested_state`](api/odrive.axis#current_state). The default state after startup is `AXIS_STATE_IDLE`. A description of all states can be found [here](api/odrive.axis.axisstate). ### Startup Procedure @@ -56,49 +33,24 @@ The ODrive will sequence all enabled startup actions selected in the order shown * `.config.startup_closed_loop_control` * `.config.startup_sensorless_control` -See [state machine](#state-machine) for a description of each state. +See [here](api/odrive.axis.axisstate) for a description of each state. ### Control Mode The default control mode is position control. If you want a different mode, you can change `.controller.config.control_mode`. -Possible values are: -* `CONTROL_MODE_POSITION_CONTROL` -* `CONTROL_MODE_VELOCITY_CONTROL` -* `CONTROL_MODE_CURRENT_CONTROL` -* `CONTROL_MODE_VOLTAGE_CONTROL` - this one is not normally used. +Possible values are listed [here](api/odrive.axis.controller.controlmode). ### Input Mode -The default input mode is `INPUT_MODE_PASSTHROUGH`. -Modes can be selected by changing `.controller.config.input_mode`. -Possible values are: -* `INPUT_MODE_INACTIVE` -* `INPUT_MODE_PASSTHROUGH` -* `INPUT_MODE_VEL_RAMP` -* `INPUT_MODE_POS_FILTER` -* `INPUT_MODE_MIX_CHANNELS` -* `INPUT_MODE_TRAP_TRAJ` -* `INPUT_MODE_CURRENT_RAMP` -* `INPUT_MODE_MIRROR` -For more information, see [input_modes](input_modes.md). +As of version v0.5.0, ODrive now intercepts the incoming commands and can apply filters to them. The old protocol values `pos_setpoint`, `vel_setpoint`, and `current_setpoint` are still used internally by the closed-loop cascade control, but the user cannot write to them directly. This allows us to condense the number of ways the ODrive accepts motion commands. The new commands are: -# Control Commands * `.controller.input_pos = ` * `.controller.input_vel = ` * `.controller.input_current = ` -### Input Mode -To modify the way the control command affects the motor, you can use the input mode. The default input mode is pass through. -If you want a different mode, you can change `.controller.config.input_mode`. -Possible values are: -* `INPUT_MODE_INACTIVE` -* `INPUT_MODE_PASSTHROUGH` -* `INPUT_MODE_VEL_RAMP` -* `INPUT_MODE_POS_FILTER` -* `INPUT_MODE_MIX_CHANNELS` -* `INPUT_MODE_TRAP_TRAJ` -* `INPUT_MODE_CURRENT_RAMP` -* `INPUT_MODE_MIRROR` +Modes can be selected by changing `.controller.config.input_mode`. +The default input mode is `INPUT_MODE_PASSTHROUGH`. +Possible values are listed [here](api/odrive.axis.controller.inputmode). ## System monitoring commands diff --git a/docs/input_modes.md b/docs/input_modes.md deleted file mode 100644 index be449e24..00000000 --- a/docs/input_modes.md +++ /dev/null @@ -1,113 +0,0 @@ -# Input Modes -As of version ###, ODrive now intercepts the incoming commands and can apply filters to them. The old protocol values `pos_setpoint`, `vel_setpoint`, and `current_setpoint` are still used internally by the closed-loop cascade control, but the user cannot write to them directly. This allows us to condense the number of ways the ODrive accepts motion commands. The new commands are: - -* `.controller.config.input_mode` -* `.controller.input_pos` -* `.controller.input_vel` -* `.controller.input_current` - -The Input Modes currently valid are: -* `INPUT_MODE_INACTIVE` -* `INPUT_MODE_PASSTHROUGH` -* `INPUT_MODE_VEL_RAMP` -* `INPUT_MODE_POS_FILTER` -* `INPUT_MODE_MIX_CHANNELS` -* `INPUT_MODE_TRAP_TRAJ` -* `INPUT_MODE_CURRENT_RAMP` -* `INPUT_MODE_MIRROR` - ---- - -## INPUT_MODE_INACTIVE -Disable inputs. Setpoints retain their last value. - -## INPUT_MODE_PASSTHROUGH -Pass `input_xxx` through to `xxx_setpoint` directly. - -### Valid Inputs: -* `input_pos` -* `input_vel` -* `input_current` - -### Valid Control modes: -* `CONTROL_MODE_VOLTAGE_CONTROL` -* `CONTROL_MODE_CURRENT_CONTROL` -* `CONTROL_MODE_VELOCITY_CONTROL` -* `CONTROL_MODE_POSITION_CONTROL` - -## INPUT_MODE_VEL_RAMP -Ramps a velocity command from the current value to the target value. - -### Configuration Values: -* `.controller.config.vel_ramp_rate` [cpr/sec] -* `.controller.config.inertia` [A/(count/s^2))] - -### Valid inputs: -* `input_vel` - -### Valid Control Modes: -* `CONTROL_MODE_VELOCITY_CONTROL` - -## INPUT_MODE_POS_FILTER -Implements a 2nd order position tracking filter. Inteded for use with step/dir interface, but can also be used with position-only commands. - -![POS Filter Response](secondOrderResponse.png) -Result of a step command from 1000 to 0 - -### Configuration Values: -* `.controller.config.input_filter_bandwidth` -* `.controller.config.inertia` - -### Valid inputs: -* `input_pos` - -### Valid Control modes: -* `CONTROL_MODE_POSITION_CONTROL` - -## INPUT_MODE_MIX_CHANNELS -Not Implemented. - - -## INPUT_MODE_TRAP_TRAJ -Implementes an online trapezoidal trajectory planner. - -![Trapezoidal Planner Response](TrapTrajPosVel.png) - -### Configuration Values: -* `.trap_traj.config.vel_limit` -* `.trap_traj.config.accel_limit` -* `.trap_traj.config.decel_limit` -* `.controller.config.inertia` - -### Valid Inputs: -* `input_pos` - -### Valid Control Modes: -* `CONTROL_MODE_POSITION_CONTROL` - -## INPUT_MODE_CURRENT_RAMP -Ramp a current command from the current value to the target value. - -### Configuration Values: -* `.controller.config.current_ramp_rate` - -### Valid Inputs: -* `input_current` - -### Valid Control Modes: -* `CONTROL_MODE_CURRENT_CONTROL` - -## INPUT_MODE_MIRROR -Implements "electronic mirroring". This is like electronic camming, but you can only mirror exactly the movements of the other motor, according to a fixed ratio - -[![](http://img.youtube.com/vi/D4_vBtyVVzM/0.jpg)](http://www.youtube.com/watch?v=D4_vBtyVVzM "Example Mirroring Video") - -### Configuration Values -* `.controller.config.axis_to_mirror` -* `.controller.config.mirror_ratio` - -### Valid Inputs -* None. Inputs are taken directly from the other axis encoder estimates - -### Valid Control modes -* `CONTROL_MODE_POSITION_CONTROL` diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 8419a8be..a06366eb 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -4,10 +4,6 @@ Table of Contents: - [Error codes](#error-codes) -- [Common Axis Errors](#common-axis-errors) -- [Common Motor Errors](#common-motor-errors) -- [Common Encoder Errors](#common-encoder-errors) -- [Common Controller Errors](#common-controller-errors) - [USB Connectivity Issues](#usb-connectivity-issues) - [Firmware Issues](#firmware-issues) - [Other issues that may not produce an error code](#other-issues-that-may-not-produce-an-error-code) @@ -17,94 +13,12 @@ Table of Contents: ## Error codes If your ODrive is not working as expected, run `odrivetool` and type `dump_errors(odrv0)` Enter. This will dump a list of all the errors that are present. To also clear all the errors, you can run `dump_errors(odrv0, True)`. -The following sections will give some guidance on the most common errors. You may also check the code for the full list of errors: -* Axis error flags defined [here](../Firmware/MotorControl/axis.hpp). -* Motor error flags defined [here](../Firmware/MotorControl/motor.hpp). -* Encoder error flags defined [here](../Firmware/MotorControl/encoder.hpp). -* Controller error flags defined [here](../Firmware/MotorControl/controller.hpp). -* Sensorless estimator error flags defined [here](../Firmware/MotorControl/sensorless_estimator.hpp). - -## Common Axis Errors - -* `ERROR_INVALID_STATE = 0x01` - -You tried to run a state before you are allowed to. Typically you tried to run encoder calibration or closed loop control before the motor was calibrated, or you tried to run closed loop control before the encoder was calibrated. - -* `ERROR_DC_BUS_UNDER_VOLTAGE = 0x02` - -Confirm that your power leads are connected securely. For initial testing a 12V PSU which can supply a couple of amps should be sufficient while the use of low current 'wall wart' plug packs may lead to inconsistent behaviour and is not recommended. - -You can monitor your PSU voltage using liveplotter in odrive tool by entering `start_liveplotter(lambda: [odrv0.vbus_voltage])`. If you see your votlage drop below ~ 8V then you will trip this error. Even a relatively small motor can draw multiple kW momentary and so unless you have a very large PSU or are running of a battery you may encounter this error when executing high speed movements with a high current limit. To limit your PSU power draw you can limit your motor current and/or velocity limit `odrv0.axis0.controller.config.vel_limit` and `odrv0.axis0.motor.config.current_lim`. - -* `ERROR_DC_BUS_OVER_VOLTAGE = 0x04` - -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 PSU 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 - -* `ERROR_PHASE_RESISTANCE_OUT_OF_RANGE = 0x0001` and `ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE = 0x0002` - -During calibration the motor resistance and [inductance](https://en.wikipedia.org/wiki/Inductance) is measured. If the measured motor resistance or inductance falls outside a set range this error will be returned. Check that all motor leads are connected securely. - -The measured values can be viewed using odrivetool as is shown below: -``` -In [2]: odrv0.axis0.motor.config.phase_inductance -Out[2]: 1.408751450071577e-05 - -In [3]: odrv0.axis0.motor.config.phase_resistance -Out[3]: 0.029788672924041748 -``` -Some motors will have a considerably different phase resistance and inductance than this. For example, gimbal motors, some small motors (e.g. < 10A peak current). If you think this applies to you try increasing `odrv0.axis0.motor.config.resistance_calib_max_voltage` from its default value of 1 using odrivetool and repeat the motor calibration process. If your motor has a small peak current draw (e.g. < 20A) you can also try decreasing `odrv0.axis0.motor.config.calibration_current` from its default value of 10A. - -In general, you need -```text -resistance_calib_max_voltage > calibration_current * phase_resistance -resistance_calib_max_voltage < 0.5 * vbus_voltage -``` - -* `ERROR_DRV_FAULT = 0x0008` - -The ODrive v3.4 is known to have a hardware issue whereby the motors would stop operating -when applying high currents to M0. The reported error of both motors in this case -is `ERROR_DRV_FAULT`. - -The conjecture is that the high switching current creates large ripples in the -power supply of the DRV8301 gate driver chips, thus tripping its under-voltage fault detection. - -To resolve this issue you can limit the M0 current to 40A. The lowest current at which the DRV fault was observed is 45A on one test motor and 50A on another test motor. Refer to [this post](https://discourse.odriverobotics.com/t/drv-fault-on-odrive-v3-4/558) for instructions for a hardware fix. - -* `ERROR_MODULATION_MAGNITUDE = 0x0080` - -The bus voltage was insufficent to push the requested current through the motor. -If you are getting this during motor calibration, make sure that `motor.config.resistance_calib_max_voltage` is no more than half your bus voltage. - -For gimbal motors, it is recommended to set the `motor.config.calibration_current` and `motor.config.current_lim` to half your bus voltage, or less. - -## Common Encoder Errors - -* `ERROR_CPR_POLEPAIRS_MISMATCH = 0x02` - -Confirm you have entered the correct count per rotation (CPR) for [your encoder](https://docs.odriverobotics.com/encoders). The ODrive uses your supplied value for the motor pole pairs to measure the CPR. So you should also double check this value. - -Note that the AMT encoders are configurable using the micro-switches on the encoder PCB and so you may need to check that these are in the right positions. If your encoder lists its pulse per rotation (PPR) multiply that number by four to get CPR. - -* `ERROR_NO_RESPONSE = 0x04` - -Confirm that your encoder is plugged into the right pins on the odrive board. - -* `ERROR_INDEX_NOT_FOUND_YET = 0x20` - -Check that your encoder is a model that has an index pulse. If your encoder does not have a wire connected to pin Z on your odrive then it does not output an index pulse. - -## Common Controller Errors - -* `ERROR_OVERSPEED = 0x01` - -Try increasing `.controller.config.vel_limit`. The default `vel_limit` of 20,000 encoder counts per second gives a motor speed of only ~146 RPM with the common CUI-AMT102 8192 count per rotation encoder. Note: Even if you do not commanded your motor to exceed `vel_limit` sudden changes in the load placed on a motor may cause this speed to be temporarily exceeded, resulting in this error. - -You can also try increasing `.controller.config.vel_limit_tolerance`. The default value of 1.2 means it will only allow a 20% violation of the speed limit. You can set the `vel_limit_tolerance` to 0 to disable the check altogether. +With this information you can look up the API documentation for your error(s): +* Axis error flags documented [here](api/odrive.axis.error). +* Motor error flags documented [here](api/odrive.motor.error). +* Encoder error flags documented [here](api/odrive.encoder.error). +* Controller error flags documented [here](api/odrive.controller.error). +* Sensorless estimator error flags documented [here](odrive.sensorlessestimator.error). ## USB Connectivity Issues From 445f41aa2c0b7fb568419fbbaa2737737e288a24 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 10 Jun 2020 16:56:08 +0200 Subject: [PATCH 463/549] Enhance API documentation --- Firmware/odrive-interface.yaml | 84 +++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 22 deletions(-) diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 39c6f27e..807e86ca 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -8,9 +8,26 @@ dictionary: [ODrive] # Prevent the word 'ODrive' from being detected as two word interfaces: ODrive: c_is_class: True + brief: Toplevel interface of your ODrive. + doc: | + The odrv0, odrv1, ... objects that appear in odrivetool implement this + toplevel interface. attributes: - vbus_voltage: readonly float32 - ibus: readonly float32 + vbus_voltage: + type: readonly float32 + unit: V + brief: Voltage on the DC bus as measured by the ODrive. + ibus: + type: readonly float32 + unit: A + brief: Current on the DC bus as calculated by the ODrive. + doc: | + A positive value means that the ODrive is consuming power from the power supply, + a negative value means that the ODrive is sourcing power to the power supply. + + This value is equal to the sum of the motor currents and the brake resistor currents. + The motor currents are measured, the brake resistor current is calculated based on + `config.brake_resistance`. serial_number: readonly uint64 hw_version_major: readonly uint8 hw_version_minor: readonly uint8 @@ -65,7 +82,8 @@ interfaces: doc: 'TODO: changing this currently requires a reboot - fix this' uart_baudrate: type: uint32 - doc: "Defines the baudrate used on the UART interface. + doc: | + Defines the baudrate used on the UART interface. Some baudrates will have a small timing error due to hardware limitations. Here's an (incomplete) list of baudrates for ODrive v3.x: @@ -85,62 +103,71 @@ interfaces: 1.792 MBps | 1.826 MBps | 1.9 1.8432 MBps | 1.826 MBps | 0.93 - For more information refer to Section 30.3.4 and Table 142 (the column with f_PCLK = 42 MHz) in the STM datasheet: - https://www.st.com/content/ccc/resource/technical/document/reference_manual/3d/6d/5a/66/b4/99/40/d4/DM00031020.pdf/files/DM00031020.pdf/jcr:content/translations/en.DM00031020.pdf" + For more information refer to Section 30.3.4 and Table 142 (the column with f_PCLK = 42 MHz) in the + [STM datasheet](https://www.st.com/content/ccc/resource/technical/document/reference_manual/3d/6d/5a/66/b4/99/40/d4/DM00031020.pdf/files/DM00031020.pdf/jcr:content/translations/en.DM00031020.pdf). enable_i2c_instead_of_can: type: bool - doc: 'Changing this requires a reboot' + doc: Changing this requires a reboot. enable_ascii_protocol_on_usb: bool max_regen_current: float32 brake_resistance: type: float32 unit: Ohm - doc: Value of the brake resistor connected to the ODrive. Set to 0 to disable. + brief: Value of the brake resistor connected to the ODrive. + doc: Set to 0 to disable. dc_bus_undervoltage_trip_level: type: float32 unit: V - doc: Minimum voltage below which the motor stops operating. + brief: Minimum voltage below which the motor stops operating. dc_bus_overvoltage_trip_level: type: float32 unit: V - doc: Maximum voltage above which the motor stops operating. + brief: Maximum voltage above which the motor stops operating. + doc: | 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. enable_dc_bus_overvoltage_ramp: type: bool - doc: 'If enabled, if the measured DC voltage exceeds `dc_bus_overvoltage_ramp_start`, + status: experimental + brief: Enables the DC bus overvoltage ramp feature. + doc: | + If enabled, if the measured DC voltage exceeds `dc_bus_overvoltage_ramp_start`, the ODrive will sink more power than usual into the the brake resistor in an attempt to bring the voltage down again. The brake duty cycle is increased by the following amount: - vbus_voltage == dc_bus_overvoltage_ramp_start => brake_duty_cycle += 0% - vbus_voltage == dc_bus_overvoltage_ramp_end => brake_duty_cycle += 100% + + * `vbus_voltage` == `dc_bus_overvoltage_ramp_start` => brake_duty_cycle += 0% + * `vbus_voltage` == `dc_bus_overvoltage_ramp_end` => brake_duty_cycle += 100% Remarks: - This feature is active even when all motors are disarmed. - - This feature is disabled if `brake_resistance` is non-positive.' + - This feature is disabled if `brake_resistance` is non-positive. dc_bus_overvoltage_ramp_start: type: float32 - doc: See `enable_dc_bus_overvoltage_ramp`. - Do not set this lower than your usual vbus_voltage, + status: experimental + brief: See `enable_dc_bus_overvoltage_ramp`. + doc: Do not set this lower than your usual `vbus_voltage`, unless you like fried brake resistors. dc_bus_overvoltage_ramp_end: type: float32 - doc: See `enable_dc_bus_overvoltage_ramp`. - Must be larger than `dc_bus_overvoltage_ramp_start`, + status: experimental + brief: See `enable_dc_bus_overvoltage_ramp`. + doc: Must be larger than `dc_bus_overvoltage_ramp_start`, otherwise the ramp feature is disabled. dc_max_positive_current: type: float32 unit: A - doc: Max current the power supply can source. + brief: Max current the power supply can source. dc_max_negative_current: type: float32 unit: A - doc: Max current the power supply can sink. You most likely want a non-positive value here. Set to -INFINITY to disable. + brief: Max current the power supply can sink. + doc: You most likely want a non-positive value here. Set to -INFINITY to disable. gpio1_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[0]'} # TODO: disable for ODrive v3.2 and older gpio2_pwm_mapping: {type: Endpoint, c_name: 'pwm_mappings[1]'} # TODO: disable for ODrive v3.2 and older @@ -192,13 +219,14 @@ interfaces: nullflag: 'None' flags: InvalidState: - doc: An invalid state was requested. + brief: An invalid state was requested. doc: | You tried to run a state before you are allowed to. Typically you tried to run encoder calibration or closed loop control before the motor was calibrated, or you tried to run closed loop control before the encoder was calibrated. DcBusUnderVoltage: + brief: The DC voltage fell below the limit configured in `config.dc_bus_undervoltage_trip_level`. doc: | Confirm that your power leads are connected securely. For initial testing a 12V PSU which can supply a couple of amps should be @@ -216,6 +244,7 @@ interfaces: current and/or velocity limit `controller.config.vel_limit` and `motor.config.current_lim`. DcBusOverVoltage: + brief: The DC voltage exceeded the limit configured in `config.dc_bus_overvoltage_trip_level`. doc: | Confirm that you have a brake resistor of the correct value connected securely and that `config.brake_resistance` is set to @@ -244,7 +273,7 @@ interfaces: doc: Check `encoder.error` for more information. ControllerFailed: PosCtrlDuringSensorless: - doc: DEPRECATED + status: deprecated WatchdogTimerExpired: MinEndstopPressed: MaxEndstopPressed: @@ -364,6 +393,7 @@ interfaces: nullflag: None flags: PhaseResistanceOutOfRange: + brief: The measured motor phase resistance is outside of the plausible range. doc: | During calibration the motor resistance and [inductance](https://en.wikipedia.org/wiki/Inductance) is measured. @@ -394,10 +424,12 @@ interfaces: resistance_calib_max_voltage < 0.5 * vbus_voltage ``` PhaseInductanceOutOfRange: + brief: The measured motor phase inductance is outside of the plausible range. doc: | See `PhaseResistanceOutOfRange` for details. AdcFailed: DrvFault: + brief: The gate driver chip reported an error. doc: | The ODrive v3.4 is known to have a hardware issue whereby the motors would stop operating when applying high currents to M0. The @@ -627,7 +659,15 @@ interfaces: cogging_ratio: readonly float32 anticogging_enabled: bool functions: - move_incremental: {in: {displacement: float32, from_input_pos: bool}} + move_incremental: + doc: Moves the axes' goal point by a specified increment. + in: + displacement: {type: float32, doc: The desired position change.} + from_input_pos: {type: bool, doc: + 'If true, the increment is applied relative to `input_pos`. + If false, the increment is applied relative to `pos_setpoint`, which + usually corresponds roughly to the current position of the axis.' + } start_anticogging_calibration: From 8bd35aaff5d795ef091c4788fe637de94e39c0c0 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 15 Jun 2020 14:20:32 +0200 Subject: [PATCH 464/549] tweak appearance --- docs/_layouts/api_documentation_template.j2 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/_layouts/api_documentation_template.j2 b/docs/_layouts/api_documentation_template.j2 index 7ac0d834..53fd127a 100644 --- a/docs/_layouts/api_documentation_template.j2 +++ b/docs/_layouts/api_documentation_template.j2 @@ -12,10 +12,10 @@ download: [%- endmacro %] [%- macro value_type_ref(type) -%] -[%- if type.builtin %] -**[[type.name]]** -[%- else %] -**[['[']][[type.name]][[']']]([[type.fullname | lower]])** +[%- if type.builtin -%] +[[type.name]] +[%- else -%] +[['[']][[type.name]][[']']]([[type.fullname | lower]]) [%- endif %] [%- endmacro %] @@ -62,9 +62,9 @@ download: [% if interface.attributes %] [% for attr in interface.attributes.values() %] [%- if attr.type.purename == 'fibre.Property' %] -[[attr.name]] - [[value_type_ref(attr.type.value_type)]]    _([[attr.type.mode]] property)_ +**[[attr.name]]**  —  [[value_type_ref(attr.type.value_type)]]    _[[attr.type.mode]]_ [%- else %] -[[attr.name]] - [[interface_ref(attr.type)]] +**[[attr.name]]**  —  [[interface_ref(attr.type)]] [%- endif %] [[-status_badge(attr.status)]] @@ -90,7 +90,7 @@ This interface has no attributes. [% if interface.functions %] [% for function in interface.functions.values() %] -[[function.name]]([% for arg in function.in.values() | skip_first %][[arg.name]]: [[value_type_ref(arg.type)]][[', ' if not loop.last]][% endfor %])[[' -> ' if function.out]][% for arg in function.out.values() %][[arg.name]]: [[value_type_ref(arg.type)]][[', ' if not loop.last]][% endfor %] +**[[function.name]]**([% for arg in function.in.values() | skip_first %][[arg.name]]: [[value_type_ref(arg.type)]][[', ' if not loop.last]][% endfor %])[% if function.out %]  ➔  [% for arg in function.out.values() %][[arg.name]]: [[value_type_ref(arg.type)]][[', ' if not loop.last]][% endfor %][% endif %]
      [% if function.doc or function.brief %] @@ -131,7 +131,7 @@ This interface has no functions. ## [% if enum.is_flags %]Flags[% else %]Values[% endif %] [% for k, value in enum['values'].items() %] -[[(enum.name + value.name) | to_macro_case]] – [% if enum.is_flags %]0x[['%08x' | format(value.value)]][% else %][[value.value]][% endif %] +**[[(enum.name + value.name) | to_macro_case]]**  —  [% if enum.is_flags %]0x[['%08x' | format(value.value)]][% else %][[value.value]][% endif %] [[-status_badge(value.status)]]
        From e2527ec468e87cb5ca106667509bb52e30ae98b9 Mon Sep 17 00:00:00 2001 From: pjohnson Date: Tue, 16 Jun 2020 15:55:18 -0400 Subject: [PATCH 465/549] added torque_constant to motor config struct and interface file changed all instances of current_setpoint to torque_setpoint modified limitVel() to use torque instead of current units removed effective_current_lim, added effective_torque_lim() added torque_lim to motor config struct and interface file --- Firmware/MotorControl/axis.cpp | 24 +++++++------- Firmware/MotorControl/controller.cpp | 49 ++++++++++++++-------------- Firmware/MotorControl/controller.hpp | 5 +-- Firmware/MotorControl/motor.cpp | 19 ++++++----- Firmware/MotorControl/motor.hpp | 8 ++++- Firmware/odrive-interface.yaml | 7 +++- 6 files changed, 63 insertions(+), 49 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index be15ae3e..2882ca2a 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -286,10 +286,10 @@ bool Axis::run_sensorless_control_loop() { run_control_loop([this](){ // Note that all estimators are updated in the loop prefix in run_control_loop - float current_setpoint; - if (!controller_.update(¤t_setpoint)) + float torque_setpoint; + if (!controller_.update(&torque_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; - if (!motor_.update(current_setpoint, sensorless_estimator_.phase_, sensorless_estimator_.vel_estimate_)) + if (!motor_.update(torque_setpoint, sensorless_estimator_.phase_, sensorless_estimator_.vel_estimate_)) return false; // set_error should update axis.error_ return true; }); @@ -311,12 +311,12 @@ bool Axis::run_closed_loop_control_loop() { set_step_dir_active(config_.enable_step_dir); run_control_loop([this](){ // Note that all estimators are updated in the loop prefix in run_control_loop - float current_setpoint; - if (!controller_.update(¤t_setpoint)) + float torque_setpoint; + if (!controller_.update(&torque_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; float phase_vel = 2 * M_PI * encoder_.vel_estimate_ / (float)encoder_.config_.cpr * motor_.config_.pole_pairs; - if (!motor_.update(current_setpoint, encoder_.phase_, phase_vel)) + if (!motor_.update(torque_setpoint, encoder_.phase_, phase_vel)) return false; // set_error should update axis.error_ return true; @@ -360,12 +360,12 @@ bool Axis::run_homing() { run_control_loop([this](){ // Note that all estimators are updated in the loop prefix in run_control_loop - float current_setpoint; - if (!controller_.update(¤t_setpoint)) + float torque_setpoint; + if (!controller_.update(&torque_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; float phase_vel = 2 * M_PI * encoder_.vel_estimate_ / (float)encoder_.config_.cpr * motor_.config_.pole_pairs; - if (!motor_.update(current_setpoint, encoder_.phase_, phase_vel)) + if (!motor_.update(torque_setpoint, encoder_.phase_, phase_vel)) return false; // set_error should update axis.error_ return !min_endstop_.get_state(); @@ -389,12 +389,12 @@ bool Axis::run_homing() { run_control_loop([this](){ // Note that all estimators are updated in the loop prefix in run_control_loop - float current_setpoint; - if (!controller_.update(¤t_setpoint)) + float torque_setpoint; + if (!controller_.update(&torque_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; float phase_vel = 2 * M_PI * encoder_.vel_estimate_ / (float)encoder_.config_.cpr * motor_.config_.pole_pairs; - if (!motor_.update(current_setpoint, encoder_.phase_, phase_vel)) + if (!motor_.update(torque_setpoint, encoder_.phase_, phase_vel)) return false; // set_error should update axis.error_ return !controller_.trajectory_done_; diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 6f918c8b..2138af3c 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -14,7 +14,7 @@ void Controller::reset() { pos_setpoint_ = 0.0f; vel_setpoint_ = 0.0f; vel_integrator_current_ = 0.0f; - current_setpoint_ = 0.0f; + torque_setpoint_ = 0.0f; } void Controller::set_error(Error error) { @@ -115,13 +115,13 @@ void Controller::update_filter_gains() { input_filter_kp_ = 0.25f * (input_filter_ki_ * input_filter_ki_); // Critically damped } -static float limitVel(const float vel_limit, const float vel_estimate, const float vel_gain, const float Iq) { - float Imax = (vel_limit - vel_estimate) * vel_gain; - float Imin = (-vel_limit - vel_estimate) * vel_gain; - return std::clamp(Iq, Imin, Imax); +static float limitVel(const float vel_limit, const float vel_estimate, const float vel_gain, const float torque) { + float Tmax = (vel_limit - vel_estimate) * vel_gain; + float Tmin = (-vel_limit - vel_estimate) * vel_gain; + return std::clamp(torque, Tmin, Tmax); } -bool Controller::update(float* current_setpoint_output) { +bool Controller::update(float* torque_setpoint_output) { float* pos_estimate_src = (pos_estimate_valid_src_ && *pos_estimate_valid_src_) ? pos_estimate_src_ : nullptr; float* vel_estimate_src = (vel_estimate_valid_src_ && *vel_estimate_valid_src_) @@ -153,7 +153,7 @@ bool Controller::update(float* current_setpoint_output) { case INPUT_MODE_PASSTHROUGH: { pos_setpoint_ = input_pos_; vel_setpoint_ = input_vel_; - current_setpoint_ = input_current_; + torque_setpoint_ = input_torque_; // } break; case INPUT_MODE_VEL_RAMP: { float max_step_size = std::abs(current_meas_period * config_.vel_ramp_rate); @@ -161,21 +161,21 @@ bool Controller::update(float* current_setpoint_output) { float step = std::clamp(full_step, -max_step_size, max_step_size); vel_setpoint_ += step; - current_setpoint_ = (step / current_meas_period) * config_.inertia; + torque_setpoint_ = (step / current_meas_period) * config_.inertia; } break; case INPUT_MODE_CURRENT_RAMP: { float max_step_size = std::abs(current_meas_period * config_.current_ramp_rate); - float full_step = input_current_ - current_setpoint_; + float full_step = input_torque_ - torque_setpoint_; float step = std::clamp(full_step, -max_step_size, max_step_size); - current_setpoint_ += step; + torque_setpoint_ += step; } break; case INPUT_MODE_POS_FILTER: { // 2nd order pos tracking filter float delta_pos = input_pos_ - pos_setpoint_; // Pos error float delta_vel = input_vel_ - vel_setpoint_; // Vel error float accel = input_filter_kp_*delta_pos + input_filter_ki_*delta_vel; // Feedback - current_setpoint_ = accel * config_.inertia; // Accel + torque_setpoint_ = accel * config_.inertia; // Accel vel_setpoint_ += current_meas_period * accel; // delta vel pos_setpoint_ += current_meas_period * vel_setpoint_; // Delta pos } break; @@ -205,13 +205,13 @@ bool Controller::update(float* current_setpoint_output) { config_.control_mode = CONTROL_MODE_POSITION_CONTROL; pos_setpoint_ = input_pos_; vel_setpoint_ = 0.0f; - current_setpoint_ = 0.0f; + torque_setpoint_ = 0.0f; trajectory_done_ = true; } else { TrapezoidalTrajectory::Step_t traj_step = axis_->trap_traj_.eval(axis_->trap_traj_.t_); pos_setpoint_ = traj_step.Y; vel_setpoint_ = traj_step.Yd; - current_setpoint_ = traj_step.Ydd * config_.inertia; + torque_setpoint_ = traj_step.Ydd * config_.inertia; axis_->trap_traj_.t_ += current_meas_period; } anticogging_pos = pos_setpoint_; // FF the position setpoint instead of the pos_estimate @@ -287,13 +287,14 @@ bool Controller::update(float* current_setpoint_output) { } // Velocity control - float Iq = current_setpoint_; + float torque = torque_setpoint_; // Anti-cogging is enabled after calibration // We get the current position and apply a current feed-forward // ensuring that we handle negative encoder positions properly (-1 == motor->encoder.encoder_cpr - 1) + // anticogging currently in units of [A], multiply by Kt to get back to torque. if (anticogging_valid_ && config_.anticogging.anticogging_enabled) { - Iq += config_.anticogging.cogging_map[std::clamp(mod((int)anticogging_pos, 3600), 0, 3600)]; + torque += config_.anticogging.cogging_map[std::clamp(mod((int)anticogging_pos, 3600), 0, 3600)] * axis_->motor_.config_.torque_constant; } float v_err = 0.0f; @@ -304,10 +305,10 @@ bool Controller::update(float* current_setpoint_output) { } v_err = vel_des - *vel_estimate_src; - Iq += (vel_gain * gain_scheduling_multiplier) * v_err; + torque += (vel_gain * gain_scheduling_multiplier) * v_err; // Velocity integral action before limiting - Iq += vel_integrator_current_; + torque += vel_integrator_current_; } // Velocity limiting in current mode @@ -316,21 +317,21 @@ bool Controller::update(float* current_setpoint_output) { set_error(ERROR_INVALID_ESTIMATE); return false; } - Iq = limitVel(config_.vel_limit, *vel_estimate_src, vel_gain, Iq); + torque = limitVel(config_.vel_limit, *vel_estimate_src, vel_gain, torque); } // Current limiting // TODO: Change to controller working in torque units // and get the torque limits from a function of the motor bool limited = false; - float Ilim = axis_->motor_.effective_current_lim(); - if (Iq > Ilim) { + float Tlim = axis_->motor_.effective_torque_lim(); + if (torque > Tlim) { limited = true; - Iq = Ilim; + torque = Tlim; } - if (Iq < -Ilim) { + if (torque < -Tlim) { limited = true; - Iq = -Ilim; + torque = -Tlim; } // Velocity integrator (behaviour dependent on limiting) @@ -346,6 +347,6 @@ bool Controller::update(float* current_setpoint_output) { } } - if (current_setpoint_output) *current_setpoint_output = Iq; + if (torque_setpoint_output) *torque_setpoint_output = torque; return true; } diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index be84d159..9529f822 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -64,7 +64,7 @@ public: bool anticogging_calibration(float pos_estimate, float vel_estimate); void update_filter_gains(); - bool update(float* current_setpoint); + bool update(float* torque_setpoint); Config_t& config_; Axis* axis_ = nullptr; // set by Axis constructor @@ -81,11 +81,12 @@ public: float vel_setpoint_ = 0.0f; // float vel_setpoint = 800.0f; float vel_integrator_current_ = 0.0f; // [A] - float current_setpoint_ = 0.0f; // [A] + float torque_setpoint_ = 0.0f; // [Nm] float input_pos_ = 0.0f; float input_vel_ = 0.0f; float input_current_ = 0.0f; + float input_torque_ = 0.0f; float input_filter_kp_ = 0.0f; float input_filter_ki_ = 0.0f; diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 76cdfa5e..568f9373 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -177,19 +177,19 @@ bool Motor::do_checks() { return true; } -float Motor::effective_current_lim() { +float Motor::effective_torque_lim() { // Configured limit - float current_lim = config_.current_lim; + float torque_lim = config_.torque_lim; // Hardware limit if (axis_->motor_.config_.motor_type == Motor::MOTOR_TYPE_GIMBAL) { - current_lim = std::min(current_lim, 0.98f*one_by_sqrt3*vbus_voltage); + torque_lim = std::min(torque_lim, 0.98f*one_by_sqrt3*vbus_voltage); //gimbal motor is voltage control, not Nm or A } else { - current_lim = std::min(current_lim, axis_->motor_.current_control_.max_allowed_current); + torque_lim = std::min(torque_lim, axis_->motor_.current_control_.max_allowed_torque); } // Thermal limit - current_lim = std::min(current_lim, thermal_current_lim_); + torque_lim = std::min(torque_lim, thermal_torque_lim_); - return current_lim; + return torque_lim; } void Motor::log_timing(TimingLog_t log_idx) { @@ -359,7 +359,7 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha ictrl.Id_measured += ictrl.I_measured_report_filter_k * (Id - ictrl.Id_measured); // Check for violation of current limit - float I_trip = effective_current_lim() + config_.current_lim_margin; + float I_trip = (effective_torque_lim() + config_.torque_lim_margin) / config_.torque_constant; if (SQ(Id) + SQ(Iq) > SQ(I_trip)) { set_error(ERROR_CURRENT_LIMIT_VIOLATION); return false; @@ -440,13 +440,14 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha } -bool Motor::update(float current_setpoint, float phase, float phase_vel) { +bool Motor::update(float torque_setpoint, float phase, float phase_vel) { + float current_setpoint = torque_setpoint / config_.torque_constant; current_setpoint *= config_.direction; phase *= config_.direction; phase_vel *= config_.direction; // TODO: 2-norm vs independent clamping (current could be sqrt(2) bigger) - float ilim = effective_current_lim(); + float ilim = effective_torque_lim() / config_.torque_constant; float id = std::clamp(current_control_.Id_setpoint, -ilim, ilim); float iq = std::clamp(current_setpoint, -ilim, ilim); diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 6e449111..6aaaaf18 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -29,6 +29,7 @@ public: float Id_measured; // [A] float I_measured_report_filter_k; float max_allowed_current; // [A] + float max_allowed_torque; // [Nm] float overcurrent_trip_level; // [A] float acim_rotor_flux; // [A] float async_phase_vel; // [rad/s electrical] @@ -45,12 +46,15 @@ public: float resistance_calib_max_voltage = 2.0f; // [V] - You may need to increase this if this voltage isn't sufficient to drive calibration_current through the motor. float phase_inductance = 0.0f; // to be set by measure_phase_inductance float phase_resistance = 0.0f; // to be set by measure_phase_resistance + float torque_constant = 1.0f; // to be set by user int32_t direction = 0; // 1 or -1 (0 = unspecified) MotorType motor_type = MOTOR_TYPE_HIGH_CURRENT; // Read out max_allowed_current to see max supported value for current_lim. // float current_lim = 70.0f; //[A] float current_lim = 10.0f; //[A] float current_lim_margin = 8.0f; // Maximum violation of current_lim + float torque_lim = 10.0f; //[Nm] + float torque_lim_margin = 8.0f; // Value used to compute shunt amplifier gains float requested_current_range = 60.0f; // [A] float current_control_bandwidth = 1000.0f; // [rad/s] @@ -92,7 +96,7 @@ public: bool do_checks(); float get_inverter_temp(); bool update_thermal_limits(float fet_temp); - float effective_current_lim(); + float effective_torque_lim(); void log_timing(TimingLog_t log_idx); float phase_current_from_adcval(uint32_t ADCValue); bool measure_phase_resistance(float test_current, float max_voltage); @@ -149,6 +153,7 @@ public: .Id_measured = 0.0f, .I_measured_report_filter_k = 1.0f, .max_allowed_current = 0.0f, + .max_allowed_torque = 0.0f, .overcurrent_trip_level = 0.0f, .acim_rotor_flux = 0.0f, .async_phase_vel = 0.0f, @@ -159,6 +164,7 @@ public: } gate_driver_exported_; DRV_SPI_8301_Vars_t gate_driver_regs_; //Local view of DRV registers (initialized by DRV8301_setup) float thermal_current_lim_ = 10.0f; //[A] + float thermal_torque_lim_ = 10.0f; //[Nm] float inverter_temp_ = NAN; // [°C] NaN while the ODrive is initializing. }; diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index f17aca63..ea118264 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -376,6 +376,7 @@ interfaces: Id_measured: float32 I_measured_report_filter_k: float32 max_allowed_current: readonly float32 + max_allowed_torque: readonly float32 overcurrent_trip_level: readonly float32 acim_rotor_flux: float32 async_phase_vel: readonly float32 @@ -427,10 +428,13 @@ interfaces: resistance_calib_max_voltage: float32 phase_inductance: {type: float32, c_setter: set_phase_inductance} phase_resistance: {type: float32, c_setter: set_phase_resistance} + torque_constant: float32 direction: int32 motor_type: MotorType current_lim: float32 current_lim_margin: float32 + torque_lim: float32 + torque_lim_margin: float32 inverter_temp_limit_lower: float32 inverter_temp_limit_upper: float32 requested_current_range: float32 @@ -458,9 +462,10 @@ interfaces: input_pos: {type: float32, c_setter: set_input_pos} input_vel: float32 input_current: float32 + input_torque: float32 pos_setpoint: readonly float32 vel_setpoint: readonly float32 - current_setpoint: readonly float32 + torque_setpoint: readonly float32 trajectory_done: readonly bool vel_integrator_current: float32 anticogging_valid: bool From d78119e29fc567fae9991624b6f89987775a4d64 Mon Sep 17 00:00:00 2001 From: pjohnson Date: Tue, 16 Jun 2020 17:10:36 -0400 Subject: [PATCH 466/549] Modified docs and communication interfaces to reflect change from A to Nm for motor control input. Renamed vel_integrator_current_ to vel_integrator_torque_ Removed input_current_ from controller, added input_torque_ --- Firmware/MotorControl/axis.cpp | 8 +++--- Firmware/MotorControl/controller.cpp | 19 +++++++------- Firmware/MotorControl/controller.hpp | 5 ++-- Firmware/communication/ascii_protocol.cpp | 30 +++++++++++------------ Firmware/communication/can_simple.cpp | 12 ++++----- Firmware/communication/can_simple.hpp | 4 +-- Firmware/odrive-interface.yaml | 5 ++-- docs/commands.md | 2 +- docs/getting-started.md | 4 +-- docs/input_modes.md | 6 ++--- tools/.vscode/launch.json | 2 +- tools/odrive/tests/can_test.py | 14 +++++------ tools/odrive/tests/closed_loop_test.py | 24 +++++++++--------- tools/odrive/tests/old_tests.py | 2 +- tools/odrive/tests/uart_ascii_test.py | 12 ++++----- 15 files changed, 73 insertions(+), 76 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 2882ca2a..172f3dc4 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -306,7 +306,7 @@ bool Axis::run_closed_loop_control_loop() { controller_.input_pos_ = *controller_.pos_estimate_src_; // Avoid integrator windup issues - controller_.vel_integrator_current_ = 0.0f; + controller_.vel_integrator_torque_ = 0.0f; set_step_dir_active(config_.enable_step_dir); run_control_loop([this](){ @@ -344,7 +344,7 @@ bool Axis::run_homing() { controller_.input_pos_ = 0.0f; controller_.input_pos_updated(); controller_.input_vel_ = -controller_.config_.homing_speed; - controller_.input_current_ = 0.0f; + controller_.input_torque_ = 0.0f; homing_.is_homed = false; @@ -356,7 +356,7 @@ bool Axis::run_homing() { controller_.pos_setpoint_ = *controller_.pos_estimate_src_; // Avoid integrator windup issues - controller_.vel_integrator_current_ = 0.0f; + controller_.vel_integrator_torque_ = 0.0f; run_control_loop([this](){ // Note that all estimators are updated in the loop prefix in run_control_loop @@ -385,7 +385,7 @@ bool Axis::run_homing() { controller_.input_pos_ = 0.0f; controller_.input_pos_updated(); controller_.input_vel_ = 0.0f; - controller_.input_current_ = 0.0f; + controller_.input_torque_ = 0.0f; run_control_loop([this](){ // Note that all estimators are updated in the loop prefix in run_control_loop diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 2138af3c..89eb9a5e 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -13,7 +13,7 @@ Controller::Controller(Config_t& config) : void Controller::reset() { pos_setpoint_ = 0.0f; vel_setpoint_ = 0.0f; - vel_integrator_current_ = 0.0f; + vel_integrator_torque_ = 0.0f; torque_setpoint_ = 0.0f; } @@ -87,13 +87,13 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) float pos_err = input_pos_ - pos_estimate; if (std::abs(pos_err) <= config_.anticogging.calib_pos_threshold && std::abs(vel_estimate) < config_.anticogging.calib_vel_threshold) { - config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_current_; + config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_torque_; } if (config_.anticogging.index < 3600) { config_.control_mode = CONTROL_MODE_POSITION_CONTROL; input_pos_ = config_.anticogging.index * axis_->encoder_.getCoggingRatio(); input_vel_ = 0.0f; - input_current_ = 0.0f; + input_torque_ = 0.0f; input_pos_updated(); return false; } else { @@ -101,7 +101,7 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) config_.control_mode = CONTROL_MODE_POSITION_CONTROL; input_pos_ = 0.0f; // Send the motor home input_vel_ = 0.0f; - input_current_ = 0.0f; + input_torque_ = 0.0f; input_pos_updated(); anticogging_valid_ = true; config_.anticogging.calib_anticogging = false; @@ -292,9 +292,8 @@ bool Controller::update(float* torque_setpoint_output) { // Anti-cogging is enabled after calibration // We get the current position and apply a current feed-forward // ensuring that we handle negative encoder positions properly (-1 == motor->encoder.encoder_cpr - 1) - // anticogging currently in units of [A], multiply by Kt to get back to torque. if (anticogging_valid_ && config_.anticogging.anticogging_enabled) { - torque += config_.anticogging.cogging_map[std::clamp(mod((int)anticogging_pos, 3600), 0, 3600)] * axis_->motor_.config_.torque_constant; + torque += config_.anticogging.cogging_map[std::clamp(mod((int)anticogging_pos, 3600), 0, 3600)]; } float v_err = 0.0f; @@ -308,7 +307,7 @@ bool Controller::update(float* torque_setpoint_output) { torque += (vel_gain * gain_scheduling_multiplier) * v_err; // Velocity integral action before limiting - torque += vel_integrator_current_; + torque += vel_integrator_torque_; } // Velocity limiting in current mode @@ -337,13 +336,13 @@ bool Controller::update(float* torque_setpoint_output) { // Velocity integrator (behaviour dependent on limiting) if (config_.control_mode < CONTROL_MODE_VELOCITY_CONTROL) { // reset integral if not in use - vel_integrator_current_ = 0.0f; + vel_integrator_torque_ = 0.0f; } else { if (limited) { // TODO make decayfactor configurable - vel_integrator_current_ *= 0.99f; + vel_integrator_torque_ *= 0.99f; } else { - vel_integrator_current_ += ((vel_integrator_gain * gain_scheduling_multiplier) * current_meas_period) * v_err; + vel_integrator_torque_ += ((vel_integrator_gain * gain_scheduling_multiplier) * current_meas_period) * v_err; } } diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 9529f822..69ae5162 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -80,12 +80,11 @@ public: float pos_setpoint_ = 0.0f; float vel_setpoint_ = 0.0f; // float vel_setpoint = 800.0f; - float vel_integrator_current_ = 0.0f; // [A] - float torque_setpoint_ = 0.0f; // [Nm] + float vel_integrator_torque_ = 0.0f; // [Nm] + float torque_setpoint_ = 0.0f; // [Nm] float input_pos_ = 0.0f; float input_vel_ = 0.0f; - float input_current_ = 0.0f; float input_torque_ = 0.0f; float input_filter_kp_ = 0.0f; float input_filter_ki_ = 0.0f; diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 39f4d891..6046214f 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -95,8 +95,8 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& // check incoming packet type if (cmd[0] == 'p') { // position control unsigned motor_number; - float pos_setpoint, vel_feed_forward, current_feed_forward; - int numscan = sscanf(cmd, "p %u %f %f %f", &motor_number, &pos_setpoint, &vel_feed_forward, ¤t_feed_forward); + float pos_setpoint, vel_feed_forward, torque_feed_forward; + int numscan = sscanf(cmd, "p %u %f %f %f", &motor_number, &pos_setpoint, &vel_feed_forward, &torque_feed_forward); if (numscan < 2) { respond(response_channel, use_checksum, "invalid command format"); } else if (motor_number >= AXIS_COUNT) { @@ -108,15 +108,15 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& if (numscan >= 3) axis->controller_.input_vel_ = vel_feed_forward; if (numscan >= 4) - axis->controller_.input_current_ = current_feed_forward; + axis->controller_.input_torque_ = torque_feed_forward; axis->controller_.input_pos_updated(); axis->watchdog_feed(); } } else if (cmd[0] == 'q') { // position control with limits unsigned motor_number; - float pos_setpoint, vel_limit, current_lim; - int numscan = sscanf(cmd, "q %u %f %f %f", &motor_number, &pos_setpoint, &vel_limit, ¤t_lim); + float pos_setpoint, vel_limit, torque_lim; + int numscan = sscanf(cmd, "q %u %f %f %f", &motor_number, &pos_setpoint, &vel_limit, &torque_lim); if (numscan < 2) { respond(response_channel, use_checksum, "invalid command format"); } else if (motor_number >= AXIS_COUNT) { @@ -128,15 +128,15 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& if (numscan >= 3) axis->controller_.config_.vel_limit = vel_limit; if (numscan >= 4) - axis->motor_.config_.current_lim = current_lim; + axis->motor_.config_.torque_lim = torque_lim; axis->controller_.input_pos_updated(); axis->watchdog_feed(); } } else if (cmd[0] == 'v') { // velocity control unsigned motor_number; - float vel_setpoint, current_feed_forward; - int numscan = sscanf(cmd, "v %u %f %f", &motor_number, &vel_setpoint, ¤t_feed_forward); + float vel_setpoint, torque_feed_forward; + int numscan = sscanf(cmd, "v %u %f %f", &motor_number, &vel_setpoint, &torque_feed_forward); if (numscan < 2) { respond(response_channel, use_checksum, "invalid command format"); } else if (motor_number >= AXIS_COUNT) { @@ -146,22 +146,22 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& axis->controller_.config_.control_mode = Controller::CONTROL_MODE_VELOCITY_CONTROL; axis->controller_.input_vel_ = vel_setpoint; if (numscan >= 3) - axis->controller_.input_current_ = current_feed_forward; + axis->controller_.input_torque_ = torque_feed_forward; axis->watchdog_feed(); } - } else if (cmd[0] == 'c') { // current control + } else if (cmd[0] == 'c') { // torque control unsigned motor_number; - float current_setpoint; - int numscan = sscanf(cmd, "c %u %f", &motor_number, ¤t_setpoint); + float torque_setpoint; + int numscan = sscanf(cmd, "c %u %f", &motor_number, &torque_setpoint); if (numscan < 2) { 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 { Axis* axis = axes[motor_number]; - axis->controller_.config_.control_mode = Controller::CONTROL_MODE_CURRENT_CONTROL; - axis->controller_.input_current_ = current_setpoint; + axis->controller_.config_.control_mode = Controller::CONTROL_MODE_TORQUE_CONTROL; + axis->controller_.input_torque_ = torque_setpoint; axis->watchdog_feed(); } @@ -200,7 +200,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "Position: q axis pos vel-lim I-lim"); respond(response_channel, use_checksum, "Position: p axis pos vel-ff I-ff"); respond(response_channel, use_checksum, "Velocity: v axis vel I-ff"); - respond(response_channel, use_checksum, "Current: c axis I"); + respond(response_channel, use_checksum, "Torque: c axis T"); respond(response_channel, use_checksum, ""); respond(response_channel, use_checksum, "Properties start at odrive root, such as axis0.requested_state"); respond(response_channel, use_checksum, "Read: r property"); diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index a6953a41..18d39838 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -82,8 +82,8 @@ void CANSimple::handle_can_message(can_Message_t& msg) { case MSG_SET_INPUT_VEL: set_input_vel_callback(axis, msg); break; - case MSG_SET_INPUT_CURRENT: - set_input_current_callback(axis, msg); + case MSG_SET_INPUT_TORQUE: + set_input_torque_callback(axis, msg); break; case MSG_SET_CONTROLLER_MODES: set_controller_modes_callback(axis, msg); @@ -281,17 +281,17 @@ void CANSimple::get_encoder_count_callback(Axis* axis, can_Message_t& msg) { void CANSimple::set_input_pos_callback(Axis* axis, can_Message_t& msg) { axis->controller_.input_pos_ = can_getSignal(msg, 0, 32, true); axis->controller_.input_vel_ = can_getSignal(msg, 32, 16, true, 0.1f, 0); - axis->controller_.input_current_ = can_getSignal(msg, 48, 16, true, 0.01f, 0); + axis->controller_.input_torque_ = can_getSignal(msg, 48, 16, true, 0.01f, 0); axis->controller_.input_pos_updated(); } void CANSimple::set_input_vel_callback(Axis* axis, can_Message_t& msg) { axis->controller_.input_vel_ = can_getSignal(msg, 0, 32, true, 0.01f, 0.0f); - axis->controller_.input_current_ = can_getSignal(msg, 32, 16, true, 0.01f, 0.0f); + axis->controller_.input_torque_ = can_getSignal(msg, 32, 16, true, 0.01f, 0.0f); } -void CANSimple::set_input_current_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.input_current_ = can_getSignal(msg, 0, 32, true, 0.01f, 0); +void CANSimple::set_input_torque_callback(Axis* axis, can_Message_t& msg) { + axis->controller_.input_torque_ = can_getSignal(msg, 0, 32, true, 0.01f, 0); } void CANSimple::set_controller_modes_callback(Axis* axis, can_Message_t& msg) { diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp index c4b6d6ed..a4ccae4d 100644 --- a/Firmware/communication/can_simple.hpp +++ b/Firmware/communication/can_simple.hpp @@ -20,7 +20,7 @@ class CANSimple { MSG_SET_CONTROLLER_MODES, MSG_SET_INPUT_POS, MSG_SET_INPUT_VEL, - MSG_SET_INPUT_CURRENT, + MSG_SET_INPUT_TORQUE, MSG_SET_VEL_LIMIT, MSG_START_ANTICOGGING, MSG_SET_TRAJ_VEL_LIMIT, @@ -51,7 +51,7 @@ class CANSimple { static void get_encoder_count_callback(Axis* axis, can_Message_t& msg); static void set_input_pos_callback(Axis* axis, can_Message_t& msg); static void set_input_vel_callback(Axis* axis, can_Message_t& msg); - static void set_input_current_callback(Axis* axis, can_Message_t& msg); + static void set_input_torque_callback(Axis* axis, can_Message_t& msg); static void set_controller_modes_callback(Axis* axis, can_Message_t& msg); static void set_vel_limit_callback(Axis* axis, can_Message_t& msg); static void start_anticogging_callback(Axis* axis, can_Message_t& msg); diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index ea118264..5fbfc10d 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -461,13 +461,12 @@ interfaces: InvalidEstimate: input_pos: {type: float32, c_setter: set_input_pos} input_vel: float32 - input_current: float32 input_torque: float32 pos_setpoint: readonly float32 vel_setpoint: readonly float32 torque_setpoint: readonly float32 trajectory_done: readonly bool - vel_integrator_current: float32 + vel_integrator_torque: float32 anticogging_valid: bool config: c_is_class: False @@ -681,7 +680,7 @@ valuetypes: # Note: these should be sorted from lowest level of control to # highest level of control, to allow "<" style comparisons. VoltageControl: - CurrentControl: + TorqueControl: VelocityControl: PositionControl: diff --git a/docs/commands.md b/docs/commands.md index 8849c9dc..ba915564 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -85,7 +85,7 @@ For more information, see [input_modes](input_modes.md). # Control Commands * `.controller.input_pos = ` * `.controller.input_vel = ` -* `.controller.input_current = ` +* `.controller.input_torque = ` ### Input Mode To modify the way the control command affects the motor, you can use the input mode. The default input mode is pass through. diff --git a/docs/getting-started.md b/docs/getting-started.md index a4a92a04..968cf34b 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -346,9 +346,9 @@ Set the velocity ramp rate (acceleration): `axis.controller.config.vel_ramp_rate Activate the ramped velocity mode: `axis.controller.config.input_mode = INPUT_MODE_VEL_RAMP`.
        You can now control the velocity with `axis.controller.input_vel = 5000` [count/s]. -### Current control +### Torque control Set `axis.controller.config.control_mode = CONTROL_MODE_CURRENT_CONTROL`.
        -You can now control the current with `axis.controller.input_current = 3` [A]. +You can now control the torque with `axis.controller.input_torque = 3` [Nm]. Note: If you exceed `vel_limit` in current control mode, the current is reduced. To disable this, set `axis.controller.enable_current_mode_vel_limit = False`. diff --git a/docs/input_modes.md b/docs/input_modes.md index be449e24..4b0657d9 100644 --- a/docs/input_modes.md +++ b/docs/input_modes.md @@ -4,7 +4,7 @@ As of version ###, ODrive now intercepts the incoming commands and can apply fil * `.controller.config.input_mode` * `.controller.input_pos` * `.controller.input_vel` -* `.controller.input_current` +* `.controller.input_torque` The Input Modes currently valid are: * `INPUT_MODE_INACTIVE` @@ -27,7 +27,7 @@ Pass `input_xxx` through to `xxx_setpoint` directly. ### Valid Inputs: * `input_pos` * `input_vel` -* `input_current` +* `input_torque` ### Valid Control modes: * `CONTROL_MODE_VOLTAGE_CONTROL` @@ -92,7 +92,7 @@ Ramp a current command from the current value to the target value. * `.controller.config.current_ramp_rate` ### Valid Inputs: -* `input_current` +* `input_torque` ### Valid Control Modes: * `CONTROL_MODE_CURRENT_CONTROL` diff --git a/tools/.vscode/launch.json b/tools/.vscode/launch.json index a3b07eff..69a77ee7 100644 --- a/tools/.vscode/launch.json +++ b/tools/.vscode/launch.json @@ -9,7 +9,7 @@ "type": "python", "request": "launch", "stopOnEntry": true, - "pythonPath": "${config:python.pythonPath}", + "pythonPath": "${config:python.interpreterPath}", "program": "${file}", "cwd": "${workspaceRoot}", "env": {}, diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index 1012250f..8e8e9c4b 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -26,7 +26,7 @@ command_set = { 'set_controller_modes': (0x00b, [('control_mode', 'i', 1), ('input_mode', 'i', 1)]), # tested 'set_input_pos': (0x00c, [('input_pos', 'i', 1), ('vel_ff', 'h', 0.1), ('cur_ff', 'h', 0.01)]), # tested 'set_input_vel': (0x00d, [('input_vel', 'i', 0.01), ('cur_ff', 'h', 0.01)]), # tested - 'set_input_current': (0x00e, [('input_current', 'i', 0.01)]), # tested + 'set_input_torque': (0x00e, [('input_torque', 'i', 0.01)]), # tested 'set_velocity_limit': (0x00f, [('velocity_limit', 'f', 1)]), # tested 'start_anticogging': (0x010, []), # untested 'set_traj_vel_limit': (0x011, [('traj_vel_limit', 'f', 1)]), # tested @@ -174,23 +174,23 @@ class TestSimpleCAN(): axis.controller.input_pos = 1234 axis.controller.input_vel = 1234 - axis.controller.input_current = 1234 + axis.controller.input_torque = 1234 my_cmd('set_input_pos', input_pos=1, vel_ff=2, cur_ff=3) fence() test_assert_eq(axis.controller.input_pos, 1.0, range=0.1) test_assert_eq(axis.controller.input_vel, 2.0, range=0.01) - test_assert_eq(axis.controller.input_current, 3.0, range=0.001) + test_assert_eq(axis.controller.input_torque, 3.0, range=0.001) axis.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL my_cmd('set_input_vel', input_vel=-10.0, cur_ff=30.1234) fence() test_assert_eq(axis.controller.input_vel, -10.0, range=0.01) - test_assert_eq(axis.controller.input_current, 30.1234, range=0.01) + test_assert_eq(axis.controller.input_torque, 30.1234, range=0.01) axis.controller.config.control_mode = CONTROL_MODE_CURRENT_CONTROL - my_cmd('set_input_current', input_current=3.1415) + my_cmd('set_input_torque', input_torque=3.1415) fence() - test_assert_eq(axis.controller.input_current, 3.1415, range=0.01) + test_assert_eq(axis.controller.input_torque, 3.1415, range=0.01) my_cmd('set_velocity_limit', velocity_limit=23456.78) fence() @@ -210,7 +210,7 @@ class TestSimpleCAN(): test_assert_eq(axis.controller.config.inertia, 55.086, range=0.0001) # any CAN cmd will feed the watchdog - test_watchdog(axis, lambda: my_cmd('set_input_current', input_current=0.0), logger) + test_watchdog(axis, lambda: my_cmd('set_input_torque', input_torque=0.0), logger) logger.debug('testing heartbeat...') # note that this will include the heartbeats that were received during the diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index 6d5bcecb..b92ada8c 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -246,19 +246,19 @@ class TestVelLimitInCurrentControl(TestClosedLoopControlBase): # Abort immediately if the absolute limits are exceeded test_assert_within(current_setpoint, -max_current, max_current) test_assert_within(velocity, -absolute_max_vel, absolute_max_vel) - return input_current, velocity, current_setpoint, get_expected_setpoint(input_current, velocity) + return input_torque, velocity, current_setpoint, get_expected_setpoint(input_torque, velocity) - axis_ctx.handle.controller.input_current = input_current = 0.0 + axis_ctx.handle.controller.input_torque = input_torque = 0.0 request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) # Move the system around its operating envelope - axis_ctx.handle.controller.input_current = input_current = 2.0 + axis_ctx.handle.controller.input_torque = input_torque = 2.0 dataA = record_log(data_getter, duration=1.0) - axis_ctx.handle.controller.input_current = input_current = -2.0 + axis_ctx.handle.controller.input_torque = input_torque = -2.0 dataA = np.concatenate([dataA, record_log(data_getter, duration=1.0)]) - axis_ctx.handle.controller.input_current = input_current = 4.0 + axis_ctx.handle.controller.input_torque = input_torque = 4.0 dataA = np.concatenate([dataA, record_log(data_getter, duration=1.0)]) - axis_ctx.handle.controller.input_current = input_current = -4.0 + axis_ctx.handle.controller.input_torque = input_torque = -4.0 dataA = np.concatenate([dataA, record_log(data_getter, duration=1.0)]) # Shrink the operating envelope while motor is moving faster than the envelope allows @@ -267,22 +267,22 @@ class TestVelLimitInCurrentControl(TestClosedLoopControlBase): axis_ctx.handle.controller.config.vel_limit = max_vel # Move the system around its operating envelope - axis_ctx.handle.controller.input_current = input_current = 2.0 + axis_ctx.handle.controller.input_torque = input_torque = 2.0 dataB = record_log(data_getter, duration=1.0) - axis_ctx.handle.controller.input_current = input_current = -2.0 + axis_ctx.handle.controller.input_torque = input_torque = -2.0 dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)]) - axis_ctx.handle.controller.input_current = input_current = 4.0 + axis_ctx.handle.controller.input_torque = input_torque = 4.0 dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)]) - axis_ctx.handle.controller.input_current = input_current = -4.0 + axis_ctx.handle.controller.input_torque = input_torque = -4.0 dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)]) # Try the shrink maneuver again at positive velocity axis_ctx.handle.controller.config.vel_limit = 20.0 * float(enc_ctx.yaml['cpr']) - axis_ctx.handle.controller.input_current = 4.0 + axis_ctx.handle.controller.input_torque = 4.0 time.sleep(0.5) axis_ctx.handle.controller.config.vel_limit = max_vel - axis_ctx.handle.controller.input_current = input_current = 2.0 + axis_ctx.handle.controller.input_torque = input_torque = 2.0 dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)]) test_assert_no_error(axis_ctx) diff --git a/tools/odrive/tests/old_tests.py b/tools/odrive/tests/old_tests.py index cf22ed10..fac90f1d 100644 --- a/tools/odrive/tests/old_tests.py +++ b/tools/odrive/tests/old_tests.py @@ -581,7 +581,7 @@ class TestVelCtrlVsPosCtrl(DualAxisTest): # Set up viscous fluid load logger.debug("activating load on {}...".format(load_ctx.name)) load_ctx.handle.controller.config.vel_integrator_gain = 0 - load_ctx.handle.controller.vel_integrator_current = 0 + load_ctx.handle.controller.vel_integrator_torque = 0 set_limits(load_ctx, logger, vel_limit=100000, current_limit=50) load_ctx.handle.controller.set_vel_setpoint(0, 0) request_state(load_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) diff --git a/tools/odrive/tests/uart_ascii_test.py b/tools/odrive/tests/uart_ascii_test.py index ee9a3fa4..6c650e28 100644 --- a/tools/odrive/tests/uart_ascii_test.py +++ b/tools/odrive/tests/uart_ascii_test.py @@ -101,28 +101,28 @@ class TestUartAscii(): # Test 'c', 'v', 'p', 'q' and 'f' commands - odrive.handle.axis0.controller.input_current = 0 + odrive.handle.axis0.controller.input_torque = 0 ser.write(b'c 0 12.5\n') test_assert_eq(ser.readline(), b'') - test_assert_eq(odrive.handle.axis0.controller.input_current, 12.5, accuracy=0.001) + test_assert_eq(odrive.handle.axis0.controller.input_torque, 12.5, accuracy=0.001) test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_CURRENT_CONTROL) odrive.handle.axis0.controller.input_vel = 0 - odrive.handle.axis0.controller.input_current = 0 + odrive.handle.axis0.controller.input_torque = 0 ser.write(b'v 0 567.8 12.5\n') test_assert_eq(ser.readline(), b'') test_assert_eq(odrive.handle.axis0.controller.input_vel, 567.8, accuracy=0.001) - test_assert_eq(odrive.handle.axis0.controller.input_current, 12.5, accuracy=0.001) + test_assert_eq(odrive.handle.axis0.controller.input_torque, 12.5, accuracy=0.001) test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_VELOCITY_CONTROL) odrive.handle.axis0.controller.input_pos = 0 odrive.handle.axis0.controller.input_vel = 0 - odrive.handle.axis0.controller.input_current = 0 + odrive.handle.axis0.controller.input_torque = 0 ser.write(b'p 0 123.4 567.8 12.5\n') test_assert_eq(ser.readline(), b'') test_assert_eq(odrive.handle.axis0.controller.input_pos, 123.4, accuracy=0.001) test_assert_eq(odrive.handle.axis0.controller.input_vel, 567.8, accuracy=0.001) - test_assert_eq(odrive.handle.axis0.controller.input_current, 12.5, accuracy=0.001) + test_assert_eq(odrive.handle.axis0.controller.input_torque, 12.5, accuracy=0.001) test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_POSITION_CONTROL) odrive.handle.axis0.controller.input_pos = 0 From 055aabc5a10e819ca3adc92283b6e4dc8c21d10d Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 17 Jun 2020 12:39:01 +0200 Subject: [PATCH 467/549] Make compile work on GCC 10.1 (again) --- Firmware/fibre/cpp/include/fibre/cpp_utils.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp b/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp index 6a81858d..b09aa400 100644 --- a/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp +++ b/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp @@ -82,6 +82,7 @@ public: #include #include #include +#include /* Backport features from C++14 and C++17 ------------------------------------*/ From ceabd24582f805fa7cd16e9267eb3e0e6003fd76 Mon Sep 17 00:00:00 2001 From: pjohnson Date: Wed, 17 Jun 2020 13:15:36 -0400 Subject: [PATCH 468/549] Made changes reflecting PR comments. Added torque_ramp_rate to controller config. Changed INPUT_MODE_CURRENT_RAMP to INPUT_MODE_TORQUE_RAMP for controller input mode enum. Torque limits and current limits are now observed seperately. Torque limit is in the controller, current limit is in the motor object. Fixed torque -> current calculation in motor_update to handle ACIM motors. --- Firmware/MotorControl/controller.cpp | 11 +++++------ Firmware/MotorControl/controller.hpp | 2 +- Firmware/MotorControl/motor.cpp | 27 +++++++++++++++++---------- Firmware/MotorControl/motor.hpp | 4 +--- Firmware/odrive-interface.yaml | 7 +++---- docs/commands.md | 4 ++-- docs/getting-started.md | 2 +- docs/input_modes.md | 8 ++++---- tools/.vscode/launch.json | 2 +- 9 files changed, 35 insertions(+), 32 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 89eb9a5e..6dbb6878 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -163,8 +163,8 @@ bool Controller::update(float* torque_setpoint_output) { vel_setpoint_ += step; torque_setpoint_ = (step / current_meas_period) * config_.inertia; } break; - case INPUT_MODE_CURRENT_RAMP: { - float max_step_size = std::abs(current_meas_period * config_.current_ramp_rate); + case INPUT_MODE_TORQUE_RAMP: { + float max_step_size = std::abs(current_meas_period * config_.torque_ramp_rate); float full_step = input_torque_ - torque_setpoint_; float step = std::clamp(full_step, -max_step_size, max_step_size); @@ -319,11 +319,10 @@ bool Controller::update(float* torque_setpoint_output) { torque = limitVel(config_.vel_limit, *vel_estimate_src, vel_gain, torque); } - // Current limiting - // TODO: Change to controller working in torque units - // and get the torque limits from a function of the motor + // Limit max torque to a user defined torque limit. This functions as an acceleration limit. + // The motor object handles current limiting bool limited = false; - float Tlim = axis_->motor_.effective_torque_lim(); + float Tlim = axis_->motor_.config_.torque_lim; if (torque > Tlim) { limited = true; torque = Tlim; diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 69ae5162..851f265c 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -28,7 +28,7 @@ public: float vel_limit = 20000.0f; // [counts/s] Infinity to disable. float vel_limit_tolerance = 1.2f; // ratio to vel_lim. Infinity to disable. float vel_ramp_rate = 10000.0f; // [(counts/s) / s] - float current_ramp_rate = 1.0f; // A / sec + float torque_ramp_rate = 0.1f; // Nm / sec bool setpoints_in_cpr = false; float inertia = 0.0f; // [A/(count/s^2)] float input_filter_bandwidth = 2.0f; // [1/s] diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 568f9373..2c7ab5f9 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -177,19 +177,19 @@ bool Motor::do_checks() { return true; } -float Motor::effective_torque_lim() { +float Motor::effective_current_lim() { // Configured limit - float torque_lim = config_.torque_lim; + float current_lim = config_.current_lim; // Hardware limit if (axis_->motor_.config_.motor_type == Motor::MOTOR_TYPE_GIMBAL) { - torque_lim = std::min(torque_lim, 0.98f*one_by_sqrt3*vbus_voltage); //gimbal motor is voltage control, not Nm or A + current_lim = std::min(current_lim, 0.98f*one_by_sqrt3*vbus_voltage); //gimbal motor is voltage control } else { - torque_lim = std::min(torque_lim, axis_->motor_.current_control_.max_allowed_torque); + current_lim = std::min(current_lim, axis_->motor_.current_control_.max_allowed_current); } // Thermal limit - torque_lim = std::min(torque_lim, thermal_torque_lim_); + current_lim = std::min(current_lim, thermal_current_lim_); - return torque_lim; + return current_lim; } void Motor::log_timing(TimingLog_t log_idx) { @@ -359,7 +359,7 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha ictrl.Id_measured += ictrl.I_measured_report_filter_k * (Id - ictrl.Id_measured); // Check for violation of current limit - float I_trip = (effective_torque_lim() + config_.torque_lim_margin) / config_.torque_constant; + float I_trip = effective_current_lim() + config_.current_lim_margin; if (SQ(Id) + SQ(Iq) > SQ(I_trip)) { set_error(ERROR_CURRENT_LIMIT_VIOLATION); return false; @@ -441,13 +441,20 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha bool Motor::update(float torque_setpoint, float phase, float phase_vel) { - float current_setpoint = torque_setpoint / config_.torque_constant; - current_setpoint *= config_.direction; + float current_setpoint; phase *= config_.direction; phase_vel *= config_.direction; + if (config_.motor_type == MOTOR_TYPE_ACIM) { + current_setpoint = torque_setpoint / (config_.torque_constant * fmax(current_control_.acim_rotor_flux, config_.acim_gain_min_flux)); + } + else { + current_setpoint = torque_setpoint / config_.torque_constant; + } + current_setpoint *= config_.direction; + // TODO: 2-norm vs independent clamping (current could be sqrt(2) bigger) - float ilim = effective_torque_lim() / config_.torque_constant; + float ilim = effective_current_lim(); float id = std::clamp(current_control_.Id_setpoint, -ilim, ilim); float iq = std::clamp(current_setpoint, -ilim, ilim); diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 6aaaaf18..178a99db 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -29,7 +29,6 @@ public: float Id_measured; // [A] float I_measured_report_filter_k; float max_allowed_current; // [A] - float max_allowed_torque; // [Nm] float overcurrent_trip_level; // [A] float acim_rotor_flux; // [A] float async_phase_vel; // [rad/s electrical] @@ -96,7 +95,7 @@ public: bool do_checks(); float get_inverter_temp(); bool update_thermal_limits(float fet_temp); - float effective_torque_lim(); + float effective_current_lim(); void log_timing(TimingLog_t log_idx); float phase_current_from_adcval(uint32_t ADCValue); bool measure_phase_resistance(float test_current, float max_voltage); @@ -153,7 +152,6 @@ public: .Id_measured = 0.0f, .I_measured_report_filter_k = 1.0f, .max_allowed_current = 0.0f, - .max_allowed_torque = 0.0f, .overcurrent_trip_level = 0.0f, .acim_rotor_flux = 0.0f, .async_phase_vel = 0.0f, diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 5fbfc10d..38e8a936 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -376,7 +376,6 @@ interfaces: Id_measured: float32 I_measured_report_filter_k: float32 max_allowed_current: readonly float32 - max_allowed_torque: readonly float32 overcurrent_trip_level: readonly float32 acim_rotor_flux: float32 async_phase_vel: readonly float32 @@ -497,9 +496,9 @@ interfaces: type: float32 doc: Ratio to `vel_limit`. Infinity to disable. vel_ramp_rate: float32 - current_ramp_rate: + torque_ramp_rate: type: float32 - unit: A / sec + unit: Nm / sec homing_speed: type: float32 unit: counts/s @@ -692,7 +691,7 @@ valuetypes: PosFilter: MixChannels: TrapTraj: - CurrentRamp: + TorqueRamp: Mirror: diff --git a/docs/commands.md b/docs/commands.md index ba915564..618ddaba 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -77,7 +77,7 @@ Possible values are: * `INPUT_MODE_POS_FILTER` * `INPUT_MODE_MIX_CHANNELS` * `INPUT_MODE_TRAP_TRAJ` -* `INPUT_MODE_CURRENT_RAMP` +* `INPUT_MODE_TORQUE_RAMP` * `INPUT_MODE_MIRROR` For more information, see [input_modes](input_modes.md). @@ -97,7 +97,7 @@ Possible values are: * `INPUT_MODE_POS_FILTER` * `INPUT_MODE_MIX_CHANNELS` * `INPUT_MODE_TRAP_TRAJ` -* `INPUT_MODE_CURRENT_RAMP` +* `INPUT_MODE_TORQUE_RAMP` * `INPUT_MODE_MIRROR` ## System monitoring commands diff --git a/docs/getting-started.md b/docs/getting-started.md index 968cf34b..9426f395 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -348,7 +348,7 @@ You can now control the velocity with `axis.controller.input_vel = 5000` [count/ ### Torque control Set `axis.controller.config.control_mode = CONTROL_MODE_CURRENT_CONTROL`.
        -You can now control the torque with `axis.controller.input_torque = 3` [Nm]. +You can now control the torque with `axis.controller.input_torque = 0.1` [Nm]. Note: If you exceed `vel_limit` in current control mode, the current is reduced. To disable this, set `axis.controller.enable_current_mode_vel_limit = False`. diff --git a/docs/input_modes.md b/docs/input_modes.md index 4b0657d9..53463ad0 100644 --- a/docs/input_modes.md +++ b/docs/input_modes.md @@ -13,7 +13,7 @@ The Input Modes currently valid are: * `INPUT_MODE_POS_FILTER` * `INPUT_MODE_MIX_CHANNELS` * `INPUT_MODE_TRAP_TRAJ` -* `INPUT_MODE_CURRENT_RAMP` +* `INPUT_MODE_TORQUE_RAMP` * `INPUT_MODE_MIRROR` --- @@ -85,11 +85,11 @@ Implementes an online trapezoidal trajectory planner. ### Valid Control Modes: * `CONTROL_MODE_POSITION_CONTROL` -## INPUT_MODE_CURRENT_RAMP -Ramp a current command from the current value to the target value. +## INPUT_MODE_TORQUE_RAMP +Ramp a torque command from the current value to the target value. ### Configuration Values: -* `.controller.config.current_ramp_rate` +* `.controller.config.torque_ramp_rate` ### Valid Inputs: * `input_torque` diff --git a/tools/.vscode/launch.json b/tools/.vscode/launch.json index 69a77ee7..9a36a076 100644 --- a/tools/.vscode/launch.json +++ b/tools/.vscode/launch.json @@ -9,7 +9,7 @@ "type": "python", "request": "launch", "stopOnEntry": true, - "pythonPath": "${config:python.interpreterPath}", + "pythonPath": "${command:python.pythonPath}", "program": "${file}", "cwd": "${workspaceRoot}", "env": {}, From 6d6c554a871214acf58e14364dddf78bf8871be0 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 15 Jun 2020 15:45:14 +0200 Subject: [PATCH 469/549] revert navbar colors, implement indentation --- docs/_layouts/api_index_template.j2 | 28 ++++++++++++++++++++-------- docs/assets/css/style.scss | 11 +++++++++-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/docs/_layouts/api_index_template.j2 b/docs/_layouts/api_index_template.j2 index 271be252..087fbced 100644 --- a/docs/_layouts/api_index_template.j2 +++ b/docs/_layouts/api_index_template.j2 @@ -1,29 +1,41 @@ -[%- macro dump_interfaces(interfaces) %] +[%- macro dump_interfaces(interfaces, level) %] [%- for intf in interfaces %] [%- if intf.interfaces or intf.value_types %]
      • {% assign myvar = (page.title + '.') | split: "[[intf.fullname + '.']]" %} - +
          -[[dump_interfaces(intf.interfaces) | indent(4)]] -[[dump_value_types(intf.enums) | indent(4)]] +[[dump_interfaces(intf.interfaces, level + 1) | indent(4)]] +[[dump_value_types(intf.enums, level + 1) | indent(4)]]
      • [%- else %]
      • - +
      • [%- endif %] [%- endfor %] [%- endmacro %] -[%- macro dump_value_types(value_types) %] +[%- macro dump_value_types(value_types, level) %] [%- for enum in value_types %]
      • - +
      • [%- endfor %] [%- endmacro %] -[[dump_interfaces(toplevel_interfaces)]] +[[dump_interfaces(toplevel_interfaces, 0)]] diff --git a/docs/assets/css/style.scss b/docs/assets/css/style.scss index 2c60c3c5..dbccc0fd 100644 --- a/docs/assets/css/style.scss +++ b/docs/assets/css/style.scss @@ -300,7 +300,7 @@ header li { } .navgroup { - background: #9c9c9c; + background: #cbcbcb; margin-top: 20px; } .navgroup:first-child { @@ -308,7 +308,7 @@ header li { } #navbar ul { - background-color: #ffffffa6; + background-color: #ffffffd9; margin: 0; } @@ -336,6 +336,13 @@ header li { .navitem a { display: block; } +.levelbar { + float: inline-start; + margin-left: 8px; + margin-right: 5px; + border-left: 1px solid #0000004d; +} + .currentitem { //-webkit-box-shadow: inset 0px 0px 5px 3px #aa0000a6; //-moz-box-shadow: inset 0px 0px 5px 3px #aa0000a6; From a9b1841092474460f78efe32974234933693d06d Mon Sep 17 00:00:00 2001 From: pjohnson Date: Thu, 18 Jun 2020 13:11:41 -0400 Subject: [PATCH 470/549] Added Motor::max_available_torque(). Use is to correctly determine torque limit for velocity anti-windup. Set default value (0.0) for current_setpoint Units documentation Removed unused variable thermal_torque_lim_ --- Firmware/MotorControl/controller.cpp | 5 ++--- Firmware/MotorControl/motor.cpp | 17 ++++++++++++++++- Firmware/MotorControl/motor.hpp | 4 ++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 6dbb6878..c15b1cbe 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -319,10 +319,9 @@ bool Controller::update(float* torque_setpoint_output) { torque = limitVel(config_.vel_limit, *vel_estimate_src, vel_gain, torque); } - // Limit max torque to a user defined torque limit. This functions as an acceleration limit. - // The motor object handles current limiting + // Torque limiting bool limited = false; - float Tlim = axis_->motor_.config_.torque_lim; + float Tlim = axis_->motor_.max_available_torque(); if (torque > Tlim) { limited = true; torque = Tlim; diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 2c7ab5f9..cbe32c5d 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -192,6 +192,21 @@ float Motor::effective_current_lim() { return current_lim; } +float Motor::max_available_torque() { + //return the maximum available torque for the motor. + //Note - for ACIM motors, available torque is allowed to be 0. + if (config_.motor_type == Motor::MOTOR_TYPE_ACIM) { + float max_torque = effective_current_lim() * config_.torque_constant * current_control_.acim_rotor_flux; + max_torque = fmin(max_torque, config_.torque_lim); + return max_torque; + } + else { + float max_torque = effective_current_lim() * config_.torque_constant; + max_torque = fmin(max_torque, config_.torque_lim); + return max_torque; + } +} + void Motor::log_timing(TimingLog_t log_idx) { static const uint16_t clocks_per_cnt = (uint16_t)((float)TIM_1_8_CLOCK_HZ / (float)TIM_APB1_CLOCK_HZ); uint16_t timing = clocks_per_cnt * htim13.Instance->CNT; // TODO: Use a hw_config @@ -441,7 +456,7 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha bool Motor::update(float torque_setpoint, float phase, float phase_vel) { - float current_setpoint; + float current_setpoint = 0.0f; phase *= config_.direction; phase_vel *= config_.direction; diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 178a99db..e491ef3b 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -45,7 +45,7 @@ public: float resistance_calib_max_voltage = 2.0f; // [V] - You may need to increase this if this voltage isn't sufficient to drive calibration_current through the motor. float phase_inductance = 0.0f; // to be set by measure_phase_inductance float phase_resistance = 0.0f; // to be set by measure_phase_resistance - float torque_constant = 1.0f; // to be set by user + float torque_constant = 1.0f; // [Nm/A] for PM motors, [Nm/A^2] for induction motors. To be set by user int32_t direction = 0; // 1 or -1 (0 = unspecified) MotorType motor_type = MOTOR_TYPE_HIGH_CURRENT; // Read out max_allowed_current to see max supported value for current_lim. @@ -96,6 +96,7 @@ public: float get_inverter_temp(); bool update_thermal_limits(float fet_temp); float effective_current_lim(); + float max_available_torque(); void log_timing(TimingLog_t log_idx); float phase_current_from_adcval(uint32_t ADCValue); bool measure_phase_resistance(float test_current, float max_voltage); @@ -162,7 +163,6 @@ public: } gate_driver_exported_; DRV_SPI_8301_Vars_t gate_driver_regs_; //Local view of DRV registers (initialized by DRV8301_setup) float thermal_current_lim_ = 10.0f; //[A] - float thermal_torque_lim_ = 10.0f; //[Nm] float inverter_temp_ = NAN; // [°C] NaN while the ODrive is initializing. }; From 63f7c09bc2d6cf41e3396e2e9459f1d38d9aa644 Mon Sep 17 00:00:00 2001 From: pjohnson Date: Thu, 18 Jun 2020 15:13:58 -0400 Subject: [PATCH 471/549] Clamped max_torque to 0 for ACIM motors in case max_torque happened to be negative. --- Firmware/MotorControl/motor.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index cbe32c5d..326d41e7 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -192,17 +192,18 @@ float Motor::effective_current_lim() { return current_lim; } +//return the maximum available torque for the motor. +//Note - for ACIM motors, available torque is allowed to be 0. float Motor::max_available_torque() { - //return the maximum available torque for the motor. - //Note - for ACIM motors, available torque is allowed to be 0. if (config_.motor_type == Motor::MOTOR_TYPE_ACIM) { float max_torque = effective_current_lim() * config_.torque_constant * current_control_.acim_rotor_flux; - max_torque = fmin(max_torque, config_.torque_lim); + max_torque = std::clamp(max_torque, 0.0f, max_torque); + max_torque = std::min(max_torque, config_.torque_lim); return max_torque; } else { float max_torque = effective_current_lim() * config_.torque_constant; - max_torque = fmin(max_torque, config_.torque_lim); + max_torque = std::min(max_torque, config_.torque_lim); return max_torque; } } From 758989e2b4d85578a28357523cf923b45cafaf7a Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 18 Jun 2020 12:58:10 -0700 Subject: [PATCH 472/549] Cleaner clamping --- Firmware/MotorControl/motor.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 326d41e7..d74fbcec 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -197,13 +197,12 @@ float Motor::effective_current_lim() { float Motor::max_available_torque() { if (config_.motor_type == Motor::MOTOR_TYPE_ACIM) { float max_torque = effective_current_lim() * config_.torque_constant * current_control_.acim_rotor_flux; - max_torque = std::clamp(max_torque, 0.0f, max_torque); - max_torque = std::min(max_torque, config_.torque_lim); + max_torque = std::clamp(max_torque, 0.0f, config_.torque_lim); return max_torque; } else { float max_torque = effective_current_lim() * config_.torque_constant; - max_torque = std::min(max_torque, config_.torque_lim); + max_torque = std::clamp(max_torque, 0.0f, config_.torque_lim); return max_torque; } } From f19f57779bdcb2267a3ec1925c9d70230ee92b6e Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Thu, 18 Jun 2020 18:27:19 -0400 Subject: [PATCH 473/549] Changed vel_gain, vel_integrator_gain and torque_constant to work out of the box with ODrive branded motors. Removed unused variable torque_lim_margin Changed default value of torque_lim to +inf --- Firmware/MotorControl/controller.hpp | 6 +++--- Firmware/MotorControl/motor.hpp | 9 ++++----- Firmware/odrive-interface.yaml | 1 - 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 851f265c..4fcc8a52 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -22,13 +22,13 @@ public: ControlMode control_mode = CONTROL_MODE_POSITION_CONTROL; //see: ControlMode_t InputMode input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t float pos_gain = 20.0f; // [(counts/s) / counts] - float vel_gain = 5.0f / 10000.0f; // [A/(counts/s)] + float vel_gain = 0.2f / 10000.0f; // [Nm/(counts/s)] // float vel_gain = 5.0f / 200.0f, // [A/(rad/s)] - float vel_integrator_gain = 10.0f / 10000.0f; // [A/(counts/s * s)] + float vel_integrator_gain = 0.4f / 10000.0f; // [Nm/(counts/s * s)] float vel_limit = 20000.0f; // [counts/s] Infinity to disable. float vel_limit_tolerance = 1.2f; // ratio to vel_lim. Infinity to disable. float vel_ramp_rate = 10000.0f; // [(counts/s) / s] - float torque_ramp_rate = 0.1f; // Nm / sec + float torque_ramp_rate = 0.01f; // Nm / sec bool setpoints_in_cpr = false; float inertia = 0.0f; // [A/(count/s^2)] float input_filter_bandwidth = 2.0f; // [1/s] diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index e491ef3b..8583e0ef 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -45,15 +45,14 @@ public: float resistance_calib_max_voltage = 2.0f; // [V] - You may need to increase this if this voltage isn't sufficient to drive calibration_current through the motor. float phase_inductance = 0.0f; // to be set by measure_phase_inductance float phase_resistance = 0.0f; // to be set by measure_phase_resistance - float torque_constant = 1.0f; // [Nm/A] for PM motors, [Nm/A^2] for induction motors. To be set by user + float torque_constant = 0.04f; // [Nm/A] for PM motors, [Nm/A^2] for induction motors. Equal to 8.27/Kv of the motor int32_t direction = 0; // 1 or -1 (0 = unspecified) MotorType motor_type = MOTOR_TYPE_HIGH_CURRENT; // Read out max_allowed_current to see max supported value for current_lim. // float current_lim = 70.0f; //[A] - float current_lim = 10.0f; //[A] - float current_lim_margin = 8.0f; // Maximum violation of current_lim - float torque_lim = 10.0f; //[Nm] - float torque_lim_margin = 8.0f; + float current_lim = 10.0f; //[A] + float current_lim_margin = 8.0f; // Maximum violation of current_lim + float torque_lim = std::numeric_limits::infinity(); //[Nm]. // Value used to compute shunt amplifier gains float requested_current_range = 60.0f; // [A] float current_control_bandwidth = 1000.0f; // [rad/s] diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 38e8a936..204e51ad 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -433,7 +433,6 @@ interfaces: current_lim: float32 current_lim_margin: float32 torque_lim: float32 - torque_lim_margin: float32 inverter_temp_limit_lower: float32 inverter_temp_limit_upper: float32 requested_current_range: float32 From e5ace9ecc831240757deb2e870e47793684ab92b Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Thu, 18 Jun 2020 18:32:24 -0400 Subject: [PATCH 474/549] Changed default values of vel_gain, vel_integrator_gain and torque_constant to work with ODrive branded motors. Changed units in comments from A to Nm where appropriate. --- Firmware/MotorControl/controller.hpp | 6 +++--- Firmware/MotorControl/motor.hpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 4fcc8a52..0b692450 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -23,12 +23,12 @@ public: InputMode input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t float pos_gain = 20.0f; // [(counts/s) / counts] float vel_gain = 0.2f / 10000.0f; // [Nm/(counts/s)] - // float vel_gain = 5.0f / 200.0f, // [A/(rad/s)] - float vel_integrator_gain = 0.4f / 10000.0f; // [Nm/(counts/s * s)] + // float vel_gain = 0.2f / 200.0f, // [Nm/(rad/s)] + float vel_integrator_gain = 0.4f / 10000.0f; // [Nm/(counts/s * s)] float vel_limit = 20000.0f; // [counts/s] Infinity to disable. float vel_limit_tolerance = 1.2f; // ratio to vel_lim. Infinity to disable. float vel_ramp_rate = 10000.0f; // [(counts/s) / s] - float torque_ramp_rate = 0.01f; // Nm / sec + float torque_ramp_rate = 0.01f; // Nm / sec bool setpoints_in_cpr = false; float inertia = 0.0f; // [A/(count/s^2)] float input_filter_bandwidth = 2.0f; // [1/s] diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 8583e0ef..fbf14af9 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -35,8 +35,8 @@ public: float async_phase_offset; // [rad electrical] }; - // NOTE: for gimbal motors, all units of A are instead V. - // example: vel_gain is [V/(count/s)] instead of [A/(count/s)] + // NOTE: for gimbal motors, all units of Nm are instead V. + // example: vel_gain is [V/(count/s)] instead of [Nm/(count/s)] // example: current_lim and calibration_current will instead determine the maximum voltage applied to the motor. struct Config_t { bool pre_calibrated = false; // can be set to true to indicate that all values here are valid From f63c7edcd0a4c608f04e5ef0ac0af44f3764acd1 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 19 Jun 2020 13:24:38 +0200 Subject: [PATCH 475/549] fix appearance on several browsers --- docs/assets/css/style.scss | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/assets/css/style.scss b/docs/assets/css/style.scss index dbccc0fd..92bf03ca 100644 --- a/docs/assets/css/style.scss +++ b/docs/assets/css/style.scss @@ -308,7 +308,7 @@ header li { } #navbar ul { - background-color: #ffffffd9; + background-color: rgba(255, 255, 255, 0.87); margin: 0; } @@ -326,7 +326,7 @@ header li { font-size:12px; font-weight: bold; color:#676767; - display:block; + display:flex; text-align:left; padding:0px 5px; margin:0px; @@ -334,13 +334,17 @@ header li { line-height:37px; } -.navitem a { display: block; } +.navitem a { + display: block; + width: 100%; +} .levelbar { float: inline-start; margin-left: 8px; margin-right: 5px; - border-left: 1px solid #0000004d; + border-left: 1px solid rgba(0, 0, 0, 0.3); + width: 1px !important; } .currentitem { From 4100fff46a651fbef676bb946f2e5c7957868128 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 22 Jun 2020 06:00:35 -0700 Subject: [PATCH 476/549] Prevent python from using __pycache__ during build Caching doesn't work well with the tup build system as tup acts offended when a build task writes files outside of tup's root directory. Particularly on Windows this manifests in errors like: tup error: File '[...]/__pycache__/unicode_escape.cpython-38.pyc' was written to, but is not in .tup/db. You probably should specify it as an output The option -B prevents python from using byte code cache. --- Firmware/Tupfile.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 35725d80..b67ad651 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -7,9 +7,9 @@ tup.include('build.lua') -- On some systems this may return a python2 command if Python3 is not installed. function find_python3() success, python_version = run_now("python --version") - if success and string.match(python_version, "Python 3") then return "python" end + if success and string.match(python_version, "Python 3") then return "python -B" end success, python_version = run_now("python3 --version") - if success and string.match(python_version, "Python 3") then return "python3" end + if success and string.match(python_version, "Python 3") then return "python3 -B" end error("Python 3 not found.") end From 2937e5bd27ea696151a42046107176c9b765c9ad Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Mon, 22 Jun 2020 23:30:56 +0100 Subject: [PATCH 477/549] Modified tolerances to work on different test rig --- tools/odrive/tests/closed_loop_test.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index 6d5bcecb..9a87feaf 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -176,7 +176,7 @@ class TestRegenProtection(TestClosedLoopControlBase): nominal_vel = float(enc_ctx.yaml['cpr']) * nominal_rps # Accept a bit of noise on Ibus - axis_ctx.parent.handle.config.dc_max_negative_current = -0.2 + axis_ctx.parent.handle.config.dc_max_negative_current = -0.1 logger.debug(f'Brake control test from {nominal_rps} rounds/s...') @@ -185,7 +185,6 @@ class TestRegenProtection(TestClosedLoopControlBase): axis_ctx.handle.controller.config.input_mode = INPUT_MODE_PASSTHROUGH request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) - # accelerate... axis_ctx.handle.controller.input_vel = nominal_vel time.sleep(1.0) @@ -198,7 +197,6 @@ class TestRegenProtection(TestClosedLoopControlBase): # once more, but this time without brake resistor axis_ctx.parent.handle.config.brake_resistance = 0 - # accelerate... axis_ctx.handle.controller.input_vel = nominal_vel time.sleep(1.0) @@ -222,7 +220,7 @@ class TestVelLimitInCurrentControl(TestClosedLoopControlBase): max_rps = 20.0 max_vel = float(enc_ctx.yaml['cpr']) * max_rps absolute_max_vel = max_vel * 1.2 - max_current = 3.0 + max_current = 10.0 axis_ctx.handle.controller.config.vel_gain /= 10 # reduce the slope to make it easier to see what's going on vel_gain = axis_ctx.handle.controller.config.vel_gain From cc87e4e6a8d11b96a9e758371c0e4f8c57027628 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Tue, 23 Jun 2020 00:34:33 +0100 Subject: [PATCH 478/549] Raised current limit from 10A to 15A for regen test. Previously, a motor overcurrent error was triggered ~50% of the time during the braking test. --- tools/odrive/tests/closed_loop_test.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index 631e3b17..dcc9b962 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -172,15 +172,17 @@ class TestRegenProtection(TestClosedLoopControlBase): def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): with self.prepare(axis_ctx, motor_ctx, enc_ctx, logger): - nominal_rps = 6.0 + nominal_rps = 10.0 nominal_vel = float(enc_ctx.yaml['cpr']) * nominal_rps + max_current = 15.0 # Accept a bit of noise on Ibus - axis_ctx.parent.handle.config.dc_max_negative_current = -0.1 + axis_ctx.parent.handle.config.dc_max_negative_current = -0.2 logger.debug(f'Brake control test from {nominal_rps} rounds/s...') - axis_ctx.handle.controller.config.vel_limit = float(enc_ctx.yaml['cpr']) * 10.0 # max 10 rps + axis_ctx.handle.controller.config.vel_limit = float(enc_ctx.yaml['cpr']) * 15.0 # max 15 rps + axis_ctx.handle.motor.config.current_lim = max_current axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL axis_ctx.handle.controller.config.input_mode = INPUT_MODE_PASSTHROUGH From 7c33fa5d27274d3ec48b86506768da1800298b7a Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Tue, 23 Jun 2020 01:23:11 +0100 Subject: [PATCH 479/549] Changed docs to reflect removal of current control mode and addition of torque control mode Regenerated enums changed can_test.py to reflect control mode change modified closed_loop_test.py to pass with A->Nm change. --- docs/commands.md | 2 +- docs/getting-started.md | 2 +- docs/input_modes.md | 4 +-- tools/odrive/enums.py | 4 +-- tools/odrive/tests/can_test.py | 6 ++--- tools/odrive/tests/closed_loop_test.py | 34 ++++++++++++++------------ 6 files changed, 27 insertions(+), 25 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 618ddaba..c647adfe 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -64,7 +64,7 @@ If you want a different mode, you can change `.controller.config.control_m Possible values are: * `CONTROL_MODE_POSITION_CONTROL` * `CONTROL_MODE_VELOCITY_CONTROL` -* `CONTROL_MODE_CURRENT_CONTROL` +* `CONTROL_MODE_TORQUE_CONTROL` * `CONTROL_MODE_VOLTAGE_CONTROL` - this one is not normally used. ### Input Mode diff --git a/docs/getting-started.md b/docs/getting-started.md index 9426f395..46b5af18 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -347,7 +347,7 @@ Activate the ramped velocity mode: `axis.controller.config.input_mode = INPUT_MO You can now control the velocity with `axis.controller.input_vel = 5000` [count/s]. ### Torque control -Set `axis.controller.config.control_mode = CONTROL_MODE_CURRENT_CONTROL`.
        +Set `axis.controller.config.control_mode = CONTROL_MODE_TORQUE_CONTROL`.
        You can now control the torque with `axis.controller.input_torque = 0.1` [Nm]. Note: If you exceed `vel_limit` in current control mode, the current is reduced. To disable this, set `axis.controller.enable_current_mode_vel_limit = False`. diff --git a/docs/input_modes.md b/docs/input_modes.md index 53463ad0..9a87bcee 100644 --- a/docs/input_modes.md +++ b/docs/input_modes.md @@ -31,7 +31,7 @@ Pass `input_xxx` through to `xxx_setpoint` directly. ### Valid Control modes: * `CONTROL_MODE_VOLTAGE_CONTROL` -* `CONTROL_MODE_CURRENT_CONTROL` +* `CONTROL_MODE_TORQUE_CONTROL` * `CONTROL_MODE_VELOCITY_CONTROL` * `CONTROL_MODE_POSITION_CONTROL` @@ -95,7 +95,7 @@ Ramp a torque command from the current value to the target value. * `input_torque` ### Valid Control Modes: -* `CONTROL_MODE_CURRENT_CONTROL` +* `CONTROL_MODE_TORQUE_CONTROL` ## INPUT_MODE_MIRROR Implements "electronic mirroring". This is like electronic camming, but you can only mirror exactly the movements of the other motor, according to a fixed ratio diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 2fe32b24..8fccc74d 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -30,7 +30,7 @@ ENCODER_MODE_SPI_ABS_AEAT = 258 # ODrive.Controller.ControlMode CONTROL_MODE_VOLTAGE_CONTROL = 0 -CONTROL_MODE_CURRENT_CONTROL = 1 +CONTROL_MODE_TORQUE_CONTROL = 1 CONTROL_MODE_VELOCITY_CONTROL = 2 CONTROL_MODE_POSITION_CONTROL = 3 @@ -41,7 +41,7 @@ INPUT_MODE_VEL_RAMP = 2 INPUT_MODE_POS_FILTER = 3 INPUT_MODE_MIX_CHANNELS = 4 INPUT_MODE_TRAP_TRAJ = 5 -INPUT_MODE_CURRENT_RAMP = 6 +INPUT_MODE_TORQUE_RAMP = 6 INPUT_MODE_MIRROR = 7 # ODrive.Motor.MotorType diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index 8e8e9c4b..93e72210 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -187,10 +187,10 @@ class TestSimpleCAN(): test_assert_eq(axis.controller.input_vel, -10.0, range=0.01) test_assert_eq(axis.controller.input_torque, 30.1234, range=0.01) - axis.controller.config.control_mode = CONTROL_MODE_CURRENT_CONTROL - my_cmd('set_input_torque', input_torque=3.1415) + axis.controller.config.control_mode = CONTROL_MODE_TORQUE_CONTROL + my_cmd('set_input_torque', input_torque=0.1) fence() - test_assert_eq(axis.controller.input_torque, 3.1415, range=0.01) + test_assert_eq(axis.controller.input_torque, 0.1, range=0.01) my_cmd('set_velocity_limit', velocity_limit=23456.78) fence() diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index dcc9b962..d900e6a4 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -211,9 +211,9 @@ class TestRegenProtection(TestClosedLoopControlBase): test_assert_eq(axis_ctx.handle.motor.error, MOTOR_ERROR_DC_BUS_OVER_REGEN_CURRENT) -class TestVelLimitInCurrentControl(TestClosedLoopControlBase): +class TestVelLimitInTorqueControl(TestClosedLoopControlBase): """ - Ensures that the current setpoint in current control is always within the + Ensures that the current setpoint in torque control is always within the parallelogram that arises from -Ilim, +Ilim, vel_limit and vel_gain. """ @@ -222,7 +222,8 @@ class TestVelLimitInCurrentControl(TestClosedLoopControlBase): max_rps = 20.0 max_vel = float(enc_ctx.yaml['cpr']) * max_rps absolute_max_vel = max_vel * 1.2 - max_current = 10.0 + max_current = 15.0 + torque_constant = 0.0305 #correct for 5065 motor axis_ctx.handle.controller.config.vel_gain /= 10 # reduce the slope to make it easier to see what's going on vel_gain = axis_ctx.handle.controller.config.vel_gain @@ -231,11 +232,12 @@ class TestVelLimitInCurrentControl(TestClosedLoopControlBase): axis_ctx.handle.controller.config.vel_limit = max_vel axis_ctx.handle.controller.config.vel_limit_tolerance = inf # disable hard limit on velocity axis_ctx.handle.motor.config.current_lim = max_current - axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_CURRENT_CONTROL + axis_ctx.handle.motor.config.torque_constant = torque_constant + axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_TORQUE_CONTROL # Returns the expected limited setpoint for a given velocity and current def get_expected_setpoint(input_setpoint, velocity): - return clamp(clamp(input_setpoint, (velocity + max_vel) * -vel_gain, (velocity - max_vel) * -vel_gain), -max_current, max_current) + return clamp(clamp(input_setpoint / torque_constant, (velocity + max_vel) * -vel_gain / torque_constant, (velocity - max_vel) * -vel_gain / torque_constant), -max_current, max_current) def data_getter(): # sample velocity twice to avoid systematic bias @@ -252,13 +254,13 @@ class TestVelLimitInCurrentControl(TestClosedLoopControlBase): request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) # Move the system around its operating envelope - axis_ctx.handle.controller.input_torque = input_torque = 2.0 + axis_ctx.handle.controller.input_torque = input_torque = 2.0 * torque_constant dataA = record_log(data_getter, duration=1.0) - axis_ctx.handle.controller.input_torque = input_torque = -2.0 + axis_ctx.handle.controller.input_torque = input_torque = -2.0 * torque_constant dataA = np.concatenate([dataA, record_log(data_getter, duration=1.0)]) - axis_ctx.handle.controller.input_torque = input_torque = 4.0 + axis_ctx.handle.controller.input_torque = input_torque = 4.0 * torque_constant dataA = np.concatenate([dataA, record_log(data_getter, duration=1.0)]) - axis_ctx.handle.controller.input_torque = input_torque = -4.0 + axis_ctx.handle.controller.input_torque = input_torque = -4.0 * torque_constant dataA = np.concatenate([dataA, record_log(data_getter, duration=1.0)]) # Shrink the operating envelope while motor is moving faster than the envelope allows @@ -267,22 +269,22 @@ class TestVelLimitInCurrentControl(TestClosedLoopControlBase): axis_ctx.handle.controller.config.vel_limit = max_vel # Move the system around its operating envelope - axis_ctx.handle.controller.input_torque = input_torque = 2.0 + axis_ctx.handle.controller.input_torque = input_torque = 2.0 * torque_constant dataB = record_log(data_getter, duration=1.0) - axis_ctx.handle.controller.input_torque = input_torque = -2.0 + axis_ctx.handle.controller.input_torque = input_torque = -2.0 * torque_constant dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)]) - axis_ctx.handle.controller.input_torque = input_torque = 4.0 + axis_ctx.handle.controller.input_torque = input_torque = 4.0 * torque_constant dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)]) - axis_ctx.handle.controller.input_torque = input_torque = -4.0 + axis_ctx.handle.controller.input_torque = input_torque = -4.0 * torque_constant dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)]) # Try the shrink maneuver again at positive velocity axis_ctx.handle.controller.config.vel_limit = 20.0 * float(enc_ctx.yaml['cpr']) - axis_ctx.handle.controller.input_torque = 4.0 + axis_ctx.handle.controller.input_torque = 4.0 * torque_constant time.sleep(0.5) axis_ctx.handle.controller.config.vel_limit = max_vel - axis_ctx.handle.controller.input_torque = input_torque = 2.0 + axis_ctx.handle.controller.input_torque = input_torque = 2.0 * torque_constant dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)]) test_assert_no_error(axis_ctx) @@ -298,5 +300,5 @@ if __name__ == '__main__': test_runner.run([ TestClosedLoopControl(), TestRegenProtection(), - TestVelLimitInCurrentControl() + TestVelLimitInTorqueControl() ]) From fa1cf2553fa5611fcbbd2ede5d58538303ab20fc Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Tue, 23 Jun 2020 03:46:45 +0100 Subject: [PATCH 480/549] Added test for torque limit - TestTorqueLimit() --- tools/odrive/tests/closed_loop_test.py | 84 +++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index d900e6a4..31adce5d 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -294,11 +294,93 @@ class TestVelLimitInTorqueControl(TestClosedLoopControlBase): test_curve_fit(dataA[:,(0,3)], dataA[:,4], max_mean_err=0.02, inlier_range=0.05, max_outliers=len(dataA[:,0]*0.01)) test_curve_fit(dataB[:,(0,3)], dataB[:,4], max_mean_err=0.1, inlier_range=0.2, max_outliers=len(dataB[:,0])*0.01) +class TestTorqueLimit(TestClosedLoopControlBase): + """ + Checks that the torque limit is respected in position, velocity, and torque control modes + """ + def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): + with self.prepare(axis_ctx, motor_ctx, enc_ctx, logger): + max_rps = 15.0 + max_vel = max_rps * float(enc_ctx.yaml['cpr']) + max_current = 30.0 + max_torque = 0.1 # must be less than max_current * torque_constant. + torque_constant = axis_ctx.handle.motor.config.torque_constant + test_pos = 5 * float(enc_ctx.yaml['cpr']) + test_vel = 10 * float(enc_ctx.yaml['cpr']) + test_torque = 0.5 + + axis_ctx.handle.controller.config.vel_limit = max_vel + axis_ctx.handle.motor.config.current_lim = max_current + axis_ctx.handle.motor.config.torque_lim = inf #disable torque limit + axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL + + def data_getter(): + current_setpoint = axis_ctx.handle.motor.current_control.Iq_setpoint + torque_setpoint = current_setpoint * torque_constant + torque_limit = axis_ctx.handle.motor.config.torque_lim + # Abort immediately if the absolute limits are exceeded + test_assert_within(current_setpoint, -max_current, max_current) + test_assert_within(torque_setpoint, -torque_limit, torque_limit) + return max_current, current_setpoint, torque_limit, torque_setpoint + + # begin test + axis_ctx.handle.motor.config.torque_lim = max_torque + request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + + # step input positions + logger.debug('input_pos step test') + axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_POSITION_CONTROL + axis_ctx.handle.controller.input_pos = test_pos + dataPos = record_log(data_getter, duration=1.0) + axis_ctx.handle.controller.input_pos = -test_pos + dataPos = np.concatenate([dataPos, record_log(data_getter, duration=1.0)]) + axis_ctx.handle.controller.input_pos = test_pos + dataPos = np.concatenate([dataPos, record_log(data_getter, duration=1.0)]) + axis_ctx.handle.controller.input_pos = -test_pos + dataPos = np.concatenate([dataPos, record_log(data_getter, duration=1.0)]) + time.sleep(0.5) + + test_assert_no_error(axis_ctx) + + # step input velocities + logger.debug('input_vel step test') + axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL + axis_ctx.handle.controller.input_vel = test_vel + dataVel = record_log(data_getter, duration=1.0) + axis_ctx.handle.controller.input_vel = -test_vel + dataVel = np.concatenate([dataVel, record_log(data_getter, duration=1.0)]) + axis_ctx.handle.controller.input_vel = test_vel + dataVel = np.concatenate([dataVel, record_log(data_getter, duration=1.0)]) + axis_ctx.handle.controller.input_vel = -test_vel + dataVel = np.concatenate([dataVel, record_log(data_getter, duration=1.0)]) + axis_ctx.handle.controller.input_vel = 0 + time.sleep(0.5) + + # step input torques + logger.debug('input_torque step test') + axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_TORQUE_CONTROL + axis_ctx.handle.controller.input_torque = test_torque + dataTq = record_log(data_getter, duration=1.0) + axis_ctx.handle.controller.input_torque = -test_torque + dataTq = np.concatenate([dataTq, record_log(data_getter, duration=1.0)]) + axis_ctx.handle.controller.input_torque = test_torque + dataTq = np.concatenate([dataTq, record_log(data_getter, duration=1.0)]) + axis_ctx.handle.controller.input_torque = -test_torque + dataTq = np.concatenate([dataTq, record_log(data_getter, duration=1.0)]) + axis_ctx.handle.controller.input_torque = 0 + time.sleep(0.5) + + # did we pass? + + test_assert_no_error(axis_ctx) + + axis_ctx.handle.requested_state=1 if __name__ == '__main__': test_runner.run([ TestClosedLoopControl(), TestRegenProtection(), - TestVelLimitInTorqueControl() + TestVelLimitInTorqueControl(), + TestTorqueLimit() ]) From 110a97688b923ccc7f5da89bf7a72971b2d7422a Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Tue, 23 Jun 2020 18:36:18 +0100 Subject: [PATCH 481/549] Found pinout typo in test-rig-rpi.yaml for teensy<->odrive encoder pins. Modified encoder_test.py to work with correct test rig pin mapping --- tools/odrive/tests/encoder_test.py | 10 +++++----- tools/test-rig-rpi.yaml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/odrive/tests/encoder_test.py b/tools/odrive/tests/encoder_test.py index 3f19c7b2..88bbe3f0 100644 --- a/tools/odrive/tests/encoder_test.py +++ b/tools/odrive/tests/encoder_test.py @@ -121,7 +121,7 @@ class TestIncrementalEncoder(TestEncoderBase): def run_test(self, enc: ODriveEncoderComponent, teensy: TeensyComponent, teensy_gpio_a: TeensyGpio, teensy_gpio_b: TeensyGpio, logger: Logger): - true_cps = 8192*-0.5 # counts per second generated by the virtual encoder + true_cps = 8192*0.5 # counts per second generated by the virtual encoder code = teensy_incremental_encoder_emulation_code.replace("{enc_a}", str(teensy_gpio_a.num)).replace("{enc_b}", str(teensy_gpio_b.num)) teensy.compile_and_program(code) @@ -249,7 +249,7 @@ class TestHallEffectEncoder(TestEncoderBase): def run_test(self, enc: ODriveEncoderComponent, teensy: TeensyComponent, teensy_gpio_a: TeensyGpio, teensy_gpio_b: TeensyGpio, teensy_gpio_c: TeensyGpio, logger: Logger): true_cpr = 90 - true_rps = -1.0 + true_rps = 1.0 code = teensy_hall_effect_encoder_emulation_code.replace("{hall_a}", str(teensy_gpio_a.num)).replace("{hall_b}", str(teensy_gpio_b.num)).replace("{hall_c}", str(teensy_gpio_c.num)) teensy.compile_and_program(code) @@ -504,9 +504,9 @@ class TestSpiEncoder(TestEncoderBase): if __name__ == '__main__': test_runner.run([ - TestIncrementalEncoder(), - TestSinCosEncoder(), - TestHallEffectEncoder(), + #TestIncrementalEncoder(), + #TestSinCosEncoder(), + #TestHallEffectEncoder(), TestSpiEncoder(ENCODER_MODE_SPI_ABS_AMS), TestSpiEncoder(ENCODER_MODE_SPI_ABS_CUI), ]) diff --git a/tools/test-rig-rpi.yaml b/tools/test-rig-rpi.yaml index 4fb0b4e3..640dda40 100644 --- a/tools/test-rig-rpi.yaml +++ b/tools/test-rig-rpi.yaml @@ -30,7 +30,7 @@ components: - type: odrive name: odrive board-version: v3.6-58V - serial-number: "20703595524B" + serial-number: "2061398A4D4D" brake-resistance: 0.47 usb: auto can: main_canbus @@ -78,11 +78,11 @@ connections: - ['teensy.gpio6', 'rpi.gpio20'] - ['teensy.gpio7', 'rpi.gpio19'] - ['teensy.gpio23', 'odrive.encoder0.z'] - - ['teensy.gpio22', 'odrive.encoder0.a'] - - ['teensy.gpio21', 'odrive.encoder0.b'] + - ['teensy.gpio22', 'odrive.encoder0.b'] + - ['teensy.gpio21', 'odrive.encoder0.a'] - ['teensy.gpio20', 'odrive.encoder1.z'] - - ['teensy.gpio19', 'odrive.encoder1.a'] - - ['teensy.gpio18', 'odrive.encoder1.b'] + - ['teensy.gpio19', 'odrive.encoder1.b'] + - ['teensy.gpio18', 'odrive.encoder1.a'] - ['teensy.gpio0', 'real_encoder.z'] - ['teensy.gpio1', 'real_encoder.a'] - ['teensy.gpio2', 'real_encoder.b'] From 57d67290bb5a8d0d2e6e87108673918e81ddc79f Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Tue, 23 Jun 2020 19:15:01 +0100 Subject: [PATCH 482/549] Merge issue, reverted to previous serial-number --- tools/test-rig-rpi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/test-rig-rpi.yaml b/tools/test-rig-rpi.yaml index 640dda40..b9954049 100644 --- a/tools/test-rig-rpi.yaml +++ b/tools/test-rig-rpi.yaml @@ -30,7 +30,7 @@ components: - type: odrive name: odrive board-version: v3.6-58V - serial-number: "2061398A4D4D" + serial-number: "20703595524B" brake-resistance: 0.47 usb: auto can: main_canbus From c63e51f8d7bd4f16e33f8befbe75991e3cda3094 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Tue, 23 Jun 2020 16:04:00 -0400 Subject: [PATCH 483/549] Changed docs to reflect change from CONTROL_MODE_CURRENT_CONTROL to CONTROL_MODE_TORQUE_CONTROL --- Firmware/odrive-interface.yaml | 18 +++++---------- docs/commands.md | 40 ---------------------------------- 2 files changed, 5 insertions(+), 53 deletions(-) diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index eab23463..7113b71c 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -867,12 +867,8 @@ valuetypes: # Note: these should be sorted from lowest level of control to # highest level of control, to allow "<" style comparisons. VoltageControl: -<<<<<<< HEAD - TorqueControl: -======= doc: this one is not normally used CurrentControl: ->>>>>>> 99507d2ece83127ca556f8fd34994075d517111e VelocityControl: PositionControl: @@ -890,7 +886,7 @@ valuetypes: ### Valid Control modes: * `CONTROL_MODE_VOLTAGE_CONTROL` - * `CONTROL_MODE_CURRENT_CONTROL` + * `CONTROL_MODE_TORQUE_CONTROL` * `CONTROL_MODE_VELOCITY_CONTROL` * `CONTROL_MODE_POSITION_CONTROL` VelRamp: @@ -926,9 +922,6 @@ valuetypes: MixChannels: brief: Not Implemented. TrapTraj: -<<<<<<< HEAD - TorqueRamp: -======= brief: Implementes an online trapezoidal trajectory planner. doc: | ![Trapezoidal Planner Response](../TrapTrajPosVel.PNG) @@ -944,18 +937,17 @@ valuetypes: ### Valid Control Modes: * `CONTROL_MODE_POSITION_CONTROL` - CurrentRamp: - brief: Ramp a current command from the current value to the target value. + TorqueRamp: + brief: Ramp a torque command from the current value to the target value. doc: | ### Configuration Values: - * `config.current_ramp_rate` + * `config.torque_ramp_rate` ### Valid Inputs: * `input_current` ### Valid Control Modes: - * `CONTROL_MODE_CURRENT_CONTROL` ->>>>>>> 99507d2ece83127ca556f8fd34994075d517111e + * `CONTROL_MODE_TORQUE_CONTROL` Mirror: brief: Implements "electronic mirroring". doc: | diff --git a/docs/commands.md b/docs/commands.md index 15bf9cb3..47a31df6 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -38,59 +38,19 @@ See [here](api/odrive.axis.axisstate) for a description of each state. ### Control Mode The default control mode is position control. If you want a different mode, you can change `.controller.config.control_mode`. -<<<<<<< HEAD -Possible values are: -* `CONTROL_MODE_POSITION_CONTROL` -* `CONTROL_MODE_VELOCITY_CONTROL` -* `CONTROL_MODE_TORQUE_CONTROL` -* `CONTROL_MODE_VOLTAGE_CONTROL` - this one is not normally used. - -### Input Mode -The default input mode is `INPUT_MODE_PASSTHROUGH`. -Modes can be selected by changing `.controller.config.input_mode`. -Possible values are: -* `INPUT_MODE_INACTIVE` -* `INPUT_MODE_PASSTHROUGH` -* `INPUT_MODE_VEL_RAMP` -* `INPUT_MODE_POS_FILTER` -* `INPUT_MODE_MIX_CHANNELS` -* `INPUT_MODE_TRAP_TRAJ` -* `INPUT_MODE_TORQUE_RAMP` -* `INPUT_MODE_MIRROR` - -For more information, see [input_modes](input_modes.md). - -# Control Commands -======= Possible values are listed [here](api/odrive.axis.controller.controlmode). ### Input Mode As of version v0.5.0, ODrive now intercepts the incoming commands and can apply filters to them. The old protocol values `pos_setpoint`, `vel_setpoint`, and `current_setpoint` are still used internally by the closed-loop cascade control, but the user cannot write to them directly. This allows us to condense the number of ways the ODrive accepts motion commands. The new commands are: ->>>>>>> 99507d2ece83127ca556f8fd34994075d517111e * `.controller.input_pos = ` * `.controller.input_vel = ` * `.controller.input_torque = ` -<<<<<<< HEAD -### Input Mode -To modify the way the control command affects the motor, you can use the input mode. The default input mode is pass through. -If you want a different mode, you can change `.controller.config.input_mode`. -Possible values are: -* `INPUT_MODE_INACTIVE` -* `INPUT_MODE_PASSTHROUGH` -* `INPUT_MODE_VEL_RAMP` -* `INPUT_MODE_POS_FILTER` -* `INPUT_MODE_MIX_CHANNELS` -* `INPUT_MODE_TRAP_TRAJ` -* `INPUT_MODE_TORQUE_RAMP` -* `INPUT_MODE_MIRROR` -======= Modes can be selected by changing `.controller.config.input_mode`. The default input mode is `INPUT_MODE_PASSTHROUGH`. Possible values are listed [here](api/odrive.axis.controller.inputmode). ->>>>>>> 99507d2ece83127ca556f8fd34994075d517111e ## System monitoring commands From eb494f409afd8e0feed28f59db55bb6e5b3ac6c1 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Tue, 23 Jun 2020 16:11:11 -0400 Subject: [PATCH 484/549] Change from CurrentControl in interface yaml to TorqueControl --- Firmware/odrive-interface.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 7113b71c..b738e0df 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -868,7 +868,7 @@ valuetypes: # highest level of control, to allow "<" style comparisons. VoltageControl: doc: this one is not normally used - CurrentControl: + TorqueControl: VelocityControl: PositionControl: From ea99feaeb98774ebea9712ad0ea7d776c1a9dcb5 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Tue, 23 Jun 2020 22:25:19 +0100 Subject: [PATCH 485/549] Accidentally commented out some encoder tests, fixed. --- tools/odrive/tests/encoder_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/odrive/tests/encoder_test.py b/tools/odrive/tests/encoder_test.py index 88bbe3f0..dafb88dd 100644 --- a/tools/odrive/tests/encoder_test.py +++ b/tools/odrive/tests/encoder_test.py @@ -504,9 +504,9 @@ class TestSpiEncoder(TestEncoderBase): if __name__ == '__main__': test_runner.run([ - #TestIncrementalEncoder(), - #TestSinCosEncoder(), - #TestHallEffectEncoder(), + TestIncrementalEncoder(), + TestSinCosEncoder(), + TestHallEffectEncoder(), TestSpiEncoder(ENCODER_MODE_SPI_ABS_AMS), TestSpiEncoder(ENCODER_MODE_SPI_ABS_CUI), ]) From 98e9d730f4a804c7d35983a3f3d905b4bfa3f58b Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Wed, 24 Jun 2020 21:40:10 +0100 Subject: [PATCH 486/549] Added "shadow" variables for counts_to_rads. Controller::update() tested and working with radian inputs. --- Firmware/MotorControl/controller.cpp | 6 +++--- Firmware/MotorControl/controller.hpp | 9 ++++++--- Firmware/MotorControl/encoder.cpp | 5 +++++ Firmware/MotorControl/encoder.hpp | 4 ++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index c15b1cbe..190157ea 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -34,14 +34,14 @@ bool Controller::select_encoder(size_t encoder_num) { if (encoder_num < AXIS_COUNT) { Axis* ax = axes[encoder_num]; if (config_.setpoints_in_cpr) { - pos_estimate_src_ = &ax->encoder_.pos_cpr_; + pos_estimate_src_ = &ax->encoder_.pos_cpr_rad_; pos_wrap_src_ = &ax->encoder_.config_.cpr; } else { - pos_estimate_src_ = &ax->encoder_.pos_estimate_; + pos_estimate_src_ = &ax->encoder_.pos_est_rad_; pos_wrap_src_ = nullptr; } pos_estimate_valid_src_ = &ax->encoder_.pos_estimate_valid_; - vel_estimate_src_ = &ax->encoder_.vel_estimate_; + vel_estimate_src_ = &ax->encoder_.vel_est_rad_; vel_estimate_valid_src_ = &ax->encoder_.vel_estimate_valid_; return true; } else { diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 0b692450..c8360643 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -22,10 +22,13 @@ public: ControlMode control_mode = CONTROL_MODE_POSITION_CONTROL; //see: ControlMode_t InputMode input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t float pos_gain = 20.0f; // [(counts/s) / counts] - float vel_gain = 0.2f / 10000.0f; // [Nm/(counts/s)] + //float vel_gain = 0.2f / 10000.0f; // [Nm/(counts/s)] + float vel_gain = 0.2f / 7.7f; // [Nm/(counts/s)] // float vel_gain = 0.2f / 200.0f, // [Nm/(rad/s)] - float vel_integrator_gain = 0.4f / 10000.0f; // [Nm/(counts/s * s)] - float vel_limit = 20000.0f; // [counts/s] Infinity to disable. + //float vel_integrator_gain = 0.4f / 10000.0f; // [Nm/(counts/s * s)] + float vel_integrator_gain = 0.4f / 7.7f; // [Nm/(counts/s * s)] + //float vel_limit = 20000.0f; // [counts/s] Infinity to disable. + float vel_limit = 20000.0f * 2.0f * M_PI / 8192.0f; // [counts/s] Infinity to disable. float vel_limit_tolerance = 1.2f; // ratio to vel_lim. Infinity to disable. float vel_ramp_rate = 10000.0f; // [(counts/s) / s] float torque_ramp_rate = 0.01f; // Nm / sec diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 8f2c7eb8..569b438b 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -522,6 +522,11 @@ bool Encoder::update() { snap_to_zero_vel = true; } + //new vars in radians + pos_est_rad_ = pos_estimate_ * 2.0f * M_PI / (float)config_.cpr; + vel_est_rad_ = vel_estimate_ * 2.0f * M_PI / (float)config_.cpr; + pos_cpr_rad_ = pos_cpr_ * 2.0f * M_PI / (float)config_.cpr; + //// run encoder count interpolation int32_t corrected_enc = count_in_cpr_ - config_.offset; // if we are stopped, make sure we don't randomly drift diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index cb97a45b..300157dc 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -84,6 +84,10 @@ public: int32_t pos_abs_ = 0; float spi_error_rate_ = 0.0f; + float pos_est_rad_ = 0.0f; + float vel_est_rad_ = 0.0f; + float pos_cpr_rad_ = 0.0f; + bool pos_estimate_valid_ = false; bool vel_estimate_valid_ = false; From a9b90eb11a5d5d12384ec1066dd4eb69833624a6 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 24 Jun 2020 20:34:44 -0400 Subject: [PATCH 487/549] Add RLS SPI encoder (single-turn) --- Firmware/MotorControl/encoder.cpp | 7 +++++++ Firmware/MotorControl/encoder.hpp | 1 + 2 files changed, 8 insertions(+) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index d1545fc1..c9f27a36 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -311,6 +311,7 @@ void Encoder::sample_now() { case MODE_SPI_ABS_AMS: case MODE_SPI_ABS_CUI: case MODE_SPI_ABS_AEAT: + case MODE_SPI_ABS_RLS: { axis_->motor_.log_timing(Motor::TIMING_LOG_SAMPLE_NOW); // Do nothing @@ -400,6 +401,11 @@ void Encoder::abs_spi_cb(){ pos = rawVal & 0x3fff; } break; + case MODE_SPI_ABS_RLS: { + uint16_t rawVal = abs_spi_dma_rx_[0]; + pos = (rawVal >> 2) & 0x3fff; + } break; + default: { set_error(ERROR_UNSUPPORTED_ENCODER_MODE); return; @@ -469,6 +475,7 @@ bool Encoder::update() { delta_enc -= 6283; } break; + case MODE_SPI_ABS_RLS: case MODE_SPI_ABS_AMS: case MODE_SPI_ABS_CUI: case MODE_SPI_ABS_AEAT: { diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index fcf94101..99f93e0f 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -27,6 +27,7 @@ public: MODE_SPI_ABS_CUI = 0x100, //!< compatible with CUI AMT23xx MODE_SPI_ABS_AMS = 0x101, //!< compatible with AMS AS5047P, AS5048A/AS5048B (no daisy chain support) MODE_SPI_ABS_AEAT = 0x102, //!< not yet implemented + MODE_SPI_ABS_RLS = 0x103, }; const uint32_t MODE_FLAG_ABS = 0x100; From 9faf46ad4778e803b031729aae6d73ad57fd38f5 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Thu, 25 Jun 2020 21:39:20 +0100 Subject: [PATCH 488/549] Changed from counts->radians internally. By default, input positions and velocities are also in radians. Added multipliers for input pos/vel to allow users to define their own units for those inputs. --- Firmware/MotorControl/axis.cpp | 6 +++--- Firmware/MotorControl/controller.cpp | 18 +++++++++--------- Firmware/MotorControl/controller.hpp | 9 ++++++++- Firmware/MotorControl/encoder.cpp | 2 +- Firmware/MotorControl/encoder.hpp | 10 +++++----- Firmware/communication/ascii_protocol.cpp | 4 ++-- Firmware/communication/can_simple.cpp | 8 ++++---- Firmware/odrive-interface.yaml | 9 +++++++-- tools/odrive/shell.py | 2 +- tools/odrivetool | 5 +++-- 10 files changed, 43 insertions(+), 30 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 172f3dc4..d501c806 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -315,7 +315,7 @@ bool Axis::run_closed_loop_control_loop() { if (!controller_.update(&torque_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; - float phase_vel = 2 * M_PI * encoder_.vel_estimate_ / (float)encoder_.config_.cpr * motor_.config_.pole_pairs; + float phase_vel = encoder_.vel_est_rad_ * motor_.config_.pole_pairs; if (!motor_.update(torque_setpoint, encoder_.phase_, phase_vel)) return false; // set_error should update axis.error_ @@ -364,7 +364,7 @@ bool Axis::run_homing() { if (!controller_.update(&torque_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; - float phase_vel = 2 * M_PI * encoder_.vel_estimate_ / (float)encoder_.config_.cpr * motor_.config_.pole_pairs; + float phase_vel = encoder_.vel_est_rad_ * motor_.config_.pole_pairs; if (!motor_.update(torque_setpoint, encoder_.phase_, phase_vel)) return false; // set_error should update axis.error_ @@ -393,7 +393,7 @@ bool Axis::run_homing() { if (!controller_.update(&torque_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; - float phase_vel = 2 * M_PI * encoder_.vel_estimate_ / (float)encoder_.config_.cpr * motor_.config_.pole_pairs; + float phase_vel = encoder_.vel_est_rad_ * motor_.config_.pole_pairs; if (!motor_.update(torque_setpoint, encoder_.phase_, phase_vel)) return false; // set_error should update axis.error_ diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 190157ea..c8ea8f05 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -85,8 +85,8 @@ void Controller::start_anticogging_calibration() { */ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) { float pos_err = input_pos_ - pos_estimate; - if (std::abs(pos_err) <= config_.anticogging.calib_pos_threshold && - std::abs(vel_estimate) < config_.anticogging.calib_vel_threshold) { + if (std::abs(pos_err) <= config_.anticogging.calib_pos_threshold * (2.0f * M_PI) / (float)axis_->encoder_.config_.cpr && + std::abs(vel_estimate) < config_.anticogging.calib_vel_threshold * (2.0f * M_PI) / (float)axis_->encoder_.config_.cpr) { config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_torque_; } if (config_.anticogging.index < 3600) { @@ -128,19 +128,19 @@ bool Controller::update(float* torque_setpoint_output) { ? vel_estimate_src_ : nullptr; // Calib_anticogging is only true when calibration is occurring, so we can't block anticogging_pos - float anticogging_pos = axis_->encoder_.pos_estimate_ / axis_->encoder_.getCoggingRatio(); + float anticogging_pos = axis_->encoder_.pos_est_rad_ / axis_->encoder_.getCoggingRatio(); if (config_.anticogging.calib_anticogging) { if (!axis_->encoder_.pos_estimate_valid_ || !axis_->encoder_.vel_estimate_valid_) { set_error(ERROR_INVALID_ESTIMATE); return false; } // non-blocking - anticogging_calibration(axis_->encoder_.pos_estimate_, axis_->encoder_.vel_estimate_); + anticogging_calibration(axis_->encoder_.pos_est_rad_, axis_->encoder_.vel_est_rad_); } // TODO also enable circular deltas for 2nd order filter, etc. if (pos_wrap_src_) { - float cpr = *pos_wrap_src_; + float cpr = *pos_wrap_src_ * 2.0f * M_PI / ((float)axis_->encoder_.config_.cpr); // Keep pos setpoint from drifting input_pos_ = fmodf_pos(input_pos_, cpr); } @@ -153,7 +153,7 @@ bool Controller::update(float* torque_setpoint_output) { case INPUT_MODE_PASSTHROUGH: { pos_setpoint_ = input_pos_; vel_setpoint_ = input_vel_; - torque_setpoint_ = input_torque_; // + torque_setpoint_ = input_torque_; } break; case INPUT_MODE_VEL_RAMP: { float max_step_size = std::abs(current_meas_period * config_.vel_ramp_rate); @@ -181,8 +181,8 @@ bool Controller::update(float* torque_setpoint_output) { } break; case INPUT_MODE_MIRROR: { if (config_.axis_to_mirror < AXIS_COUNT) { - pos_setpoint_ = axes[config_.axis_to_mirror]->encoder_.pos_estimate_ * config_.mirror_ratio; - vel_setpoint_ = axes[config_.axis_to_mirror]->encoder_.vel_estimate_ * config_.mirror_ratio; + pos_setpoint_ = axes[config_.axis_to_mirror]->encoder_.pos_est_rad_ * config_.mirror_ratio; + vel_setpoint_ = axes[config_.axis_to_mirror]->encoder_.vel_est_rad_ * config_.mirror_ratio; } else { set_error(ERROR_INVALID_MIRROR_AXIS); return false; @@ -235,7 +235,7 @@ bool Controller::update(float* torque_setpoint_output) { } if (pos_wrap_src_) { - float cpr = *pos_wrap_src_; + float cpr = *pos_wrap_src_ * 2.0f * M_PI / ((float)axis_->encoder_.config_.cpr); // Keep pos setpoint from drifting pos_setpoint_ = fmodf_pos(pos_setpoint_, cpr); // Circular delta diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index c8360643..ee85ba76 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -45,6 +45,8 @@ public: uint8_t axis_to_mirror = -1; float mirror_ratio = 1.0f; uint8_t load_encoder_axis = -1; // default depends on Axis number and is set in load_configuration() + float input_pos_multiplier = 1.0f; // if input_pos is set by user, it is multiplied by this + float input_vel_multiplier = 1.0f; // if input_vel is set by user, it is multiplied by this // custom setters Controller* parent; @@ -99,7 +101,12 @@ public: bool anticogging_valid_ = false; // custom setters - void set_input_pos(float value) { input_pos_ = value; input_pos_updated(); } + void set_input_pos(float value) { input_pos_ = value * config_.input_pos_multiplier; input_pos_updated(); } + void set_input_vel(float value) { input_vel_ = value * config_.input_vel_multiplier;} + + // custom getters + float get_input_pos(void) { return input_pos_ / (config_.input_pos_multiplier == 0.0f ? 1.0f : config_.input_pos_multiplier);} + float get_input_vel(void) { return input_vel_ / (config_.input_vel_multiplier == 0.0f ? 1.0f : config_.input_vel_multiplier);} }; #endif // __CONTROLLER_HPP diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 569b438b..ad9c02e5 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -522,7 +522,7 @@ bool Encoder::update() { snap_to_zero_vel = true; } - //new vars in radians + //expose pos/vel estimates in radians for Controller pos_est_rad_ = pos_estimate_ * 2.0f * M_PI / (float)config_.cpr; vel_est_rad_ = vel_estimate_ * 2.0f * M_PI / (float)config_.cpr; pos_cpr_rad_ = pos_cpr_ * 2.0f * M_PI / (float)config_.cpr; diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 300157dc..3fe8b3a2 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -74,7 +74,7 @@ public: int32_t shadow_count_ = 0; int32_t count_in_cpr_ = 0; float interpolation_ = 0.0f; - float phase_ = 0.0f; // [count] + float phase_ = 0.0f; // [count] float pos_estimate_ = 0.0f; // [count] float pos_cpr_ = 0.0f; // [count] float vel_estimate_ = 0.0f; // [count/s] @@ -84,9 +84,9 @@ public: int32_t pos_abs_ = 0; float spi_error_rate_ = 0.0f; - float pos_est_rad_ = 0.0f; - float vel_est_rad_ = 0.0f; - float pos_cpr_rad_ = 0.0f; + float pos_est_rad_ = 0.0f; // [rad] + float vel_est_rad_ = 0.0f; // [rad] + float pos_cpr_rad_ = 0.0f; // [rad] bool pos_estimate_valid_ = false; bool vel_estimate_valid_ = false; @@ -111,7 +111,7 @@ public: uint32_t abs_spi_cr2; constexpr float getCoggingRatio(){ - return config_.cpr / 3600.0f; + return 2.0f * M_PI / 3600.0f; } }; diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 6046214f..96e03c1b 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -189,8 +189,8 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "invalid motor %u", motor_number); } else { respond(response_channel, use_checksum, "%f %f", - (double)axes[motor_number]->encoder_.pos_estimate_, - (double)axes[motor_number]->encoder_.vel_estimate_); + (double)axes[motor_number]->encoder_.pos_est_rad_, + (double)axes[motor_number]->encoder_.vel_est_rad_); } } else if (cmd[0] == 'h') { // Help diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 18d39838..5679c73d 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -206,16 +206,16 @@ void CANSimple::get_encoder_estimates_callback(Axis* axis, can_Message_t& msg) { // uint32_t floatBytes = *(reinterpret_cast(&(axis->encoder_.pos_estimate_))); uint32_t floatBytes; - static_assert(sizeof axis->encoder_.pos_estimate_ == sizeof floatBytes); - std::memcpy(&floatBytes, &axis->encoder_.pos_estimate_, sizeof floatBytes); + static_assert(sizeof axis->encoder_.pos_est_rad_ == sizeof floatBytes); + std::memcpy(&floatBytes, &axis->encoder_.pos_est_rad_, sizeof floatBytes); txmsg.buf[0] = floatBytes; txmsg.buf[1] = floatBytes >> 8; txmsg.buf[2] = floatBytes >> 16; txmsg.buf[3] = floatBytes >> 24; - static_assert(sizeof floatBytes == sizeof axis->encoder_.vel_estimate_); - std::memcpy(&floatBytes, &axis->encoder_.vel_estimate_, sizeof floatBytes); + static_assert(sizeof floatBytes == sizeof axis->encoder_.vel_est_rad_); + std::memcpy(&floatBytes, &axis->encoder_.vel_est_rad_, sizeof floatBytes); txmsg.buf[4] = floatBytes; txmsg.buf[5] = floatBytes >> 8; txmsg.buf[6] = floatBytes >> 16; diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index b738e0df..f8b2fbfc 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -593,8 +593,8 @@ interfaces: InvalidMirrorAxis: InvalidLoadEncoder: InvalidEstimate: - input_pos: {type: float32, c_setter: set_input_pos} - input_vel: float32 + input_pos: {type: float32, c_setter: set_input_pos, c_getter: get_input_pos()} + input_vel: {type: float32, c_setter: set_input_vel, c_getter: get_input_vel()} input_torque: float32 pos_setpoint: readonly float32 vel_setpoint: readonly float32 @@ -660,6 +660,8 @@ interfaces: calib_vel_threshold: float32 cogging_ratio: readonly float32 anticogging_enabled: bool + input_pos_multiplier: float32 + input_vel_multiplier: float32 functions: move_incremental: doc: Moves the axes' goal point by a specified increment. @@ -712,9 +714,12 @@ interfaces: interpolation: readonly float32 phase: readonly float32 pos_estimate: readonly float32 + pos_est_rad: readonly float32 pos_cpr: readonly float32 + pos_cpr_rad: readonly float32 hall_state: readonly uint8 vel_estimate: readonly float32 + vel_est_rad: readonly float32 calib_scan_response: readonly float32 pos_abs: int32 spi_error_rate: readonly float32 diff --git a/tools/odrive/shell.py b/tools/odrive/shell.py index 1f83aa9c..eb4395ff 100644 --- a/tools/odrive/shell.py +++ b/tools/odrive/shell.py @@ -33,7 +33,7 @@ def print_help(args, have_devices): print('') print('For example: "odrv0.motor0.encoder.pos_estimate"') print('will print the current encoder position on motor 0') - print('and "odrv0.motor0.pos_setpoint = 10000"') + print('and "odrv0.motor0.input_pos = 10000"') print('will send motor0 to 10000') print('') diff --git a/tools/odrivetool b/tools/odrivetool index 5c3af5b7..6011de93 100755 --- a/tools/odrivetool +++ b/tools/odrivetool @@ -8,6 +8,7 @@ import sys import os import argparse import time +import math sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname( os.path.realpath(__file__))), @@ -151,8 +152,8 @@ try: # If you want to plot different values, change them here. # You can plot any number of values concurrently. cancellation_token = start_liveplotter(lambda: [ - my_odrive.axis0.encoder.pos_estimate, - my_odrive.axis1.encoder.pos_estimate, + my_odrive.axis0.encoder.pos_est_rad, + my_odrive.axis1.encoder.pos_est_rad, ]) print("Showing plot. Press Ctrl+C to exit.") From f4a9636df4bda71df46f95dddc3c988371f856b1 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Thu, 25 Jun 2020 23:06:28 +0100 Subject: [PATCH 489/549] Modified tests for counts->rad conversion, all passing on my test rig (PJ) --- tools/odrive/tests/can_test.py | 3 +- tools/odrive/tests/closed_loop_test.py | 47 +++++++++++++------------- tools/odrive/tests/step_dir_test.py | 12 +++---- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index 93e72210..4e3d3ad8 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -5,6 +5,7 @@ import struct import can import asyncio import time +import math from fibre.utils import Logger from odrive.enums import * @@ -141,7 +142,7 @@ class TestSimpleCAN(): test_assert_eq(axis.error, AXIS_ERROR_NONE) axis.encoder.set_linear_count(123) - test_assert_eq(my_req('get_encoder_estimates')['encoder_pos_estimate'], 123.0, accuracy=0.01) + test_assert_eq(my_req('get_encoder_estimates')['encoder_pos_estimate'], 123.0 * 2.0 * math.pi / axis.encoder.config.cpr, accuracy=0.01) test_assert_eq(my_req('get_encoder_count')['encoder_shadow_count'], 123.0, accuracy=0.01) my_cmd('clear_errors') diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index 31adce5d..1ac09df6 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -77,7 +77,7 @@ class TestClosedLoopControl(TestClosedLoopControlBase): def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): with self.prepare(axis_ctx, motor_ctx, enc_ctx, logger): nominal_rps = 1.0 - nominal_vel = float(enc_ctx.yaml['cpr']) * nominal_rps + nominal_vel = 2.0 * pi * nominal_rps logger.debug(f'Testing closed loop velocity control at {nominal_rps} rounds/s...') axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL @@ -87,7 +87,7 @@ class TestClosedLoopControl(TestClosedLoopControlBase): request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) axis_ctx.handle.controller.input_vel = nominal_vel - data = record_log(lambda: [axis_ctx.handle.encoder.vel_estimate, axis_ctx.handle.encoder.pos_estimate], duration=5.0) + data = record_log(lambda: [axis_ctx.handle.encoder.vel_est_rad, axis_ctx.handle.encoder.pos_est_rad], duration=5.0) test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) test_assert_no_error(axis_ctx) @@ -109,29 +109,30 @@ class TestClosedLoopControl(TestClosedLoopControlBase): axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_POSITION_CONTROL axis_ctx.handle.controller.input_pos = 0 - axis_ctx.handle.controller.config.vel_limit = float(enc_ctx.yaml['cpr']) * 5.0 # max 5 rps + axis_ctx.handle.controller.config.vel_limit = 2.0 * pi * 5.0 # max 5 rps axis_ctx.handle.encoder.set_linear_count(0) request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) # Test small position changes - axis_ctx.handle.controller.input_pos = 5000 + test_pos = 5000 / float(enc_ctx.yaml['cpr']) * 2.0 * pi + axis_ctx.handle.controller.input_pos = test_pos time.sleep(0.3) test_assert_no_error(axis_ctx) - test_assert_eq(axis_ctx.handle.encoder.pos_estimate, 5000, range=2000) # large range needed because of cogging torque - axis_ctx.handle.controller.input_pos = -5000 + test_assert_eq(axis_ctx.handle.encoder.pos_est_rad, test_pos, range=0.4*test_pos) # large range needed because of cogging torque + axis_ctx.handle.controller.input_pos = -1 * test_pos time.sleep(0.3) test_assert_no_error(axis_ctx) - test_assert_eq(axis_ctx.handle.encoder.pos_estimate, -5000, range=2000) + test_assert_eq(axis_ctx.handle.encoder.pos_est_rad, -1 * test_pos, range=0.4*test_pos) axis_ctx.handle.controller.input_pos = 0 time.sleep(0.3) - nominal_vel = float(enc_ctx.yaml['cpr']) * 5.0 + nominal_vel = 2 * pi * 5.0 axis_ctx.handle.controller.input_pos = nominal_vel * 2.0 # 10 turns (takes 2 seconds) # Test large position change with bounded velocity - data = record_log(lambda: [axis_ctx.handle.encoder.vel_estimate, axis_ctx.handle.encoder.pos_estimate], duration=4.0) + data = record_log(lambda: [axis_ctx.handle.encoder.vel_est_rad, axis_ctx.handle.encoder.pos_est_rad], duration=4.0) test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) test_assert_no_error(axis_ctx) @@ -140,24 +141,24 @@ class TestClosedLoopControl(TestClosedLoopControlBase): data_motion = data[data[:,0] < 1.9] data_still = data[data[:,0] > 2.1] - # encoder.vel_estimate + # encoder.vel_est_rad slope, offset, fitted_curve = fit_line(data_motion[:,(0,1)]) test_assert_eq(slope, 0.0, range = nominal_vel * 0.05) test_assert_eq(offset, nominal_vel, accuracy = 0.05) test_curve_fit(data_motion[:,(0,1)], fitted_curve, max_mean_err = nominal_vel * 0.05, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) - # encoder.pos_estimate + # encoder.pos_est_rad slope, offset, fitted_curve = fit_line(data_motion[:,(0,2)]) test_assert_eq(slope, nominal_vel, accuracy = 0.01) test_curve_fit(data_motion[:,(0,2)], fitted_curve, max_mean_err = nominal_vel * 0.01, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) - # encoder.vel_estimate + # encoder.vel_est_rad slope, offset, fitted_curve = fit_line(data_still[:,(0,1)]) test_assert_eq(slope, 0.0, range = nominal_vel * 0.05) test_assert_eq(offset, 0.0, range = nominal_vel * 0.05) test_curve_fit(data_still[:,(0,1)], fitted_curve, max_mean_err = nominal_vel * 0.05, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) - # encoder.pos_estimate + # encoder.pos_est_rad slope, offset, fitted_curve = fit_line(data_still[:,(0,2)]) test_assert_eq(slope, 0.0, range = nominal_vel * 0.05) test_assert_eq(offset, nominal_vel*2, range = nominal_vel * 0.02) @@ -173,7 +174,7 @@ class TestRegenProtection(TestClosedLoopControlBase): def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): with self.prepare(axis_ctx, motor_ctx, enc_ctx, logger): nominal_rps = 10.0 - nominal_vel = float(enc_ctx.yaml['cpr']) * nominal_rps + nominal_vel = 2.0 * pi * nominal_rps max_current = 15.0 # Accept a bit of noise on Ibus @@ -181,7 +182,7 @@ class TestRegenProtection(TestClosedLoopControlBase): logger.debug(f'Brake control test from {nominal_rps} rounds/s...') - axis_ctx.handle.controller.config.vel_limit = float(enc_ctx.yaml['cpr']) * 15.0 # max 15 rps + axis_ctx.handle.controller.config.vel_limit = 2.0 * pi * 15.0 # max 15 rps axis_ctx.handle.motor.config.current_lim = max_current axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL axis_ctx.handle.controller.config.input_mode = INPUT_MODE_PASSTHROUGH @@ -220,7 +221,7 @@ class TestVelLimitInTorqueControl(TestClosedLoopControlBase): def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): with self.prepare(axis_ctx, motor_ctx, enc_ctx, logger): max_rps = 20.0 - max_vel = float(enc_ctx.yaml['cpr']) * max_rps + max_vel = 2.0 * pi * max_rps absolute_max_vel = max_vel * 1.2 max_current = 15.0 torque_constant = 0.0305 #correct for 5065 motor @@ -241,9 +242,9 @@ class TestVelLimitInTorqueControl(TestClosedLoopControlBase): def data_getter(): # sample velocity twice to avoid systematic bias - velocity0 = axis_ctx.handle.encoder.vel_estimate + velocity0 = axis_ctx.handle.encoder.vel_est_rad current_setpoint = axis_ctx.handle.motor.current_control.Iq_setpoint - velocity1 = axis_ctx.handle.encoder.vel_estimate + velocity1 = axis_ctx.handle.encoder.vel_est_rad velocity = ((velocity0 + velocity1) / 2) # Abort immediately if the absolute limits are exceeded test_assert_within(current_setpoint, -max_current, max_current) @@ -265,7 +266,7 @@ class TestVelLimitInTorqueControl(TestClosedLoopControlBase): # Shrink the operating envelope while motor is moving faster than the envelope allows max_rps = 5.0 - max_vel = float(enc_ctx.yaml['cpr']) * max_rps + max_vel = 2.0 * pi * max_rps axis_ctx.handle.controller.config.vel_limit = max_vel # Move the system around its operating envelope @@ -279,7 +280,7 @@ class TestVelLimitInTorqueControl(TestClosedLoopControlBase): dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)]) # Try the shrink maneuver again at positive velocity - axis_ctx.handle.controller.config.vel_limit = 20.0 * float(enc_ctx.yaml['cpr']) + axis_ctx.handle.controller.config.vel_limit = 20.0 * 2.0 * pi axis_ctx.handle.controller.input_torque = 4.0 * torque_constant time.sleep(0.5) axis_ctx.handle.controller.config.vel_limit = max_vel @@ -301,13 +302,13 @@ class TestTorqueLimit(TestClosedLoopControlBase): def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): with self.prepare(axis_ctx, motor_ctx, enc_ctx, logger): max_rps = 15.0 - max_vel = max_rps * float(enc_ctx.yaml['cpr']) + max_vel = max_rps * 2.0 * pi max_current = 30.0 max_torque = 0.1 # must be less than max_current * torque_constant. torque_constant = axis_ctx.handle.motor.config.torque_constant - test_pos = 5 * float(enc_ctx.yaml['cpr']) - test_vel = 10 * float(enc_ctx.yaml['cpr']) + test_pos = 5 * 2.0 * pi + test_vel = 10 * 2.0 * pi test_torque = 0.5 axis_ctx.handle.controller.config.vel_limit = max_vel diff --git a/tools/odrive/tests/step_dir_test.py b/tools/odrive/tests/step_dir_test.py index 161ed2ec..55bf225d 100644 --- a/tools/odrive/tests/step_dir_test.py +++ b/tools/odrive/tests/step_dir_test.py @@ -22,8 +22,8 @@ class TestStepDir(): gpio_conns = [ list(testrig.get_connected_components((odrive.gpio1, False), LinuxGpioComponent)), list(testrig.get_connected_components((odrive.gpio2, False), LinuxGpioComponent)), - list(testrig.get_connected_components((odrive.gpio3, False), LinuxGpioComponent)), - list(testrig.get_connected_components((odrive.gpio4, False), LinuxGpioComponent)), + #list(testrig.get_connected_components((odrive.gpio3, False), LinuxGpioComponent)), # connected to LPF on test rig + #list(testrig.get_connected_components((odrive.gpio4, False), LinuxGpioComponent)), # connected to LPF on test rig list(testrig.get_connected_components((odrive.gpio5, False), LinuxGpioComponent)), list(testrig.get_connected_components((odrive.gpio6, False), LinuxGpioComponent)), list(testrig.get_connected_components((odrive.gpio7, False), LinuxGpioComponent)), @@ -31,11 +31,11 @@ class TestStepDir(): ] yield (odrive.axes[0], 1, gpio_conns[0], 2, gpio_conns[1]) - yield (odrive.axes[0], 3, gpio_conns[2], 4, gpio_conns[3]) - yield (odrive.axes[0], 5, gpio_conns[4], 6, gpio_conns[5]) # broken - yield (odrive.axes[0], 7, gpio_conns[6], 8, gpio_conns[7]) # broken + yield (odrive.axes[0], 5, gpio_conns[2], 6, gpio_conns[3]) + yield (odrive.axes[0], 7, gpio_conns[4], 8, gpio_conns[5]) # broken + # yield (odrive.axes[0], 7, gpio_conns[6], 8, gpio_conns[7]) # broken - yield (odrive.axes[1], 7, gpio_conns[6], 8, gpio_conns[7]) + yield (odrive.axes[1], 7, gpio_conns[4], 8, gpio_conns[5]) def run_test(self, axis: ODriveAxisComponent, step_gpio_num: int, step_gpio: LinuxGpioComponent, dir_gpio_num: int, dir_gpio: LinuxGpioComponent, logger: Logger): step_gpio.config(output=True) From e476b9553658377c299bc3f8208ac53f523bb5aa Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Fri, 26 Jun 2020 18:03:44 +0100 Subject: [PATCH 490/549] Removed unit-conversion abiltiy for input_pos and input_vel Updated documentation to reflect unit change. --- Firmware/MotorControl/controller.hpp | 37 +++++++++----------------- Firmware/MotorControl/trapTraj.hpp | 6 ++--- Firmware/odrive-interface.yaml | 18 +++++++------ docs/commands.md | 9 +++---- docs/control.md | 12 ++++----- docs/encoders.md | 6 ++--- docs/getting-started.md | 24 ++++++++--------- docs/interfaces.md | 6 ++--- docs/odrivetool.md | 4 +-- tools/odrive/tests/closed_loop_test.py | 2 +- 10 files changed, 57 insertions(+), 67 deletions(-) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index ee85ba76..2489acc9 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -21,16 +21,14 @@ public: struct Config_t { ControlMode control_mode = CONTROL_MODE_POSITION_CONTROL; //see: ControlMode_t InputMode input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t - float pos_gain = 20.0f; // [(counts/s) / counts] - //float vel_gain = 0.2f / 10000.0f; // [Nm/(counts/s)] - float vel_gain = 0.2f / 7.7f; // [Nm/(counts/s)] + float pos_gain = 20.0f; // [(rad/s) / rad] + //float vel_gain = 0.2f / 10000.0f; // [Nm/(rad/s)] + float vel_gain = 0.2f / 7.7f; // [Nm/(rad/s)] // float vel_gain = 0.2f / 200.0f, // [Nm/(rad/s)] - //float vel_integrator_gain = 0.4f / 10000.0f; // [Nm/(counts/s * s)] - float vel_integrator_gain = 0.4f / 7.7f; // [Nm/(counts/s * s)] - //float vel_limit = 20000.0f; // [counts/s] Infinity to disable. - float vel_limit = 20000.0f * 2.0f * M_PI / 8192.0f; // [counts/s] Infinity to disable. + float vel_integrator_gain = 0.4f / 7.7f; // [Nm/(rad/s * s)] + float vel_limit = 4.0f * M_PI; // [rad/s] Infinity to disable. float vel_limit_tolerance = 1.2f; // ratio to vel_lim. Infinity to disable. - float vel_ramp_rate = 10000.0f; // [(counts/s) / s] + float vel_ramp_rate = 2.0f * M_PI; // [(rad/s) / s] float torque_ramp_rate = 0.01f; // Nm / sec bool setpoints_in_cpr = false; float inertia = 0.0f; // [A/(count/s^2)] @@ -41,12 +39,10 @@ public: bool enable_gain_scheduling = false; bool enable_vel_limit = true; bool enable_overspeed_error = true; - bool enable_current_mode_vel_limit = true; // enable velocity limit in current control mode (requires a valid velocity estimator) + bool enable_current_mode_vel_limit = true; // enable velocity limit in current control mode (requires a valid velocity estimator) uint8_t axis_to_mirror = -1; float mirror_ratio = 1.0f; - uint8_t load_encoder_axis = -1; // default depends on Axis number and is set in load_configuration() - float input_pos_multiplier = 1.0f; // if input_pos is set by user, it is multiplied by this - float input_vel_multiplier = 1.0f; // if input_vel is set by user, it is multiplied by this + uint8_t load_encoder_axis = -1; // default depends on Axis number and is set in load_configuration() // custom setters Controller* parent; @@ -82,15 +78,15 @@ public: bool* vel_estimate_valid_src_ = nullptr; int32_t* pos_wrap_src_ = nullptr; // enables circular position setpoints if not null. The value pointed to is the maximum position value. - float pos_setpoint_ = 0.0f; - float vel_setpoint_ = 0.0f; + float pos_setpoint_ = 0.0f; // [radians] + float vel_setpoint_ = 0.0f; // [rad/s] // float vel_setpoint = 800.0f; float vel_integrator_torque_ = 0.0f; // [Nm] float torque_setpoint_ = 0.0f; // [Nm] - float input_pos_ = 0.0f; - float input_vel_ = 0.0f; - float input_torque_ = 0.0f; + float input_pos_ = 0.0f; // [radians] + float input_vel_ = 0.0f; // [rad/s] + float input_torque_ = 0.0f; // [Nm] float input_filter_kp_ = 0.0f; float input_filter_ki_ = 0.0f; @@ -100,13 +96,6 @@ public: bool anticogging_valid_ = false; - // custom setters - void set_input_pos(float value) { input_pos_ = value * config_.input_pos_multiplier; input_pos_updated(); } - void set_input_vel(float value) { input_vel_ = value * config_.input_vel_multiplier;} - - // custom getters - float get_input_pos(void) { return input_pos_ / (config_.input_pos_multiplier == 0.0f ? 1.0f : config_.input_pos_multiplier);} - float get_input_vel(void) { return input_vel_ / (config_.input_vel_multiplier == 0.0f ? 1.0f : config_.input_vel_multiplier);} }; #endif // __CONTROLLER_HPP diff --git a/Firmware/MotorControl/trapTraj.hpp b/Firmware/MotorControl/trapTraj.hpp index c3df57b9..335dfafc 100644 --- a/Firmware/MotorControl/trapTraj.hpp +++ b/Firmware/MotorControl/trapTraj.hpp @@ -4,9 +4,9 @@ class TrapezoidalTrajectory { public: struct Config_t { - float vel_limit = 20000.0f; // [count/s] - float accel_limit = 5000.0f; // [count/s^2] - float decel_limit = 5000.0f; // [count/s^2] + float vel_limit = 4.0f * M_PI; // [rad/s] + float accel_limit = M_PI; // [rad/s^2] + float decel_limit = M_PI; // [rad/s^2] }; struct Step_t { diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index f8b2fbfc..d76a4ec8 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -593,8 +593,12 @@ interfaces: InvalidMirrorAxis: InvalidLoadEncoder: InvalidEstimate: - input_pos: {type: float32, c_setter: set_input_pos, c_getter: get_input_pos()} - input_vel: {type: float32, c_setter: set_input_vel, c_getter: get_input_vel()} + input_pos: + type: float32 + unit: rad + input_vel: + type: float32 + unit: rad/s input_torque: float32 pos_setpoint: readonly float32 vel_setpoint: readonly float32 @@ -616,16 +620,16 @@ interfaces: input_mode: InputMode pos_gain: type: float32 - unit: (counts/s) / counts + unit: (rad/s) / rad vel_gain: type: float32 - unit: 'A/(counts/s) (or A/(rad/s) in sensorless mode' + unit: 'Nm/(rad/s)' vel_integrator_gain: type: float32 - unit: A/(counts/s * s) + unit: Nm/(rad/s * s) vel_limit: type: float32 - unit: counts/s + unit: rad/s doc: Infinity to disable. vel_limit_tolerance: type: float32 @@ -660,8 +664,6 @@ interfaces: calib_vel_threshold: float32 cogging_ratio: readonly float32 anticogging_enabled: bool - input_pos_multiplier: float32 - input_vel_multiplier: float32 functions: move_incremental: doc: Moves the axes' goal point by a specified increment. diff --git a/docs/commands.md b/docs/commands.md index 47a31df6..17250726 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -44,8 +44,8 @@ Possible values are listed [here](api/odrive.axis.controller.controlmode). As of version v0.5.0, ODrive now intercepts the incoming commands and can apply filters to them. The old protocol values `pos_setpoint`, `vel_setpoint`, and `current_setpoint` are still used internally by the closed-loop cascade control, but the user cannot write to them directly. This allows us to condense the number of ways the ODrive accepts motion commands. The new commands are: -* `.controller.input_pos = ` -* `.controller.input_vel = ` +* `.controller.input_pos = ` +* `.controller.input_vel = ` * `.controller.input_torque = ` Modes can be selected by changing `.controller.config.input_mode`. @@ -55,8 +55,8 @@ Possible values are listed [here](api/odrive.axis.controller.inputmode). ## System monitoring commands ### Encoder position and velocity -* View encoder position with `.encoder.pos_estimate` [counts] -* View rotational velocity with `.encoder.vel_estimate` [counts/s] +* View encoder position with `.encoder.pos_estimate` [counts] or `.encoder.pos_est_rad` [radians] +* View rotational velocity with `.encoder.vel_estimate` [counts/s] or `.encoder.vel_est_rad` [radians] ### Motor current and torque estimation * View the commanded motor current with `.motor.current_control.Iq_setpoint` [A] @@ -81,7 +81,6 @@ All variables that are part of a `[...].config` object can be saved to non-volat ## Setting up sensorless The ODrive can run without encoder/hall feedback, but there is a minimum speed, usually around a few hunderd RPM. -However the units of this mode is different from when using an encoder. Velocities are not measured in counts/s, instead it is electrical rad/s. This also applies to the gains. For example, `vel_gain` is in units of `A / (rad/s)` instead of `A / (count/s)`. To give an example, suppose you have a motor with 7 pole pairs, and you want to spin it at 3000 RPM. Then you would set the `input_vel` to `3000 * 2*pi/60 * 7 = 2199 rad/s electrical`. diff --git a/docs/control.md b/docs/control.md index 11bd8931..95f3b61d 100644 --- a/docs/control.md +++ b/docs/control.md @@ -1,6 +1,6 @@ # Control -The motor controller is a cascaded style position, velocity and current control loop, as per the diagram below. When the control mode is set to position control, the whole loop runs. When running in velocity control mode, the position control part is removed and the velocity command is fed directly in to the second stage input. In current control mode, only the current controller is used. +The motor controller is a cascaded style position, velocity and current control loop, as per the diagram below. When the control mode is set to position control, the whole loop runs. When running in velocity control mode, the position control part is removed and the velocity command is fed directly in to the second stage input. In torque control mode, only the current controller is used. ![Cascaded pos vel I loops](controller_with_ff.png) @@ -34,15 +34,15 @@ For more detail refer to [controller.cpp](https://github.com/madcowswe/ODrive/bl ### Controller Details: The ultimate output of the controller is the voltage applied to the gate of each FET to deliver current through each coil of the motor. The current through the motor linearly relates to the torque output of the motor. This means that the inputs to the cascaded controller are theoretically the position (angle), velocity (angle/time), and acceleration (angle/time/time) of the motor. Note that when thinking about the controller from the perpective of the physics of the motor you would expect to see the time in the Velocity and Current loops, but it is absent because the time difference between iterations is always 125 microseconds (8kHz). Because the time difference between controller loops is a constant and can simply be wrapped into the controller gains. -The output of each stage of the controller is clamped before being fed into the next stage. So after the `vel_cmd` is calculated from the position controller, the `vel_cmd` is clamped to the velocity limit. The `current_cmd` output of the velocity controller is then clamped and fed to the current controller. Oddly enough the controller class does not contain the current controller, but instead the current controller is housed in the motor class due to the complexity of the motor driver schema. +The output of each stage of the controller is clamped before being fed into the next stage. So after the `vel_cmd` is calculated from the position controller, the `vel_cmd` is clamped to the velocity limit. The `torque_cmd` output of the velocity controller is then clamped and fed to the motor controller. Oddly enough the controller class does not contain the motor controller, but instead the motor controller is housed in the motor class due to the complexity of the motor driver schema. -The feedforward terms available when using the position or velocity control mode are meant to enable better performance when the dynamics of a system are known and the host controller can predict the motion based on the load. A perfect example of this is the use of the trajectory controller that sets the position, velocity, and current based on the desired position, velocity, and acceleration. If you take a trapezoidal velocity profile for example, you can imagine on the ramp upward the velocity will be increasing over time, while the current is a non-zero constant. At the flat portion of the profile the velocity will be a non-zero constant, but the acceleration will be zero. This trajectory controller use case uses the cascaded controller with multiple inputs to achieve the desired motion with the best performance. +The feedforward terms available when using the position or velocity control mode are meant to enable better performance when the dynamics of a system are known and the host controller can predict the motion based on the load. A perfect example of this is the use of the trajectory controller that sets the position, velocity, and current based on the desired position, velocity, and acceleration. If you take a trapezoidal velocity profile for example, you can imagine on the ramp upward the velocity will be increasing over time, while the torque is a non-zero constant. At the flat portion of the profile the velocity will be a non-zero constant, but the acceleration will be zero. This trajectory controller use case uses the cascaded controller with multiple inputs to achieve the desired motion with the best performance. ## Tuning Tuning the motor controller is an essential step to unlock the full potential of the ODrive. Tuning allows for the controller to quickly respond to disturbances or changes in the system (such as an external force being applied or a change in the setpoint) without becoming unstable. Correctly setting the three tuning parameters (called gains) ensures that ODrive can control your motors in the most effective way possible. The three values are: -* `.controller.config.pos_gain = 20.0` [(counts/s) / counts] -* `.controller.config.vel_gain = 5.0 / 10000.0` [A/(counts/s)] -* `.controller.config.vel_integrator_gain = 10.0 / 10000.0` [A/((counts/s) * s)] +* `.controller.config.pos_gain = 20.0` [(rad/s) / rad] +* `.controller.config.vel_gain = 0.025 ` [Nm/(rad/s)] +* `.controller.config.vel_integrator_gain = 0.05` [Nm/((rad/s) * s)] An upcoming feature will enable automatic tuning. Until then, here is a rough tuning procedure: * Set vel_integrator_gain gain to 0 diff --git a/docs/encoders.md b/docs/encoders.md index 10861040..8398f2fe 100644 --- a/docs/encoders.md +++ b/docs/encoders.md @@ -62,15 +62,15 @@ The following are examples of values that MAY impact the success of calibration. * `.encoder.config.calib_range = 0.05` helps to relax the accuracy of encoder counts during calibration * `.motor.config.calibration_current = 10.0` _sometimes_ needed if this is a large motor * `.motor.config.resistance_calib_max_voltage = 12.0` _sometimes_ needed depending on motor -* `.controller.config.vel_limit = 50000` low values result in the spinning motor stopping abruptly during calibration +* `.controller.config.vel_limit = 30` [rad/s] low values result in the spinning motor stopping abruptly during calibration -Lots of other values can get you. It's a process. Thankfully there is a lot of good people that will help you debug calibration problems. +Lots of other values can get you. It's a process. Thankfully there are a lot of good people that will help you debug calibration problems. If calibration works, congratulations. Now try: * `.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL` -* `.controller.input_vel = 3000` +* `.controller.input_vel = 1.5` let it loop a few times and then set: * `.requested_state = AXIS_STATE_IDLE` diff --git a/docs/getting-started.md b/docs/getting-started.md index 46b5af18..3e973782 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -183,7 +183,7 @@ The largest effect on modulation magnitude is speed. There are other smaller fac
    **Velocity limit**
    -`odrv0.axis0.controller.config.vel_limit` [counts/s]. +`odrv0.axis0.controller.config.vel_limit` [rad/s]. The motor will be limited to this speed. Again the default value is quite slow. **Calibration current**
    @@ -248,7 +248,7 @@ Let's get motor 0 up and running. The procedure for motor 1 is exactly the same, 2. Type `odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL` Enter. From now on the ODrive will try to hold the motor's position. If you try to turn it by hand, it will fight you gently. That is unless you bump up `odrv0.axis0.motor.config.current_lim`, in which case it will fight you more fiercely. If the motor begins to vibrate either immediately or after being disturbed you will need to [lower the controller gains](control.md). -3. Send the motor a new position setpoint. `odrv0.axis0.controller.input_pos = 10000` Enter. The units are in encoder counts. +3. Send the motor a new position setpoint. `odrv0.axis0.controller.input_pos = 10` Enter. The units are in radians. 4. At this point you will probably want to [Properly tune](control.md) the motor controller in order to maximize system performance. ## Other control modes @@ -271,7 +271,7 @@ Asking the ODrive controller to go as hard as it can to raw setpoints may result You can use the second order position filter in these cases. Set the filter bandwidth: `axis.controller.config.input_filter_bandwidth = 2.0` [1/s]
    Activate the setpoint filter: `axis.controller.config.input_mode = INPUT_MODE_POS_FILTER`.
    -You can now control the velocity with `axis.controller.input_pos = 1000` [counts]. +You can now control the velocity with `axis.controller.input_pos = 10` [radians]. ![secondOrderResponse](secondOrderResponse.PNG)
    Step response of a 1000 to 0 position input with a filter bandwidth of 1.0 [/sec]. @@ -292,8 +292,8 @@ In the above image blue is position and orange is velocity. ``` `vel_limit` is the maximum planned trajectory speed. This sets your coasting speed.
    -`accel_limit` is the maximum acceleration in counts / sec^2
    -`decel_limit` is the maximum deceleration in counts / sec^2
    +`accel_limit` is the maximum acceleration in radians / sec^2
    +`decel_limit` is the maximum deceleration in radians / sec^2
    `controller.config.inertia` is a value which correlates acceleration (in counts / sec^2) and motor current. It is 0 by default. It is optional, but can improve response of your system if correctly tuned. Keep in mind this will need to change with the load / mass of your system. All values should be strictly positive (>= 0). @@ -328,23 +328,23 @@ You can also execute a move with the [appropriate ascii command](ascii-protocol. To enable Circular position control, set `axis.controller.config.setpoints_in_cpr = True` -This mode is useful for continuos incremental position movement. For example a robot rolling indefinitely, or an extruder motor or conveyor belt moving with controlled increments indefinitely. +This mode is useful for continuous incremental position movement. For example a robot rolling indefinitely, or an extruder motor or conveyor belt moving with controlled increments indefinitely. In the regular position mode, the `input_pos` would grow to a very large value and would lose precision due to floating point rounding. -In this mode, the controller will try to track the position within only one turn of the motor. Specifically, `input_pos` is expected in the range `[0, cpr-1]`, where `cpr` is the number of encoder counts in one revolution. If the `input_pos` is incremented to outside this range (say via step/dir input), it is automatically wrapped around into the correct value. -Note that in this mode `encoder.pos_cpr` is used for feedback in stead of `encoder.pos_estimate`. +In this mode, the controller will try to track the position within only one turn of the motor. Specifically, `input_pos` is expected in the range `[0, 2*Pi]`. If the `input_pos` is incremented to outside this range (say via step/dir input), it is automatically wrapped around into the correct value. +Note that in this mode `encoder.pos_cpr_rad` is used for feedback in stead of `encoder.pos_est_rad`. -If you try to increment the axis with a large step in one go that exceeds `cpr/2` steps, the motor will go to the same angle around the wrong way. This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a virtual CPR that is an integer times larger than your encoder's actual CPR. Set `encoder.config.cpr = N * your_enc_cpr`, where N is some integer. Choose N to give you an appropriate circular space for your application. +If you try to increment the axis with a large step in one go that exceeds `Pi` steps, the motor will go to the same angle around the wrong way. This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a virtual CPR for your encoder that is an integer times larger than your encoder's actual CPR. Set `encoder.config.cpr = N * your_enc_cpr`, where N is some integer. Choose N to give you an appropriate circular space for your application. `encoder.config.cpr` is automatically converted to radians internally. ### Velocity control Set `axis.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL`.
    -You can now control the velocity with `axis.controller.input_vel = 5000` [count/s]. +You can now control the velocity with `axis.controller.input_vel = 3` [rad/s]. ### Ramped velocity control Set `axis.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL`.
    -Set the velocity ramp rate (acceleration): `axis.controller.config.vel_ramp_rate = 2000` [counts/s^2]
    +Set the velocity ramp rate (acceleration): `axis.controller.config.vel_ramp_rate = 1.5` [rad/s^2]
    Activate the ramped velocity mode: `axis.controller.config.input_mode = INPUT_MODE_VEL_RAMP`.
    -You can now control the velocity with `axis.controller.input_vel = 5000` [count/s]. +You can now control the velocity with `axis.controller.input_vel = 3` [rad/s]. ### Torque control Set `axis.controller.config.control_mode = CONTROL_MODE_TORQUE_CONTROL`.
    diff --git a/docs/interfaces.md b/docs/interfaces.md index 01173e93..b8e76ad6 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -113,14 +113,14 @@ You can control the ODrive directly from an hobby RC receiver. Some GPIO pins can be used for PWM input, if they are not allocated to other functions. For example, you must disable the UART to use GPIO 1,2. See the [pin function priorities](#pin-function-priorities) for more detail. Any of the numerical parameters that are writable from the ODrive Tool can be hooked up to a PWM input. -As an example, we'll configure GPIO4 to control the angle of axis 0. We want the axis to move within a range of -1500 to 1500 encoder counts. +As an example, we'll configure GPIO4 to control the angle of axis 0. We want the axis to move within a range of -2 to 2 radians. 1. Make sure you're able control the axis 0 angle by writing to `odrv0.axis0.controller.input_pos`. If you need help with this follow the [getting started guide](getting-started.md). 2. If you want to control your ODrive with the PWM input without using anything else to activate the ODrive, you can configure the ODrive such that axis 0 automatically goes operational at startup. See [here](commands.md#startup-procedure) for more information. 3. In ODrive Tool, configure the PWM input mapping ``` - In [1]: odrv0.config.gpio4_pwm_mapping.min = -1500 - In [2]: odrv0.config.gpio4_pwm_mapping.max = 1500 + In [1]: odrv0.config.gpio4_pwm_mapping.min = -2 + In [2]: odrv0.config.gpio4_pwm_mapping.max = 2 In [3]: odrv0.config.gpio4_pwm_mapping.endpoint = odrv0.axis0.controller._remote_attributes['input_pos'] ``` Note: you can disable the input by setting `odrv0.config.gpio4_pwm_mapping.endpoint = None` diff --git a/docs/odrivetool.md b/docs/odrivetool.md index dddd5ea1..dd83c1bf 100644 --- a/docs/odrivetool.md +++ b/docs/odrivetool.md @@ -216,8 +216,8 @@ For example, to plot the approximate motor torque [N.cm] and the velocity [RPM] # If you want to plot different values, change them here. # You can plot any number of values concurrently. cancellation_token = start_liveplotter(lambda: [ - (((my_odrive.axis0.encoder.pll_vel)/8192)*60), # 8192 CPR encoder - ((8.27*my_odrive.axis0.motor.current_control.Iq_setpoint/150) * 100), # Torque [N.cm] = (8.27 * Current [A] / KV) * 100 + ((my_odrive.axis0.encoder.vel_est_rad*60/(6.2832)), # radians to rpm + ((my_odrive.axis0.motor.current_control.Iq_setpoint * my_odrive.axis0.motor.config.torque_constant), # Torque [Nm] ]) ``` In the example below the motor is forced off axis by hand and held there. In response the motor controller increases the torque (orange line) to counteract this disturbance up to a peak of 500 N.cm at which point the motor current limit is reached. When the motor is released it returns back to its commanded position very quickly as can be seen by the spike in the motor velocity (blue line). diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index 1ac09df6..cabcc57c 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -180,7 +180,7 @@ class TestRegenProtection(TestClosedLoopControlBase): # Accept a bit of noise on Ibus axis_ctx.parent.handle.config.dc_max_negative_current = -0.2 - logger.debug(f'Brake control test from {nominal_rps} rounds/s...') + logger.debug(f'Brake control test from {nominal_rps} rounds/s...' axis_ctx.handle.controller.config.vel_limit = 2.0 * pi * 15.0 # max 15 rps axis_ctx.handle.motor.config.current_lim = max_current From 193dddad00851f60a3a3e64e2f573f8a316c00f4 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Fri, 26 Jun 2020 18:37:22 +0100 Subject: [PATCH 491/549] Removed commented line in controller.hpp Added missing ')' to closed_loop_test.py Invalid enum definitions in uart_ascii_test.py fixed. --- Firmware/MotorControl/controller.hpp | 1 - tools/odrive/tests/closed_loop_test.py | 2 +- tools/odrive/tests/uart_ascii_test.py | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 2489acc9..743992c2 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -22,7 +22,6 @@ public: ControlMode control_mode = CONTROL_MODE_POSITION_CONTROL; //see: ControlMode_t InputMode input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t float pos_gain = 20.0f; // [(rad/s) / rad] - //float vel_gain = 0.2f / 10000.0f; // [Nm/(rad/s)] float vel_gain = 0.2f / 7.7f; // [Nm/(rad/s)] // float vel_gain = 0.2f / 200.0f, // [Nm/(rad/s)] float vel_integrator_gain = 0.4f / 7.7f; // [Nm/(rad/s * s)] diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index cabcc57c..1ac09df6 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -180,7 +180,7 @@ class TestRegenProtection(TestClosedLoopControlBase): # Accept a bit of noise on Ibus axis_ctx.parent.handle.config.dc_max_negative_current = -0.2 - logger.debug(f'Brake control test from {nominal_rps} rounds/s...' + logger.debug(f'Brake control test from {nominal_rps} rounds/s...') axis_ctx.handle.controller.config.vel_limit = 2.0 * pi * 15.0 # max 15 rps axis_ctx.handle.motor.config.current_lim = max_current diff --git a/tools/odrive/tests/uart_ascii_test.py b/tools/odrive/tests/uart_ascii_test.py index 6c650e28..ff5a41de 100644 --- a/tools/odrive/tests/uart_ascii_test.py +++ b/tools/odrive/tests/uart_ascii_test.py @@ -105,7 +105,7 @@ class TestUartAscii(): ser.write(b'c 0 12.5\n') test_assert_eq(ser.readline(), b'') test_assert_eq(odrive.handle.axis0.controller.input_torque, 12.5, accuracy=0.001) - test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_CURRENT_CONTROL) + test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_TORQUE_CONTROL) odrive.handle.axis0.controller.input_vel = 0 odrive.handle.axis0.controller.input_torque = 0 @@ -132,7 +132,7 @@ class TestUartAscii(): test_assert_eq(ser.readline(), b'') test_assert_eq(odrive.handle.axis0.controller.input_pos, 123.4, accuracy=0.001) test_assert_eq(odrive.handle.axis0.controller.config.vel_limit, 567.8, accuracy=0.001) - test_assert_eq(odrive.handle.axis0.motor.config.current_lim, 12.5, accuracy=0.001) + test_assert_eq(odrive.handle.axis0.motor.config.torque_lim, 12.5, accuracy=0.001) test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_POSITION_CONTROL) ser.write(b'f 0\n') From 744bfeb110d87b5caa534ac921ef9cad2654e772 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Fri, 26 Jun 2020 19:48:21 +0100 Subject: [PATCH 492/549] Un-removed custom setter for input_pos_ removed explicit casts where variables would be promoted anyway --- Firmware/MotorControl/controller.cpp | 8 ++++---- Firmware/MotorControl/controller.hpp | 2 ++ Firmware/MotorControl/encoder.cpp | 8 ++++---- Firmware/odrive-interface.yaml | 1 + 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index c8ea8f05..37b899f6 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -85,8 +85,8 @@ void Controller::start_anticogging_calibration() { */ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) { float pos_err = input_pos_ - pos_estimate; - if (std::abs(pos_err) <= config_.anticogging.calib_pos_threshold * (2.0f * M_PI) / (float)axis_->encoder_.config_.cpr && - std::abs(vel_estimate) < config_.anticogging.calib_vel_threshold * (2.0f * M_PI) / (float)axis_->encoder_.config_.cpr) { + if (std::abs(pos_err) <= config_.anticogging.calib_pos_threshold * (2.0f * M_PI) / axis_->encoder_.config_.cpr && + std::abs(vel_estimate) < config_.anticogging.calib_vel_threshold * (2.0f * M_PI) / axis_->encoder_.config_.cpr) { config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_torque_; } if (config_.anticogging.index < 3600) { @@ -140,7 +140,7 @@ bool Controller::update(float* torque_setpoint_output) { // TODO also enable circular deltas for 2nd order filter, etc. if (pos_wrap_src_) { - float cpr = *pos_wrap_src_ * 2.0f * M_PI / ((float)axis_->encoder_.config_.cpr); + float cpr = *pos_wrap_src_ * 2.0f * M_PI / axis_->encoder_.config_.cpr; // Keep pos setpoint from drifting input_pos_ = fmodf_pos(input_pos_, cpr); } @@ -235,7 +235,7 @@ bool Controller::update(float* torque_setpoint_output) { } if (pos_wrap_src_) { - float cpr = *pos_wrap_src_ * 2.0f * M_PI / ((float)axis_->encoder_.config_.cpr); + float cpr = *pos_wrap_src_ * 2.0f * M_PI / axis_->encoder_.config_.cpr; // Keep pos setpoint from drifting pos_setpoint_ = fmodf_pos(pos_setpoint_, cpr); // Circular delta diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 743992c2..7bb8e270 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -95,6 +95,8 @@ public: bool anticogging_valid_ = false; + // custom setters + void set_input_pos(float value) { input_pos_ = value; input_pos_updated();} }; #endif // __CONTROLLER_HPP diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index ad9c02e5..300859e4 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -510,7 +510,7 @@ bool Encoder::update() { // discrete phase detector float delta_pos = (float)(shadow_count_ - (int32_t)std::floor(pos_estimate_)); float delta_pos_cpr = (float)(count_in_cpr_ - (int32_t)std::floor(pos_cpr_)); - delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * (float)(config_.cpr)); + delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * config_.cpr); // pll feedback pos_estimate_ += current_meas_period * pll_kp_ * delta_pos; pos_cpr_ += current_meas_period * pll_kp_ * delta_pos_cpr; @@ -523,9 +523,9 @@ bool Encoder::update() { } //expose pos/vel estimates in radians for Controller - pos_est_rad_ = pos_estimate_ * 2.0f * M_PI / (float)config_.cpr; - vel_est_rad_ = vel_estimate_ * 2.0f * M_PI / (float)config_.cpr; - pos_cpr_rad_ = pos_cpr_ * 2.0f * M_PI / (float)config_.cpr; + pos_est_rad_ = pos_estimate_ * 2.0f * M_PI / config_.cpr; + vel_est_rad_ = vel_estimate_ * 2.0f * M_PI / config_.cpr; + pos_cpr_rad_ = pos_cpr_ * 2.0f * M_PI / config_.cpr; //// run encoder count interpolation int32_t corrected_enc = count_in_cpr_ - config_.offset; diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index d76a4ec8..1ab58698 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -596,6 +596,7 @@ interfaces: input_pos: type: float32 unit: rad + c_setter: set_input_pos input_vel: type: float32 unit: rad/s From a673a50b43e93d9895dba3b449ad1a5c299acad1 Mon Sep 17 00:00:00 2001 From: Kyle Bartholomew Date: Mon, 29 Jun 2020 21:40:56 -0700 Subject: [PATCH 493/549] Replace pandas with numpy, fixed naming, added plot title --- tools/odrive/utils.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 8f279eba..27fd8608 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -142,17 +142,17 @@ class BulkCapture: def __init__(self, get_var_callback, data_rate=500.0, - length=2.0): + duration=2.0): from threading import Event, Thread - import pandas as pd + import numpy as np + self.get_var_callback = get_var_callback self.event = Event() def loop(): vals = [] start_time = time.monotonic() - total_samples = int(length * data_rate) period = 1.0 / data_rate - for i in range(total_samples): + while time.monotonic() - start_time < duration: try: data = get_var_callback() except Exception as ex: @@ -163,17 +163,26 @@ class BulkCapture: relative_time = time.monotonic() - start_time vals.append([relative_time] + data) time.sleep(period - (relative_time % period)) # this ensures consistently timed samples - self.data = pd.DataFrame(vals) # A lock is not really necessary due to the event - print("Achieved average data rate: {}Hz".format(total_samples / self.data.iloc[-1, 0])) - print("If this rate is significantly lower than what you specified, consider lowering it below the achieved value for more consistent sampling.") + self.data = np.array(vals) # A lock is not really necessary due to the event + print("Capture complete") + achieved_data_rate = len(self.data) / self.data[-1, 0] + if achieved_data_rate < (data_rate * 0.9): + print("Achieved average data rate: {}Hz".format(achieved_data_rate)) + print("If this rate is significantly lower than what you specified, consider lowering it below the achieved value for more consistent sampling.") self.event.set() # tell the main thread that the bulk capture is complete Thread(target=loop, daemon=True).start() - def plot_data(self): + def plot(self): import matplotlib.pyplot as plt - plt.plot(self.data[0], self.data.drop(0, axis=1)) + import inspect + from textwrap import wrap + plt.plot(self.data[:,0], self.data[:,1:]) plt.xlabel("Time (seconds)") - plt.ylabel("Counts") + title = (str(inspect.getsource(self.get_var_callback)) + .strip("['\\n']") + .split(" = ")[1]) + plt.title("\n".join(wrap(title, 60))) + plt.legend(range(self.data.shape[1]-1)) plt.show() @@ -205,7 +214,7 @@ def step_and_plot( axis, capture = BulkCapture(get_var_callback, data_rate=data_rate, - length = initial_settle_time + settle_time) + duration=initial_settle_time + settle_time) set_setpoint(initial_setpoint) time.sleep(initial_settle_time) From b794178f0e4ec7efcd2c57ff65187b77e0707ed8 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 30 Jun 2020 15:07:21 +0200 Subject: [PATCH 494/549] fix HWIL tests --- tools/odrive/tests/closed_loop_test.py | 5 +++-- tools/odrive/tests/uart_ascii_test.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index 31adce5d..a06f75e4 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -177,7 +177,7 @@ class TestRegenProtection(TestClosedLoopControlBase): max_current = 15.0 # Accept a bit of noise on Ibus - axis_ctx.parent.handle.config.dc_max_negative_current = -0.2 + axis_ctx.parent.handle.config.dc_max_negative_current = -0.5 logger.debug(f'Brake control test from {nominal_rps} rounds/s...') @@ -227,6 +227,7 @@ class TestVelLimitInTorqueControl(TestClosedLoopControlBase): axis_ctx.handle.controller.config.vel_gain /= 10 # reduce the slope to make it easier to see what's going on vel_gain = axis_ctx.handle.controller.config.vel_gain + direction = axis_ctx.handle.motor.config.direction logger.debug(f'vel gain is {vel_gain}') axis_ctx.handle.controller.config.vel_limit = max_vel @@ -237,7 +238,7 @@ class TestVelLimitInTorqueControl(TestClosedLoopControlBase): # Returns the expected limited setpoint for a given velocity and current def get_expected_setpoint(input_setpoint, velocity): - return clamp(clamp(input_setpoint / torque_constant, (velocity + max_vel) * -vel_gain / torque_constant, (velocity - max_vel) * -vel_gain / torque_constant), -max_current, max_current) + return clamp(clamp(input_setpoint / torque_constant, (velocity + max_vel) * -vel_gain / torque_constant, (velocity - max_vel) * -vel_gain / torque_constant), -max_current, max_current) * direction def data_getter(): # sample velocity twice to avoid systematic bias diff --git a/tools/odrive/tests/uart_ascii_test.py b/tools/odrive/tests/uart_ascii_test.py index 6c650e28..ff5a41de 100644 --- a/tools/odrive/tests/uart_ascii_test.py +++ b/tools/odrive/tests/uart_ascii_test.py @@ -105,7 +105,7 @@ class TestUartAscii(): ser.write(b'c 0 12.5\n') test_assert_eq(ser.readline(), b'') test_assert_eq(odrive.handle.axis0.controller.input_torque, 12.5, accuracy=0.001) - test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_CURRENT_CONTROL) + test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_TORQUE_CONTROL) odrive.handle.axis0.controller.input_vel = 0 odrive.handle.axis0.controller.input_torque = 0 @@ -132,7 +132,7 @@ class TestUartAscii(): test_assert_eq(ser.readline(), b'') test_assert_eq(odrive.handle.axis0.controller.input_pos, 123.4, accuracy=0.001) test_assert_eq(odrive.handle.axis0.controller.config.vel_limit, 567.8, accuracy=0.001) - test_assert_eq(odrive.handle.axis0.motor.config.current_lim, 12.5, accuracy=0.001) + test_assert_eq(odrive.handle.axis0.motor.config.torque_lim, 12.5, accuracy=0.001) test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_POSITION_CONTROL) ser.write(b'f 0\n') From 400727e337a2be2a847427b79158a1f2177f0dd6 Mon Sep 17 00:00:00 2001 From: Kyle Bartholomew Date: Tue, 30 Jun 2020 22:54:32 -0700 Subject: [PATCH 495/549] Fixed call to old function name --- tools/odrive/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 27fd8608..84ec3d96 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -224,7 +224,7 @@ def step_and_plot( axis, axis.requested_state = AXIS_STATE_IDLE axis.controller.config.control_mode = initial_control_mode - capture.plot_data() + capture.plot() def print_drv_regs(name, motor): From 60482c9e8b3dbd067270b088629ccef5d21e25ae Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 1 Jul 2020 12:42:01 +0200 Subject: [PATCH 496/549] Fix ASCII protocol bug when writing to uint8_t When writing to uint8_t properties or equivalently sized enum properties, the ASCII protocol handler would write beyond the variable itself and overwrite adjacent memory. This manifested for instance in the following: r axis0.controller.config.input_mode 1 w axis0.controller.config.control_mode 3 r axis0.controller.config.input_mode 0 This boils down to what appears to be misbehavior of `sscanf`. This fix adds an intermediate union to provide a safe memory area for the `sscanf` call. --- Firmware/fibre/cpp/include/fibre/protocol.hpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index 89091f74..c1fb8d6d 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -591,7 +591,17 @@ static bool to_string(const T& value, char * buffer, size_t length, ...) { template::type> static bool from_string(const char * buffer, size_t length, T* property, int) { - return sscanf(buffer, format_traits_t::fmt, property) == 1; + // Note for T == uint8_t: Even though we supposedly use the correct format + // string sscanf treats our pointer as pointer-to-int instead of + // pointer-to-uint8_t. To avoid an unexpected memory access we first read + // into a union. + union { T t; int i; } val; + if (sscanf(buffer, format_traits_t::fmt, &val.t) == 1) { + *property = val.t; + return true; + } else { + return false; + } } // Special case for float because printf promotes float to double, and we get warnings template From 2d05a50e8928394a98e043fa129926c095b7f8c6 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 1 Jul 2020 12:42:01 +0200 Subject: [PATCH 497/549] Fix ASCII protocol bug when writing to uint8_t When writing to uint8_t properties or equivalently sized enum properties, the ASCII protocol handler would write beyond the variable itself and overwrite adjacent memory. This manifested for instance in the following: r axis0.controller.config.input_mode 1 w axis0.controller.config.control_mode 3 r axis0.controller.config.input_mode 0 This boils down to what appears to be misbehavior of `sscanf`. This fix adds an intermediate union to provide a safe memory area for the `sscanf` call. --- Firmware/fibre/cpp/include/fibre/protocol.hpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index 0ad7dc38..2a01e220 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -513,7 +513,17 @@ static bool to_string(const T& value, char * buffer, size_t length, ...) { template::type> static bool from_string(const char * buffer, size_t length, T* property, int) { - return sscanf(buffer, format_traits_t::fmt, property) == 1; + // Note for T == uint8_t: Even though we supposedly use the correct format + // string sscanf treats our pointer as pointer-to-int instead of + // pointer-to-uint8_t. To avoid an unexpected memory access we first read + // into a union. + union { T t; int i; } val; + if (sscanf(buffer, format_traits_t::fmt, &val.t) == 1) { + *property = val.t; + return true; + } else { + return false; + } } // Special case for float because printf promotes float to double, and we get warnings template From fc82ed6610f5512807466bf206560c462a78b114 Mon Sep 17 00:00:00 2001 From: Cam Buss Date: Wed, 1 Jul 2020 14:50:25 -0600 Subject: [PATCH 498/549] Allow reversal of homing direction and ability to clear SPI Error Rate (#427) * clear spi_error_rate on error clear * negative homing speed and negative endtop offset --- Firmware/MotorControl/axis.cpp | 5 ++--- Firmware/MotorControl/axis.hpp | 1 + docs/endstops.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 172f3dc4..ffd6fffc 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -173,10 +173,9 @@ bool Axis::do_checks() { // controller_.do_checks(); // Check for endstop presses - bool vel_dependent_stopping = (current_state_ == AXIS_STATE_HOMING) && (controller_.config_.control_mode >= Controller::CONTROL_MODE_VELOCITY_CONTROL); - if (min_endstop_.config_.enabled && min_endstop_.get_state() && (!vel_dependent_stopping || controller_.vel_setpoint_ < 0.0f)) { + if (min_endstop_.config_.enabled && min_endstop_.get_state() && !(current_state_ == AXIS_STATE_HOMING)) { error_ |= ERROR_MIN_ENDSTOP_PRESSED; - } else if (max_endstop_.config_.enabled && max_endstop_.get_state() && (!vel_dependent_stopping || controller_.vel_setpoint_ > 0.0f)) { + } else if (max_endstop_.config_.enabled && max_endstop_.get_state() && !(current_state_ == AXIS_STATE_HOMING)) { error_ |= ERROR_MAX_ENDSTOP_PRESSED; } diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 480de844..39870ddb 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -106,6 +106,7 @@ public: controller_.error_ = Controller::ERROR_NONE; sensorless_estimator_.error_ = SensorlessEstimator::ERROR_NONE; encoder_.error_ = Encoder::ERROR_NONE; + encoder_.spi_error_rate_ = 0.0f; error_ = ERROR_NONE; } diff --git a/docs/endstops.md b/docs/endstops.md index 6379fb0b..0336b6dd 100644 --- a/docs/endstops.md +++ b/docs/endstops.md @@ -96,7 +96,7 @@ Name | Type | Default --- | -- | -- homing_speed | float | 2000.0f -`homing_speed` is the axis travel speed during homing, in counts/second. +`homing_speed` is the axis travel speed during homing, in counts/second. If you are using SPI based encoders and the axis is homing in the wrong direction, you can enter a negative value for the homing speed and a negative value for the minimum endstop offset. ### Performing the Homing Sequence From 2571ab308d62ceedd0fd190420daa03be81dfe88 Mon Sep 17 00:00:00 2001 From: Cam Buss Date: Wed, 1 Jul 2020 14:50:25 -0600 Subject: [PATCH 499/549] Allow reversal of homing direction and ability to clear SPI Error Rate (#427) * clear spi_error_rate on error clear * negative homing speed and negative endtop offset --- Firmware/MotorControl/axis.cpp | 5 ++--- Firmware/MotorControl/axis.hpp | 1 + docs/endstops.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index d7659d35..6130f782 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -173,10 +173,9 @@ bool Axis::do_checks() { // controller_.do_checks(); // Check for endstop presses - bool vel_dependent_stopping = (current_state_ == AXIS_STATE_HOMING) && (controller_.config_.control_mode >= Controller::CTRL_MODE_VELOCITY_CONTROL); - if (min_endstop_.config_.enabled && min_endstop_.get_state() && (!vel_dependent_stopping || controller_.vel_setpoint_ < 0.0f)) { + if (min_endstop_.config_.enabled && min_endstop_.get_state() && !(current_state_ == AXIS_STATE_HOMING)) { error_ |= ERROR_MIN_ENDSTOP_PRESSED; - } else if (max_endstop_.config_.enabled && max_endstop_.get_state() && (!vel_dependent_stopping || controller_.vel_setpoint_ > 0.0f)) { + } else if (max_endstop_.config_.enabled && max_endstop_.get_state() && !(current_state_ == AXIS_STATE_HOMING)) { error_ |= ERROR_MAX_ENDSTOP_PRESSED; } diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 6be3c60c..7335502c 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -143,6 +143,7 @@ public: controller_.error_ = Controller::ERROR_NONE; sensorless_estimator_.error_ = SensorlessEstimator::ERROR_NONE; encoder_.error_ = Encoder::ERROR_NONE; + encoder_.spi_error_rate_ = 0.0f; error_ = Axis::ERROR_NONE; } diff --git a/docs/endstops.md b/docs/endstops.md index 6379fb0b..0336b6dd 100644 --- a/docs/endstops.md +++ b/docs/endstops.md @@ -96,7 +96,7 @@ Name | Type | Default --- | -- | -- homing_speed | float | 2000.0f -`homing_speed` is the axis travel speed during homing, in counts/second. +`homing_speed` is the axis travel speed during homing, in counts/second. If you are using SPI based encoders and the axis is homing in the wrong direction, you can enter a negative value for the homing speed and a negative value for the minimum endstop offset. ### Performing the Homing Sequence From 9360c5df6571e1ec3055351b1d60a109d7f1600e Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 2 Jul 2020 21:34:51 -0400 Subject: [PATCH 500/549] Fix SPI DMA with different SPI transfer sizes --- Firmware/Board/v3/Src/spi.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Firmware/Board/v3/Src/spi.c b/Firmware/Board/v3/Src/spi.c index fe2ab4ea..a3a3311e 100644 --- a/Firmware/Board/v3/Src/spi.c +++ b/Firmware/Board/v3/Src/spi.c @@ -115,8 +115,15 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle) hdma_spi3_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_spi3_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_spi3_tx.Init.MemInc = DMA_MINC_ENABLE; - hdma_spi3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; - hdma_spi3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + + if(spiHandle->Init.DataSize == SPI_DATASIZE_8BIT){ + hdma_spi3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; + hdma_spi3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; + } else { + hdma_spi3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + hdma_spi3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + } + hdma_spi3_tx.Init.Mode = DMA_NORMAL; hdma_spi3_tx.Init.Priority = DMA_PRIORITY_MEDIUM; hdma_spi3_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; @@ -133,8 +140,13 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle) hdma_spi3_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_spi3_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_spi3_rx.Init.MemInc = DMA_MINC_ENABLE; - hdma_spi3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; - hdma_spi3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + if (spiHandle->Init.DataSize == SPI_DATASIZE_8BIT) { + hdma_spi3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; + hdma_spi3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; + } else { + hdma_spi3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + hdma_spi3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + } hdma_spi3_rx.Init.Mode = DMA_NORMAL; hdma_spi3_rx.Init.Priority = DMA_PRIORITY_MEDIUM; hdma_spi3_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; From b36e352ee0684185a0b1f5dd689e5a8007172909 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 2 Jul 2020 21:34:51 -0400 Subject: [PATCH 501/549] Fix SPI DMA with different SPI transfer sizes --- Firmware/Board/v3/Src/spi.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Firmware/Board/v3/Src/spi.c b/Firmware/Board/v3/Src/spi.c index fe2ab4ea..a3a3311e 100644 --- a/Firmware/Board/v3/Src/spi.c +++ b/Firmware/Board/v3/Src/spi.c @@ -115,8 +115,15 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle) hdma_spi3_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_spi3_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_spi3_tx.Init.MemInc = DMA_MINC_ENABLE; - hdma_spi3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; - hdma_spi3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + + if(spiHandle->Init.DataSize == SPI_DATASIZE_8BIT){ + hdma_spi3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; + hdma_spi3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; + } else { + hdma_spi3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + hdma_spi3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + } + hdma_spi3_tx.Init.Mode = DMA_NORMAL; hdma_spi3_tx.Init.Priority = DMA_PRIORITY_MEDIUM; hdma_spi3_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; @@ -133,8 +140,13 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle) hdma_spi3_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_spi3_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_spi3_rx.Init.MemInc = DMA_MINC_ENABLE; - hdma_spi3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; - hdma_spi3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + if (spiHandle->Init.DataSize == SPI_DATASIZE_8BIT) { + hdma_spi3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; + hdma_spi3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; + } else { + hdma_spi3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + hdma_spi3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + } hdma_spi3_rx.Init.Mode = DMA_NORMAL; hdma_spi3_rx.Init.Priority = DMA_PRIORITY_MEDIUM; hdma_spi3_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; From ce7073baf525359df3ee8f950766955e85ad2895 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 8 Jul 2020 22:12:51 -0400 Subject: [PATCH 502/549] Fix ASCII 't' command --- Firmware/communication/ascii_protocol.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index f0ee9f9c..a5c5d623 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -170,7 +170,8 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& } else { Axis* axis = axes[motor_number]; axis->controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ; - axis->controller_.move_to_pos(goal_point); + axis->controller_.input_pos_ = goal_point; + axis->controller_.input_pos_updated(); axis->watchdog_feed(); } From 0ee6c92cea45f4acab0f99563c974a1cde556ae1 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 8 Jul 2020 22:18:19 -0400 Subject: [PATCH 503/549] Force position control when using ASCII `t` command --- Firmware/communication/ascii_protocol.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index f0200b71..9a5eb90d 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -176,6 +176,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& } else { Axis* axis = axes[motor_number]; axis->controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ; + axis->controller_.config_.control_mode = Controller::CONTROL_MODE_POSITION_CONTROL; axis->controller_.input_pos_ = goal_point; axis->controller_.input_pos_updated(); axis->watchdog_feed(); From 462bdfb99ed5090a7a97d9dd3b47ef2deb0f7a75 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 8 Jul 2020 22:19:58 -0400 Subject: [PATCH 504/549] Force position control when using ASCII `t` command --- Firmware/communication/ascii_protocol.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index a5c5d623..22761c4a 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -170,6 +170,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& } else { Axis* axis = axes[motor_number]; axis->controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ; + axis->controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; axis->controller_.input_pos_ = goal_point; axis->controller_.input_pos_updated(); axis->watchdog_feed(); From b58d169acc2933054ccb0e88ae80655e9cf0d73d Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 10 Jul 2020 16:30:37 -0400 Subject: [PATCH 505/549] CTRL_MODE --> CONTROL_MODE in utils.py --- tools/odrive/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index a8b402a4..5b9ff4c8 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -201,14 +201,14 @@ def step_and_plot( axis, step_size=100.0, settle_time=0.5, data_rate=500.0, - ctrl_mode=CTRL_MODE_POSITION_CONTROL): + ctrl_mode=CONTROL_MODE_POSITION_CONTROL): - if ctrl_mode is CTRL_MODE_POSITION_CONTROL: + if ctrl_mode is CONTROL_MODE_POSITION_CONTROL: get_var_callback = lambda :[axis.encoder.pos_estimate, axis.controller.pos_setpoint] initial_setpoint = axis.encoder.pos_estimate def set_setpoint(setpoint): axis.controller.pos_setpoint = setpoint - elif ctrl_mode is CTRL_MODE_VELOCITY_CONTROL: + elif ctrl_mode is CONTROL_MODE_VELOCITY_CONTROL: get_var_callback = lambda :[axis.encoder.vel_estimate, axis.controller.vel_setpoint] initial_setpoint = 0 def set_setpoint(setpoint): From 4d7bf9ec2130607bde0bb4c8ac9a419cebec5314 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 11 Jul 2020 16:41:03 -0400 Subject: [PATCH 506/549] Fix image extension case --- docs/input_modes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/input_modes.md b/docs/input_modes.md index 32d7df86..ad248979 100644 --- a/docs/input_modes.md +++ b/docs/input_modes.md @@ -51,7 +51,7 @@ Ramps a velocity command from the current value to the target value. ## INPUT_MODE_POS_FILTER Implements a 2nd order position tracking filter. Inteded for use with step/dir interface, but can also be used with position-only commands. -![POS Filter Response](secondOrderResponse.png) +![POS Filter Response](secondOrderResponse.PNG) Result of a step command from 1000 to 0 ### Configuration Values: @@ -71,7 +71,7 @@ Not Implemented. ## INPUT_MODE_TRAP_TRAJ Implementes an online trapezoidal trajectory planner. -![Trapezoidal Planner Response](TrapTrajPosVel.png) +![Trapezoidal Planner Response](TrapTrajPosVel.PNG) ### Configuration Values: * `.trap_traj.config.vel_limit` From 4b01f54afb6ed38162c29bcdd6136602ecd9b162 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 11 Jul 2020 15:43:07 -0700 Subject: [PATCH 507/549] Revert "Un-removed custom setter for input_pos_" This reverts commit 744bfeb110d87b5caa534ac921ef9cad2654e772. --- Firmware/MotorControl/controller.cpp | 8 ++++---- Firmware/MotorControl/controller.hpp | 2 -- Firmware/MotorControl/encoder.cpp | 8 ++++---- Firmware/odrive-interface.yaml | 1 - 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 37b899f6..c8ea8f05 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -85,8 +85,8 @@ void Controller::start_anticogging_calibration() { */ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) { float pos_err = input_pos_ - pos_estimate; - if (std::abs(pos_err) <= config_.anticogging.calib_pos_threshold * (2.0f * M_PI) / axis_->encoder_.config_.cpr && - std::abs(vel_estimate) < config_.anticogging.calib_vel_threshold * (2.0f * M_PI) / axis_->encoder_.config_.cpr) { + if (std::abs(pos_err) <= config_.anticogging.calib_pos_threshold * (2.0f * M_PI) / (float)axis_->encoder_.config_.cpr && + std::abs(vel_estimate) < config_.anticogging.calib_vel_threshold * (2.0f * M_PI) / (float)axis_->encoder_.config_.cpr) { config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_torque_; } if (config_.anticogging.index < 3600) { @@ -140,7 +140,7 @@ bool Controller::update(float* torque_setpoint_output) { // TODO also enable circular deltas for 2nd order filter, etc. if (pos_wrap_src_) { - float cpr = *pos_wrap_src_ * 2.0f * M_PI / axis_->encoder_.config_.cpr; + float cpr = *pos_wrap_src_ * 2.0f * M_PI / ((float)axis_->encoder_.config_.cpr); // Keep pos setpoint from drifting input_pos_ = fmodf_pos(input_pos_, cpr); } @@ -235,7 +235,7 @@ bool Controller::update(float* torque_setpoint_output) { } if (pos_wrap_src_) { - float cpr = *pos_wrap_src_ * 2.0f * M_PI / axis_->encoder_.config_.cpr; + float cpr = *pos_wrap_src_ * 2.0f * M_PI / ((float)axis_->encoder_.config_.cpr); // Keep pos setpoint from drifting pos_setpoint_ = fmodf_pos(pos_setpoint_, cpr); // Circular delta diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 7bb8e270..743992c2 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -95,8 +95,6 @@ public: bool anticogging_valid_ = false; - // custom setters - void set_input_pos(float value) { input_pos_ = value; input_pos_updated();} }; #endif // __CONTROLLER_HPP diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 300859e4..ad9c02e5 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -510,7 +510,7 @@ bool Encoder::update() { // discrete phase detector float delta_pos = (float)(shadow_count_ - (int32_t)std::floor(pos_estimate_)); float delta_pos_cpr = (float)(count_in_cpr_ - (int32_t)std::floor(pos_cpr_)); - delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * config_.cpr); + delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * (float)(config_.cpr)); // pll feedback pos_estimate_ += current_meas_period * pll_kp_ * delta_pos; pos_cpr_ += current_meas_period * pll_kp_ * delta_pos_cpr; @@ -523,9 +523,9 @@ bool Encoder::update() { } //expose pos/vel estimates in radians for Controller - pos_est_rad_ = pos_estimate_ * 2.0f * M_PI / config_.cpr; - vel_est_rad_ = vel_estimate_ * 2.0f * M_PI / config_.cpr; - pos_cpr_rad_ = pos_cpr_ * 2.0f * M_PI / config_.cpr; + pos_est_rad_ = pos_estimate_ * 2.0f * M_PI / (float)config_.cpr; + vel_est_rad_ = vel_estimate_ * 2.0f * M_PI / (float)config_.cpr; + pos_cpr_rad_ = pos_cpr_ * 2.0f * M_PI / (float)config_.cpr; //// run encoder count interpolation int32_t corrected_enc = count_in_cpr_ - config_.offset; diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 1ab58698..d76a4ec8 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -596,7 +596,6 @@ interfaces: input_pos: type: float32 unit: rad - c_setter: set_input_pos input_vel: type: float32 unit: rad/s From 4d920849a09d1e3368216f7786e9c1c9b9cb2ce3 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 11 Jul 2020 15:44:27 -0700 Subject: [PATCH 508/549] un-remove custom setter for input-pos --- Firmware/odrive-interface.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index d76a4ec8..1ab58698 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -596,6 +596,7 @@ interfaces: input_pos: type: float32 unit: rad + c_setter: set_input_pos input_vel: type: float32 unit: rad/s From 883dd27a17ece18a2c76ff86dc6af1756d05390a Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 11 Jul 2020 22:19:41 -0400 Subject: [PATCH 509/549] Fix static in encoder.cpp, constexpr the things --- Firmware/MotorControl/encoder.cpp | 4 ++-- Firmware/MotorControl/low_level.cpp | 8 ++++---- Firmware/MotorControl/motor.cpp | 6 +++--- Firmware/communication/can_simple.cpp | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index d1545fc1..de3b7330 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -181,8 +181,8 @@ bool Encoder::run_direction_find() { // and the encoder state 0. // TODO: Do the scan with current, not voltage! bool Encoder::run_offset_calibration() { - static const float start_lock_duration = 1.0f; - static const int num_steps = (int)(config_.calib_scan_distance / config_.calib_scan_omega * (float)current_meas_hz); + const float start_lock_duration = 1.0f; + const int num_steps = (int)(config_.calib_scan_distance / config_.calib_scan_omega * (float)current_meas_hz); // Require index found if enabled if (config_.use_index && !index_found_) { diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 0dcc8754..2949ac64 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -28,8 +28,8 @@ /* Private macros ------------------------------------------------------------*/ /* Private typedef -----------------------------------------------------------*/ /* Global constant data ------------------------------------------------------*/ -const float adc_full_scale = (float)(1 << 12); -const float adc_ref_voltage = 3.3f; +constexpr float adc_full_scale = static_cast(1UL << 12UL); +constexpr float adc_ref_voltage = 3.3f; /* Global variables ----------------------------------------------------------*/ // This value is updated by the DC-bus reading ADC. @@ -439,7 +439,7 @@ float get_adc_voltage(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin) { //-------------------------------- void vbus_sense_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { - static const float voltage_scale = adc_ref_voltage * VBUS_S_DIVIDER_RATIO / adc_full_scale; + constexpr float voltage_scale = adc_ref_voltage * VBUS_S_DIVIDER_RATIO / adc_full_scale; // Only one conversion in sequence, so only rank1 uint32_t ADCValue = HAL_ADCEx_InjectedGetValue(hadc, ADC_INJECTED_RANK_1); vbus_voltage = ADCValue * voltage_scale; @@ -478,7 +478,7 @@ static void decode_hall_samples(Encoder& enc, uint16_t GPIO_samples[num_GPIO]) { // TODO: Document how the phasing is done, link to timing diagram void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { #define calib_tau 0.2f //@TOTO make more easily configurable - static const float calib_filter_k = CURRENT_MEAS_PERIOD / calib_tau; + constexpr float calib_filter_k = CURRENT_MEAS_PERIOD / calib_tau; // Ensure ADCs are expected ones to simplify the logic below if (!(hadc == &hadc2 || hadc == &hadc3)) { diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index ac03e863..c599d9f6 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -73,9 +73,9 @@ void Motor::DRV8301_setup() { // Solve for exact gain, then snap down to have equal or larger range as requested // or largest possible range otherwise - static const float kMargin = 0.90f; - static const float kTripMargin = 1.0f; // Trip level is at edge of linear range of amplifer - static const float max_output_swing = 1.35f; // [V] out of amplifier + constexpr float kMargin = 0.90f; + constexpr float kTripMargin = 1.0f; // Trip level is at edge of linear range of amplifer + constexpr float max_output_swing = 1.35f; // [V] out of amplifier float max_unity_gain_current = kMargin * max_output_swing * hw_config_.shunt_conductance; // [A] float requested_gain = max_unity_gain_current / config_.requested_current_range; // [V/V] diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index b00b13e0..69b84f78 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -4,7 +4,7 @@ #include -static const uint8_t NUM_NODE_ID_BITS = 6; +static constexpr uint8_t NUM_NODE_ID_BITS = 6; static constexpr uint8_t NUM_CMD_ID_BITS = 11 - NUM_NODE_ID_BITS; void CANSimple::handle_can_message(can_Message_t& msg) { From 7fade2b95aaa6d44ec8037be71528565fb8dcf4e Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 13 Jul 2020 16:00:10 +0200 Subject: [PATCH 510/549] add filter to the reported `.ibus` --- Firmware/MotorControl/low_level.cpp | 2 +- Firmware/MotorControl/odrive_main.h | 1 + Firmware/odrive-interface.yaml | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 8aafca4e..0c423346 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -636,7 +636,7 @@ void update_brake_current() { // Special handling to avoid the case 0.0/0.0 == NaN. Ibus_sum += brake_duty ? (brake_duty * vbus_voltage / odrv.config_.brake_resistance) : 0.0f; - ibus_ = Ibus_sum; + ibus_ += odrv.ibus_report_filter_k_ * (Ibus_sum - ibus_); if (Ibus_sum > odrv.config_.dc_max_positive_current) { low_level_fault(Motor::ERROR_DC_BUS_OVER_CURRENT); diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 62bd4f57..b112a19d 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -239,6 +239,7 @@ public: float& vbus_voltage_ = ::vbus_voltage; // TODO: make this the actual variable float& ibus_ = ::ibus_; // TODO: make this the actual variable + float ibus_report_filter_k_ = 1.0f; const uint64_t& serial_number_ = ::serial_number; diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index b738e0df..79f0c1e8 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -28,6 +28,12 @@ interfaces: This value is equal to the sum of the motor currents and the brake resistor currents. The motor currents are measured, the brake resistor current is calculated based on `config.brake_resistance`. + ibus_report_filter_k: + type: float32 + doc: | + Filter gain for the reported `ibus`. Set to a value below 1.0 to get a smoother + line when plotting `ibus`. Set to 1.0 to disable. This filter is only applied to + the reported value and not for internal calculations. serial_number: readonly uint64 hw_version_major: readonly uint8 hw_version_minor: readonly uint8 From 7ce896c2ced892a60957d977bb592e5d61bb61f9 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Mon, 13 Jul 2020 20:59:35 +0100 Subject: [PATCH 511/549] In encoder.*pp, XXX_estimate -> XXX_estimate_counts and XXX_est_rad -> XXX_estimate. Changed HWIL tests to reflect unit change Corrected sources for position and velocity estimates elsewhere. --- Firmware/MotorControl/axis.cpp | 6 ++-- Firmware/MotorControl/controller.cpp | 14 +++++----- Firmware/MotorControl/controller.hpp | 12 ++++---- Firmware/MotorControl/encoder.cpp | 34 +++++++++++------------ Firmware/MotorControl/encoder.hpp | 12 ++++---- Firmware/communication/ascii_protocol.cpp | 4 +-- Firmware/communication/can_simple.cpp | 8 +++--- Firmware/odrive-interface.yaml | 6 ++-- docs/commands.md | 4 +-- docs/getting-started.md | 2 +- docs/odrivetool.md | 2 +- tools/odrive/tests/closed_loop_test.py | 26 ++++++++--------- tools/odrive/tests/encoder_test.py | 6 ++-- tools/odrivetool | 4 +-- 14 files changed, 70 insertions(+), 70 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 65b6ede6..98709de0 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -314,7 +314,7 @@ bool Axis::run_closed_loop_control_loop() { if (!controller_.update(&torque_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; - float phase_vel = encoder_.vel_est_rad_ * motor_.config_.pole_pairs; + float phase_vel = encoder_.vel_estimate_ * motor_.config_.pole_pairs; if (!motor_.update(torque_setpoint, encoder_.phase_, phase_vel)) return false; // set_error should update axis.error_ @@ -363,7 +363,7 @@ bool Axis::run_homing() { if (!controller_.update(&torque_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; - float phase_vel = encoder_.vel_est_rad_ * motor_.config_.pole_pairs; + float phase_vel = encoder_.vel_estimate_ * motor_.config_.pole_pairs; if (!motor_.update(torque_setpoint, encoder_.phase_, phase_vel)) return false; // set_error should update axis.error_ @@ -392,7 +392,7 @@ bool Axis::run_homing() { if (!controller_.update(&torque_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; - float phase_vel = encoder_.vel_est_rad_ * motor_.config_.pole_pairs; + float phase_vel = encoder_.vel_estimate_ * motor_.config_.pole_pairs; if (!motor_.update(torque_setpoint, encoder_.phase_, phase_vel)) return false; // set_error should update axis.error_ diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 37b899f6..9d859b37 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -34,14 +34,14 @@ bool Controller::select_encoder(size_t encoder_num) { if (encoder_num < AXIS_COUNT) { Axis* ax = axes[encoder_num]; if (config_.setpoints_in_cpr) { - pos_estimate_src_ = &ax->encoder_.pos_cpr_rad_; + pos_estimate_src_ = &ax->encoder_.pos_cpr_; pos_wrap_src_ = &ax->encoder_.config_.cpr; } else { - pos_estimate_src_ = &ax->encoder_.pos_est_rad_; + pos_estimate_src_ = &ax->encoder_.pos_estimate_; pos_wrap_src_ = nullptr; } pos_estimate_valid_src_ = &ax->encoder_.pos_estimate_valid_; - vel_estimate_src_ = &ax->encoder_.vel_est_rad_; + vel_estimate_src_ = &ax->encoder_.vel_estimate_; vel_estimate_valid_src_ = &ax->encoder_.vel_estimate_valid_; return true; } else { @@ -128,14 +128,14 @@ bool Controller::update(float* torque_setpoint_output) { ? vel_estimate_src_ : nullptr; // Calib_anticogging is only true when calibration is occurring, so we can't block anticogging_pos - float anticogging_pos = axis_->encoder_.pos_est_rad_ / axis_->encoder_.getCoggingRatio(); + float anticogging_pos = axis_->encoder_.pos_estimate_ / axis_->encoder_.getCoggingRatio(); if (config_.anticogging.calib_anticogging) { if (!axis_->encoder_.pos_estimate_valid_ || !axis_->encoder_.vel_estimate_valid_) { set_error(ERROR_INVALID_ESTIMATE); return false; } // non-blocking - anticogging_calibration(axis_->encoder_.pos_est_rad_, axis_->encoder_.vel_est_rad_); + anticogging_calibration(axis_->encoder_.pos_estimate_, axis_->encoder_.vel_estimate_); } // TODO also enable circular deltas for 2nd order filter, etc. @@ -181,8 +181,8 @@ bool Controller::update(float* torque_setpoint_output) { } break; case INPUT_MODE_MIRROR: { if (config_.axis_to_mirror < AXIS_COUNT) { - pos_setpoint_ = axes[config_.axis_to_mirror]->encoder_.pos_est_rad_ * config_.mirror_ratio; - vel_setpoint_ = axes[config_.axis_to_mirror]->encoder_.vel_est_rad_ * config_.mirror_ratio; + pos_setpoint_ = axes[config_.axis_to_mirror]->encoder_.pos_estimate_ * config_.mirror_ratio; + vel_setpoint_ = axes[config_.axis_to_mirror]->encoder_.vel_estimate_ * config_.mirror_ratio; } else { set_error(ERROR_INVALID_MIRROR_AXIS); return false; diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 7bb8e270..22833af8 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -77,15 +77,15 @@ public: bool* vel_estimate_valid_src_ = nullptr; int32_t* pos_wrap_src_ = nullptr; // enables circular position setpoints if not null. The value pointed to is the maximum position value. - float pos_setpoint_ = 0.0f; // [radians] - float vel_setpoint_ = 0.0f; // [rad/s] + float pos_setpoint_ = 0.0f; // [radians] + float vel_setpoint_ = 0.0f; // [rad/s] // float vel_setpoint = 800.0f; float vel_integrator_torque_ = 0.0f; // [Nm] - float torque_setpoint_ = 0.0f; // [Nm] + float torque_setpoint_ = 0.0f; // [Nm] - float input_pos_ = 0.0f; // [radians] - float input_vel_ = 0.0f; // [rad/s] - float input_torque_ = 0.0f; // [Nm] + float input_pos_ = 0.0f; // [radians] + float input_vel_ = 0.0f; // [rad/s] + float input_torque_ = 0.0f; // [Nm] float input_filter_kp_ = 0.0f; float input_filter_ki_ = 0.0f; diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 300859e4..1f84d153 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -110,7 +110,7 @@ void Encoder::set_linear_count(int32_t count) { // Update states shadow_count_ = count; - pos_estimate_ = (float)count; + pos_estimate_counts_ = (float)count; tim_cnt_sample_ = count; //Write hardware last @@ -132,7 +132,7 @@ void Encoder::set_circular_count(int32_t count, bool update_offset) { // Update states count_in_cpr_ = mod(count, config_.cpr); - pos_cpr_ = (float)count_in_cpr_; + pos_cpr_counts_ = (float)count_in_cpr_; cpu_exit_critical(prim); } @@ -505,27 +505,27 @@ bool Encoder::update() { //// run pll (for now pll is in units of encoder counts) // Predict current pos - pos_estimate_ += current_meas_period * vel_estimate_; - pos_cpr_ += current_meas_period * vel_estimate_; + pos_estimate_counts_ += current_meas_period * vel_estimate_counts_; + pos_cpr_counts_ += current_meas_period * vel_estimate_counts_; // discrete phase detector - float delta_pos = (float)(shadow_count_ - (int32_t)std::floor(pos_estimate_)); - float delta_pos_cpr = (float)(count_in_cpr_ - (int32_t)std::floor(pos_cpr_)); + float delta_pos = (float)(shadow_count_ - (int32_t)std::floor(pos_estimate_counts_)); + float delta_pos_cpr = (float)(count_in_cpr_ - (int32_t)std::floor(pos_cpr_counts_)); delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * config_.cpr); // pll feedback - pos_estimate_ += current_meas_period * pll_kp_ * delta_pos; - pos_cpr_ += current_meas_period * pll_kp_ * delta_pos_cpr; - pos_cpr_ = fmodf_pos(pos_cpr_, (float)(config_.cpr)); - vel_estimate_ += current_meas_period * pll_ki_ * delta_pos_cpr; + pos_estimate_counts_ += current_meas_period * pll_kp_ * delta_pos; + pos_cpr_counts_ += current_meas_period * pll_kp_ * delta_pos_cpr; + pos_cpr_counts_ = fmodf_pos(pos_cpr_counts_, (float)(config_.cpr)); + vel_estimate_counts_ += current_meas_period * pll_ki_ * delta_pos_cpr; bool snap_to_zero_vel = false; - if (std::abs(vel_estimate_) < 0.5f * current_meas_period * pll_ki_) { - vel_estimate_ = 0.0f; //align delta-sigma on zero to prevent jitter + if (std::abs(vel_estimate_counts_) < 0.5f * current_meas_period * pll_ki_) { + vel_estimate_counts_ = 0.0f; //align delta-sigma on zero to prevent jitter snap_to_zero_vel = true; } - //expose pos/vel estimates in radians for Controller - pos_est_rad_ = pos_estimate_ * 2.0f * M_PI / config_.cpr; - vel_est_rad_ = vel_estimate_ * 2.0f * M_PI / config_.cpr; - pos_cpr_rad_ = pos_cpr_ * 2.0f * M_PI / config_.cpr; + //outputs from encoder for controller + pos_estimate_ = pos_estimate_counts_ * 2.0f * M_PI / config_.cpr; + vel_estimate_ = vel_estimate_counts_ * 2.0f * M_PI / config_.cpr; + pos_cpr_= pos_cpr_counts_ * 2.0f * M_PI / config_.cpr; //// run encoder count interpolation int32_t corrected_enc = count_in_cpr_ - config_.offset; @@ -539,7 +539,7 @@ bool Encoder::update() { interpolation_ = 1.0f; } else { // Interpolate (predict) between encoder counts using vel_estimate, - interpolation_ += current_meas_period * vel_estimate_; + interpolation_ += current_meas_period * vel_estimate_counts_; // don't allow interpolation indicated position outside of [enc, enc+1) if (interpolation_ > 1.0f) interpolation_ = 1.0f; if (interpolation_ < 0.0f) interpolation_ = 0.0f; diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 3fe8b3a2..515af104 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -75,18 +75,18 @@ public: int32_t count_in_cpr_ = 0; float interpolation_ = 0.0f; float phase_ = 0.0f; // [count] - float pos_estimate_ = 0.0f; // [count] - float pos_cpr_ = 0.0f; // [count] - float vel_estimate_ = 0.0f; // [count/s] + float pos_estimate_counts_ = 0.0f; // [count] + float pos_cpr_counts_ = 0.0f; // [count] + float vel_estimate_counts_ = 0.0f; // [count/s] float pll_kp_ = 0.0f; // [count/s / count] float pll_ki_ = 0.0f; // [(count/s^2) / count] float calib_scan_response_ = 0.0f; // debug report from offset calib int32_t pos_abs_ = 0; float spi_error_rate_ = 0.0f; - float pos_est_rad_ = 0.0f; // [rad] - float vel_est_rad_ = 0.0f; // [rad] - float pos_cpr_rad_ = 0.0f; // [rad] + float pos_estimate_ = 0.0f; // [rad] + float vel_estimate_ = 0.0f; // [rad] + float pos_cpr_ = 0.0f; // [rad] bool pos_estimate_valid_ = false; bool vel_estimate_valid_ = false; diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index fc95dbc3..9a5eb90d 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -191,8 +191,8 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "invalid motor %u", motor_number); } else { respond(response_channel, use_checksum, "%f %f", - (double)axes[motor_number]->encoder_.pos_est_rad_, - (double)axes[motor_number]->encoder_.vel_est_rad_); + (double)axes[motor_number]->encoder_.pos_estimate_, + (double)axes[motor_number]->encoder_.vel_estimate_); } } else if (cmd[0] == 'h') { // Help diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 5679c73d..18d39838 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -206,16 +206,16 @@ void CANSimple::get_encoder_estimates_callback(Axis* axis, can_Message_t& msg) { // uint32_t floatBytes = *(reinterpret_cast(&(axis->encoder_.pos_estimate_))); uint32_t floatBytes; - static_assert(sizeof axis->encoder_.pos_est_rad_ == sizeof floatBytes); - std::memcpy(&floatBytes, &axis->encoder_.pos_est_rad_, sizeof floatBytes); + static_assert(sizeof axis->encoder_.pos_estimate_ == sizeof floatBytes); + std::memcpy(&floatBytes, &axis->encoder_.pos_estimate_, sizeof floatBytes); txmsg.buf[0] = floatBytes; txmsg.buf[1] = floatBytes >> 8; txmsg.buf[2] = floatBytes >> 16; txmsg.buf[3] = floatBytes >> 24; - static_assert(sizeof floatBytes == sizeof axis->encoder_.vel_est_rad_); - std::memcpy(&floatBytes, &axis->encoder_.vel_est_rad_, sizeof floatBytes); + static_assert(sizeof floatBytes == sizeof axis->encoder_.vel_estimate_); + std::memcpy(&floatBytes, &axis->encoder_.vel_estimate_, sizeof floatBytes); txmsg.buf[4] = floatBytes; txmsg.buf[5] = floatBytes >> 8; txmsg.buf[6] = floatBytes >> 16; diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index c48b662c..9fb4b874 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -723,12 +723,12 @@ interfaces: interpolation: readonly float32 phase: readonly float32 pos_estimate: readonly float32 - pos_est_rad: readonly float32 + pos_estimate_counts: readonly float32 pos_cpr: readonly float32 - pos_cpr_rad: readonly float32 + pos_cpr_counts: readonly float32 hall_state: readonly uint8 vel_estimate: readonly float32 - vel_est_rad: readonly float32 + vel_estimate_counts: readonly float32 calib_scan_response: readonly float32 pos_abs: int32 spi_error_rate: readonly float32 diff --git a/docs/commands.md b/docs/commands.md index 17250726..ff11356f 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -55,8 +55,8 @@ Possible values are listed [here](api/odrive.axis.controller.inputmode). ## System monitoring commands ### Encoder position and velocity -* View encoder position with `.encoder.pos_estimate` [counts] or `.encoder.pos_est_rad` [radians] -* View rotational velocity with `.encoder.vel_estimate` [counts/s] or `.encoder.vel_est_rad` [radians] +* View encoder position with `.encoder.pos_estimate` [rad] or `.encoder.pos_est_counts` [counts] +* View rotational velocity with `.encoder.vel_estimate` [rad/s] or `.encoder.vel_est_counts` [count/s] ### Motor current and torque estimation * View the commanded motor current with `.motor.current_control.Iq_setpoint` [A] diff --git a/docs/getting-started.md b/docs/getting-started.md index 3e973782..913f2140 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -332,7 +332,7 @@ This mode is useful for continuous incremental position movement. For example a In the regular position mode, the `input_pos` would grow to a very large value and would lose precision due to floating point rounding. In this mode, the controller will try to track the position within only one turn of the motor. Specifically, `input_pos` is expected in the range `[0, 2*Pi]`. If the `input_pos` is incremented to outside this range (say via step/dir input), it is automatically wrapped around into the correct value. -Note that in this mode `encoder.pos_cpr_rad` is used for feedback in stead of `encoder.pos_est_rad`. +Note that in this mode `encoder.pos_cpr_` is used for feedback instead of `encoder.pos_estimate`. If you try to increment the axis with a large step in one go that exceeds `Pi` steps, the motor will go to the same angle around the wrong way. This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a virtual CPR for your encoder that is an integer times larger than your encoder's actual CPR. Set `encoder.config.cpr = N * your_enc_cpr`, where N is some integer. Choose N to give you an appropriate circular space for your application. `encoder.config.cpr` is automatically converted to radians internally. diff --git a/docs/odrivetool.md b/docs/odrivetool.md index dd83c1bf..cbcff477 100644 --- a/docs/odrivetool.md +++ b/docs/odrivetool.md @@ -216,7 +216,7 @@ For example, to plot the approximate motor torque [N.cm] and the velocity [RPM] # If you want to plot different values, change them here. # You can plot any number of values concurrently. cancellation_token = start_liveplotter(lambda: [ - ((my_odrive.axis0.encoder.vel_est_rad*60/(6.2832)), # radians to rpm + ((my_odrive.axis0.encoder.vel_estimate*60/(6.2832)), # radians to rpm ((my_odrive.axis0.motor.current_control.Iq_setpoint * my_odrive.axis0.motor.config.torque_constant), # Torque [Nm] ]) ``` diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index 2aa58784..2bcfa9cf 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -87,7 +87,7 @@ class TestClosedLoopControl(TestClosedLoopControlBase): request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) axis_ctx.handle.controller.input_vel = nominal_vel - data = record_log(lambda: [axis_ctx.handle.encoder.vel_est_rad, axis_ctx.handle.encoder.pos_est_rad], duration=5.0) + data = record_log(lambda: [axis_ctx.handle.encoder.vel_estimate, axis_ctx.handle.encoder.pos_estimate], duration=5.0) test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) test_assert_no_error(axis_ctx) @@ -119,11 +119,11 @@ class TestClosedLoopControl(TestClosedLoopControlBase): axis_ctx.handle.controller.input_pos = test_pos time.sleep(0.3) test_assert_no_error(axis_ctx) - test_assert_eq(axis_ctx.handle.encoder.pos_est_rad, test_pos, range=0.4*test_pos) # large range needed because of cogging torque + test_assert_eq(axis_ctx.handle.encoder.pos_estimate, test_pos, range=0.4*test_pos) # large range needed because of cogging torque axis_ctx.handle.controller.input_pos = -1 * test_pos time.sleep(0.3) test_assert_no_error(axis_ctx) - test_assert_eq(axis_ctx.handle.encoder.pos_est_rad, -1 * test_pos, range=0.4*test_pos) + test_assert_eq(axis_ctx.handle.encoder.pos_estimate, -1 * test_pos, range=0.4*test_pos) axis_ctx.handle.controller.input_pos = 0 time.sleep(0.3) @@ -132,7 +132,7 @@ class TestClosedLoopControl(TestClosedLoopControlBase): axis_ctx.handle.controller.input_pos = nominal_vel * 2.0 # 10 turns (takes 2 seconds) # Test large position change with bounded velocity - data = record_log(lambda: [axis_ctx.handle.encoder.vel_est_rad, axis_ctx.handle.encoder.pos_est_rad], duration=4.0) + data = record_log(lambda: [axis_ctx.handle.encoder.vel_estimate, axis_ctx.handle.encoder.pos_estimate], duration=4.0) test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) test_assert_no_error(axis_ctx) @@ -141,24 +141,24 @@ class TestClosedLoopControl(TestClosedLoopControlBase): data_motion = data[data[:,0] < 1.9] data_still = data[data[:,0] > 2.1] - # encoder.vel_est_rad + # encoder.vel_estimate slope, offset, fitted_curve = fit_line(data_motion[:,(0,1)]) test_assert_eq(slope, 0.0, range = nominal_vel * 0.05) test_assert_eq(offset, nominal_vel, accuracy = 0.05) test_curve_fit(data_motion[:,(0,1)], fitted_curve, max_mean_err = nominal_vel * 0.05, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) - # encoder.pos_est_rad + # encoder.pos_estimate slope, offset, fitted_curve = fit_line(data_motion[:,(0,2)]) test_assert_eq(slope, nominal_vel, accuracy = 0.01) test_curve_fit(data_motion[:,(0,2)], fitted_curve, max_mean_err = nominal_vel * 0.01, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) - # encoder.vel_est_rad + # encoder.vel_estimate slope, offset, fitted_curve = fit_line(data_still[:,(0,1)]) test_assert_eq(slope, 0.0, range = nominal_vel * 0.05) test_assert_eq(offset, 0.0, range = nominal_vel * 0.05) test_curve_fit(data_still[:,(0,1)], fitted_curve, max_mean_err = nominal_vel * 0.05, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) - # encoder.pos_est_rad + # encoder.pos_estimate slope, offset, fitted_curve = fit_line(data_still[:,(0,2)]) test_assert_eq(slope, 0.0, range = nominal_vel * 0.05) test_assert_eq(offset, nominal_vel*2, range = nominal_vel * 0.02) @@ -173,7 +173,7 @@ class TestRegenProtection(TestClosedLoopControlBase): def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): with self.prepare(axis_ctx, motor_ctx, enc_ctx, logger): - nominal_rps = 10.0 + nominal_rps = 15.0 nominal_vel = 2.0 * pi * nominal_rps max_current = 15.0 @@ -182,7 +182,7 @@ class TestRegenProtection(TestClosedLoopControlBase): logger.debug(f'Brake control test from {nominal_rps} rounds/s...') - axis_ctx.handle.controller.config.vel_limit = 2.0 * pi * 15.0 # max 15 rps + axis_ctx.handle.controller.config.vel_limit = 2.0 * pi * 25.0 # max 15 rps axis_ctx.handle.motor.config.current_lim = max_current axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL axis_ctx.handle.controller.config.input_mode = INPUT_MODE_PASSTHROUGH @@ -223,7 +223,7 @@ class TestVelLimitInTorqueControl(TestClosedLoopControlBase): max_rps = 20.0 max_vel = 2.0 * pi * max_rps absolute_max_vel = max_vel * 1.2 - max_current = 15.0 + max_current = 30.0 torque_constant = 0.0305 #correct for 5065 motor axis_ctx.handle.controller.config.vel_gain /= 10 # reduce the slope to make it easier to see what's going on @@ -243,9 +243,9 @@ class TestVelLimitInTorqueControl(TestClosedLoopControlBase): def data_getter(): # sample velocity twice to avoid systematic bias - velocity0 = axis_ctx.handle.encoder.vel_est_rad + velocity0 = axis_ctx.handle.encoder.vel_estimate current_setpoint = axis_ctx.handle.motor.current_control.Iq_setpoint - velocity1 = axis_ctx.handle.encoder.vel_est_rad + velocity1 = axis_ctx.handle.encoder.vel_estimate velocity = ((velocity0 + velocity1) / 2) # Abort immediately if the absolute limits are exceeded test_assert_within(current_setpoint, -max_current, max_current) diff --git a/tools/odrive/tests/encoder_test.py b/tools/odrive/tests/encoder_test.py index dafb88dd..8d40778b 100644 --- a/tools/odrive/tests/encoder_test.py +++ b/tools/odrive/tests/encoder_test.py @@ -32,9 +32,9 @@ class TestEncoderBase(): encoder.shadow_count, encoder.count_in_cpr, encoder.phase, - encoder.pos_estimate, - encoder.pos_cpr, - encoder.vel_estimate, + encoder.pos_estimate_counts, + encoder.pos_cpr_counts, + encoder.vel_estimate_counts, ], duration=5.0) short_period = (abs(1 / true_rps) < 5.0) diff --git a/tools/odrivetool b/tools/odrivetool index 6011de93..60ed99bb 100755 --- a/tools/odrivetool +++ b/tools/odrivetool @@ -152,8 +152,8 @@ try: # If you want to plot different values, change them here. # You can plot any number of values concurrently. cancellation_token = start_liveplotter(lambda: [ - my_odrive.axis0.encoder.pos_est_rad, - my_odrive.axis1.encoder.pos_est_rad, + my_odrive.axis0.encoder.pos_estimate, + my_odrive.axis1.encoder.pos_estimate, ]) print("Showing plot. Press Ctrl+C to exit.") From 9d5b2bc6d819e261f58185edc4c1699d25d74dfb Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Mon, 13 Jul 2020 23:31:57 +0100 Subject: [PATCH 512/549] Changed conditions and sources of wrapping for the circular setpoint feature as it related to this unit change. setpoints_in_cpr is still not exposed as an endpoint due to implementation changes when input_mode was introduced. --- Firmware/MotorControl/controller.cpp | 14 ++++++-------- Firmware/MotorControl/controller.hpp | 6 +++++- Firmware/MotorControl/encoder.cpp | 13 +++---------- docs/getting-started.md | 2 +- tools/odrive/tests/closed_loop_test.py | 2 +- 5 files changed, 16 insertions(+), 21 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 0ab4cd1c..3bd00a00 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -35,7 +35,7 @@ bool Controller::select_encoder(size_t encoder_num) { Axis* ax = axes[encoder_num]; if (config_.setpoints_in_cpr) { pos_estimate_src_ = &ax->encoder_.pos_cpr_; - pos_wrap_src_ = &ax->encoder_.config_.cpr; + pos_wrap_src_ = &config_.circular_setpoint_range; } else { pos_estimate_src_ = &ax->encoder_.pos_estimate_; pos_wrap_src_ = nullptr; @@ -139,10 +139,9 @@ bool Controller::update(float* torque_setpoint_output) { } // TODO also enable circular deltas for 2nd order filter, etc. - if (pos_wrap_src_) { - float cpr = *pos_wrap_src_ * 2.0f * M_PI / ((float)axis_->encoder_.config_.cpr); + if (config_.setpoints_in_cpr) { // Keep pos setpoint from drifting - input_pos_ = fmodf_pos(input_pos_, cpr); + input_pos_ = fmodf_pos(input_pos_, config_.circular_setpoint_range); } // Update inputs @@ -234,13 +233,12 @@ bool Controller::update(float* torque_setpoint_output) { return false; } - if (pos_wrap_src_) { - float cpr = *pos_wrap_src_ * 2.0f * M_PI / ((float)axis_->encoder_.config_.cpr); + if (config_.setpoints_in_cpr) { // Keep pos setpoint from drifting - pos_setpoint_ = fmodf_pos(pos_setpoint_, cpr); + pos_setpoint_ = fmodf_pos(pos_setpoint_, config_.circular_setpoint_range); // Circular delta pos_err = pos_setpoint_ - *pos_estimate_src; - pos_err = wrap_pm(pos_err, 0.5f * cpr); + pos_err = wrap_pm(pos_err, 0.5f * config_.circular_setpoint_range); } else { pos_err = pos_setpoint_ - *pos_estimate_src; } diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 6e2ef7f6..d866937c 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -30,6 +30,7 @@ public: float vel_ramp_rate = 2.0f * M_PI; // [(rad/s) / s] float torque_ramp_rate = 0.01f; // Nm / sec bool setpoints_in_cpr = false; + float circular_setpoint_range = 2.0f * M_PI; //circular space if setpoints_in_cpr is used for controller float inertia = 0.0f; // [A/(count/s^2)] float input_filter_bandwidth = 2.0f; // [1/s] float homing_speed = 2000.0f; // [counts/s] @@ -75,7 +76,7 @@ public: bool* pos_estimate_valid_src_ = nullptr; float* vel_estimate_src_ = nullptr; bool* vel_estimate_valid_src_ = nullptr; - int32_t* pos_wrap_src_ = nullptr; // enables circular position setpoints if not null. The value pointed to is the maximum position value. + float* pos_wrap_src_ = nullptr; // enables circular position setpoints if not null. The value pointed to is the maximum position value. float pos_setpoint_ = 0.0f; // [radians] float vel_setpoint_ = 0.0f; // [rad/s] @@ -95,6 +96,9 @@ public: bool anticogging_valid_ = false; + // custom setters + void set_input_pos(float value) { input_pos_ = value; input_pos_updated();} + }; #endif // __CONTROLLER_HPP diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 01ea8740..1dfa47de 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -522,17 +522,10 @@ bool Encoder::update() { snap_to_zero_vel = true; } -<<<<<<< HEAD //outputs from encoder for controller - pos_estimate_ = pos_estimate_counts_ * 2.0f * M_PI / config_.cpr; - vel_estimate_ = vel_estimate_counts_ * 2.0f * M_PI / config_.cpr; - pos_cpr_= pos_cpr_counts_ * 2.0f * M_PI / config_.cpr; -======= - //expose pos/vel estimates in radians for Controller - pos_est_rad_ = pos_estimate_ * 2.0f * M_PI / (float)config_.cpr; - vel_est_rad_ = vel_estimate_ * 2.0f * M_PI / (float)config_.cpr; - pos_cpr_rad_ = pos_cpr_ * 2.0f * M_PI / (float)config_.cpr; ->>>>>>> 4d920849a09d1e3368216f7786e9c1c9b9cb2ce3 + pos_estimate_ = pos_estimate_counts_ * 2.0f * M_PI / (float)config_.cpr; + vel_estimate_ = vel_estimate_counts_ * 2.0f * M_PI / (float)config_.cpr; + pos_cpr_= pos_cpr_counts_ * 2.0f * M_PI / (float)config_.cpr; //// run encoder count interpolation int32_t corrected_enc = count_in_cpr_ - config_.offset; diff --git a/docs/getting-started.md b/docs/getting-started.md index 913f2140..bdfe8d85 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -334,7 +334,7 @@ In the regular position mode, the `input_pos` would grow to a very large value a In this mode, the controller will try to track the position within only one turn of the motor. Specifically, `input_pos` is expected in the range `[0, 2*Pi]`. If the `input_pos` is incremented to outside this range (say via step/dir input), it is automatically wrapped around into the correct value. Note that in this mode `encoder.pos_cpr_` is used for feedback instead of `encoder.pos_estimate`. -If you try to increment the axis with a large step in one go that exceeds `Pi` steps, the motor will go to the same angle around the wrong way. This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a virtual CPR for your encoder that is an integer times larger than your encoder's actual CPR. Set `encoder.config.cpr = N * your_enc_cpr`, where N is some integer. Choose N to give you an appropriate circular space for your application. `encoder.config.cpr` is automatically converted to radians internally. +If you try to increment the axis with a large step in one go that exceeds `Pi` steps, the motor will go to the same angle around the wrong way. This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a virtual CPR for your encoder that is an integer times larger than your encoder's actual CPR. Set `encoder.config.cpr = N * 2.0 * Pi`, where N is some integer. Choose N to give you an appropriate circular space for your application. ### Velocity control Set `axis.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL`.
    diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index 2bcfa9cf..ba452135 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -175,7 +175,7 @@ class TestRegenProtection(TestClosedLoopControlBase): with self.prepare(axis_ctx, motor_ctx, enc_ctx, logger): nominal_rps = 15.0 nominal_vel = 2.0 * pi * nominal_rps - max_current = 15.0 + max_current = 30.0 # Accept a bit of noise on Ibus axis_ctx.parent.handle.config.dc_max_negative_current = -0.5 From bdb935ee99e2eb372c7419b75f9604e07d7fa838 Mon Sep 17 00:00:00 2001 From: Stephen Mounioloux Date: Mon, 6 Jul 2020 12:39:37 -0700 Subject: [PATCH 513/549] Added pos_abs_ latch in encoder::update() --- Firmware/MotorControl/encoder.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index d1545fc1..52775826 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -434,6 +434,7 @@ void Encoder::abs_spi_cs_pin_init(){ bool Encoder::update() { // update internal encoder state. int32_t delta_enc = 0; + int32_t pos_abs_latched = pos_abs_; //LATCH switch (mode_) { case MODE_INCREMENTAL: { @@ -483,7 +484,7 @@ bool Encoder::update() { } abs_spi_pos_updated_ = false; - delta_enc = pos_abs_ - count_in_cpr_; + delta_enc = pos_abs_latched - count_in_cpr_; //LATCH delta_enc = mod(delta_enc, config_.cpr); if (delta_enc > config_.cpr/2) { delta_enc -= config_.cpr; @@ -501,7 +502,7 @@ bool Encoder::update() { count_in_cpr_ = mod(count_in_cpr_, config_.cpr); if(mode_ & MODE_FLAG_ABS) - count_in_cpr_ = pos_abs_; + count_in_cpr_ = pos_abs_latched; //// run pll (for now pll is in units of encoder counts) // Predict current pos From d54384c4c8505d26f7897c7e0d10d4d1b983de72 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 14 Jul 2020 16:59:13 +0200 Subject: [PATCH 514/549] fix TestRegenProtection (again) --- tools/odrive/tests/closed_loop_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index a06f75e4..908a4b98 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -168,6 +168,9 @@ class TestRegenProtection(TestClosedLoopControlBase): """ Tries to brake with a disabled brake resistor. This should result in a low level error disabling all power outputs. + + Note: If this test fails then try to run it at a DC voltage of 24V. + Ibus seems to be more noisy/sensitive at lower DC voltages. """ def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): @@ -177,7 +180,7 @@ class TestRegenProtection(TestClosedLoopControlBase): max_current = 15.0 # Accept a bit of noise on Ibus - axis_ctx.parent.handle.config.dc_max_negative_current = -0.5 + axis_ctx.parent.handle.config.dc_max_negative_current = -0.2 logger.debug(f'Brake control test from {nominal_rps} rounds/s...') From a2626889c56682539d5e648f9f8df2820d9d63b5 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Tue, 14 Jul 2020 17:22:39 -0400 Subject: [PATCH 515/549] Added pos_circular_ to Encoder for tracking position in a circular space Corrected docs as they relate to unit changes Added pos_estimate_linear and pos_estimate_circular to Controller and eliminated pointer switching logic in Controller::select_encoder() --- Firmware/MotorControl/axis.cpp | 9 +++--- Firmware/MotorControl/controller.cpp | 30 +++++++++---------- Firmware/MotorControl/controller.hpp | 44 +++++++++++++++------------- Firmware/MotorControl/encoder.cpp | 19 +++++++----- Firmware/MotorControl/encoder.hpp | 1 + Firmware/odrive-interface.yaml | 6 ++++ docs/control.md | 4 +-- docs/getting-started.md | 6 ++-- 8 files changed, 66 insertions(+), 53 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 98709de0..a331a86d 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -278,7 +278,8 @@ bool Axis::run_lockin_spin(const LockinConfig_t &lockin_config) { // Note run_sensorless_control_loop and run_closed_loop_control_loop are very similar and differ only in where we get the estimate from. bool Axis::run_sensorless_control_loop() { - controller_.pos_estimate_src_ = nullptr; + controller_.pos_estimate_linear_ = nullptr; + controller_.pos_estimate_circular_ = nullptr; controller_.pos_estimate_valid_src_ = nullptr; controller_.vel_estimate_src_ = &sensorless_estimator_.vel_estimate_; controller_.vel_estimate_valid_src_ = &sensorless_estimator_.vel_estimate_valid_; @@ -301,8 +302,8 @@ bool Axis::run_closed_loop_control_loop() { } // To avoid any transient on startup, we intialize the setpoint to be the current position - controller_.pos_setpoint_ = *controller_.pos_estimate_src_; - controller_.input_pos_ = *controller_.pos_estimate_src_; + controller_.pos_setpoint_ = *controller_.pos_estimate_linear_; + controller_.input_pos_ = *controller_.pos_estimate_linear_; // Avoid integrator windup issues controller_.vel_integrator_torque_ = 0.0f; @@ -352,7 +353,7 @@ bool Axis::run_homing() { } // To avoid any transient on startup, we intialize the setpoint to be the current position - controller_.pos_setpoint_ = *controller_.pos_estimate_src_; + controller_.pos_setpoint_ = *controller_.pos_estimate_linear_; // Avoid integrator windup issues controller_.vel_integrator_torque_ = 0.0f; diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 3bd00a00..16521399 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -33,13 +33,9 @@ void Controller::input_pos_updated() { bool Controller::select_encoder(size_t encoder_num) { if (encoder_num < AXIS_COUNT) { Axis* ax = axes[encoder_num]; - if (config_.setpoints_in_cpr) { - pos_estimate_src_ = &ax->encoder_.pos_cpr_; - pos_wrap_src_ = &config_.circular_setpoint_range; - } else { - pos_estimate_src_ = &ax->encoder_.pos_estimate_; - pos_wrap_src_ = nullptr; - } + pos_estimate_circular_ = &ax->encoder_.pos_circular_; + pos_wrap_src_ = &config_.circular_setpoint_range; + pos_estimate_linear_ = &ax->encoder_.pos_estimate_; pos_estimate_valid_src_ = &ax->encoder_.pos_estimate_valid_; vel_estimate_src_ = &ax->encoder_.vel_estimate_; vel_estimate_valid_src_ = &ax->encoder_.vel_estimate_valid_; @@ -122,8 +118,10 @@ static float limitVel(const float vel_limit, const float vel_estimate, const flo } bool Controller::update(float* torque_setpoint_output) { - float* pos_estimate_src = (pos_estimate_valid_src_ && *pos_estimate_valid_src_) - ? pos_estimate_src_ : nullptr; + float* pos_estimate_linear = (pos_estimate_valid_src_ && *pos_estimate_valid_src_) + ? pos_estimate_linear_ : nullptr; + float* pos_estimate_circular = (pos_estimate_valid_src_ && *pos_estimate_valid_src_) + ? pos_estimate_circular_ : nullptr; float* vel_estimate_src = (vel_estimate_valid_src_ && *vel_estimate_valid_src_) ? vel_estimate_src_ : nullptr; @@ -139,7 +137,7 @@ bool Controller::update(float* torque_setpoint_output) { } // TODO also enable circular deltas for 2nd order filter, etc. - if (config_.setpoints_in_cpr) { + if (config_.circular_setpoints && pos_estimate_circular) { // Keep pos setpoint from drifting input_pos_ = fmodf_pos(input_pos_, config_.circular_setpoint_range); } @@ -228,19 +226,19 @@ bool Controller::update(float* torque_setpoint_output) { float vel_des = vel_setpoint_; if (config_.control_mode >= CONTROL_MODE_POSITION_CONTROL) { float pos_err; - if (!pos_estimate_src) { + if (!pos_estimate_linear || !pos_estimate_circular) { set_error(ERROR_INVALID_ESTIMATE); return false; } - if (config_.setpoints_in_cpr) { + if (config_.circular_setpoints && pos_estimate_circular) { // Keep pos setpoint from drifting - pos_setpoint_ = fmodf_pos(pos_setpoint_, config_.circular_setpoint_range); + pos_setpoint_ = fmodf_pos(pos_setpoint_, *pos_wrap_src_); // Circular delta - pos_err = pos_setpoint_ - *pos_estimate_src; - pos_err = wrap_pm(pos_err, 0.5f * config_.circular_setpoint_range); + pos_err = pos_setpoint_ - *pos_estimate_circular; + pos_err = wrap_pm(pos_err, 0.5f * *pos_wrap_src_); } else { - pos_err = pos_setpoint_ - *pos_estimate_src; + pos_err = pos_setpoint_ - *pos_estimate_linear; } vel_des += config_.pos_gain * pos_err; diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index d866937c..3e8d5e4e 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -20,29 +20,29 @@ public: struct Config_t { ControlMode control_mode = CONTROL_MODE_POSITION_CONTROL; //see: ControlMode_t - InputMode input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t - float pos_gain = 20.0f; // [(rad/s) / rad] - float vel_gain = 0.2f / 7.7f; // [Nm/(rad/s)] + InputMode input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t + float pos_gain = 20.0f; // [(rad/s) / rad] + float vel_gain = 0.2f / 7.7f; // [Nm/(rad/s)] // float vel_gain = 0.2f / 200.0f, // [Nm/(rad/s)] - float vel_integrator_gain = 0.4f / 7.7f; // [Nm/(rad/s * s)] - float vel_limit = 4.0f * M_PI; // [rad/s] Infinity to disable. - float vel_limit_tolerance = 1.2f; // ratio to vel_lim. Infinity to disable. - float vel_ramp_rate = 2.0f * M_PI; // [(rad/s) / s] - float torque_ramp_rate = 0.01f; // Nm / sec - bool setpoints_in_cpr = false; - float circular_setpoint_range = 2.0f * M_PI; //circular space if setpoints_in_cpr is used for controller - float inertia = 0.0f; // [A/(count/s^2)] - float input_filter_bandwidth = 2.0f; // [1/s] - float homing_speed = 2000.0f; // [counts/s] + float vel_integrator_gain = 0.4f / 7.7f; // [Nm/(rad/s * s)] + float vel_limit = 4.0f * M_PI; // [rad/s] Infinity to disable. + float vel_limit_tolerance = 1.2f; // ratio to vel_lim. Infinity to disable. + float vel_ramp_rate = 2.0f * M_PI; // [(rad/s) / s] + float torque_ramp_rate = 0.01f; // Nm / sec + bool circular_setpoints = false; + float circular_setpoint_range = 2.0f * M_PI; // Circular range when circular_setpoints is true. [rad] + float inertia = 0.0f; // [A/(count/s^2)] + float input_filter_bandwidth = 2.0f; // [1/s] + float homing_speed = 2000.0f; // [counts/s] Anticogging_t anticogging; float gain_scheduling_width = 10.0f; bool enable_gain_scheduling = false; bool enable_vel_limit = true; bool enable_overspeed_error = true; - bool enable_current_mode_vel_limit = true; // enable velocity limit in current control mode (requires a valid velocity estimator) + bool enable_current_mode_vel_limit = true; // enable velocity limit in current control mode (requires a valid velocity estimator) uint8_t axis_to_mirror = -1; float mirror_ratio = 1.0f; - uint8_t load_encoder_axis = -1; // default depends on Axis number and is set in load_configuration() + uint8_t load_encoder_axis = -1; // default depends on Axis number and is set in load_configuration() // custom setters Controller* parent; @@ -72,11 +72,13 @@ public: Error error_ = ERROR_NONE; - float* pos_estimate_src_ = nullptr; + float* pos_estimate_linear_ = nullptr; + float* pos_estimate_circular_ = nullptr; bool* pos_estimate_valid_src_ = nullptr; float* vel_estimate_src_ = nullptr; bool* vel_estimate_valid_src_ = nullptr; - float* pos_wrap_src_ = nullptr; // enables circular position setpoints if not null. The value pointed to is the maximum position value. + float* pos_wrap_src_ = nullptr; + float pos_setpoint_ = 0.0f; // [radians] float vel_setpoint_ = 0.0f; // [rad/s] @@ -84,9 +86,9 @@ public: float vel_integrator_torque_ = 0.0f; // [Nm] float torque_setpoint_ = 0.0f; // [Nm] - float input_pos_ = 0.0f; // [radians] - float input_vel_ = 0.0f; // [rad/s] - float input_torque_ = 0.0f; // [Nm] + float input_pos_ = 0.0f; // [radians] + float input_vel_ = 0.0f; // [rad/s] + float input_torque_ = 0.0f; // [Nm] float input_filter_kp_ = 0.0f; float input_filter_ki_ = 0.0f; @@ -97,7 +99,7 @@ public: bool anticogging_valid_ = false; // custom setters - void set_input_pos(float value) { input_pos_ = value; input_pos_updated();} + void set_input_pos(float value) { input_pos_ = value; input_pos_updated(); } }; diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 1dfa47de..ec4abbca 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -434,6 +434,7 @@ void Encoder::abs_spi_cs_pin_init(){ bool Encoder::update() { // update internal encoder state. int32_t delta_enc = 0; + float delta_pos_cpr = 0; switch (mode_) { case MODE_INCREMENTAL: { @@ -508,24 +509,28 @@ bool Encoder::update() { pos_estimate_counts_ += current_meas_period * vel_estimate_counts_; pos_cpr_counts_ += current_meas_period * vel_estimate_counts_; // discrete phase detector - float delta_pos = (float)(shadow_count_ - (int32_t)std::floor(pos_estimate_counts_)); - float delta_pos_cpr = (float)(count_in_cpr_ - (int32_t)std::floor(pos_cpr_counts_)); - delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * (float)(config_.cpr)); + float delta_pos_counts = (float)(shadow_count_ - (int32_t)std::floor(pos_estimate_counts_)); + float delta_pos_cpr_counts = (float)(count_in_cpr_ - (int32_t)std::floor(pos_cpr_counts_)); + delta_pos_cpr_counts = wrap_pm(delta_pos_cpr_counts, 0.5f * (float)(config_.cpr)); // pll feedback - pos_estimate_counts_ += current_meas_period * pll_kp_ * delta_pos; - pos_cpr_counts_ += current_meas_period * pll_kp_ * delta_pos_cpr; + pos_estimate_counts_ += current_meas_period * pll_kp_ * delta_pos_counts; + pos_cpr_counts_ += current_meas_period * pll_kp_ * delta_pos_cpr_counts; pos_cpr_counts_ = fmodf_pos(pos_cpr_counts_, (float)(config_.cpr)); - vel_estimate_counts_ += current_meas_period * pll_ki_ * delta_pos_cpr; + vel_estimate_counts_ += current_meas_period * pll_ki_ * delta_pos_cpr_counts; bool snap_to_zero_vel = false; if (std::abs(vel_estimate_counts_) < 0.5f * current_meas_period * pll_ki_) { vel_estimate_counts_ = 0.0f; //align delta-sigma on zero to prevent jitter snap_to_zero_vel = true; } - //outputs from encoder for controller + // Outputs from Encoder for Controller + delta_pos_cpr = pos_cpr_; pos_estimate_ = pos_estimate_counts_ * 2.0f * M_PI / (float)config_.cpr; vel_estimate_ = vel_estimate_counts_ * 2.0f * M_PI / (float)config_.cpr; pos_cpr_= pos_cpr_counts_ * 2.0f * M_PI / (float)config_.cpr; + delta_pos_cpr = wrap_pm(pos_cpr_ - delta_pos_cpr, M_PI); + pos_circular_ += delta_pos_cpr; + pos_circular_ = fmodf_pos(pos_circular_, axis_->controller_.config_.circular_setpoint_range); //// run encoder count interpolation int32_t corrected_enc = count_in_cpr_ - config_.offset; diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 515af104..cef84d5a 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -87,6 +87,7 @@ public: float pos_estimate_ = 0.0f; // [rad] float vel_estimate_ = 0.0f; // [rad] float pos_cpr_ = 0.0f; // [rad] + float pos_circular_ = 0.0f; // [rad] bool pos_estimate_valid_ = false; bool vel_estimate_valid_ = false; diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 9fb4b874..c4036172 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -645,6 +645,11 @@ interfaces: torque_ramp_rate: type: float32 unit: Nm / sec + circular_setpoints: + type: bool + circular_setpoint_range: + type: float32 + doc: circular range in [rad] for position setpoints when circular_setpoints is True homing_speed: type: float32 unit: counts/s @@ -726,6 +731,7 @@ interfaces: pos_estimate_counts: readonly float32 pos_cpr: readonly float32 pos_cpr_counts: readonly float32 + pos_circular: readonly float32 hall_state: readonly uint8 vel_estimate: readonly float32 vel_estimate_counts: readonly float32 diff --git a/docs/control.md b/docs/control.md index 95f3b61d..bb052316 100644 --- a/docs/control.md +++ b/docs/control.md @@ -34,9 +34,9 @@ For more detail refer to [controller.cpp](https://github.com/madcowswe/ODrive/bl ### Controller Details: The ultimate output of the controller is the voltage applied to the gate of each FET to deliver current through each coil of the motor. The current through the motor linearly relates to the torque output of the motor. This means that the inputs to the cascaded controller are theoretically the position (angle), velocity (angle/time), and acceleration (angle/time/time) of the motor. Note that when thinking about the controller from the perpective of the physics of the motor you would expect to see the time in the Velocity and Current loops, but it is absent because the time difference between iterations is always 125 microseconds (8kHz). Because the time difference between controller loops is a constant and can simply be wrapped into the controller gains. -The output of each stage of the controller is clamped before being fed into the next stage. So after the `vel_cmd` is calculated from the position controller, the `vel_cmd` is clamped to the velocity limit. The `torque_cmd` output of the velocity controller is then clamped and fed to the motor controller. Oddly enough the controller class does not contain the motor controller, but instead the motor controller is housed in the motor class due to the complexity of the motor driver schema. +The output of each stage of the controller is clamped before being fed into the next stage. So after the `vel_cmd` is calculated from the position controller, the `vel_cmd` is clamped to the velocity limit. The `torque_cmd` output of the velocity controller is then clamped and fed to the current controller. Oddly enough the controller class does not contain the current controller, but instead the current controller is housed in the motor class due to the complexity of the motor driver schema. -The feedforward terms available when using the position or velocity control mode are meant to enable better performance when the dynamics of a system are known and the host controller can predict the motion based on the load. A perfect example of this is the use of the trajectory controller that sets the position, velocity, and current based on the desired position, velocity, and acceleration. If you take a trapezoidal velocity profile for example, you can imagine on the ramp upward the velocity will be increasing over time, while the torque is a non-zero constant. At the flat portion of the profile the velocity will be a non-zero constant, but the acceleration will be zero. This trajectory controller use case uses the cascaded controller with multiple inputs to achieve the desired motion with the best performance. +The feedforward terms available when using the position or velocity control mode are meant to enable better performance when the dynamics of a system are known and the host controller can predict the motion based on the load. A perfect example of this is the use of the trajectory controller that sets the position, velocity, and torque based on the desired position, velocity, and acceleration. If you take a trapezoidal velocity profile for example, you can imagine on the ramp upward the velocity will be increasing over time, while the torque is a non-zero constant. At the flat portion of the profile the velocity will be a non-zero constant, but the acceleration will be zero. This trajectory controller use case uses the cascaded controller with multiple inputs to achieve the desired motion with the best performance. ## Tuning Tuning the motor controller is an essential step to unlock the full potential of the ODrive. Tuning allows for the controller to quickly respond to disturbances or changes in the system (such as an external force being applied or a change in the setpoint) without becoming unstable. Correctly setting the three tuning parameters (called gains) ensures that ODrive can control your motors in the most effective way possible. The three values are: diff --git a/docs/getting-started.md b/docs/getting-started.md index bdfe8d85..2193bf5c 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -326,15 +326,15 @@ You can also execute a move with the [appropriate ascii command](ascii-protocol. ### Circular position control -To enable Circular position control, set `axis.controller.config.setpoints_in_cpr = True` +To enable Circular position control, set `axis.controller.config.circular_setpoints = True` This mode is useful for continuous incremental position movement. For example a robot rolling indefinitely, or an extruder motor or conveyor belt moving with controlled increments indefinitely. In the regular position mode, the `input_pos` would grow to a very large value and would lose precision due to floating point rounding. In this mode, the controller will try to track the position within only one turn of the motor. Specifically, `input_pos` is expected in the range `[0, 2*Pi]`. If the `input_pos` is incremented to outside this range (say via step/dir input), it is automatically wrapped around into the correct value. -Note that in this mode `encoder.pos_cpr_` is used for feedback instead of `encoder.pos_estimate`. +Note that in this mode `encoder.pos_circular` is used for feedback instead of `encoder.pos_estimate`. -If you try to increment the axis with a large step in one go that exceeds `Pi` steps, the motor will go to the same angle around the wrong way. This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a virtual CPR for your encoder that is an integer times larger than your encoder's actual CPR. Set `encoder.config.cpr = N * 2.0 * Pi`, where N is some integer. Choose N to give you an appropriate circular space for your application. +If you try to increment the axis with a large step in one go that exceeds `Pi` steps, the motor will go to the same angle around the wrong way. This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a larger circular range that is an integer multiple of `2*Pi`. Set `controller.config.circular_setpoints_range = N * 2.0 * Pi`, where N is some integer. Choose N to give you an appropriate circular space for your application. ### Velocity control Set `axis.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL`.
    From 256a76fed2f4b591112c93bd6304737e2c528ce6 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Tue, 14 Jul 2020 18:23:30 -0400 Subject: [PATCH 516/549] Changed old-school variable reuse to modern style declarations --- Firmware/MotorControl/encoder.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index ec4abbca..95f436ba 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -434,7 +434,6 @@ void Encoder::abs_spi_cs_pin_init(){ bool Encoder::update() { // update internal encoder state. int32_t delta_enc = 0; - float delta_pos_cpr = 0; switch (mode_) { case MODE_INCREMENTAL: { @@ -524,11 +523,11 @@ bool Encoder::update() { } // Outputs from Encoder for Controller - delta_pos_cpr = pos_cpr_; + float pos_cpr_last = pos_cpr_; pos_estimate_ = pos_estimate_counts_ * 2.0f * M_PI / (float)config_.cpr; vel_estimate_ = vel_estimate_counts_ * 2.0f * M_PI / (float)config_.cpr; pos_cpr_= pos_cpr_counts_ * 2.0f * M_PI / (float)config_.cpr; - delta_pos_cpr = wrap_pm(pos_cpr_ - delta_pos_cpr, M_PI); + float delta_pos_cpr = wrap_pm(pos_cpr_ - pos_cpr_last, M_PI); pos_circular_ += delta_pos_cpr; pos_circular_ = fmodf_pos(pos_circular_, axis_->controller_.config_.circular_setpoint_range); From e1b1641d70e096dd3d48613eb011e58885cf9dd7 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Tue, 14 Jul 2020 20:02:59 -0400 Subject: [PATCH 517/549] Changed from radians internally to units of "turns" where a full rotation is 1.0 turn Changed documentation to reflect the unit change --- Firmware/MotorControl/axis.cpp | 2 +- Firmware/MotorControl/axis.hpp | 2 +- Firmware/MotorControl/controller.cpp | 4 ++-- Firmware/MotorControl/controller.hpp | 24 ++++++++++++------------ Firmware/MotorControl/encoder.cpp | 8 ++++---- Firmware/MotorControl/encoder.hpp | 10 +++++----- Firmware/MotorControl/trapTraj.hpp | 6 +++--- Firmware/odrive-interface.yaml | 14 +++++++------- docs/commands.md | 4 ++-- docs/control.md | 6 +++--- docs/encoders.md | 2 +- docs/getting-started.md | 12 ++++++------ docs/interfaces.md | 2 +- tools/odrive/tests/can_test.py | 2 +- tools/odrive/tests/closed_loop_test.py | 24 ++++++++++++------------ tools/odrive/tests/step_dir_test.py | 14 +++++++------- 16 files changed, 68 insertions(+), 68 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index a331a86d..7fcff0a3 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -109,7 +109,7 @@ void Axis::step_cb() { if (step_dir_active_) { GPIO_PinState dir_pin = HAL_GPIO_ReadPin(dir_port_, dir_pin_); float dir = (dir_pin == GPIO_PIN_SET) ? 1.0f : -1.0f; - controller_.input_pos_ += dir * config_.counts_per_step; + controller_.input_pos_ += dir * config_.turns_per_step; controller_.input_pos_updated(); } }; diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 39870ddb..0b97acae 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -39,7 +39,7 @@ public: //encoder_.config_.cpr && - std::abs(vel_estimate) < config_.anticogging.calib_vel_threshold * (2.0f * M_PI) / (float)axis_->encoder_.config_.cpr) { + if (std::abs(pos_err) <= config_.anticogging.calib_pos_threshold / (float)axis_->encoder_.config_.cpr && + std::abs(vel_estimate) < config_.anticogging.calib_vel_threshold / (float)axis_->encoder_.config_.cpr) { config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_torque_; } if (config_.anticogging.index < 3600) { diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 3e8d5e4e..e7eebc0a 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -21,19 +21,19 @@ public: struct Config_t { ControlMode control_mode = CONTROL_MODE_POSITION_CONTROL; //see: ControlMode_t InputMode input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t - float pos_gain = 20.0f; // [(rad/s) / rad] - float vel_gain = 0.2f / 7.7f; // [Nm/(rad/s)] + float pos_gain = 20.0f; // [(turn/s) / turn] + float vel_gain = 1.0f / 6.0f; // [Nm/(turn/s)] // float vel_gain = 0.2f / 200.0f, // [Nm/(rad/s)] - float vel_integrator_gain = 0.4f / 7.7f; // [Nm/(rad/s * s)] - float vel_limit = 4.0f * M_PI; // [rad/s] Infinity to disable. - float vel_limit_tolerance = 1.2f; // ratio to vel_lim. Infinity to disable. - float vel_ramp_rate = 2.0f * M_PI; // [(rad/s) / s] + float vel_integrator_gain = 2.0f / 6.0f; // [Nm/(turn/s * s)] + float vel_limit = 2.0f; // [turn/s] Infinity to disable. + float vel_limit_tolerance = 1.2f; // ratio to vel_lim. Infinity to disable. + float vel_ramp_rate = 1.0f; // [(turn/s) / s] float torque_ramp_rate = 0.01f; // Nm / sec bool circular_setpoints = false; - float circular_setpoint_range = 2.0f * M_PI; // Circular range when circular_setpoints is true. [rad] + float circular_setpoint_range = 1.0f; // Circular range when circular_setpoints is true. [turn] float inertia = 0.0f; // [A/(count/s^2)] float input_filter_bandwidth = 2.0f; // [1/s] - float homing_speed = 2000.0f; // [counts/s] + float homing_speed = 0.25f; // [turn/s] Anticogging_t anticogging; float gain_scheduling_width = 10.0f; bool enable_gain_scheduling = false; @@ -80,14 +80,14 @@ public: float* pos_wrap_src_ = nullptr; - float pos_setpoint_ = 0.0f; // [radians] - float vel_setpoint_ = 0.0f; // [rad/s] + float pos_setpoint_ = 0.0f; // [turns] + float vel_setpoint_ = 0.0f; // [turn/s] // float vel_setpoint = 800.0f; float vel_integrator_torque_ = 0.0f; // [Nm] float torque_setpoint_ = 0.0f; // [Nm] - float input_pos_ = 0.0f; // [radians] - float input_vel_ = 0.0f; // [rad/s] + float input_pos_ = 0.0f; // [turns] + float input_vel_ = 0.0f; // [turn/s] float input_torque_ = 0.0f; // [Nm] float input_filter_kp_ = 0.0f; float input_filter_ki_ = 0.0f; diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 95f436ba..7760b53c 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -524,10 +524,10 @@ bool Encoder::update() { // Outputs from Encoder for Controller float pos_cpr_last = pos_cpr_; - pos_estimate_ = pos_estimate_counts_ * 2.0f * M_PI / (float)config_.cpr; - vel_estimate_ = vel_estimate_counts_ * 2.0f * M_PI / (float)config_.cpr; - pos_cpr_= pos_cpr_counts_ * 2.0f * M_PI / (float)config_.cpr; - float delta_pos_cpr = wrap_pm(pos_cpr_ - pos_cpr_last, M_PI); + pos_estimate_ = pos_estimate_counts_ / (float)config_.cpr; + vel_estimate_ = vel_estimate_counts_ / (float)config_.cpr; + pos_cpr_= pos_cpr_counts_ / (float)config_.cpr; + float delta_pos_cpr = wrap_pm(pos_cpr_ - pos_cpr_last, 0.5f); pos_circular_ += delta_pos_cpr; pos_circular_ = fmodf_pos(pos_circular_, axis_->controller_.config_.circular_setpoint_range); diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index cef84d5a..15be6576 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -84,10 +84,10 @@ public: int32_t pos_abs_ = 0; float spi_error_rate_ = 0.0f; - float pos_estimate_ = 0.0f; // [rad] - float vel_estimate_ = 0.0f; // [rad] - float pos_cpr_ = 0.0f; // [rad] - float pos_circular_ = 0.0f; // [rad] + float pos_estimate_ = 0.0f; // [turn] + float vel_estimate_ = 0.0f; // [turn/s] + float pos_cpr_ = 0.0f; // [turn] + float pos_circular_ = 0.0f; // [turn] bool pos_estimate_valid_ = false; bool vel_estimate_valid_ = false; @@ -112,7 +112,7 @@ public: uint32_t abs_spi_cr2; constexpr float getCoggingRatio(){ - return 2.0f * M_PI / 3600.0f; + return 1.0f / 3600.0f; } }; diff --git a/Firmware/MotorControl/trapTraj.hpp b/Firmware/MotorControl/trapTraj.hpp index 335dfafc..82a1e060 100644 --- a/Firmware/MotorControl/trapTraj.hpp +++ b/Firmware/MotorControl/trapTraj.hpp @@ -4,9 +4,9 @@ class TrapezoidalTrajectory { public: struct Config_t { - float vel_limit = 4.0f * M_PI; // [rad/s] - float accel_limit = M_PI; // [rad/s^2] - float decel_limit = M_PI; // [rad/s^2] + float vel_limit = 2.0f; // [turn/s] + float accel_limit = 0.5f; // [turn/s^2] + float decel_limit = 0.5f; // [turn/s^2] }; struct Step_t { diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index c4036172..489e6b5f 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -330,7 +330,7 @@ interfaces: This is ignored if enable_step_dir is false. This setting only takes effect on a state transition into idle or out of closed loop control. - counts_per_step: float32 + turns_per_step: float32 watchdog_timeout: type: float32 unit: s @@ -601,11 +601,11 @@ interfaces: InvalidEstimate: input_pos: type: float32 - unit: rad + unit: turn c_setter: set_input_pos input_vel: type: float32 - unit: rad/s + unit: turn/s input_torque: float32 pos_setpoint: readonly float32 vel_setpoint: readonly float32 @@ -627,16 +627,16 @@ interfaces: input_mode: InputMode pos_gain: type: float32 - unit: (rad/s) / rad + unit: (turn/s) / turn vel_gain: type: float32 - unit: 'Nm/(rad/s)' + unit: 'Nm/(turn/s)' vel_integrator_gain: type: float32 - unit: Nm/(rad/s * s) + unit: Nm/(turn/s * s) vel_limit: type: float32 - unit: rad/s + unit: turn/s doc: Infinity to disable. vel_limit_tolerance: type: float32 diff --git a/docs/commands.md b/docs/commands.md index ff11356f..ebb47322 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -55,8 +55,8 @@ Possible values are listed [here](api/odrive.axis.controller.inputmode). ## System monitoring commands ### Encoder position and velocity -* View encoder position with `.encoder.pos_estimate` [rad] or `.encoder.pos_est_counts` [counts] -* View rotational velocity with `.encoder.vel_estimate` [rad/s] or `.encoder.vel_est_counts` [count/s] +* View encoder position with `.encoder.pos_estimate` [turns] or `.encoder.pos_est_counts` [counts] +* View rotational velocity with `.encoder.vel_estimate` [turn/s] or `.encoder.vel_est_counts` [count/s] ### Motor current and torque estimation * View the commanded motor current with `.motor.current_control.Iq_setpoint` [A] diff --git a/docs/control.md b/docs/control.md index bb052316..0f00d05f 100644 --- a/docs/control.md +++ b/docs/control.md @@ -40,9 +40,9 @@ The feedforward terms available when using the position or velocity control mode ## Tuning Tuning the motor controller is an essential step to unlock the full potential of the ODrive. Tuning allows for the controller to quickly respond to disturbances or changes in the system (such as an external force being applied or a change in the setpoint) without becoming unstable. Correctly setting the three tuning parameters (called gains) ensures that ODrive can control your motors in the most effective way possible. The three values are: -* `.controller.config.pos_gain = 20.0` [(rad/s) / rad] -* `.controller.config.vel_gain = 0.025 ` [Nm/(rad/s)] -* `.controller.config.vel_integrator_gain = 0.05` [Nm/((rad/s) * s)] +* `.controller.config.pos_gain = 20.0` [(turn/s) / turn] +* `.controller.config.vel_gain = 0.16 ` [Nm/(turn/s)] +* `.controller.config.vel_integrator_gain = 0.32` [Nm/((turn/s) * s)] An upcoming feature will enable automatic tuning. Until then, here is a rough tuning procedure: * Set vel_integrator_gain gain to 0 diff --git a/docs/encoders.md b/docs/encoders.md index 8398f2fe..dfe27fd8 100644 --- a/docs/encoders.md +++ b/docs/encoders.md @@ -62,7 +62,7 @@ The following are examples of values that MAY impact the success of calibration. * `.encoder.config.calib_range = 0.05` helps to relax the accuracy of encoder counts during calibration * `.motor.config.calibration_current = 10.0` _sometimes_ needed if this is a large motor * `.motor.config.resistance_calib_max_voltage = 12.0` _sometimes_ needed depending on motor -* `.controller.config.vel_limit = 30` [rad/s] low values result in the spinning motor stopping abruptly during calibration +* `.controller.config.vel_limit = 5` [turn/s] low values result in the spinning motor stopping abruptly during calibration Lots of other values can get you. It's a process. Thankfully there are a lot of good people that will help you debug calibration problems. diff --git a/docs/getting-started.md b/docs/getting-started.md index 2193bf5c..ed755a8c 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -183,7 +183,7 @@ The largest effect on modulation magnitude is speed. There are other smaller fac **Velocity limit**
    -`odrv0.axis0.controller.config.vel_limit` [rad/s]. +`odrv0.axis0.controller.config.vel_limit` [turn/s]. The motor will be limited to this speed. Again the default value is quite slow. **Calibration current**
    @@ -331,20 +331,20 @@ To enable Circular position control, set `axis.controller.config.circular_setpoi This mode is useful for continuous incremental position movement. For example a robot rolling indefinitely, or an extruder motor or conveyor belt moving with controlled increments indefinitely. In the regular position mode, the `input_pos` would grow to a very large value and would lose precision due to floating point rounding. -In this mode, the controller will try to track the position within only one turn of the motor. Specifically, `input_pos` is expected in the range `[0, 2*Pi]`. If the `input_pos` is incremented to outside this range (say via step/dir input), it is automatically wrapped around into the correct value. +In this mode, the controller will try to track the position within only one turn of the motor. Specifically, `input_pos` is expected in the range `[0, 1]`. If the `input_pos` is incremented to outside this range (say via step/dir input), it is automatically wrapped around into the correct value. Note that in this mode `encoder.pos_circular` is used for feedback instead of `encoder.pos_estimate`. -If you try to increment the axis with a large step in one go that exceeds `Pi` steps, the motor will go to the same angle around the wrong way. This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a larger circular range that is an integer multiple of `2*Pi`. Set `controller.config.circular_setpoints_range = N * 2.0 * Pi`, where N is some integer. Choose N to give you an appropriate circular space for your application. +If you try to increment the axis with a large step in one go that exceeds `1` turn, the motor will go to the same angle around the wrong way. This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a larger circular range. Set `controller.config.circular_setpoints_range = N`. Choose N to give you an appropriate circular space for your application. ### Velocity control Set `axis.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL`.
    -You can now control the velocity with `axis.controller.input_vel = 3` [rad/s]. +You can now control the velocity with `axis.controller.input_vel = 1` [turn/s]. ### Ramped velocity control Set `axis.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL`.
    -Set the velocity ramp rate (acceleration): `axis.controller.config.vel_ramp_rate = 1.5` [rad/s^2]
    +Set the velocity ramp rate (acceleration): `axis.controller.config.vel_ramp_rate = 0.5` [turn/s^2]
    Activate the ramped velocity mode: `axis.controller.config.input_mode = INPUT_MODE_VEL_RAMP`.
    -You can now control the velocity with `axis.controller.input_vel = 3` [rad/s]. +You can now control the velocity with `axis.controller.input_vel = 1` [turn/s]. ### Torque control Set `axis.controller.config.control_mode = CONTROL_MODE_TORQUE_CONTROL`.
    diff --git a/docs/interfaces.md b/docs/interfaces.md index b8e76ad6..225cd7c4 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -102,7 +102,7 @@ Pinout: To enable step/dir mode for the GPIO, set `.config.enable_step_dir` to true for each axis that you wish to use this on. Axis 0 step/dir pins conflicts with UART, and the UART takes priority. So to be able to use step/dir on Axis 0, you must also set `odrv0.config.enable_uart = False`. See the [pin function priorities](#pin-function-priorities) for more detail. Don't forget to save configuration and reboot. -There is also a config variable called `.config.counts_per_step`, which specifies how many encoder counts a "step" corresponds to. It can be any floating point value. +There is also a config variable called `.config.turns_per_step`, which specifies how many encoder counts a "step" corresponds to. It can be any floating point value. The maximum step rate is pending tests, but it should handle at least 50kHz. If you want to test it, please be aware that the failure mode on too high step rates is expected to be that the motors shuts down and coasts. Please be aware that there is no enable line right now, and the step/direction interface is enabled by default, and remains active as long as the ODrive is in position control mode. To get the ODrive to go into position control mode at bootup, see how to configure the [startup procedure](commands.md#startup-procedure). diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index 4e3d3ad8..13d337ec 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -142,7 +142,7 @@ class TestSimpleCAN(): test_assert_eq(axis.error, AXIS_ERROR_NONE) axis.encoder.set_linear_count(123) - test_assert_eq(my_req('get_encoder_estimates')['encoder_pos_estimate'], 123.0 * 2.0 * math.pi / axis.encoder.config.cpr, accuracy=0.01) + test_assert_eq(my_req('get_encoder_estimates')['encoder_pos_estimate'], 123.0 / axis.encoder.config.cpr, accuracy=0.01) test_assert_eq(my_req('get_encoder_count')['encoder_shadow_count'], 123.0, accuracy=0.01) my_cmd('clear_errors') diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py index 9f512e61..263c8181 100644 --- a/tools/odrive/tests/closed_loop_test.py +++ b/tools/odrive/tests/closed_loop_test.py @@ -77,7 +77,7 @@ class TestClosedLoopControl(TestClosedLoopControlBase): def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): with self.prepare(axis_ctx, motor_ctx, enc_ctx, logger): nominal_rps = 1.0 - nominal_vel = 2.0 * pi * nominal_rps + nominal_vel = nominal_rps logger.debug(f'Testing closed loop velocity control at {nominal_rps} rounds/s...') axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL @@ -109,13 +109,13 @@ class TestClosedLoopControl(TestClosedLoopControlBase): axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_POSITION_CONTROL axis_ctx.handle.controller.input_pos = 0 - axis_ctx.handle.controller.config.vel_limit = 2.0 * pi * 5.0 # max 5 rps + axis_ctx.handle.controller.config.vel_limit = 5.0 # max 5 rps axis_ctx.handle.encoder.set_linear_count(0) request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) # Test small position changes - test_pos = 5000 / float(enc_ctx.yaml['cpr']) * 2.0 * pi + test_pos = 5000 / float(enc_ctx.yaml['cpr']) axis_ctx.handle.controller.input_pos = test_pos time.sleep(0.3) test_assert_no_error(axis_ctx) @@ -128,7 +128,7 @@ class TestClosedLoopControl(TestClosedLoopControlBase): axis_ctx.handle.controller.input_pos = 0 time.sleep(0.3) - nominal_vel = 2 * pi * 5.0 + nominal_vel = 5.0 axis_ctx.handle.controller.input_pos = nominal_vel * 2.0 # 10 turns (takes 2 seconds) # Test large position change with bounded velocity @@ -177,7 +177,7 @@ class TestRegenProtection(TestClosedLoopControlBase): def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): with self.prepare(axis_ctx, motor_ctx, enc_ctx, logger): nominal_rps = 15.0 - nominal_vel = 2.0 * pi * nominal_rps + nominal_vel = nominal_rps max_current = 30.0 # Accept a bit of noise on Ibus @@ -185,7 +185,7 @@ class TestRegenProtection(TestClosedLoopControlBase): logger.debug(f'Brake control test from {nominal_rps} rounds/s...') - axis_ctx.handle.controller.config.vel_limit = 2.0 * pi * 25.0 # max 15 rps + axis_ctx.handle.controller.config.vel_limit = 25.0 # max 15 rps axis_ctx.handle.motor.config.current_lim = max_current axis_ctx.handle.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL axis_ctx.handle.controller.config.input_mode = INPUT_MODE_PASSTHROUGH @@ -224,7 +224,7 @@ class TestVelLimitInTorqueControl(TestClosedLoopControlBase): def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): with self.prepare(axis_ctx, motor_ctx, enc_ctx, logger): max_rps = 20.0 - max_vel = 2.0 * pi * max_rps + max_vel = max_rps absolute_max_vel = max_vel * 1.2 max_current = 30.0 torque_constant = 0.0305 #correct for 5065 motor @@ -270,7 +270,7 @@ class TestVelLimitInTorqueControl(TestClosedLoopControlBase): # Shrink the operating envelope while motor is moving faster than the envelope allows max_rps = 5.0 - max_vel = 2.0 * pi * max_rps + max_vel = max_rps axis_ctx.handle.controller.config.vel_limit = max_vel # Move the system around its operating envelope @@ -284,7 +284,7 @@ class TestVelLimitInTorqueControl(TestClosedLoopControlBase): dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)]) # Try the shrink maneuver again at positive velocity - axis_ctx.handle.controller.config.vel_limit = 20.0 * 2.0 * pi + axis_ctx.handle.controller.config.vel_limit = 20.0 axis_ctx.handle.controller.input_torque = 4.0 * torque_constant time.sleep(0.5) axis_ctx.handle.controller.config.vel_limit = max_vel @@ -306,13 +306,13 @@ class TestTorqueLimit(TestClosedLoopControlBase): def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): with self.prepare(axis_ctx, motor_ctx, enc_ctx, logger): max_rps = 15.0 - max_vel = max_rps * 2.0 * pi + max_vel = max_rps max_current = 30.0 max_torque = 0.1 # must be less than max_current * torque_constant. torque_constant = axis_ctx.handle.motor.config.torque_constant - test_pos = 5 * 2.0 * pi - test_vel = 10 * 2.0 * pi + test_pos = 5 + test_vel = 10 test_torque = 0.5 axis_ctx.handle.controller.config.vel_limit = max_vel diff --git a/tools/odrive/tests/step_dir_test.py b/tools/odrive/tests/step_dir_test.py index 55bf225d..948a5eca 100644 --- a/tools/odrive/tests/step_dir_test.py +++ b/tools/odrive/tests/step_dir_test.py @@ -53,14 +53,14 @@ class TestStepDir(): ref = axis.handle.controller.input_pos - axis.handle.config.counts_per_step = counts_per_step = 10 + axis.handle.config.turns_per_step = turns_per_step = 10 # On the RPi 4 a ~5kHz GPIO signal can be generated from Python for i in range(100): step_gpio.write(True) step_gpio.write(False) - test_assert_eq(axis.handle.controller.input_pos, ref + (i + 1) * counts_per_step, range = 0.4 * counts_per_step) + test_assert_eq(axis.handle.controller.input_pos, ref + (i + 1) * turns_per_step, range = 0.4 * turns_per_step) ref = axis.handle.controller.input_pos dir_gpio.write(False) @@ -68,24 +68,24 @@ class TestStepDir(): for i in range(100): step_gpio.write(True) step_gpio.write(False) - test_assert_eq(axis.handle.controller.input_pos, ref - (i + 1) * counts_per_step, range = 0.4 * counts_per_step) + test_assert_eq(axis.handle.controller.input_pos, ref - (i + 1) * turns_per_step, range = 0.4 * turns_per_step) ref = axis.handle.controller.input_pos dir_gpio.write(True) - axis.handle.config.counts_per_step = counts_per_step = 1 + axis.handle.config.turns_per_step = turns_per_step = 1 for i in range(100): step_gpio.write(True) step_gpio.write(False) - test_assert_eq(axis.handle.controller.input_pos, ref + (i + 1) * counts_per_step, range = 0.4 * counts_per_step) + test_assert_eq(axis.handle.controller.input_pos, ref + (i + 1) * turns_per_step, range = 0.4 * turns_per_step) ref = axis.handle.controller.input_pos - axis.handle.config.counts_per_step = counts_per_step = -1 + axis.handle.config.turns_per_step = turns_per_step = -1 for i in range(100): step_gpio.write(True) step_gpio.write(False) - test_assert_eq(axis.handle.controller.input_pos, ref + (i + 1) * counts_per_step, range = 0.4 * abs(counts_per_step)) + test_assert_eq(axis.handle.controller.input_pos, ref + (i + 1) * turns_per_step, range = 0.4 * abs(turns_per_step)) if __name__ == '__main__': From 219c818fddf1f725a170caabc0777e5dbe500e67 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 16 Jul 2020 19:27:35 +0200 Subject: [PATCH 518/549] turn version.h into version.c This speeds up the build process in many cases because this file is refreshed on every build and by making it a code file much less off the build graph depends on it. --- Firmware/MotorControl/odrive_main.h | 16 +++++++++++----- Firmware/Tupfile.lua | 5 +++-- Firmware/build.lua | 2 +- Firmware/communication/ascii_protocol.cpp | 1 - Firmware/communication/communication.cpp | 2 -- tools/odrive/version.py | 10 +++++----- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index b112a19d..9171778a 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -210,7 +210,13 @@ enum TimingLog_t { #include #include -#include "autogen/version.h" +// Defined in autogen/version.c based on git-derived version numbers +extern "C" { +extern const unsigned char fw_version_major_; +extern const unsigned char fw_version_minor_; +extern const unsigned char fw_version_revision_; +extern const unsigned char fw_version_unreleased_; +} // general system functions defined in main.cpp @@ -266,10 +272,10 @@ public: #endif // the corresponding macros are defined in the autogenerated version.h - const uint8_t fw_version_major_ = FW_VERSION_MAJOR; - const uint8_t fw_version_minor_ = FW_VERSION_MINOR; - const uint8_t fw_version_revision_ = FW_VERSION_REVISION; - const uint8_t fw_version_unreleased_ = FW_VERSION_UNRELEASED; // 0 for official releases, 1 otherwise + const uint8_t fw_version_major_ = ::fw_version_major_; + const uint8_t fw_version_minor_ = ::fw_version_minor_; + const uint8_t fw_version_revision_ = ::fw_version_revision_; + const uint8_t fw_version_unreleased_ = ::fw_version_unreleased_; // 0 for official releases, 1 otherwise bool& brake_resistor_armed_ = ::brake_resistor_armed; // TODO: make this the actual variable bool& brake_resistor_saturated_ = ::brake_resistor_saturated; // TODO: make this the actual variable diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index b67ad651..6acbd87e 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -31,7 +31,7 @@ tup.frule{inputs={'fibre/cpp/type_info_template.j2'}, command=python_command..' tup.frule{ command=python_command..' ../tools/odrive/version.py --output %o', - outputs={'autogen/version.h'} + outputs={'autogen/version.c'} } @@ -207,7 +207,8 @@ build{ 'communication/interface_can.cpp', 'communication/interface_i2c.cpp', 'fibre/cpp/protocol.cpp', - 'FreeRTOS-openocd.c' + 'FreeRTOS-openocd.c', + 'autogen/version.c' }, includes={ 'Drivers/DRV8301', diff --git a/Firmware/build.lua b/Firmware/build.lua index e2661f0c..e6d93e41 100644 --- a/Firmware/build.lua +++ b/Firmware/build.lua @@ -80,7 +80,7 @@ function GCCToolchain(prefix, builddir, compiler_flags, linker_flags) else extra_outputs = {} end - extra_inputs = {'autogen/version.h', 'autogen/interfaces.hpp', 'autogen/function_stubs.hpp', 'autogen/endpoints.hpp', 'autogen/type_info.hpp'} -- TODO: fix hack + extra_inputs = {'autogen/interfaces.hpp', 'autogen/function_stubs.hpp', 'autogen/endpoints.hpp', 'autogen/type_info.hpp'} -- TODO: fix hack tup.frule{ inputs= { src, extra_inputs=extra_inputs }, command=compiler..' -c %f '.. diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 9a5eb90d..c8cc3299 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -8,7 +8,6 @@ /* Includes ------------------------------------------------------------------*/ #include "odrive_main.h" -#include "../autogen/version.h" // autogenerated based on Git state #include "communication.h" #include "ascii_protocol.hpp" #include diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 794debaf..c9eaa589 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -13,8 +13,6 @@ #include "utils.hpp" #include "gpio_utils.hpp" -#include "../autogen/version.h" // autogenerated based on Git state - #include #include //#include diff --git a/tools/odrive/version.py b/tools/odrive/version.py index 3b88a255..45f81777 100644 --- a/tools/odrive/version.py +++ b/tools/odrive/version.py @@ -74,11 +74,11 @@ if __name__ == '__main__': print('Firmware version {}.{}.{}{} ({})'.format( major, minor, revision, '-dev' if unreleased else '', git_name)) - args.output.write('#define FW_VERSION "{}"\n'.format(git_name)) - args.output.write('#define FW_VERSION_MAJOR {}\n'.format(major)) - args.output.write('#define FW_VERSION_MINOR {}\n'.format(minor)) - args.output.write('#define FW_VERSION_REVISION {}\n'.format(revision)) - args.output.write('#define FW_VERSION_UNRELEASED {}\n'.format(1 if unreleased else 0)) + #args.output.write('const unsigned char fw_version = "{}"\n'.format(git_name)) + args.output.write('const unsigned char fw_version_major_ = {};\n'.format(major)) + args.output.write('const unsigned char fw_version_minor_ = {};\n'.format(minor)) + args.output.write('const unsigned char fw_version_revision_ = {};\n'.format(revision)) + args.output.write('const unsigned char fw_version_unreleased_ = {};\n'.format(1 if unreleased else 0)) def setup_udev_rules(logger): if platform.system() != 'Linux': From 06989366a5d5b27aac283eb187f9230740f21132 Mon Sep 17 00:00:00 2001 From: Stephen Mounioloux Date: Mon, 6 Jul 2020 12:39:37 -0700 Subject: [PATCH 519/549] Added pos_abs_ latch in encoder::update() --- Firmware/MotorControl/encoder.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 8f2c7eb8..65bdd662 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -434,6 +434,7 @@ void Encoder::abs_spi_cs_pin_init(){ bool Encoder::update() { // update internal encoder state. int32_t delta_enc = 0; + int32_t pos_abs_latched = pos_abs_; //LATCH switch (mode_) { case MODE_INCREMENTAL: { @@ -483,7 +484,7 @@ bool Encoder::update() { } abs_spi_pos_updated_ = false; - delta_enc = pos_abs_ - count_in_cpr_; + delta_enc = pos_abs_latched - count_in_cpr_; //LATCH delta_enc = mod(delta_enc, config_.cpr); if (delta_enc > config_.cpr/2) { delta_enc -= config_.cpr; @@ -501,7 +502,7 @@ bool Encoder::update() { count_in_cpr_ = mod(count_in_cpr_, config_.cpr); if(mode_ & MODE_FLAG_ABS) - count_in_cpr_ = pos_abs_; + count_in_cpr_ = pos_abs_latched; //// run pll (for now pll is in units of encoder counts) // Predict current pos From a4e03ffd077eae11f1d3cdc62d3c20ef38f9fb3f Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 17 Jul 2020 13:25:33 +0200 Subject: [PATCH 520/549] disable Travis, enable Github Workflow push trigger --- .github/workflows/compile.yaml | 6 +-- .travis.yml | 74 ---------------------------------- 2 files changed, 3 insertions(+), 77 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/workflows/compile.yaml b/.github/workflows/compile.yaml index eefd6d22..a433132f 100644 --- a/.github/workflows/compile.yaml +++ b/.github/workflows/compile.yaml @@ -4,9 +4,9 @@ on: pull_request: branches: [master, devel] tags: ['fw-v*'] - #push: - # branches: [master, devel] - # tags: ['fw-v*'] + push: + branches: [master, devel] + tags: ['fw-v*'] jobs: compile: diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2e86de28..00000000 --- a/.travis.yml +++ /dev/null @@ -1,74 +0,0 @@ -# adapted from https://github.com/andysworkshop/stm32plus/blob/master/.travis.yml - -branches: - only: - - master - - devel - - /^fw-v/ - -language: c -sudo: false - -addons: - apt: - packages: - - libc6-i386 - - python3 - - python3-yaml - - python3-jinja2 - - python3-jsonschema - -cache: - directories: - - "$HOME/dl" - -install: -# - export GCC_DIR=$HOME/dl/gcc-arm-none-eabi-5_2-2015q4 -# - export GCC_ARCHIVE=$HOME/dl/gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 -# - export GCC_URL=https://launchpad.net/gcc-arm-embedded/5.0/5-2015-q4-major/+download/gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 -# - if [ ! -e $GCC_DIR/bin/arm-none-eabi-gcc ]; then wget $GCC_URL -O $GCC_ARCHIVE; tar xfj $GCC_ARCHIVE -C $HOME/dl; fi -# - export PATH=$PATH:$GCC_DIR/bin - -- export GCC_DIR=$HOME/dl/gcc-arm-none-eabi-7-2017-q4-major -- export GCC_ARCHIVE=$HOME/dl/gcc-arm-none-eabi-7-2017-q4-major-linux.tar.bz2 -- export GCC_URL=https://developer.arm.com/-/media/Files/downloads/gnu-rm/7-2017q4/gcc-arm-none-eabi-7-2017-q4-major-linux.tar.bz2 -- if [ ! -e $GCC_DIR/bin/arm-none-eabi-gcc ]; then wget $GCC_URL -O $GCC_ARCHIVE; tar xfj $GCC_ARCHIVE -C $HOME/dl; fi -- export PATH=$PATH:$GCC_DIR/bin - -- export TUP_VER=tup_0.7.8-3~16.04.york0_amd64 -- export TUP_DIR=$HOME/dl/$TUP_VER -- export TUP_ARCHIVE=$HOME/dl/$TUP_VER.deb -- export TUP_URL=http://ppa.launchpad.net/jonathonf/tup/ubuntu/pool/main/t/tup/$TUP_VER.deb -- if [ ! -e $TUP_DIR/bin/tup ]; then wget $TUP_URL -O $TUP_ARCHIVE; dpkg-deb -R $TUP_ARCHIVE $TUP_DIR; fi -- export PATH=$PATH:$TUP_DIR/usr/bin - -env: - # Build default configuration for each board - - CONFIG_BOARD_VERSION=v3.2 DEPLOY=v3.2 - - CONFIG_BOARD_VERSION=v3.3 DEPLOY=v3.3 - - CONFIG_BOARD_VERSION=v3.4-24V DEPLOY=v3.4-24V - - CONFIG_BOARD_VERSION=v3.4-48V DEPLOY=v3.4-48V - - CONFIG_BOARD_VERSION=v3.5-24V DEPLOY=v3.5-24V - - CONFIG_BOARD_VERSION=v3.5-48V DEPLOY=v3.5-48V - - CONFIG_BOARD_VERSION=v3.6-24V DEPLOY=v3.6-24V - - CONFIG_BOARD_VERSION=v3.6-56V DEPLOY=v3.6-56V - - # Various protocol combinations - #- CONFIG_BOARD_VERSION=v3.4-24V CONFIG_USB_PROTOCOL=native-stream CONFIG_UART_PROTOCOL=native - #- CONFIG_BOARD_VERSION=v3.4-24V CONFIG_USB_PROTOCOL=stdout CONFIG_UART_PROTOCOL=stdout - #- CONFIG_BOARD_VERSION=v3.4-24V CONFIG_USB_PROTOCOL=none CONFIG_UART_PROTOCOL=none - -script: -- "./Firmware/build.sh" - -deploy: - provider: releases - api_key: - secure: RM66joGTn11Z5PmG7Nlj8cRcVY0w7ga5qUl2ahinbDm6jMV8OxroaXKivEa478alx9fygMHVeKdJZUlrAkRJ5OYJ1trfpW+43S3OYEnfy1nyXEXRwhgeIlb9LqdrumXVAp7TZ0Vppfom8A2ZWbxKxW3lG/EAmA4G9fnxHf0S9rF0y95YVfGrxdapTKcxvbP7Yojo53474ZI6+VYrqx8lq0JAnn4FwNT9ZJ1QASrmIw4w08f60XXv25BzndCTscvLb2qUu0AaGLbQUosde0Bb7P+aQsBVY6uSkg9MWV8gWPQjtO3u5IRR1bTshxf2kPqtzwK+SpcYrddoGN6BkKAB3lVorJIW5VguUkRmtPZ1K9+NhIztNevB2qr0ASutumNLF3aqMt19KL3A+SRx6froj5VhRHf4i/Xjm3SDLTaTcc8ZIh2PEE6scUMUMs5Mzu8LQWjInRe25MSb+pQB1mNOHmoFBtVb0J3u7Nvs8jdImN5gQWvvowWXfXRNE0ncT1YsLmevwi3q+YdEjpAIPnrD/rouY8WaqQZ/vE15JM9uwdQRqKAbzGtMaKHDk7EZ7ANTyaP+UrQ/M5cVDa0bWsWSvqSqDJMy4IVHRlirYA/5u74lXNhmA8DGDB/gFVlVCmoEzas/pnYiAE1hh4RpsYxts78Ix+wbeo1hmt7t65X8cyo= - skip_cleanup: true - file_glob: true - file: Firmware/deploy/* - on: - repo: madcowswe/ODrive - branch: master - tags: true From 1a0d6acace9c622e13215ff8f560871f62f0a5dd Mon Sep 17 00:00:00 2001 From: Rowan Goemans Date: Fri, 17 Jul 2020 14:07:39 +0200 Subject: [PATCH 521/549] - Refactored thermal and current lmiting sub-systems and introduced motor thermistor support. - Added thermal errors to dump_errors call in odrivetool. - Added function in odrivetool to set thermistor coefficients based on thermistor specs. - Added documentation for users on how to connect and configure their own thermistors. --- CHANGELOG.md | 5 +- Firmware/MotorControl/axis.cpp | 20 ++++- Firmware/MotorControl/axis.hpp | 11 +++ Firmware/MotorControl/board_config_v3.h | 27 ++++-- Firmware/MotorControl/current_limiter.hpp | 14 +++ Firmware/MotorControl/low_level.cpp | 19 ++++- Firmware/MotorControl/low_level.h | 4 +- Firmware/MotorControl/main.cpp | 19 ++++- Firmware/MotorControl/motor.cpp | 37 ++------ Firmware/MotorControl/motor.hpp | 5 +- Firmware/MotorControl/odrive_main.h | 2 + Firmware/MotorControl/thermistor.cpp | 80 ++++++++++++++++++ Firmware/MotorControl/thermistor.hpp | 74 ++++++++++++++++ Firmware/MotorControl/utils.hpp | 23 ++++- Firmware/Tupfile.lua | 1 + Firmware/odrive-interface.yaml | 58 +++++++++++-- analysis/thermistors.py | 27 +----- docs/_data/index.yaml | 2 + docs/getting-started.md | 2 + .../thermistor-voltage-divider.png | Bin 0 -> 13489 bytes docs/thermistors.md | 34 ++++++++ tools/odrive/enums.py | 6 +- tools/odrive/shell.py | 6 +- tools/odrive/utils.py | 36 ++++++++ 24 files changed, 427 insertions(+), 85 deletions(-) create mode 100644 Firmware/MotorControl/current_limiter.hpp create mode 100644 Firmware/MotorControl/thermistor.cpp create mode 100644 Firmware/MotorControl/thermistor.hpp create mode 100644 docs/screenshots/thermistor-voltage-divider.png create mode 100644 docs/thermistors.md diff --git a/CHANGELOG.md b/CHANGELOG.md index d480c4aa..29223f0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ Please add a note of your changes below this heading if you make a Pull Request. * [Preliminary support for Absolute Encoders](docs/encoders.md) * [Preliminary support for endstops and homing](docs/endstops.md) * [CAN Communication with CANSimple stack](can-protocol.md) +* [Motor thermistors support](docs/thermistors.md) +* Enable/disable of thermistor thermal limits according `setting axis..enabled`. * Gain scheduling for anti-hunt when close to 0 position error * Velocity Limiting in Current Control mode according to `vel_limit` and `vel_gain` * Regen current limiting according to `max_regen_current`, in Amps @@ -40,7 +42,8 @@ Please add a note of your changes below this heading if you make a Pull Request. * Added JSON caching to Fibre. This drastically reduces the time odrivetool needs to connect to an ODrive (except for the first time or after firmware updates). * Fix IPython `RuntimeWarning` that would occur every time `odrivetool` was started. * Reboot on `erase_configuration()`. This avoids unexpected behavior of a subsequent `save_configuration()` call, since the configuration is only erased from NVM, not from RAM. -* Change `motor.get_inverter_temp()` to use a property which was already being sampled at `motor.inverter_temp` +* `axis.motor.thermal_current_lim` has been removed. Instead a new property is available `axis.motor.effective_current_lim` which contains the effective current limit including any thermal limits. +* `axis.motor.get_inverter_temp()`, `axis.motor.inverter_temp_limit_lower` and `axis.motor.inverter_temp_limit_upper` have been moved to seperate fet thermistor object under `axis.fet_thermistor`. `get_inverter_temp()` function has been renamed to `temp` and is now a read-only property. * Fixed a numerical issue in the trajectory planner that could cause sudden jumps of the position setpoint # Releases diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index ffd6fffc..1c7f8652 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -14,6 +14,8 @@ Axis::Axis(int axis_num, Encoder& encoder, SensorlessEstimator& sensorless_estimator, Controller& controller, + OnboardThermistorCurrentLimiter& fet_thermistor, + OffboardThermistorCurrentLimiter& motor_thermistor, Motor& motor, TrapezoidalTrajectory& trap, Endstop& min_endstop, @@ -24,14 +26,24 @@ Axis::Axis(int axis_num, encoder_(encoder), sensorless_estimator_(sensorless_estimator), controller_(controller), + fet_thermistor_(fet_thermistor), + motor_thermistor_(motor_thermistor), motor_(motor), trap_traj_(trap), min_endstop_(min_endstop), - max_endstop_(max_endstop) + max_endstop_(max_endstop), + current_limiters_(make_array( + static_cast(&fet_thermistor), + static_cast(&motor_thermistor))), + thermistors_(make_array( + static_cast(&fet_thermistor), + static_cast(&motor_thermistor))) { encoder_.axis_ = this; sensorless_estimator_.axis_ = this; controller_.axis_ = this; + fet_thermistor_.axis_ = this; + motor_thermistor.axis_ = this; motor_.axis_ = this; trap_traj_.axis_ = this; min_endstop_.axis_ = this; @@ -167,6 +179,9 @@ bool Axis::do_checks() { error_ |= ERROR_DC_BUS_OVER_VOLTAGE; // Sub-components should use set_error which will propegate to this error_ + for (ThermistorCurrentLimiter* thermistor : thermistors_) { + thermistor->do_checks(); + } motor_.do_checks(); // encoder_.do_checks(); // sensorless_estimator_.do_checks(); @@ -185,6 +200,9 @@ bool Axis::do_checks() { // @brief Update all esitmators bool Axis::do_updates() { // Sub-components should use set_error which will propegate to this error_ + for (ThermistorCurrentLimiter* thermistor : thermistors_) { + thermistor->update(); + } encoder_.update(); sensorless_estimator_.update(); min_endstop_.update(); diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 39870ddb..2c749fa6 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -5,6 +5,8 @@ #error "This file should not be included directly. Include odrive_main.h instead." #endif +#include + class Axis : public ODriveIntf::AxisIntf { public: struct LockinConfig_t { @@ -75,6 +77,8 @@ public: Encoder& encoder, SensorlessEstimator& sensorless_estimator, Controller& controller, + OnboardThermistorCurrentLimiter& fet_thermistor, + OffboardThermistorCurrentLimiter& motor_thermistor, Motor& motor, TrapezoidalTrajectory& trap, Endstop& min_endstop, @@ -196,11 +200,18 @@ public: Encoder& encoder_; SensorlessEstimator& sensorless_estimator_; Controller& controller_; + OnboardThermistorCurrentLimiter& fet_thermistor_; + OffboardThermistorCurrentLimiter& motor_thermistor_; Motor& motor_; TrapezoidalTrajectory& trap_traj_; Endstop& min_endstop_; Endstop& max_endstop_; + // List of current_limiters and thermistors to + // provide easy iteration. + std::array current_limiters_; + std::array thermistors_; + osThreadId thread_id_; const uint32_t stack_size_ = 2048; // Bytes volatile bool thread_id_valid_ = false; diff --git a/Firmware/MotorControl/board_config_v3.h b/Firmware/MotorControl/board_config_v3.h index 7051d411..64cbdc78 100644 --- a/Firmware/MotorControl/board_config_v3.h +++ b/Firmware/MotorControl/board_config_v3.h @@ -42,8 +42,12 @@ typedef struct { TIM_HandleTypeDef* timer; uint16_t control_deadline; float shunt_conductance; - size_t inverter_thermistor_adc_ch; } MotorHardwareConfig_t; +typedef struct { + const float* const coeffs; + size_t num_coeffs; + size_t adc_ch; +} ThermistorHardwareConfig_t; typedef struct { SPI_HandleTypeDef* spi; GPIO_TypeDef* enable_port; @@ -57,18 +61,17 @@ typedef struct { AxisHardwareConfig_t axis_config; EncoderHardwareConfig_t encoder_config; MotorHardwareConfig_t motor_config; + ThermistorHardwareConfig_t thermistor_config; GateDriverHardwareConfig_t gate_driver_config; } BoardHardwareConfig_t; extern const BoardHardwareConfig_t hw_configs[2]; -extern const float thermistor_poly_coeffs[]; -extern const size_t thermistor_num_coeffs; //TODO stick this in a C file #ifdef __MAIN_CPP__ -const float thermistor_poly_coeffs[] = +const float fet_thermistor_poly_coeffs[] = {363.93910201f, -462.15369634f, 307.55129571f, -27.72569531f}; -const size_t thermistor_num_coeffs = sizeof(thermistor_poly_coeffs)/sizeof(thermistor_poly_coeffs[1]); +const size_t fet_thermistor_num_coeffs = sizeof(fet_thermistor_poly_coeffs)/sizeof(fet_thermistor_poly_coeffs[1]); const BoardHardwareConfig_t hw_configs[2] = { { //M0 @@ -93,7 +96,11 @@ const BoardHardwareConfig_t hw_configs[2] = { { .timer = &htim1, .control_deadline = TIM_1_8_PERIOD_CLOCKS, .shunt_conductance = 1.0f / SHUNT_RESISTANCE, //[S] - .inverter_thermistor_adc_ch = 15, + }, + .thermistor_config = { + .coeffs = &fet_thermistor_poly_coeffs[0], + .num_coeffs = fet_thermistor_num_coeffs, + .adc_ch = 15, }, .gate_driver_config = { .spi = &hspi3, @@ -133,10 +140,14 @@ const BoardHardwareConfig_t hw_configs[2] = { { .timer = &htim8, .control_deadline = (3 * TIM_1_8_PERIOD_CLOCKS) / 2, .shunt_conductance = 1.0f / SHUNT_RESISTANCE, //[S] + }, + .thermistor_config = { + .coeffs = &fet_thermistor_poly_coeffs[0], + .num_coeffs = fet_thermistor_num_coeffs, #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 - .inverter_thermistor_adc_ch = 4, + .adc_ch = 4, #else - .inverter_thermistor_adc_ch = 1, + .adc_ch = 1, #endif }, .gate_driver_config = { diff --git a/Firmware/MotorControl/current_limiter.hpp b/Firmware/MotorControl/current_limiter.hpp new file mode 100644 index 00000000..4334f5c0 --- /dev/null +++ b/Firmware/MotorControl/current_limiter.hpp @@ -0,0 +1,14 @@ +#ifndef __CURRENT_LIMITER_HPP +#define __CURRENT_LIMITER_HPP + +#ifndef __ODRIVE_MAIN_H +#error "This file should not be included directly. Include odrive_main.h instead." +#endif + +class CurrentLimiter { +public: + virtual ~CurrentLimiter() = default; + virtual float get_current_limit(float base_current_lim) const = 0; +}; + +#endif // __CURRENT_LIMITER_HPP diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 0c423346..b6cd64be 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -390,8 +390,16 @@ void start_general_purpose_adc() { // 21000kHz / (15+26) / 16 = 32kHz // The true frequency is slightly lower because of the injected vbus // measurements -float get_adc_voltage(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin) { - uint32_t channel = UINT32_MAX; +float get_adc_voltage(const GPIO_TypeDef* const GPIO_port, uint16_t GPIO_pin) { + const uint16_t channel = channel_from_gpio(GPIO_port, GPIO_pin); + return get_adc_voltage_channel(channel); +} + +// @brief Given a GPIO_port and pin return the associated adc_channel. +// returns UINT16_MAX if there is no adc_channel; +uint16_t channel_from_gpio(const GPIO_TypeDef* const GPIO_port, uint16_t GPIO_pin) +{ + uint16_t channel = UINT16_MAX; if (GPIO_port == GPIOA) { if (GPIO_pin == GPIO_PIN_0) channel = 0; @@ -428,6 +436,13 @@ float get_adc_voltage(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin) { else if (GPIO_pin == GPIO_PIN_5) channel = 15; } + return channel; +} + +// @brief Given an adc channel return the measured voltage. +// returns NaN if the channel is not valid. +float get_adc_voltage_channel(uint16_t channel) +{ if (channel < ADC_CHANNEL_COUNT) return ((float)adc_measurements_[channel]) * (adc_ref_voltage / adc_full_scale); else diff --git a/Firmware/MotorControl/low_level.h b/Firmware/MotorControl/low_level.h index f0b72ecb..11494cbf 100644 --- a/Firmware/MotorControl/low_level.h +++ b/Firmware/MotorControl/low_level.h @@ -51,7 +51,9 @@ void sync_timers(TIM_HandleTypeDef* htim_a, TIM_HandleTypeDef* htim_b, uint16_t TIM_CLOCKSOURCE_ITRx, uint16_t count_offset, TIM_HandleTypeDef* htim_refbase = nullptr); void start_general_purpose_adc(); -float get_adc_voltage(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin); +float get_adc_voltage(const GPIO_TypeDef* const GPIO_port, uint16_t GPIO_pin); +uint16_t channel_from_gpio(const GPIO_TypeDef* const GPIO_port, uint16_t GPIO_pin); +float get_adc_voltage_channel(uint16_t channel); void pwm_in_init(); void start_analog_thread(); diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 5ea0a827..2809f5da 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -15,6 +15,8 @@ Encoder::Config_t encoder_configs[AXIS_COUNT]; SensorlessEstimator::Config_t sensorless_configs[AXIS_COUNT]; Controller::Config_t controller_configs[AXIS_COUNT]; Motor::Config_t motor_configs[AXIS_COUNT]; +OnboardThermistorCurrentLimiter::Config_t fet_thermistor_configs[AXIS_COUNT]; +OffboardThermistorCurrentLimiter::Config_t motor_thermistor_configs[AXIS_COUNT]; Axis::Config_t axis_configs[AXIS_COUNT]; TrapezoidalTrajectory::Config_t trap_configs[AXIS_COUNT]; Endstop::Config_t min_endstop_configs[AXIS_COUNT]; @@ -31,6 +33,8 @@ typedef Config< SensorlessEstimator::Config_t[AXIS_COUNT], Controller::Config_t[AXIS_COUNT], Motor::Config_t[AXIS_COUNT], + OnboardThermistorCurrentLimiter::Config_t[AXIS_COUNT], + OffboardThermistorCurrentLimiter::Config_t[AXIS_COUNT], TrapezoidalTrajectory::Config_t[AXIS_COUNT], Endstop::Config_t[AXIS_COUNT], Endstop::Config_t[AXIS_COUNT], @@ -44,6 +48,8 @@ void ODrive::save_configuration(void) { &sensorless_configs, &controller_configs, &motor_configs, + &fet_thermistor_configs, + &motor_thermistor_configs, &trap_configs, &min_endstop_configs, &max_endstop_configs, @@ -64,6 +70,8 @@ extern "C" int load_configuration(void) { &sensorless_configs, &controller_configs, &motor_configs, + &fet_thermistor_configs, + &motor_thermistor_configs, &trap_configs, &min_endstop_configs, &max_endstop_configs, @@ -76,6 +84,8 @@ extern "C" int load_configuration(void) { sensorless_configs[i] = SensorlessEstimator::Config_t(); controller_configs[i] = Controller::Config_t(); motor_configs[i] = Motor::Config_t(); + fet_thermistor_configs[i] = OnboardThermistorCurrentLimiter::Config_t(); + motor_thermistor_configs[i] = OffboardThermistorCurrentLimiter::Config_t(); trap_configs[i] = TrapezoidalTrajectory::Config_t(); axis_configs[i] = Axis::Config_t(); // Default step/dir pins are different, so we need to explicitly load them @@ -173,6 +183,11 @@ extern "C" int construct_objects(){ encoder_configs[i], motor_configs[i]); SensorlessEstimator *sensorless_estimator = new SensorlessEstimator(sensorless_configs[i]); Controller *controller = new Controller(controller_configs[i]); + + OnboardThermistorCurrentLimiter *fet_thermistor = new OnboardThermistorCurrentLimiter(hw_configs[i].thermistor_config, + fet_thermistor_configs[i]); + OffboardThermistorCurrentLimiter *motor_thermistor = new OffboardThermistorCurrentLimiter(motor_thermistor_configs[i]); + Motor *motor = new Motor(hw_configs[i].motor_config, hw_configs[i].gate_driver_config, motor_configs[i]); @@ -180,10 +195,12 @@ extern "C" int construct_objects(){ Endstop *min_endstop = new Endstop(min_endstop_configs[i]); Endstop *max_endstop = new Endstop(max_endstop_configs[i]); axes[i] = new Axis(i, hw_configs[i].axis_config, axis_configs[i], - *encoder, *sensorless_estimator, *controller, *motor, *trap, *min_endstop, *max_endstop); + *encoder, *sensorless_estimator, *controller, *fet_thermistor, + *motor_thermistor, *motor, *trap, *min_endstop, *max_endstop); controller_configs[i].parent = controller; encoder_configs[i].parent = encoder; + motor_thermistor_configs[i].parent = motor_thermistor; motor_configs[i].parent = motor; min_endstop_configs[i].parent = min_endstop; max_endstop_configs[i].parent = max_endstop; diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index d74fbcec..a27ed5b1 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -144,36 +144,12 @@ void Motor::set_error(Motor::Error error){ update_brake_current(); } -float Motor::get_inverter_temp() { - float adc = adc_measurements_[hw_config_.inverter_thermistor_adc_ch]; - float normalized_voltage = adc / adc_full_scale; - return horner_fma(normalized_voltage, thermistor_poly_coeffs, thermistor_num_coeffs); -} - -bool Motor::update_thermal_limits(float fet_temp) { - float temp_margin = config_.inverter_temp_limit_upper - fet_temp; - float derating_range = config_.inverter_temp_limit_upper - config_.inverter_temp_limit_lower; - thermal_current_lim_ = config_.current_lim * (temp_margin / derating_range); - if (!(thermal_current_lim_ >= 0.0f)) { //Funny polarity to also catch NaN - thermal_current_lim_ = 0.0f; - } - if (fet_temp > config_.inverter_temp_limit_upper + 5) { - set_error(ERROR_INVERTER_OVER_TEMP); - return false; - } - return true; -} - bool Motor::do_checks() { if (!check_DRV_fault()) { set_error(ERROR_DRV_FAULT); return false; } - inverter_temp_ = get_inverter_temp(); - if (!update_thermal_limits(inverter_temp_)) { - //error already set in function - return false; - } + return true; } @@ -186,10 +162,15 @@ float Motor::effective_current_lim() { } else { current_lim = std::min(current_lim, axis_->motor_.current_control_.max_allowed_current); } - // Thermal limit - current_lim = std::min(current_lim, thermal_current_lim_); - return current_lim; + // Apply axis current limiters + for (const CurrentLimiter* const limiter : axis_->current_limiters_) { + current_lim = std::min(current_lim, limiter->get_current_limit(config_.current_lim)); + } + + effective_current_lim_ = current_lim; + + return effective_current_lim_; } //return the maximum available torque for the motor. diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index fbf14af9..85c2c1e9 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -92,8 +92,6 @@ public: bool check_DRV_fault(); void set_error(Error error); bool do_checks(); - float get_inverter_temp(); - bool update_thermal_limits(float fet_temp); float effective_current_lim(); float max_available_torque(); void log_timing(TimingLog_t log_idx); @@ -161,8 +159,7 @@ public: DrvFault drv_fault = DRV_FAULT_NO_FAULT; } gate_driver_exported_; DRV_SPI_8301_Vars_t gate_driver_regs_; //Local view of DRV registers (initialized by DRV8301_setup) - float thermal_current_lim_ = 10.0f; //[A] - float inverter_temp_ = NAN; // [°C] NaN while the ODrive is initializing. + float effective_current_lim_ = 10.0f; }; #endif // __MOTOR_HPP diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 9171778a..392b9c7e 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -205,6 +205,8 @@ enum TimingLog_t { #include #include #include +#include +#include #include #include #include diff --git a/Firmware/MotorControl/thermistor.cpp b/Firmware/MotorControl/thermistor.cpp new file mode 100644 index 00000000..68656315 --- /dev/null +++ b/Firmware/MotorControl/thermistor.cpp @@ -0,0 +1,80 @@ +#include "odrive_main.h" + +#include "low_level.h" + +ThermistorCurrentLimiter::ThermistorCurrentLimiter(uint16_t adc_channel, + const float* const coefficients, + size_t num_coeffs, + const float& temp_limit_lower, + const float& temp_limit_upper, + const bool& enabled) : + adc_channel_(adc_channel), + coefficients_(coefficients), + num_coeffs_(num_coeffs), + temperature_(NAN), + temp_limit_lower_(temp_limit_lower), + temp_limit_upper_(temp_limit_upper), + enabled_(enabled), + error_(ERROR_NONE) +{ +} + +void ThermistorCurrentLimiter::update() { + const float voltage = get_adc_voltage_channel(adc_channel_); + const float normalized_voltage = voltage / adc_ref_voltage; + temperature_ = horner_fma(normalized_voltage, coefficients_, num_coeffs_); +} + +bool ThermistorCurrentLimiter::do_checks() { + if (enabled_ && temperature_ >= temp_limit_upper_ + 5) { + error_ = ERROR_OVER_TEMP; + axis_->error_ |= Axis::ERROR_OVER_TEMP; + return false; + } + return true; +} + +float ThermistorCurrentLimiter::get_current_limit(float base_current_lim) const { + if (!enabled_) { + return base_current_lim; + } + + const float temp_margin = temp_limit_upper_ - temperature_; + const float derating_range = temp_limit_upper_ - temp_limit_lower_; + float thermal_current_lim = base_current_lim * (temp_margin / derating_range); + if (!(thermal_current_lim >= 0.0f)) { // Funny polarity to also catch NaN + thermal_current_lim = 0.0f; + } + + return std::min(thermal_current_lim, base_current_lim); +} + +OnboardThermistorCurrentLimiter::OnboardThermistorCurrentLimiter(const ThermistorHardwareConfig_t& hw_config, Config_t& config) : + ThermistorCurrentLimiter(hw_config.adc_ch, + hw_config.coeffs, + hw_config.num_coeffs, + config.temp_limit_lower, + config.temp_limit_upper, + config.enabled), + config_(config) +{ +} + +OffboardThermistorCurrentLimiter::OffboardThermistorCurrentLimiter(Config_t& config) : + ThermistorCurrentLimiter(UINT16_MAX, + &config.thermistor_poly_coeffs[0], + num_coeffs_, + config.temp_limit_lower, + config.temp_limit_upper, + config.enabled), + config_(config) +{ + decode_pin(); +} + +void OffboardThermistorCurrentLimiter::decode_pin() { + const GPIO_TypeDef* const port = get_gpio_port_by_pin(config_.gpio_pin); + const uint16_t pin = get_gpio_pin_by_pin(config_.gpio_pin); + + adc_channel_ = channel_from_gpio(port, pin); +} diff --git a/Firmware/MotorControl/thermistor.hpp b/Firmware/MotorControl/thermistor.hpp new file mode 100644 index 00000000..c403f189 --- /dev/null +++ b/Firmware/MotorControl/thermistor.hpp @@ -0,0 +1,74 @@ +#ifndef __THERMISTOR_HPP +#define __THERMISTOR_HPP + +#ifndef __ODRIVE_MAIN_H +#error "This file should not be included directly. Include odrive_main.h instead." +#endif + +class ThermistorCurrentLimiter : public CurrentLimiter, public ODriveIntf::ThermistorCurrentLimiterIntf { +public: + virtual ~ThermistorCurrentLimiter() = default; + + ThermistorCurrentLimiter(uint16_t adc_channel, + const float* const coefficients, + size_t num_coeffs, + const float& temp_limit_lower, + const float& temp_limit_upper, + const bool& enabled); + + void update(); + bool do_checks(); + float get_current_limit(float base_current_lim) const override; + + uint16_t adc_channel_; + const float* const coefficients_; + const size_t num_coeffs_; + float temperature_; + const float& temp_limit_lower_; + const float& temp_limit_upper_; + const bool& enabled_; + Error error_; + Axis* axis_ = nullptr; // set by Axis constructor +}; + +class OnboardThermistorCurrentLimiter : public ThermistorCurrentLimiter, public ODriveIntf::OnboardThermistorCurrentLimiterIntf { +public: + struct Config_t { + float temp_limit_lower = 100; + float temp_limit_upper = 120; + bool enabled = true; + }; + + virtual ~OnboardThermistorCurrentLimiter() = default; + OnboardThermistorCurrentLimiter(const ThermistorHardwareConfig_t& hw_config, Config_t& config); + + Config_t& config_; +}; + +class OffboardThermistorCurrentLimiter : public ThermistorCurrentLimiter, public ODriveIntf::OffboardThermistorCurrentLimiterIntf { +public: + static const size_t num_coeffs_ = 4; + + struct Config_t { + float thermistor_poly_coeffs[num_coeffs_]; + + uint16_t gpio_pin = 4; + float temp_limit_lower = 100; + float temp_limit_upper = 120; + bool enabled = false; + + // custom setters + OffboardThermistorCurrentLimiter* parent; + void set_gpio_pin(uint16_t value) { gpio_pin = value; parent->decode_pin(); } + }; + + virtual ~OffboardThermistorCurrentLimiter() = default; + OffboardThermistorCurrentLimiter(Config_t& config); + + Config_t& config_; + +private: + void decode_pin(); +}; + +#endif // __THERMISTOR_HPP diff --git a/Firmware/MotorControl/utils.hpp b/Firmware/MotorControl/utils.hpp index 0c191dfe..49f9434d 100644 --- a/Firmware/MotorControl/utils.hpp +++ b/Firmware/MotorControl/utils.hpp @@ -2,10 +2,6 @@ #ifndef __UTILS_H #define __UTILS_H -#ifdef __cplusplus -extern "C" { -#endif - #include #include @@ -65,6 +61,25 @@ extern "C" { #define SQ(x) ((x) * (x)) +#ifdef __cplusplus + +#include + +/** + * @brief Small helper to make array with known size + * in contrast to initializer lists the number of arguments + * has to match exactly. Whereas initializer lists allow + * less arguments. + */ +template +std::array make_array(T head, Tail... tail) +{ + return std::array({ head, tail ... }); +} + +extern "C" { +#endif + static const float one_by_sqrt3 = 0.57735026919f; static const float two_by_sqrt3 = 1.15470053838f; static const float sqrt3_by_2 = 0.86602540378f; diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 6acbd87e..bf077598 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -193,6 +193,7 @@ build{ 'MotorControl/nvm.c', 'MotorControl/axis.cpp', 'MotorControl/motor.cpp', + 'MotorControl/thermistor.cpp', 'MotorControl/encoder.cpp', 'MotorControl/endstop.cpp', 'MotorControl/controller.cpp', diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 79f0c1e8..438226c2 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -287,6 +287,8 @@ interfaces: HomingWithoutEndstop: bit: 17 doc: the min endstop was not enabled during homing + OverTemp: + doc: Check `fet_thermistor.error` and `motor_thermistor.error` for more information. step_dir_active: readonly bool current_state: readonly AxisState requested_state: AxisState @@ -353,6 +355,8 @@ interfaces: doc: Both axes will have the same id to start can_node_id_extended: bool can_heartbeat_rate_ms: uint32 + fet_thermistor: OnboardThermistorCurrentLimiter + motor_thermistor: OffboardThermistorCurrentLimiter motor: Motor controller: Controller encoder: Encoder @@ -391,6 +395,45 @@ interfaces: finish_on_distance: bool finish_on_enc_idx: bool + ODrive.ThermistorCurrentLimiter: + c_is_class: False + + ODrive.OnboardThermistorCurrentLimiter: + c_is_class: True + attributes: + error: ThermistorCurrentLimiter.Error + temperature: readonly float32 + config: + c_is_class: False + attributes: + temp_limit_lower: + type: float32 + doc: The lower limit when the controller starts limiting current. + temp_limit_upper: + type: float32 + doc: The upper limit when current limit reaches 0 Amps and an over temperature error is triggered. + enabled: {type: bool, doc: Whether this thermistor is enabled. } + + ODrive.OffboardThermistorCurrentLimiter: + c_is_class: True + attributes: + error: ThermistorCurrentLimiter.Error + temperature: readonly float32 + config: + c_is_class: False + attributes: + gpio_pin: {type: uint16, c_setter: set_gpio_pin} + poly_coefficient_0: {type: float32, c_name: 'thermistor_poly_coeffs[0]'} + poly_coefficient_1: {type: float32, c_name: 'thermistor_poly_coeffs[1]'} + poly_coefficient_2: {type: float32, c_name: 'thermistor_poly_coeffs[2]'} + poly_coefficient_3: {type: float32, c_name: 'thermistor_poly_coeffs[3]'} + temp_limit_lower: + type: float32 + doc: The lower limit when the controller starts limiting current. + temp_limit_upper: + type: float32 + doc: The upper limit when current limit reaches 0 Amps and an over temperature error is triggered. + enabled: {type: bool, doc: Whether this thermistor is enabled. } ODrive.Motor: c_is_class: True @@ -467,8 +510,7 @@ interfaces: BrakeDeadtimeViolation: UnexpectedTimerCallback: CurrentSenseSaturation: - InverterOverTemp: - CurrentLimitViolation: + CurrentLimitViolation: {bit: 12} BrakeDutyCycleNan: DcBusOverRegenCurrent: {doc: too much current pushed into the power supply} DcBusOverCurrent: {doc: too much current pulled out of the power supply} @@ -485,11 +527,7 @@ interfaces: DC_calib_phB: {type: float32, c_name: DC_calib_.phB} DC_calib_phC: {type: float32, c_name: DC_calib_.phC} phase_current_rev_gain: float32 - thermal_current_lim: readonly float32 - inverter_temp: - type: readonly float32 - unit: °C - doc: NaN while the ODrive is initializing. + effective_current_lim: readonly float32 current_control: c_is_class: False attributes: @@ -853,6 +891,12 @@ valuetypes: doc: Endstops must be enabled to use this feature. + ODrive.ThermistorCurrentLimiter.Error: + nullflag: None + flags: + OverTemp: + doc: The thermistor temperature upper limit was exceeded. + ODrive.Encoder.Mode: values: Incremental: diff --git a/analysis/thermistors.py b/analysis/thermistors.py index 2a3792a9..5d6a30e2 100644 --- a/analysis/thermistors.py +++ b/analysis/thermistors.py @@ -1,32 +1,9 @@ #%% -import matplotlib.pyplot as plt -import numpy as np +from odrive.utils import calculate_thermistor_coeffs Rload = 3300 R_25 = 10000 -T_25 = 25 + 273.15 #Kelvin Beta = 3434 Tmin = 0 Tmax = 140 - -temps = np.linspace(Tmin, Tmax, 1000) -tempsK = temps + 273.15 - -# https://en.wikipedia.org/wiki/Thermistor#B_or_%CE%B2_parameter_equation -r_inf = R_25 * np.exp(-Beta/T_25) -R_temps = r_inf * np.exp(Beta/tempsK) -V = Rload / (Rload + R_temps) - -fit = np.polyfit(V, temps, 3) -p1 = np.poly1d(fit) -fit_temps = p1(V) - -#%% -print(fit) - -plt.plot(V, temps, label='actual') -plt.plot(V, fit_temps, label='fit') -plt.xlabel('normalized voltage') -plt.ylabel('Temp [C]') -plt.legend(loc=0) -plt.show() \ No newline at end of file +calculate_thermistor_coeffs(3, Rload, R_25, Beta, Tmin, Tmax, True) diff --git a/docs/_data/index.yaml b/docs/_data/index.yaml index d14d3c73..0ff47762 100644 --- a/docs/_data/index.yaml +++ b/docs/_data/index.yaml @@ -17,6 +17,8 @@ sections: url: /encoders - title: Homing & Endstops url: /endstops + - title: Thermistors + url: /thermistors - title: Control & Tuning url: /control - title: Troubleshooting diff --git a/docs/getting-started.md b/docs/getting-started.md index 46b5af18..bdb2ee04 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -221,6 +221,8 @@ This is 4x the Pulse Per Revolution (PPR) value. Usually this is indicated in th * If you wish to run in sensorless mode, please see [Setting up sensorless](commands.md#setting-up-sensorless). * If you are using hall sensor feedback, please see the [hoverboard motor example](hoverboard.md). +**If using motor thermistor**
    +Please see the [Thermistors](thermistors.md) page for setup. ### 3. Save configuration You can save all `.config` parameters to persistent memory so the ODrive remembers them between power cycles. diff --git a/docs/screenshots/thermistor-voltage-divider.png b/docs/screenshots/thermistor-voltage-divider.png new file mode 100644 index 0000000000000000000000000000000000000000..e01c7cf99756650cdca31e4dd4633341330c6bc9 GIT binary patch literal 13489 zcma*NWmF_V)2P|FyE}usySvlq;O@}4yAAH{?#v(qgS)#7?(Q(S`|^JGzI%7i{@K%~ zx~ihHGApAZq9dNHNM)cj5*Em>{3utdT}7SoJNT|s-^bz6_}VF*7xZ)J@+fTI_R6$oX@>~{BTboY+kxf*u*)# zEKJ)xZ5GeHM5um*$uH~(m`ykCG`I_RJQ1Ok<98{xayNRoZFZeD|i{ zF)(?_+-qPP!Fe=|}R;9N>rJOK`b&-L91ukB~~zjdtTPyqCVV+{_(! z*-aQO+&Lph6OcLedbGK5Ay~T-s~%zHr8$rB@5%{JhzHKg7fA1`>CYChbku&k@pcuR z;?VuJ?-NQK(_)CDx@;UmFm>D3XA&_Q_uXJJZSY7ddZCT+fSSrL9K`w1VoL5Z9;Y@{ z7TI?m90wuiX3pWUd-E;V!Q)ajBG}=QhmvwxeF;%KbIw6XQmoJK zh*22q86gRrnX42JRs&ePK%ACHe-v}de#8rcmTM#)Z zRkh6D#%BKS2y1zc%P1#zx(_jBnz}P>DR$6$MO|4IsIswX^~|cSG)>pKv2k(jc##}8 z!{?;2dGi(b5Sklb4|Mh1n`q5G#c|8AT z5vGLaWM2&^cz(yu)absa$i3x!X4kxRO$plBXxa++lv?cL2R)d{<^M9QA5zgg8>S52 zrl7Ypn@}a)`;J#opvZg6SYkGo<9ygYgSmohreVL1JcdiFK&{oaBY)9lagly===@sR zvGO^dqjvfcIl-&?xYY4Z3epv5&Wq`6Q)azSyl8hr0>xb?5Pt0De-hMQTpDa!;^5oG z=Yh>Ej6YoGtau&`Ats*d*z52=5?^Eu9K1AI20u9;Penv4ia7nt5{IA<3i<>3-T68i z+M#7f&Sf(AY06(x* zm*D;^z}Ql9$_x9}&LdS?f8ELXwBjM`BvpH_X@-b^o@Vx&*|?PLFK zN1LJ0x5SZXs})x?);2-SwbLdE$)_C+sXCwHG7U-b8E`L}tHq|~ilS5Q#}gNvH6Zz4 z?VJud#P^8~cm3=2?LOIb)Bp*M*87K@bo+0Sd5cR{-{`q%eh80i3m~=-I;=@g^4mXh=Nj$ zNhOwy(gf?LEkCzzKQeN|Md`xG$CGxAae5Djc;W=_iubyK;}zf2)Vyu(VR8z-E+UndY)+$}?8P2GL}kD;t8Y0W5EbcJ0s z&PW4%RN#d6&Z=w(7fY?}+e{~W?-FlwQy+je=+F!nL52umkt8u?*K-qgVK_+FRV%3I}f;*dWLi+g^^)Jn&pO1?x#cx5HxLJpyiRv@&W``z39;BA(QKkasa55X%oe42p{elxOcKh8xqHPlK8Ye5<4K zv4J&MZlM8{-%@#B+YORXqBo}{&9R!p`~EVTF#4nANa8}IVz^W~DK&e9PH$hae>;{E z}HE$%xeHbSF zO)d@6h)Yc23gojJojMlKZaPExrMU{emf+(R_Im*M`n^6lu*OWAfdL+3H z-s994Wr@6iCdk7~!~=~UDCJl5y*Um0_@|ajD-(%@;f{tAX@wjPn8*lR6}E{`Z#T|p zphfM>VpbRsuHoDD<#tQAIVQRnVM~~d!#+`}VdcYLbiQ)imbAxPJlHzw0U5Z4+fq}? z*4=`!3Sqvz8pblHZn)b49Az-RLKxvxOi|(nC)RRO%+5$bo<$F2X>Oee0aEzhp-^v$ zm?J!LCo(q^^v(j-zz3U ze06=)RdKm`z~He(T3rJ<*YS*x#8tngOWB&bj?ek7hvxryK+&=;CR6Dy0T&E9{<)2u z2-(v*4%LTu4>oc(eCM|QevcHUG-3>XnO!8ZQ;sB1d z*XO_~E#BAfGw=0bGMbJtsYK=nF}dO|+N7|kTyYHv@dh6bWp3!Ws+4mn>id<3kzmh) zjWwof+owRpbk@Jh7{XgvrVTSgZ*y^;uZ--Tyf~xk|z>JtcXv6Qlarb5|{61 zpHhQn(eX&y`XHQ_g`-q{VJbY6@oV_Sji(E#f%I!&`U}mZJAlnlc&KJ~!)o7<;^og5 zc)*^Y3wRh|1mG7r6_x7(6QXw|bNU#F)>_E&f1*-F*`@X{Fqa6UcqTEYOJ4_xC({P9 z69#0RguxOAJ?f6p?v>#H>4#7Ad)XGh?>KW%v&GP{xRPE+BDz723G9lnn(-Q}E?~h@ z`S2#^GC>6%g)XWPWN$`tqWNL6pd;g3{RAvi*$qfu#r&3YKz7Qx4abXvog)?P5hl$v z`_X0d8*`|{=UDlU5+8~iW_hI$l5ezvi#BtaLXptl-y?{Zl;2UNG;|llkMmMM_g3MF zOyPAg3FE3Sg5hM)w;WSWg_!k+Egc)9OE$?M5r3|cR z2!X?KF}+Q)$N>xqi4sdM>6XEmm0^UaaN`k(XEzIY^v3^kNuQUJYMr>R57aaz5G5+R z(drH!Cn7ZfRK~a~9S0Njvg%9JX}opN(&EupL>8i!<+*UXsL$C8Y@vw1rLVAkbdZ5VeO7}7vH#|Ivy5liOk>&YaPOBL9LC#Z~>gwar5 zi@A$fa9IF67d~i4fq>kMA{VZSfsY;)#W}t}0MFvbPDPnmywc>rH*au7ltV2;oiNEm zvs}mhbrwB{+0g}@>JAh4L_4#%dC!)Fs1zh|eeQG>&Hb3&B;6lfhD|X=Pdm;Cs^H&$ z-B5d&dJj1Bia0qXr_1M$3;npA#7u*T_v|Rz3HauGZs?l%TJCwNa9-;5CoS)!Wgy2` z2gQ!)++7yfe_hWRvVq~mNCk{^T(?RgDBnQ2w-LeknXD06LSh_rCr&xe`i*9k?o9v+ zCdj}4aX_EsPO7u>m{8X)^TE1cJ|Q(V3*i=bDBd~gmTt8>N@cAQ*X^_oA$o=hof^R6 zE%qp;14zZ>D(nms{(*?dy58LX!v~T;;exXJ0fHK$9O+6f^=~B-L`Vs^PNTEg^q9e5 zDrDP($gb+xaHap#Sq^S52yw^)e4kZX){9rK;m&?!Ha7kM`lClv!R4KFGXP2)c=4Ycu10T#R`Rrw}C_taXH}6kd4HORp_jy!ezv#Er zq8;+6MNs8>pzS-AqJ~o&Sn<`&XuB9|ScUg3=AE1Tvz``OY+M(ip9Ie<2B@ zrNRycR%YG?a*3n|x`?w$KS)KJUv0I%9V&W0NN*Vv%ea=V@5yWo$)L>K1IbfN3p&%Q zKloKJ4^~9G+LTuod$RL%(h;4n?3;`cm=0*f3N2&6S4E^USyi6;RkSew#QjcPPn0{e z8ZR%EzL;!+ym31!*DR`F_qvDf?bQ_7t-A>~+uT+Jv7pT+CyXv5g(KPK_-J`Taz3{Q z_omwK2NB)4cwxafpI6b()p4O7pe_eB_<-~#Wz{M`L`Uj(-Q0Xx$0!(C(_~b+5238@ zvWKkOq{+bvXG_j6Njo9)7{N$3WQ>S|-<__*6mbP-sqNqIKddVK5R`4Z)S*6l3eo>fAa0Ur5^$M!3 zi${sEu=lB%2|_L)(X-wf#Ca)3O|b0orpq2hc_f5*0Vv=$f;IQK8%QH98!c++a1QBFlk*)nfm!nOhQ4C` z1^Uhf=9+=R6)P2)BJ0~k5D7&>V z^`5Ta#FOf|0kkEz1~H15Xsh(^C_kb65RkwrWl7_kMaW2fN7Tfxh1c#zj3oM?5YPzT}6fA8hQw3$un`>B|a z9l^pYzOxGYhCdj!MVVzI;_F%%*Y>7`+`!!(!8+hl_OQLJOV#r^6?O%P-3{nU-2QOg z3v?IMA!LYlHGRv)CEkwIVvk{leY6dfxi3E6X9%B-=7I>KJa4ZTkC5=C)MNZ^^~k|f z{aNvNp4hQpPi$9&5^w2Q-3-?rwpeB^MEknt;tfIn96H zVO)wA74dLG)1RF*0>=L<%`d6lLck~6^jcq%=T;W7xrN|Os>j|utDc5X)6IF=Q18l* zd_Avhh?H9}*>(X_QZ!*E$a`4+3s0FF<5Z`%IFowP&hkz3UO(P5MHi3~-7k{6cm9)* zkVAVOUdP`VNkGB)+g$>xJ`r>|>lj10*iJAi3#grQ`@6%;O;Y;f=xk1Uoz0$^!8%$O zZjrxF>~2gmAFU(l>PG4-eXi3p-giEypQ7Un?$TM=MH^{2#ZobhDvV7UiXI$ zUrPpXYtt%37B@xPSr%UA#D~iEaH=3^3hj;^tl!Qhf+k*hyhE!*Gt!zj9qSa?mp5#) zA2^d9sG&H`4jDpZUL`k{!t2U$6&;BCfaSoNMhDFbhe}S{gr?q?+Z$7ri!SKzR#Ljq z_}a;#n&-yyh$0wpbBkb2NgF3{FN3BQq9%z~XKf5_Lrq?=C)yTFrMw?TT6rZTP_=5J zf8ZrES&L@Os&J-1kIwTOyinRy3R6w4kPM>tJ95D_O%1zV@Xtis6#A*ur1@a_eg0-* z3Q4!eF#7gha=sJIdN>$PJn}ykmZ<7WPu!OiVGg2a$m5ufwydYJ@fxz*gV>FJ4XlSU zyA7z{=htIcEEpuZkJu{_Yz5TB&6^gV2wS?gWd%)>f#=2Io0Prc864`BH20=>$d-P7 z2B8ApSCCleKU4jkK`H-03#qiA(F*npF;8Kf1veaoY?kuSo-R$i{>U zl*$$%DxFqO4r&qQT^h>lmy_r&zrJ0I*PNadBl?aq<7s^DkzewpC_Rst$Qhx|>kQ zr;Uo`G*5?FC{}@`vxhOj2Nnh0p zZLik}<#OY7(_;%vtaF0nmnq_3*EI!d3=^(qvM__0P?i^f!srB>1tl%i*0oza!0>Wc zVEl7EzgT)w7ZEmMfPI3+Zr9EKvdqqLlRBQhU5*!|8bE{A;w2G5%TZzE)Pk+h!u|2 zJmJynvJ*bF$Q548tnP;#QN{;F=i;o1w8$lwqtnK0ir}_046Y}enVNeksbZlrU~~F7 z+gaq35!K-SWpRD@1R|l(pwvF_hqzIR%nC^8^1}Xdt=lr|{S{1d+&R^~-~Pwfss^-) zytD-1^FN=0&hq3h37n&hwhI7&fbpLV43L$B`z3^Nl~t64*@uQl2cyWAlJExr2pnW3 zMAbc4&vPwB)Q49CNwlybX`^J+f{er&XbS3K;!XBHE*3F&r-*{Vqykt& z=-{HLPLQGlXpm^qs1k(#bMpU7up?=rnicT;Yt3VKwF3_u>s%%>hRpF~x@2%@hu;}8 z&fp%<@;rp33cc0c(5fV^B)e7h&eNM{b$ zsFunuExUd-EiIKDqU9xgcNVhLtcLCxTZvp+Z8)4wGpwsU&vc3T;{l;CG3oShedw=+ z`=Q^qv(QZ<)E~CXZcAgaxHzEK0ok)PCrov^?v>@ogLYXcZpC6nx)iWQUac@d%eyY< zgxZH;sr8Sikwk#fEI6kxjmj+=o67$p8iMGU#ovKz>gM|qa&Z* zrAp;uXz%JYg4d4%)#5O(se{#esNU}PxMEE~%(60Qx9?e8y~_gy03Zd=#?7KBydg4uD8*UL3G`sKIu{ce+we&f7MZbTX*Uq#28>`BkC<+8eP zta)rGG@I;)OBC>8Qpa*`$}p%= zTkb6HbnJkcz-p>DEr8qWom6VhKs5vm(k3(872;P%v$JxzrKg9w$(f^ub3#(J3rVS2 zQI(~Xe2w^_m9&wYnNBA$q?xa#Mwsj1>JNF`_(Ip%|n zja$a%-@gL;Ce$I(0GULLuw-$h5j?@9yRg9#V&>0$d2`t^m@MkdPKHzbxth!~6c1EB=23a)R|WtK?4{Y?I9bY;;ktkI7e6 z2Z;{v7r8b$xrpMtmKOr}^N&UHM;;WvDG{|b&(Y8^VsV8OvdabbE#PHkE1x{iZsz?s z$Uj+#`FtTo2GE@3V49k|KltBNyw*S7mmGEb#x-Mz>8Bw2?5XQ~B~fW#`O@RRJ;z6J~F(T@7}7bR=~z|1yyrMW!l{oe+Fmnl*L|^4=`@!)&zRWVZqDv5b+hd^n$hrRU;jn$ zlp>b0anD!lXn<|+mbT>7ujaWge`Z?~v-novRi%`1KROKUW?=!(Rdv}Hf`Z~m&u|iX6d5gV`FQyakzf}}qol&nxkmtNX7-(S-k}y62fP-ds*hAi;$!&UysVKCyw?BPAFExM7 zMoy+T$P7BXor%eucy;9>QOu|7P3Z+IB=y(1Zinz3nW5q zeU5~ym9akeGY6qil9I(Kqi%h+Bt((`jeD1F@i?|2;36W(F$Yi(a9I6!&AMJ6Ktlm6 zKyWfY!a`DPCY?SQ465sXk1ZDa39J9rvS?5q@39vW+rK-IwOCqCMB+aUne~S<9*1w| z;l4Jm=@1W(54#tB-CH5#m^M8p%2 z0Vj*#fvU4O=iA*4XSdpR4nw@4{$KsIZ{~}NhpEljb1LFLLN%ac?$nK>5EylSNg$ZZPK%ge9S%aQtMug4%F;VV8c>X@0) zMW^SPzGqEAgMbKRQ}oicQ=X-lm!~%U@2_-#B(-`+_ofMLbnb0W3HPDQMG2jX^`CGk zb9=&^ygp3$f%56Q$BUF6k!!>oAl0q$JhG>wZzBPkzWtbF<&j|)|nwHp2(?@39EsgLjyid4wDh;Q^>LVcf>l+)6W z07gU=l}a8y9TPK(x^$s9WviSV2Ffqn88v!zfOr42Xu(l@%H=O9E7?8KmItSZ;EA7o z&VB1{jE{{aB_m7udEwOz3J9p003{2>B`~vFOj*26>FXi$(f6X7sVn|B$V0<~n23pu zOprk_yC&MFC8KhayS1YMCVM90QeqF}cotmdGVn@82Qg{ue2&o(YG^$3E&x(? z)*sIH2NjEj_@ba-8CcnZeh(rRRostip{r~Eu@vs%;$JY_4|>!Wa)cVGRWl&B`4csm zb{#CFgG`-dQ^T&8339x0zzfQ<;-dk7MC>0@_FK;NpYH=nrAPAmWuqX(N@ zH&xib)8J()9+^zb$%%mTI5_y?aeV3(O_YwXRj!gjcT>v(p3Jr~DO0|P?k9xb37jrP z!m<8b$mR|Aa<7#f=|Z(XM>%M8zn}=AQ11H4I3vpXoX2%-0e*6Q(!%j8DImW*i&2qE z-SShK-SJ^1I?^jMDcuSA<6P60U^&~tL9Ci9=gHdII_h(EYVg5BwcIkn^9rZNwlmn$ zWBSP5pp%#*6Wf2Ck7f0~L;N?HeU z7^hRKHS=Dd8R^mv9(GvRFkEmXRnj<1cW4Zo?zg1gAMpj|yrP3S*m$lm?@lXCUZ(=l z@A(B2j;z^~OsvSzs4t6_PGmbWHuG;{QhVNJSF^7&l zQN&>NK_J<;*a-6d#G}PtamkBSOnA@g@Az)-FPNDibSFp%FM&4qG!aa2yzdzQJL99N z@AxYO6cZDxLl~Lbc>ZK;_3uwkNtqvB@I^6V`Io|MgW?EE+w$ zqeW$BBPDb)-4%ejIX`rQ@%S)7e*<)L;I?owDVR zl4z9n5Tmxr!bV2jPLn#+5YQj1nC((NnVm&vwb)SK3$pQ2FG!PqP}r41%B|38%z;#8 z1D;R{2xQ)Wq~r0gmMaf`-h1F$pV$b1R%EZf{vJld6g4Z3I+Lb_L4yTBZtwy^yxNiq z3i!kE(kz$Jd*~uxnO*MF{lmcMOwtJnNtqpi=2ya}B(S-<8uRgPqtC&9*9`QCN)d9iVEj^z5meQ<-=r3xsbv zocJ^(`1fyOrR9Wf>m79@(iEaZQjX;4b9zdj`{^l}+?S^g?JJdG^)`bL6GM-TRO}Df z!`eJiPhDZpX8k-%w%tk;bh^2GH}-w~uIugoXmorf$b9ep_>MOAyU@V*)lF9_{x#X> zkqz;bot+aBhI+fZ38AbE{?IS66R6wXR3kB5A_roK#5*!iPAxTm8u9zLIYTrTj{WxG z>W*L^LNR-C{*#9%1M0CsCFH*0z6?fK^AajntE&o4YCc6JuZ zhGj8u5HFU7hU3`ZWq_mpVbafk>E6E*h?3G@b&L*i)pmH+g-K3%aTwI(tscw}h9vzC zv(;4!*Y?{9hP zX1tEqsYX>>&y1tM+!X<`s$e=M8M+Yo^k^}k#>i~!+lzBli|+*jA``(q1F>E0VPHH!*aeSQnVOg=T-|pd{Ag)i-Aek2SWhusm<&%DWaK&@-8??GJmAysZ#nW?@GCwbeVJ)zClt}9b9wp}5SSBVHnaQ3s?l4A zqjqBKfXI5yAL4wqO{K1gx6{WnQLA(8>U}tA2WJ)!Nkw6nlv&wZ>%q8razp=1!?i?qPi|H$nugi6IrvCYOpFRH z60b$Qjtkd#JU%6bVRmi-`Ui~C4<9%7GT@AWaUHTaF~cGnT1{U~aM<#)YfdgBE7#O{ zc4_%96*{c12sbcTYHIVdXJ|-4C*LdwC%W1$Qw(KNM?2HR-26qZW^P_R@iIgaA$mQ| ze=rJr_Ov9P9QMnuMsoS3T3Whu)}qn+8yNJf(v+lWK6ceBi!2x5Ilc9Fl>Bezt<#uv zr^d|X^G}O@swbA3g`&?7;|W}yl)YqUhyQk6q1z6c$S?MIKFv-}p|>3RmpSPa zFzGm1H=&@|g&}xLex<w{|iFmX5wACfdTEGwt#bnM83&}A)6 zX-U6%-o-f08lQcfNzx@0r`Lr(4YKEoA)E=MCFugTIoUy;VylczUEOl<7a zzqz!x-&cs+&N#E2HSKQqs11e89N06vkg1TK&Ad+Jh~uLbKo@ zNvTd>VZ@~iAu?96cyH|jdKT~uyi=!(#p8WJK_*0$u2g5Sz5#o~s@Km|vWaEa_e|sc(R@#NZt-dc}qvJf(K&jd4SFW*@46s*OnTh>@IouvTHFO~Cm986X5O1FB_#tpn?67u=NXySlswq%SxgVq|X&u=DNRv-&y zqUWC6EQ>DVCYvvY7SM3vA2+c~YiYC7kGiLWSeFp<#Z0&4Kx6+8Lznv}i? z27eaXJ98n8FZ?u}7&XYoQVt|=|12shmXVX|D=rIfz~32+ayihU`ul4c%6$L$E$`7I zb&;%t&}p^FesX=fDC)ZixYA*_=WYjc*ZyOt4;+uPj7}_RJJ$X=!GSRQ+PVNyDA`Hf zz1WEuvDtrfl>Y;W|C6*BfZqrLzg^k4WZjA$iZHJ>ZjF3&zsM+4?7=D(1zplj!3-8G z{V;GC=6}*SXuH5*rU85bK>1St2Sjt}K?DzT+G4dHkLwd~6h14G z!UpXKN`9^_RlW{cd3^DV7auj~W2?c`3{t;cJv~irj23&%x5H-B-x7iP(XdY}T}-#T zsY?4j4pQ=qZo&5lW&UYxa=|J7F(tjiplrd(J>FSTrjZdQ3z6nI~ zy}tMJzZT|&OE#>m%x_FEI;l}@e%^C-3(nTolrgH$0&!~Z(90Tb)O#O(rznJht<`ox zwX(3Sk#xLx)R)kq-th<@1TtL&qN!zyC8_^`*~c9CZ|xoOs-8Ul{LQAi3-7JDJ}%Qj z5|Gfd9!KtkrwgeXRDL<=chT7ye?6AmZT0k)|K96=y(bmL@dXX(_R*{*2osMoOL)m= zBY4QB=j8t=mY_LF8zQ{;< z%GXW=`-~37({AF9vR zWy4#hU)X(J(CZWpbZ2|I0wXUijo&Sdy@l3)P0W_@TRfch%t)#K6Pem^1^y$4PwtT&6Q^rlw{-4Lxm2oHq_|iVWGQ2tvG?CB zfU2C_<4$u7pCKg*wkw5RFfP`@)ocgU`b(7m>E&Q`YQxj@B&M_&Vqwca`p++oqj_@* zDihT*g^Q8MI@ZU2`&+f#7psNSoSU&)?xKF~+m+>FoXQzisIKzN#>U<=D75lrH1{Xi z9WbyQora8?iwnq)4#Z9%OrT%x>VRHLg3s?nv^WC;Tv|-^3i}pn;JYY9@G^6MKU&qyqFb@I5J7k?5K*4E}U2wzz^|DC1$0i zCI|Z=vwgdf!!~T}NH0?VLGi(e$p0J3|Ci7I-;Dmh0;8T!upSQ3fO?_W_}6M2Kvohc KQ6pv){C@!urq@6K literal 0 HcmV?d00001 diff --git a/docs/thermistors.md b/docs/thermistors.md new file mode 100644 index 00000000..4b2a225d --- /dev/null +++ b/docs/thermistors.md @@ -0,0 +1,34 @@ +# Thermistors + +## Introduction +Thermistors are elements that change their resistance based on the temperature. They can be used to electrically measure temperature. The ODrive itself has thermistors on board near the FETs to ensure that they don't burn themselves out. In addition to this it's possible to connect your own thermistor to measure the temperature of the connected motors. There are two types of thermistors, Negative Temperature Coefficient (NTC) and Positive Temperature Coefficient (PTC). This indicates whether the resistance goes up or down when the temperature goes up or down. The ODrive only supports the NTC type thermistor. + +## FET thermistor +The temperature of the onboard FET thermistors can be read out by using the `odrivetool` under `.fet_thermistor.temp`. The odrive will automatically start current limiting the motor when the `.fet_thermistor.config.temp_limit_lower` threshold is exceeded and once `.fet_thermistor.config.temp_limit_upper` is exceeded the ODrive will stop controlling the motor and set an error. The lower and upper threshold can be changed, but this is not recommended. + +## Connecting motor thermistors + +To use your own thermistors with the ODrive a few things have to be clarified first. The use of your own thermistor requires one analog input pin. Under `.motor_thermistor.config` the configuration of your own thermistor is available with the following fields: + +* `gpio_pin`: The GPIO input in used for this thermistor. +* `poly_coefficient_0` to `poly_coefficient_3`: Coefficient that needs to be set for your specific setup more on that in [Thermistor coefficients](#Thermistor coefficients). +* `temp_limit_lower` and `temp_limit_upper`: Same principle as the FET temperature limits. +* `enabled`: Whether this thermistor is enabled or not. + +## Voltage divider circuit +To measure a temperature with a thermistor a voltage divider circuit is used in addition with an ADC. The screenshot below is taken directly from the ODrive schematic. + +![Launch Configurations](screenshots/thermistor-voltage-divider.png "Thermistor voltage divider") + +The way this works is that the thermistor is connected in series with a known resistance value. By connecting an ADC directly after the thermistor the resistance value can be determined. For further information see [Voltage divider](https://en.wikipedia.org/wiki/Voltage_divider). While not strictly necessary, it is a good idea to add a capacitor as shown as well. This will help reduce the effect of electrical noise. A value between 470nF and 4.7uF is recommended, and any voltage rating 4V or higher. Put the capacitor physically close to the ODrive. + +To use a thermistor with the ODrive a voltage divider circuit has to be made that uses `VCCA` as the power source with `GNDA` as the ground. The voltage divider output can be connected to a GPIO pin that supports analog input. + +## Thermistor coefficients +Every thermistor and voltage divider circuit is different and thus it's necessary to let the ODrive know how to relate a voltage it measures at the GPIO pin to a temperature. The `poly_coefficient_0` to `poly_coefficient_3` under `.motor_thermistor.config` are used for this. The `odrivetool` has a convenience function `set_motor_thermistor_coeffs(axis, Rload, R_25, Beta, Tmin, Tmax)` which can be used to calculate and set these coefficients. + +* `axis`: Which axis do set the motor thermistor coefficients for (`odrv0.axis0` or `odrv0.axis1`). +* `Rload`: The Ohm value of the resistor used in the voltage divider circuit. +* `R_25`: The resistance of the thermistor when the temperature is 25 degrees celsius. Can usually be found in the datasheet of your thermistor. Can also be measured manually with a multimeter. +* `Beta`: A constant specific to your thermistor. Can be found in the datasheet of your thermistor. +* `Tmin` and `Tmax`: The temperature range that is used to create the coefficients. Make sure to set this range to be wider than what is expected during operation. A good example may be -10 to 150. diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 8fccc74d..c836f39d 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -20,6 +20,10 @@ AXIS_STATE_LOCKIN_SPIN = 9 AXIS_STATE_ENCODER_DIR_FIND = 10 AXIS_STATE_HOMING = 11 +# ODrive.ThermistorCurrentLimiter.Error +THERMISTOR_CURRENT_LIMITER_ERROR_NONE = 0x00000000 +THERMISTOR_CURRENT_LIMITER_ERROR_OVER_TEMP = 0x00000001 + # ODrive.Encoder.Mode ENCODER_MODE_INCREMENTAL = 0 ENCODER_MODE_HALL = 1 @@ -71,6 +75,7 @@ AXIS_ERROR_MIN_ENDSTOP_PRESSED = 0x00001000 AXIS_ERROR_MAX_ENDSTOP_PRESSED = 0x00002000 AXIS_ERROR_ESTOP_REQUESTED = 0x00004000 AXIS_ERROR_HOMING_WITHOUT_ENDSTOP = 0x00020000 +AXIS_ERROR_OVER_TEMP = 0x00040000 # ODrive.Axis.LockinState LOCKIN_STATE_INACTIVE = 0 @@ -91,7 +96,6 @@ MOTOR_ERROR_MODULATION_MAGNITUDE = 0x00000080 MOTOR_ERROR_BRAKE_DEADTIME_VIOLATION = 0x00000100 MOTOR_ERROR_UNEXPECTED_TIMER_CALLBACK = 0x00000200 MOTOR_ERROR_CURRENT_SENSE_SATURATION = 0x00000400 -MOTOR_ERROR_INVERTER_OVER_TEMP = 0x00000800 MOTOR_ERROR_CURRENT_LIMIT_VIOLATION = 0x00001000 MOTOR_ERROR_BRAKE_DUTY_CYCLE_NAN = 0x00002000 MOTOR_ERROR_DC_BUS_OVER_REGEN_CURRENT = 0x00004000 diff --git a/tools/odrive/shell.py b/tools/odrive/shell.py index a62fd30a..9de5e626 100644 --- a/tools/odrive/shell.py +++ b/tools/odrive/shell.py @@ -5,7 +5,7 @@ import threading import fibre import odrive import odrive.enums -from odrive.utils import start_liveplotter, dump_errors, oscilloscope_dump, BulkCapture, step_and_plot +from odrive.utils import calculate_thermistor_coeffs, set_motor_thermistor_coeffs, start_liveplotter, dump_errors, oscilloscope_dump, BulkCapture, step_and_plot def print_banner(): print("Website: https://odriverobotics.com/") @@ -86,7 +86,9 @@ def launch_shell(args, logger, app_shutdown_token): 'dump_errors': dump_errors, 'oscilloscope_dump': oscilloscope_dump, 'BulkCapture': BulkCapture, - 'step_and_plot': step_and_plot + 'step_and_plot': step_and_plot, + 'calculate_thermistor_coeffs': calculate_thermistor_coeffs, + 'set_motor_thermistor_coeffs': set_motor_thermistor_coeffs } # Expose all enums from odrive.enums diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 5b9ff4c8..dd51f5ac 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -6,6 +6,8 @@ import threading import platform import subprocess import os +import numpy as np +import matplotlib.pyplot as plt from fibre.utils import Event import odrive.enums from odrive.enums import * @@ -29,9 +31,41 @@ _VT100Colors = { 'default': '\x1b[0m' } +def calculate_thermistor_coeffs(degree, Rload, R_25, Beta, Tmin, Tmax, plot = False): + T_25 = 25 + 273.15 #Kelvin + temps = np.linspace(Tmin, Tmax, 1000) + tempsK = temps + 273.15 + + # https://en.wikipedia.org/wiki/Thermistor#B_or_%CE%B2_parameter_equation + r_inf = R_25 * np.exp(-Beta/T_25) + R_temps = r_inf * np.exp(Beta/tempsK) + V = Rload / (Rload + R_temps) + + fit = np.polyfit(V, temps, degree) + p1 = np.poly1d(fit) + fit_temps = p1(V) + + if plot: + print(fit) + plt.plot(V, temps, label='actual') + plt.plot(V, fit_temps, label='fit') + plt.xlabel('normalized voltage') + plt.ylabel('Temp [C]') + plt.legend(loc=0) + plt.show() + + return p1 + class OperationAbortedException(Exception): pass +def set_motor_thermistor_coeffs(axis, Rload, R_25, Beta, Tmin, TMax): + coeffs = calculate_thermistor_coeffs(3, Rload, R_25, Beta, Tmin, TMax) + axis.motor_thermistor.config.poly_coefficient_0 = float(coeffs[3]) + axis.motor_thermistor.config.poly_coefficient_1 = float(coeffs[2]) + axis.motor_thermistor.config.poly_coefficient_2 = float(coeffs[1]) + axis.motor_thermistor.config.poly_coefficient_3 = float(coeffs[0]) + def dump_errors(odrv, clear=False): axes = [(name, axis) for name, axis in odrv._remote_attributes.items() if 'axis' in name] axes.sort() @@ -43,6 +77,8 @@ def dump_errors(odrv, clear=False): module_decode_map = [ ('axis', axis, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("AXIS_ERROR_")}), ('motor', axis.motor, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("MOTOR_ERROR_")}), + ('fet_thermistor', axis.fet_thermistor, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("THERMISTOR_CURRENT_LIMITER_ERROR")}), + ('motor_thermistor', axis.motor_thermistor, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("THERMISTOR_CURRENT_LIMITER_ERROR")}), ('encoder', axis.encoder, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("ENCODER_ERROR_")}), ('controller', axis.controller, {k: v for k, v in odrive.enums.__dict__ .items() if k.startswith("CONTROLLER_ERROR_")}), ] From 1808bedf1a44de321e8aac293efd72985639e1ad Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 18 Jul 2020 19:02:28 -0700 Subject: [PATCH 522/549] Fix phase_vel units --- Firmware/MotorControl/axis.cpp | 6 +++--- Firmware/MotorControl/motor.cpp | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 7fcff0a3..bf34f2eb 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -315,7 +315,7 @@ bool Axis::run_closed_loop_control_loop() { if (!controller_.update(&torque_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; - float phase_vel = encoder_.vel_estimate_ * motor_.config_.pole_pairs; + float phase_vel = (2*M_PI) * encoder_.vel_estimate_ * motor_.config_.pole_pairs; if (!motor_.update(torque_setpoint, encoder_.phase_, phase_vel)) return false; // set_error should update axis.error_ @@ -364,7 +364,7 @@ bool Axis::run_homing() { if (!controller_.update(&torque_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; - float phase_vel = encoder_.vel_estimate_ * motor_.config_.pole_pairs; + float phase_vel = (2*M_PI) * encoder_.vel_estimate_ * motor_.config_.pole_pairs; if (!motor_.update(torque_setpoint, encoder_.phase_, phase_vel)) return false; // set_error should update axis.error_ @@ -393,7 +393,7 @@ bool Axis::run_homing() { if (!controller_.update(&torque_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; - float phase_vel = encoder_.vel_estimate_ * motor_.config_.pole_pairs; + float phase_vel = (2*M_PI) * encoder_.vel_estimate_ * motor_.config_.pole_pairs; if (!motor_.update(torque_setpoint, encoder_.phase_, phase_vel)) return false; // set_error should update axis.error_ diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index d74fbcec..b104619c 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -454,7 +454,9 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha return true; } - +// torque_setpoint [Nm] +// phase [rad electrical] +// phase_vel [rad/s electrical] bool Motor::update(float torque_setpoint, float phase, float phase_vel) { float current_setpoint = 0.0f; phase *= config_.direction; From 8c85d2fe79909f398445c91a64a51e0dbe8c0f0b Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 18 Jul 2020 19:05:34 -0700 Subject: [PATCH 523/549] Add TODOs Avoid transients when entering closed loop also on Circular position mode. Phase interpolation is snapping to hard. --- Firmware/MotorControl/axis.cpp | 1 + Firmware/MotorControl/encoder.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 1c7f8652..f66f8f32 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -319,6 +319,7 @@ bool Axis::run_closed_loop_control_loop() { } // To avoid any transient on startup, we intialize the setpoint to be the current position + // TODO: Also do this for circular position mode controller_.pos_setpoint_ = *controller_.pos_estimate_src_; controller_.input_pos_ = *controller_.pos_estimate_src_; diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 65bdd662..97801945 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -529,6 +529,7 @@ bool Encoder::update() { if (snap_to_zero_vel || !config_.enable_phase_interpolation) { interpolation_ = 0.5f; // reset interpolation if encoder edge comes + // TODO: This isn't correct. At high velocities the first phase in this count may very well not be at the edge. } else if (delta_enc > 0) { interpolation_ = 0.0f; } else if (delta_enc < 0) { From 09daacda9f4c34873a4e7de927d1d72b357b1bd0 Mon Sep 17 00:00:00 2001 From: Rowan Goemans Date: Sun, 19 Jul 2020 18:49:50 +0200 Subject: [PATCH 524/549] Changed bash shebang to more a general version. --- Firmware/build.sh | 2 +- Firmware/find_programmer.sh | 2 +- tools/odrive/tests/test_runner.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Firmware/build.sh b/Firmware/build.sh index 8f3a0730..88570b15 100755 --- a/Firmware/build.sh +++ b/Firmware/build.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Builds the firmware with the configuration specified by # environment variables named CONFIG_... # If DEPLOY is set, the deliverables are copied to Firmware/deploy/* diff --git a/Firmware/find_programmer.sh b/Firmware/find_programmer.sh index 4b184b6a..cea57038 100755 --- a/Firmware/find_programmer.sh +++ b/Firmware/find_programmer.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash openocd -d3 -f board/stm32f4discovery.cfg -c "hla_serial wrong_serial" 2>&1 | \ xxd -p | \ tr -d '\n' | \ diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index 8295a1af..9633b92f 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -794,7 +794,7 @@ if args.setup_host: if not os.path.isfile('/usr/share/arduino/hardware/tools/teensy_post_compile_old'): os.rename('/usr/share/arduino/hardware/tools/teensy_post_compile', '/usr/share/arduino/hardware/tools/teensy_post_compile_old') with open('/usr/share/arduino/hardware/tools/teensy_post_compile', 'w') as scr: - scr.write('#!/bin/bash\n') + scr.write('#!/usr/bin/env bash\n') scr.write('if [ "$ARDUINO_COMPILE_DESTINATION" != "" ]; then\n') scr.write(' cp -r ${2#-path=}/*.ino.hex ${ARDUINO_COMPILE_DESTINATION}\n') scr.write('fi\n') From deb4a68d896a6acd2a6739bcdee266ff70bcfc2c Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 20 Jul 2020 11:12:01 +0200 Subject: [PATCH 525/549] remove build.sh This script was used by Travis CI which got disabled in a4e03ffd077eae11f1d3cdc62d3c20ef38f9fb3f in favor of GitHub Workflows. Developers should use `make` instead of this script. --- Firmware/build.sh | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100755 Firmware/build.sh diff --git a/Firmware/build.sh b/Firmware/build.sh deleted file mode 100755 index 88570b15..00000000 --- a/Firmware/build.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# Builds the firmware with the configuration specified by -# environment variables named CONFIG_... -# If DEPLOY is set, the deliverables are copied to Firmware/deploy/* -# with the suffix $DEPLOY -set -euo pipefail - -THIS_DIR="$(dirname "$0")" -cd "$THIS_DIR" - -# Treat warnings as errors -export CONFIG_STRICT=true - -# Write all environment variables that start with "CONFIG_" to tup.config -rm -rdf build -mkdir -p build -env | grep ^CONFIG > tup.config -tup init -tup generate ./tup_build.sh -bash -xe ./tup_build.sh - -# Deploy -if ! [ -z ${DEPLOY+x} ]; then - mkdir -p deploy - cp build/ODriveFirmware.elf deploy/ODriveFirmware_"$DEPLOY".elf - cp build/ODriveFirmware.hex deploy/ODriveFirmware_"$DEPLOY".hex -fi From ff6146f05516d915ee42f8eed6e33c20312f3625 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 20 Jul 2020 11:47:18 +0200 Subject: [PATCH 526/549] Fix bugs in protocol code - Fix incorrect use of snprintf. - Fix TreatPacketSinkAsStreamSink::process_bytes() quitting when being passed more than 64 bytes. Reported in https://github.com/madcowswe/ODrive/issues/438 These bugs are not known to have an effect currently but could have one if code somewhere else is changed. --- Firmware/communication/ascii_protocol.cpp | 4 +++- Firmware/communication/interface_usb.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index c8cc3299..5efbed46 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -36,14 +36,16 @@ static Introspectable root_obj = ODriveTypeInfo::make_introspectable(odr // @brief Sends a line on the specified output. template void respond(StreamSink& output, bool include_checksum, const char * fmt, TArgs&& ... args) { - char response[64]; + char response[64]; // Hardcoded max buffer size. We silently truncate the output if it's too long for the buffer. size_t len = snprintf(response, sizeof(response), fmt, std::forward(args)...); + len = std::min(len, sizeof(response)); output.process_bytes((uint8_t*)response, len, nullptr); // TODO: use process_all instead if (include_checksum) { uint8_t checksum = 0; for (size_t i = 0; i < len; ++i) checksum ^= response[i]; len = snprintf(response, sizeof(response), "*%u", checksum); + len = std::min(len, sizeof(response)); output.process_bytes((uint8_t*)response, len, nullptr); } output.process_bytes((const uint8_t*)"\r\n", 2, nullptr); diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index 83632bcd..bbdabb3d 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -61,7 +61,7 @@ public: // Loop to ensure all bytes get sent while (length) { size_t chunk = length < USB_TX_DATA_SIZE ? length : USB_TX_DATA_SIZE; - if (output_.process_packet(buffer, length) != 0) + if (output_.process_packet(buffer, chunk) != 0) return -1; buffer += chunk; length -= chunk; From 4cc9b16077de431eeb1429302b2af2362328eac6 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 20 Jul 2020 12:24:21 +0200 Subject: [PATCH 527/549] Fix motor subtree being unreachable from ASCII protocol When get_direct_child() was invoked on an Axis Instrospectable with the string "motor" it instead returned an Introspectable for "motor_thermistor". This is because strncmp("motor", "motor_thermistor", 5) compares to 0. --- Firmware/fibre/cpp/include/fibre/introspection.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/fibre/cpp/include/fibre/introspection.hpp b/Firmware/fibre/cpp/include/fibre/introspection.hpp index f52b73a8..f7e43f68 100644 --- a/Firmware/fibre/cpp/include/fibre/introspection.hpp +++ b/Firmware/fibre/cpp/include/fibre/introspection.hpp @@ -96,7 +96,7 @@ public: private: Introspectable get_direct_child(const char * name, size_t length) const { for (size_t i = 0; i < type_info_->property_table_length_; ++i) { - if (!strncmp(name, type_info_->property_table_[i].name, length)) { + if (!strncmp(name, type_info_->property_table_[i].name, length) && (length == strlen(type_info_->property_table_[i].name))) { Introspectable result; result.storage_ = type_info_->get_child(storage_, i); result.type_info_ = type_info_->property_table_[i].type_info; From 97469235d712da1297dd07ae399e21183cb9d8e7 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 25 Jul 2020 20:24:55 -0700 Subject: [PATCH 528/549] Add _src to estimate pointers --- Firmware/MotorControl/axis.cpp | 11 ++++++----- Firmware/MotorControl/controller.cpp | 8 ++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index bf34f2eb..f3cf93f3 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -278,8 +278,8 @@ bool Axis::run_lockin_spin(const LockinConfig_t &lockin_config) { // Note run_sensorless_control_loop and run_closed_loop_control_loop are very similar and differ only in where we get the estimate from. bool Axis::run_sensorless_control_loop() { - controller_.pos_estimate_linear_ = nullptr; - controller_.pos_estimate_circular_ = nullptr; + controller_.pos_estimate_linear_src_ = nullptr; + controller_.pos_estimate_circular_src_ = nullptr; controller_.pos_estimate_valid_src_ = nullptr; controller_.vel_estimate_src_ = &sensorless_estimator_.vel_estimate_; controller_.vel_estimate_valid_src_ = &sensorless_estimator_.vel_estimate_valid_; @@ -302,8 +302,9 @@ bool Axis::run_closed_loop_control_loop() { } // To avoid any transient on startup, we intialize the setpoint to be the current position - controller_.pos_setpoint_ = *controller_.pos_estimate_linear_; - controller_.input_pos_ = *controller_.pos_estimate_linear_; + // TODO: use circular src if in circular mode. + controller_.pos_setpoint_ = *controller_.pos_estimate_linear_src_; + controller_.input_pos_ = *controller_.pos_estimate_linear_src_; // Avoid integrator windup issues controller_.vel_integrator_torque_ = 0.0f; @@ -353,7 +354,7 @@ bool Axis::run_homing() { } // To avoid any transient on startup, we intialize the setpoint to be the current position - controller_.pos_setpoint_ = *controller_.pos_estimate_linear_; + controller_.pos_setpoint_ = *controller_.pos_estimate_linear_src_; // Avoid integrator windup issues controller_.vel_integrator_torque_ = 0.0f; diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index c0845ff3..a25bdefe 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -33,9 +33,9 @@ void Controller::input_pos_updated() { bool Controller::select_encoder(size_t encoder_num) { if (encoder_num < AXIS_COUNT) { Axis* ax = axes[encoder_num]; - pos_estimate_circular_ = &ax->encoder_.pos_circular_; + pos_estimate_circular_src_ = &ax->encoder_.pos_circular_; pos_wrap_src_ = &config_.circular_setpoint_range; - pos_estimate_linear_ = &ax->encoder_.pos_estimate_; + pos_estimate_linear_src_ = &ax->encoder_.pos_estimate_; pos_estimate_valid_src_ = &ax->encoder_.pos_estimate_valid_; vel_estimate_src_ = &ax->encoder_.vel_estimate_; vel_estimate_valid_src_ = &ax->encoder_.vel_estimate_valid_; @@ -119,9 +119,9 @@ static float limitVel(const float vel_limit, const float vel_estimate, const flo bool Controller::update(float* torque_setpoint_output) { float* pos_estimate_linear = (pos_estimate_valid_src_ && *pos_estimate_valid_src_) - ? pos_estimate_linear_ : nullptr; + ? pos_estimate_linear_src_ : nullptr; float* pos_estimate_circular = (pos_estimate_valid_src_ && *pos_estimate_valid_src_) - ? pos_estimate_circular_ : nullptr; + ? pos_estimate_circular_src_ : nullptr; float* vel_estimate_src = (vel_estimate_valid_src_ && *vel_estimate_valid_src_) ? vel_estimate_src_ : nullptr; From aa73bc907e109ea1359c549e8fe96001534d826a Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 25 Jul 2020 23:27:08 -0400 Subject: [PATCH 529/549] Add simple analysis --- analysis/Simulation/TranslationalMass.py | 35 ++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 analysis/Simulation/TranslationalMass.py diff --git a/analysis/Simulation/TranslationalMass.py b/analysis/Simulation/TranslationalMass.py new file mode 100644 index 00000000..3cb1a4aa --- /dev/null +++ b/analysis/Simulation/TranslationalMass.py @@ -0,0 +1,35 @@ +import os +import matplotlib.pyplot as plt +from control.matlab import * + +# Input: Current (A) +# Output: Torque (Nm) +# Params: Kt (Nm/A) +def motor(Kt): + return tf(Kt, 1) + +# Mass-Spring-Damper +# Input: Force +# Output: Position +# Params: m (kg) +# b +# k (N/m) +def mass(m, b, k): + A = [[0, 1.], [-k/m, -b/m]] + B = [[0], [1/m]] + C = [[1., 0]] + return ss(A, B, C, 0) + +# Input: Torque (Nm) +# Output: Force (N) +# Params: r (m) +def pulley(r): + return tf(r, 1) + +sys = series(motor(2.5), pulley(0.015), mass(0.10, 0, 0)) +yout, T, xout = step(sys, return_x=True) +print(yout) +# plt.plot(T, yout) +plt.plot(T, xout) +plt.legend(['Displacement', 'Velocity']) +plt.show() \ No newline at end of file From ff291ae5ec6195d6c59c8871e34ae27731bee841 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 25 Jul 2020 20:33:41 -0700 Subject: [PATCH 530/549] Also add _src in header --- Firmware/MotorControl/controller.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index e7eebc0a..52c3b246 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -72,8 +72,8 @@ public: Error error_ = ERROR_NONE; - float* pos_estimate_linear_ = nullptr; - float* pos_estimate_circular_ = nullptr; + float* pos_estimate_linear_src_ = nullptr; + float* pos_estimate_circular_src_ = nullptr; bool* pos_estimate_valid_src_ = nullptr; float* vel_estimate_src_ = nullptr; bool* vel_estimate_valid_src_ = nullptr; From 88b3ed42606f2d4eb909bec893c55e4a39472038 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 26 Jul 2020 16:16:10 -0400 Subject: [PATCH 531/549] Optimize step/dir callback --- Firmware/.vscode/c_cpp_properties.json | 6 +++--- Firmware/MotorControl/axis.cpp | 10 ++++------ Firmware/MotorControl/controller.cpp | 3 --- Firmware/MotorControl/controller.hpp | 5 ++++- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index 9c1aec97..1d5ab7cf 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -3,7 +3,7 @@ { "name": "Win32", "includePath": [ - "${workspaceRoot}/**" + "${workspaceFolder}/**" ], "defines": [ "STM32F405xx", @@ -24,7 +24,7 @@ { "name": "Linux", "includePath": [ - "${workspaceRoot}/**" + "${workspaceFolder}/**" ], "defines": [ "STM32F405xx", @@ -44,7 +44,7 @@ { "name": "Mac", "includePath": [ - "${workspaceRoot}/**" + "${workspaceFolder}/**" ], "defines": [ "STM32F405xx", diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index f66f8f32..bc11d1e4 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -118,12 +118,10 @@ bool Axis::wait_for_current_meas() { // step/direction interface void Axis::step_cb() { - if (step_dir_active_) { - GPIO_PinState dir_pin = HAL_GPIO_ReadPin(dir_port_, dir_pin_); - float dir = (dir_pin == GPIO_PIN_SET) ? 1.0f : -1.0f; - controller_.input_pos_ += dir * config_.counts_per_step; - controller_.input_pos_updated(); - } + const GPIO_PinState dir_pin = (GPIO_PinState)(dir_port_->IDR & dir_pin_); + const int32_t dir = (1 - 2 * (int32_t)dir_pin) * step_dir_active_; + controller_.input_pos_ += dir * config_.counts_per_step; + controller_.input_pos_updated(); }; void Axis::load_default_step_dir_pin_config( diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index c15b1cbe..7b3fe8ef 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -26,9 +26,6 @@ void Controller::set_error(Error error) { // Command Handling //-------------------------------- -void Controller::input_pos_updated() { - input_pos_updated_ = true; -} bool Controller::select_encoder(size_t encoder_num) { if (encoder_num < AXIS_COUNT) { diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 0b692450..b1f687dc 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -52,7 +52,10 @@ public: void reset(); void set_error(Error error); - void input_pos_updated(); + constexpr void input_pos_updated() { + input_pos_updated_ = true; + } + bool select_encoder(size_t encoder_num); // Trajectory-Planned control From 7d94b6c7866201397f0fb9ac418d47bb2ac3b2ad Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 27 Jul 2020 18:20:14 -0400 Subject: [PATCH 532/549] Fix step_cb per @PAJohnson --- Firmware/MotorControl/axis.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index bc11d1e4..375d75b8 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -118,8 +118,8 @@ bool Axis::wait_for_current_meas() { // step/direction interface void Axis::step_cb() { - const GPIO_PinState dir_pin = (GPIO_PinState)(dir_port_->IDR & dir_pin_); - const int32_t dir = (1 - 2 * (int32_t)dir_pin) * step_dir_active_; + const bool dir_pin = dir_port_->IDR & dir_pin_; + const int32_t dir = (-1 + 2 * dir_pin) * step_dir_active_; controller_.input_pos_ += dir * config_.counts_per_step; controller_.input_pos_updated(); }; From d650c8caba183e177ce0fb2853d91034e03efd52 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 27 Jul 2020 16:47:48 -0700 Subject: [PATCH 533/549] Update controller.hpp --- Firmware/MotorControl/controller.hpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 52c3b246..795176da 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -21,19 +21,19 @@ public: struct Config_t { ControlMode control_mode = CONTROL_MODE_POSITION_CONTROL; //see: ControlMode_t InputMode input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t - float pos_gain = 20.0f; // [(turn/s) / turn] - float vel_gain = 1.0f / 6.0f; // [Nm/(turn/s)] - // float vel_gain = 0.2f / 200.0f, // [Nm/(rad/s)] - float vel_integrator_gain = 2.0f / 6.0f; // [Nm/(turn/s * s)] - float vel_limit = 2.0f; // [turn/s] Infinity to disable. - float vel_limit_tolerance = 1.2f; // ratio to vel_lim. Infinity to disable. - float vel_ramp_rate = 1.0f; // [(turn/s) / s] - float torque_ramp_rate = 0.01f; // Nm / sec + float pos_gain = 20.0f; // [(turn/s) / turn] + float vel_gain = 1.0f / 6.0f; // [Nm/(turn/s)] + // float vel_gain = 0.2f / 200.0f, // [Nm/(rad/s)] + float vel_integrator_gain = 2.0f / 6.0f; // [Nm/(turn/s * s)] + float vel_limit = 2.0f; // [turn/s] Infinity to disable. + float vel_limit_tolerance = 1.2f; // ratio to vel_lim. Infinity to disable. + float vel_ramp_rate = 1.0f; // [(turn/s) / s] + float torque_ramp_rate = 0.01f; // Nm / sec bool circular_setpoints = false; - float circular_setpoint_range = 1.0f; // Circular range when circular_setpoints is true. [turn] - float inertia = 0.0f; // [A/(count/s^2)] - float input_filter_bandwidth = 2.0f; // [1/s] - float homing_speed = 0.25f; // [turn/s] + float circular_setpoint_range = 1.0f; // Circular range when circular_setpoints is true. [turn] + float inertia = 0.0f; // [A/(count/s^2)] + float input_filter_bandwidth = 2.0f; // [1/s] + float homing_speed = 0.25f; // [turn/s] Anticogging_t anticogging; float gain_scheduling_width = 10.0f; bool enable_gain_scheduling = false; From be2d1037e38855dd1a2e3450b2652e49ac2604c0 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Mon, 27 Jul 2020 21:26:41 -0400 Subject: [PATCH 534/549] Fixed null pointer checks in controller.cpp Fixed formatting in trapTraj.hpp Fixed how pos_setpoint is set in axis.cpp to avoid transients on startup so that it will handle circular position setpoints More unit corrections in the docs: radians -> turns --- Firmware/MotorControl/axis.cpp | 20 +++++++++++++++----- Firmware/MotorControl/controller.cpp | 16 ++++++++++------ Firmware/MotorControl/trapTraj.hpp | 6 +++--- docs/commands.md | 4 ++-- docs/getting-started.md | 12 ++++++------ docs/interfaces.md | 2 +- docs/odrivetool.md | 2 +- tools/odrive/shell.py | 8 ++++---- 8 files changed, 42 insertions(+), 28 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index ce9e14df..d593e7bc 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -320,9 +320,14 @@ bool Axis::run_closed_loop_control_loop() { } // To avoid any transient on startup, we intialize the setpoint to be the current position - // TODO: use circular src if in circular mode. - controller_.pos_setpoint_ = *controller_.pos_estimate_linear_src_; - controller_.input_pos_ = *controller_.pos_estimate_linear_src_; + if(controller_.config_.circular_setpoints == true) { + controller_.pos_setpoint_ = *controller_.pos_estimate_circular_src_; + controller_.input_pos_ = *controller_.pos_estimate_circular_src_; + } + else { + controller_.pos_setpoint_ = *controller_.pos_estimate_linear_src_; + controller_.input_pos_ = *controller_.pos_estimate_linear_src_; + } // Avoid integrator windup issues controller_.vel_integrator_torque_ = 0.0f; @@ -372,8 +377,13 @@ bool Axis::run_homing() { } // To avoid any transient on startup, we intialize the setpoint to be the current position - controller_.pos_setpoint_ = *controller_.pos_estimate_linear_src_; - + // note - input_pos_ is not set here. It is set to 0 earlier in this method and velocity control is used. + if(controller_.config_.circular_setpoints == true) { + controller_.pos_setpoint_ = *controller_.pos_estimate_circular_src_; + } + else { + controller_.pos_setpoint_ = *controller_.pos_estimate_linear_src_; + } // Avoid integrator windup issues controller_.vel_integrator_torque_ = 0.0f; diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index a25bdefe..da8ca254 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -137,7 +137,7 @@ bool Controller::update(float* torque_setpoint_output) { } // TODO also enable circular deltas for 2nd order filter, etc. - if (config_.circular_setpoints && pos_estimate_circular) { + if (config_.circular_setpoints) { // Keep pos setpoint from drifting input_pos_ = fmodf_pos(input_pos_, config_.circular_setpoint_range); } @@ -226,18 +226,22 @@ bool Controller::update(float* torque_setpoint_output) { float vel_des = vel_setpoint_; if (config_.control_mode >= CONTROL_MODE_POSITION_CONTROL) { float pos_err; - if (!pos_estimate_linear || !pos_estimate_circular) { - set_error(ERROR_INVALID_ESTIMATE); - return false; - } - if (config_.circular_setpoints && pos_estimate_circular) { + if (config_.circular_setpoints) { + if(!pos_estimate_circular) { + set_error(ERROR_INVALID_ESTIMATE); + return false; + } // Keep pos setpoint from drifting pos_setpoint_ = fmodf_pos(pos_setpoint_, *pos_wrap_src_); // Circular delta pos_err = pos_setpoint_ - *pos_estimate_circular; pos_err = wrap_pm(pos_err, 0.5f * *pos_wrap_src_); } else { + if(!pos_estimate_linear) { + set_error(ERROR_INVALID_ESTIMATE); + return false; + } pos_err = pos_setpoint_ - *pos_estimate_linear; } diff --git a/Firmware/MotorControl/trapTraj.hpp b/Firmware/MotorControl/trapTraj.hpp index 82a1e060..9fa5ae33 100644 --- a/Firmware/MotorControl/trapTraj.hpp +++ b/Firmware/MotorControl/trapTraj.hpp @@ -4,9 +4,9 @@ class TrapezoidalTrajectory { public: struct Config_t { - float vel_limit = 2.0f; // [turn/s] - float accel_limit = 0.5f; // [turn/s^2] - float decel_limit = 0.5f; // [turn/s^2] + float vel_limit = 2.0f; // [turn/s] + float accel_limit = 0.5f; // [turn/s^2] + float decel_limit = 0.5f; // [turn/s^2] }; struct Step_t { diff --git a/docs/commands.md b/docs/commands.md index ebb47322..1b41b30f 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -44,8 +44,8 @@ Possible values are listed [here](api/odrive.axis.controller.controlmode). As of version v0.5.0, ODrive now intercepts the incoming commands and can apply filters to them. The old protocol values `pos_setpoint`, `vel_setpoint`, and `current_setpoint` are still used internally by the closed-loop cascade control, but the user cannot write to them directly. This allows us to condense the number of ways the ODrive accepts motion commands. The new commands are: -* `.controller.input_pos = ` -* `.controller.input_vel = ` +* `.controller.input_pos = ` +* `.controller.input_vel = ` * `.controller.input_torque = ` Modes can be selected by changing `.controller.config.input_mode`. diff --git a/docs/getting-started.md b/docs/getting-started.md index f8afa2da..0f794782 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -250,7 +250,7 @@ Let's get motor 0 up and running. The procedure for motor 1 is exactly the same, 2. Type `odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL` Enter. From now on the ODrive will try to hold the motor's position. If you try to turn it by hand, it will fight you gently. That is unless you bump up `odrv0.axis0.motor.config.current_lim`, in which case it will fight you more fiercely. If the motor begins to vibrate either immediately or after being disturbed you will need to [lower the controller gains](control.md). -3. Send the motor a new position setpoint. `odrv0.axis0.controller.input_pos = 10` Enter. The units are in radians. +3. Send the motor a new position setpoint. `odrv0.axis0.controller.input_pos = 1` Enter. The units are in turns. 4. At this point you will probably want to [Properly tune](control.md) the motor controller in order to maximize system performance. ## Other control modes @@ -273,7 +273,7 @@ Asking the ODrive controller to go as hard as it can to raw setpoints may result You can use the second order position filter in these cases. Set the filter bandwidth: `axis.controller.config.input_filter_bandwidth = 2.0` [1/s]
    Activate the setpoint filter: `axis.controller.config.input_mode = INPUT_MODE_POS_FILTER`.
    -You can now control the velocity with `axis.controller.input_pos = 10` [radians]. +You can now control the velocity with `axis.controller.input_pos = 1` [turns]. ![secondOrderResponse](secondOrderResponse.PNG)
    Step response of a 1000 to 0 position input with a filter bandwidth of 1.0 [/sec]. @@ -294,9 +294,9 @@ In the above image blue is position and orange is velocity. ``` `vel_limit` is the maximum planned trajectory speed. This sets your coasting speed.
    -`accel_limit` is the maximum acceleration in radians / sec^2
    -`decel_limit` is the maximum deceleration in radians / sec^2
    -`controller.config.inertia` is a value which correlates acceleration (in counts / sec^2) and motor current. It is 0 by default. It is optional, but can improve response of your system if correctly tuned. Keep in mind this will need to change with the load / mass of your system. +`accel_limit` is the maximum acceleration in turns / sec^2
    +`decel_limit` is the maximum deceleration in turns / sec^2
    +`controller.config.inertia` is a value which correlates acceleration (in turns / sec^2) and motor current. It is 0 by default. It is optional, but can improve response of your system if correctly tuned. Keep in mind this will need to change with the load / mass of your system. All values should be strictly positive (>= 0). @@ -333,7 +333,7 @@ To enable Circular position control, set `axis.controller.config.circular_setpoi This mode is useful for continuous incremental position movement. For example a robot rolling indefinitely, or an extruder motor or conveyor belt moving with controlled increments indefinitely. In the regular position mode, the `input_pos` would grow to a very large value and would lose precision due to floating point rounding. -In this mode, the controller will try to track the position within only one turn of the motor. Specifically, `input_pos` is expected in the range `[0, 1]`. If the `input_pos` is incremented to outside this range (say via step/dir input), it is automatically wrapped around into the correct value. +In this mode, the controller will try to track the position within only one turn of the motor. Specifically, `input_pos` is expected in the range `[0, 1)`. If the `input_pos` is incremented to outside this range (say via step/dir input), it is automatically wrapped around into the correct value. Note that in this mode `encoder.pos_circular` is used for feedback instead of `encoder.pos_estimate`. If you try to increment the axis with a large step in one go that exceeds `1` turn, the motor will go to the same angle around the wrong way. This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a larger circular range. Set `controller.config.circular_setpoints_range = N`. Choose N to give you an appropriate circular space for your application. diff --git a/docs/interfaces.md b/docs/interfaces.md index 225cd7c4..31bd305e 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -113,7 +113,7 @@ You can control the ODrive directly from an hobby RC receiver. Some GPIO pins can be used for PWM input, if they are not allocated to other functions. For example, you must disable the UART to use GPIO 1,2. See the [pin function priorities](#pin-function-priorities) for more detail. Any of the numerical parameters that are writable from the ODrive Tool can be hooked up to a PWM input. -As an example, we'll configure GPIO4 to control the angle of axis 0. We want the axis to move within a range of -2 to 2 radians. +As an example, we'll configure GPIO4 to control the angle of axis 0. We want the axis to move within a range of -2 to 2 turns. 1. Make sure you're able control the axis 0 angle by writing to `odrv0.axis0.controller.input_pos`. If you need help with this follow the [getting started guide](getting-started.md). 2. If you want to control your ODrive with the PWM input without using anything else to activate the ODrive, you can configure the ODrive such that axis 0 automatically goes operational at startup. See [here](commands.md#startup-procedure) for more information. diff --git a/docs/odrivetool.md b/docs/odrivetool.md index cbcff477..4aab38a0 100644 --- a/docs/odrivetool.md +++ b/docs/odrivetool.md @@ -216,7 +216,7 @@ For example, to plot the approximate motor torque [N.cm] and the velocity [RPM] # If you want to plot different values, change them here. # You can plot any number of values concurrently. cancellation_token = start_liveplotter(lambda: [ - ((my_odrive.axis0.encoder.vel_estimate*60/(6.2832)), # radians to rpm + ((my_odrive.axis0.encoder.vel_estimate*60), # turns/s to rpm ((my_odrive.axis0.motor.current_control.Iq_setpoint * my_odrive.axis0.motor.config.torque_constant), # Torque [Nm] ]) ``` diff --git a/tools/odrive/shell.py b/tools/odrive/shell.py index adbef677..661416d5 100644 --- a/tools/odrive/shell.py +++ b/tools/odrive/shell.py @@ -30,10 +30,10 @@ def print_help(args, have_devices): print('Type "odrv0." and press ') print('This will present you with all the properties that you can reference') print('') - print('For example: "odrv0.motor0.encoder.pos_estimate"') - print('will print the current encoder position on motor 0') - print('and "odrv0.motor0.input_pos = 10000"') - print('will send motor0 to 10000') + print('For example: "odrv0.axis0.encoder.pos_estimate"') + print('will print the current encoder position on axis 0') + print('and "odrv0.axis0.controller.input_pos = 0.5"') + print('will send axis 0 to 0.5 turns') print('') From 0670017a252f6d41632171cf8c10e2fc0eb02d24 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Mon, 27 Jul 2020 21:39:57 -0400 Subject: [PATCH 535/549] In axis: counts_per_step to turns_per_step (broken during previous merge) --- Firmware/MotorControl/axis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 99339d50..648a20a6 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -120,7 +120,7 @@ bool Axis::wait_for_current_meas() { void Axis::step_cb() { const bool dir_pin = dir_port_->IDR & dir_pin_; const int32_t dir = (-1 + 2 * dir_pin) * step_dir_active_; - controller_.input_pos_ += dir * config_.counts_per_step; + controller_.input_pos_ += dir * config_.turns_per_step; controller_.input_pos_updated(); }; From d0984f03de73a3f20d219e030fc71b5e0fa97cdd Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 28 Jul 2020 17:15:55 +0200 Subject: [PATCH 536/549] HWIL tests: tolerate more sin/cos encoder noise --- tools/odrive/tests/encoder_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/odrive/tests/encoder_test.py b/tools/odrive/tests/encoder_test.py index dafb88dd..d24632dc 100644 --- a/tools/odrive/tests/encoder_test.py +++ b/tools/odrive/tests/encoder_test.py @@ -23,7 +23,7 @@ class TestEncoderBase(): or a constant. """ - def run_generic_encoder_test(self, encoder, true_cpr, true_rps): + def run_generic_encoder_test(self, encoder, true_cpr, true_rps, noise=1): encoder.config.cpr = true_cpr true_cps = true_cpr * true_rps @@ -69,7 +69,7 @@ class TestEncoderBase(): slope, offset, fitted_curve = fit_line(data[:,(0,6)]) test_assert_eq(slope, 0.0, range = true_cpr * abs(true_rps) * 0.01) test_assert_eq(offset, true_cpr * true_rps, accuracy = 0.02) - test_curve_fit(data[:,(0,6)], fitted_curve, max_mean_err = true_cpr * 0.05, inlier_range = true_cpr * 0.05, max_outliers = len(data[:,0]) * 0.05) + test_curve_fit(data[:,(0,6)], fitted_curve, max_mean_err = true_cpr * 0.05, inlier_range = true_cpr * 0.05 * noise, max_outliers = len(data[:,0]) * 0.05) @@ -193,7 +193,7 @@ class TestSinCosEncoder(TestEncoderBase): enc.handle.config.bandwidth = 100 - self.run_generic_encoder_test(enc.handle, 6283, 1.0) + self.run_generic_encoder_test(enc.handle, 6283, 1.0, 2.0) From b0700ef37153f0031b4d5a0ae4e035b794e89bdd Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Tue, 28 Jul 2020 20:49:55 -0400 Subject: [PATCH 537/549] Documentation fixes for unit change Fixed naming, docs, and test for CAN protocol - set trap traj a per css is now set trap traj inertia --- Firmware/MotorControl/controller.hpp | 2 +- Firmware/MotorControl/motor.hpp | 2 +- Firmware/communication/can_simple.cpp | 6 ++--- Firmware/communication/can_simple.hpp | 4 +-- Firmware/odrive-interface.yaml | 15 +++++------ docs/ascii-protocol.md | 38 +++++++++++++-------------- docs/can-protocol.md | 2 +- docs/endstops.md | 4 +-- docs/getting-started.md | 2 +- docs/interfaces.md | 2 +- docs/odrivetool.md | 2 +- tools/odrive/tests/can_test.py | 4 +-- 12 files changed, 41 insertions(+), 42 deletions(-) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index cb27e808..8022e946 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -31,7 +31,7 @@ public: float torque_ramp_rate = 0.01f; // Nm / sec bool circular_setpoints = false; float circular_setpoint_range = 1.0f; // Circular range when circular_setpoints is true. [turn] - float inertia = 0.0f; // [A/(count/s^2)] + float inertia = 0.0f; // [Nm/(turn/s^2)] float input_filter_bandwidth = 2.0f; // [1/s] float homing_speed = 0.25f; // [turn/s] Anticogging_t anticogging; diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 85c2c1e9..b8fdcc33 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -36,7 +36,7 @@ public: }; // NOTE: for gimbal motors, all units of Nm are instead V. - // example: vel_gain is [V/(count/s)] instead of [Nm/(count/s)] + // example: vel_gain is [V/(turn/s)] instead of [Nm/(turn/s)] // example: current_lim and calibration_current will instead determine the maximum voltage applied to the motor. struct Config_t { bool pre_calibrated = false; // can be set to true to indicate that all values here are valid diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 18d39838..027bfbea 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -94,8 +94,8 @@ void CANSimple::handle_can_message(can_Message_t& msg) { case MSG_START_ANTICOGGING: start_anticogging_callback(axis, msg); break; - case MSG_SET_TRAJ_A_PER_CSS: - set_traj_A_per_css_callback(axis, msg); + case MSG_SET_TRAJ_INERTIA: + set_traj_inertia_callback(axis, msg); break; case MSG_SET_TRAJ_ACCEL_LIMITS: set_traj_accel_limits_callback(axis, msg); @@ -316,7 +316,7 @@ void CANSimple::set_traj_accel_limits_callback(Axis* axis, can_Message_t& msg) { axis->trap_traj_.config_.decel_limit = can_getSignal(msg, 32, 32, true); } -void CANSimple::set_traj_A_per_css_callback(Axis* axis, can_Message_t& msg) { +void CANSimple::set_traj_inertia_callback(Axis* axis, can_Message_t& msg) { axis->controller_.config_.inertia = can_getSignal(msg, 0, 32, true); } diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp index a4ccae4d..0168b978 100644 --- a/Firmware/communication/can_simple.hpp +++ b/Firmware/communication/can_simple.hpp @@ -25,7 +25,7 @@ class CANSimple { MSG_START_ANTICOGGING, MSG_SET_TRAJ_VEL_LIMIT, MSG_SET_TRAJ_ACCEL_LIMITS, - MSG_SET_TRAJ_A_PER_CSS, + MSG_SET_TRAJ_INERTIA, MSG_GET_IQ, MSG_GET_SENSORLESS_ESTIMATES, MSG_RESET_ODRIVE, @@ -57,7 +57,7 @@ class CANSimple { static void start_anticogging_callback(Axis* axis, can_Message_t& msg); static void set_traj_vel_limit_callback(Axis* axis, can_Message_t& msg); static void set_traj_accel_limits_callback(Axis* axis, can_Message_t& msg); - static void set_traj_A_per_css_callback(Axis* axis, can_Message_t& msg); + static void set_traj_inertia_callback(Axis* axis, can_Message_t& msg); static void get_iq_callback(Axis* axis, can_Message_t& msg); static void get_sensorless_estimates_callback(Axis* axis, can_Message_t& msg); static void get_vbus_voltage_callback(Axis* axis, can_Message_t& msg); diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 8ac43ba0..271a7837 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -621,9 +621,8 @@ interfaces: flags: Overspeed: doc: | - Try increasing `config.vel_limit`. The default of 20,000 encoder - counts per second gives a motor speed of only ~146 RPM with the - common CUI-AMT102 8192 count per rotation encoder. Note: Even if + Try increasing `config.vel_limit`. The default of 2 turns per second + gives a motor speed of only 120 RPM. Note: Even if you do not commanded your motor to exceed `config.vel_limit` sudden changes in the load placed on a motor may cause this speed to be temporarily exceeded, resulting in this error. @@ -687,13 +686,13 @@ interfaces: type: bool circular_setpoint_range: type: float32 - doc: circular range in [rad] for position setpoints when circular_setpoints is True + doc: circular range in [turns] for position setpoints when circular_setpoints is True homing_speed: type: float32 - unit: counts/s + unit: turns/s inertia: type: float32 - unit: A/(count/s^2) + unit: A/(turn/s^2) axis_to_mirror: uint8 mirror_ratio: float32 load_encoder_axis: @@ -957,8 +956,8 @@ valuetypes: brief: Ramps a velocity command from the current value to the target value. doc: | ### Configuration Values: - * `config.vel_ramp_rate` [cpr/sec] - * `config.inertia` [A/(count/s^2))] + * `config.vel_ramp_rate` [turn/sec] + * `config.inertia` [A/(turn/s^2))] ### Valid inputs: * `input_vel` diff --git a/docs/ascii-protocol.md b/docs/ascii-protocol.md index 93ba8a02..948e7307 100644 --- a/docs/ascii-protocol.md +++ b/docs/ascii-protocol.md @@ -32,9 +32,9 @@ t motor destination ``` * `t` for trajectory * `motor` is the motor number, `0` or `1`. -* `destination` is the goal position, in encoder counts. +* `destination` is the goal position, in [turns]. -Example: `t 0 -20000` +Example: `t 0 -2` For general moving around of the axis, this is the recommended command. @@ -45,26 +45,26 @@ 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. ``` -q motor position velocity_lim current_lim +q motor position velocity_lim torque_lim ``` * `q` for position * `motor` is the motor number, `0` or `1`. -* `position` is the desired position, in encoder counts. -* `velocity_lim` is the velocity limit, in counts/s (optional). -* `current_lim` is the current limit, in A (optional). +* `position` is the desired position, in [turns]. +* `velocity_lim` is the velocity limit, in [turns/s] (optional). +* `torque_lim` is the torque limit, in [Nm] (optional). -Example: `q 0 -20000 10000 10` +Example: `q 0 -2 1 0.1` ``` p motor position velocity_ff current_ff ``` * `p` for position * `motor` is the motor number, `0` or `1`. -* `position` is the desired position, in encoder counts. -* `velocity_ff` is the velocity feed-forward term, in counts/s (optional). -* `current_ff` is the current feed-forward term, in A (optional). +* `position` is the desired position, in [turns]. +* `velocity_ff` is the velocity feed-forward term, in [turns/s] (optional). +* `torque_ff` is the current feed-forward term, in [Nm] (optional). -Example: `p 0 -20000 0 0` +Example: `p 0 -2 0 0` Note that if you don't know what feed-forward is or what it's used for, simply omit it. @@ -72,14 +72,14 @@ This command updates the watchdog timer for the motor. #### Motor Velocity command ``` -v motor velocity current_ff +v motor velocity torque_ff ``` * `v` for velocity * `motor` is the motor number, `0` or `1`. -* `velocity` is the desired velocity in counts/s. -* `current_ff` is the current feed-forward term, in A (optional). +* `velocity` is the desired velocity in [turns/s]. +* `torque_ff` is the torque feed-forward term, in [Nm] (optional). -Example: `v 0 1000 0` +Example: `v 0 1 0` Note that if you don't know what feed-forward is or what it's used for, simply omit it. @@ -87,11 +87,11 @@ This command updates the watchdog timer for the motor. #### Motor Current command ``` -c motor current +c motor torque ``` * `c` for current * `motor` is the motor number, `0` or `1`. -* `current` is the desired current in A. +* `torque` is the desired current in [Nm]. This command updates the watchdog timer for the motor. @@ -103,8 +103,8 @@ response: pos vel ``` * `f` for feedback -* `pos` is the encoder position in counts (float) -* `vel` is the encoder velocity in counts/s (float) +* `pos` is the encoder position in [turns] (float) +* `vel` is the encoder velocity in [turns/s] (float) #### Update motor watchdog ``` diff --git a/docs/can-protocol.md b/docs/can-protocol.md index e0f4c75e..8368625a 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -54,7 +54,7 @@ CMD ID | Name | Sender | Signals | Start byte | Signal Type | Bits | Factor | Of 0x010 | Start Anticogging | Master | - | - | - | - | - | - | - 0x011 | Set Traj Vel Limit | Master | Traj Vel Limit | 0 | IEEE 754 Float | 32 | 1 | 0 | Intel 0x012 | Set Traj Accel Limits | Master | Traj Accel Limit
    Traj Decel Limit | 0
    4 | IEEE 754 Float
    IEEE 754 Float | 32
    32 | 1
    1 | 0
    0 | Intel
    Intel -0x013 | Set Traj A per Count / s^2 | Master | Traj A per CSS | 0 | IEEE 754 Float | 32 | 1 | 0 | Intel +0x013 | Set Traj Inertia | Master | Traj Inertia | 0 | IEEE 754 Float | 32 | 1 | 0 | Intel 0x014 | Get IQ\* | Axis | Iq Setpoint
    Iq Measured | 0
    4 | IEEE 754 Float
    IEEE 754 Float | 32
    32 | 1
    1 | 0
    0 | Intel
    Intel 0x015 | Get Sensorless Estimates\* | Master | Sensorless Pos Estimate
    Sensorless Vel Estimate | 0
    4 | IEEE 754 Float
    IEEE 754 Float | 32
    32 | 1
    1 | 0
    0 | Intel
    Intel 0x016 | Reboot ODrive | Master\*\*\* | - | - | - | - | - | - | - diff --git a/docs/endstops.md b/docs/endstops.md index 0336b6dd..9dc84e6c 100644 --- a/docs/endstops.md +++ b/docs/endstops.md @@ -94,9 +94,9 @@ There is one additional configuration parameter specifically for the homing proc Name | Type | Default --- | -- | -- -homing_speed | float | 2000.0f +homing_speed | float | 0.25f -`homing_speed` is the axis travel speed during homing, in counts/second. If you are using SPI based encoders and the axis is homing in the wrong direction, you can enter a negative value for the homing speed and a negative value for the minimum endstop offset. +`homing_speed` is the axis travel speed during homing, in [turns/second]. If you are using SPI based encoders and the axis is homing in the wrong direction, you can enter a negative value for the homing speed and a negative value for the minimum endstop offset. ### Performing the Homing Sequence diff --git a/docs/getting-started.md b/docs/getting-started.md index 0f794782..94bcf15e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -296,7 +296,7 @@ In the above image blue is position and orange is velocity. `vel_limit` is the maximum planned trajectory speed. This sets your coasting speed.
    `accel_limit` is the maximum acceleration in turns / sec^2
    `decel_limit` is the maximum deceleration in turns / sec^2
    -`controller.config.inertia` is a value which correlates acceleration (in turns / sec^2) and motor current. It is 0 by default. It is optional, but can improve response of your system if correctly tuned. Keep in mind this will need to change with the load / mass of your system. +`controller.config.inertia` is a value which correlates acceleration (in turns / sec^2) and motor torque. It is 0 by default. It is optional, but can improve response of your system if correctly tuned. Keep in mind this will need to change with the load / mass of your system. All values should be strictly positive (>= 0). diff --git a/docs/interfaces.md b/docs/interfaces.md index 31bd305e..ebe279e9 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -102,7 +102,7 @@ Pinout: To enable step/dir mode for the GPIO, set `.config.enable_step_dir` to true for each axis that you wish to use this on. Axis 0 step/dir pins conflicts with UART, and the UART takes priority. So to be able to use step/dir on Axis 0, you must also set `odrv0.config.enable_uart = False`. See the [pin function priorities](#pin-function-priorities) for more detail. Don't forget to save configuration and reboot. -There is also a config variable called `.config.turns_per_step`, which specifies how many encoder counts a "step" corresponds to. It can be any floating point value. +There is also a config variable called `.config.turns_per_step`, which specifies how many turns a "step" corresponds to. The default value is 1.0f/1024.0f. It can be any floating point value. The maximum step rate is pending tests, but it should handle at least 50kHz. If you want to test it, please be aware that the failure mode on too high step rates is expected to be that the motors shuts down and coasts. Please be aware that there is no enable line right now, and the step/direction interface is enabled by default, and remains active as long as the ODrive is in position control mode. To get the ODrive to go into position control mode at bootup, see how to configure the [startup procedure](commands.md#startup-procedure). diff --git a/docs/odrivetool.md b/docs/odrivetool.md index 4aab38a0..3cd01972 100644 --- a/docs/odrivetool.md +++ b/docs/odrivetool.md @@ -211,7 +211,7 @@ To change what parameters are plotted open odrivetool (located in Anaconda3\Scri my_odrive.axis1.encoder.pos_estimate, ]) ``` -For example, to plot the approximate motor torque [N.cm] and the velocity [RPM] of axis1 with a 150KV motor and an 8192 count per rotation econder you would modify the function to read: +For example, to plot the approximate motor torque [Nm] and the velocity [RPM] of axis0, you would modify the function to read: ``` # If you want to plot different values, change them here. # You can plot any number of values concurrently. diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index 13d337ec..512076e8 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -32,7 +32,7 @@ command_set = { 'start_anticogging': (0x010, []), # untested 'set_traj_vel_limit': (0x011, [('traj_vel_limit', 'f', 1)]), # tested 'set_traj_accel_limits': (0x012, [('traj_accel_limit', 'f', 1), ('traj_decel_limit', 'f', 1)]), # tested - 'set_traj_a_per_css': (0x013, [('a_per_css', 'f', 1)]), # tested + 'set_traj_inertia': (0x013, [('inertia', 'f', 1)]), # tested 'get_iq': (0x014, [('iq_setpoint', 'f', 1), ('iq_measured', 'f', 1)]), # untested 'get_sensorless_estimates': (0x015, [('sensorless_pos_estimate', 'f', 1), ('sensorless_vel_estimate', 'f', 1)]), # untested 'reboot': (0x016, []), # tested @@ -206,7 +206,7 @@ class TestSimpleCAN(): test_assert_eq(axis.trap_traj.config.accel_limit, 98.231, range=0.0001) test_assert_eq(axis.trap_traj.config.decel_limit, -12.234, range=0.0001) - my_cmd('set_traj_a_per_css', a_per_css=55.086) + my_cmd('set_traj_inertia', inertia=55.086) fence() test_assert_eq(axis.controller.config.inertia, 55.086, range=0.0001) From e0f9665d2c966fa2e9135d1ec5f16131888e4fbe Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Tue, 28 Jul 2020 21:25:30 -0400 Subject: [PATCH 538/549] Fixed units for inertia --- Firmware/odrive-interface.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index 271a7837..fd36a11c 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -692,7 +692,7 @@ interfaces: unit: turns/s inertia: type: float32 - unit: A/(turn/s^2) + unit: Nm/(turn/s^2) axis_to_mirror: uint8 mirror_ratio: float32 load_encoder_axis: @@ -957,7 +957,7 @@ valuetypes: doc: | ### Configuration Values: * `config.vel_ramp_rate` [turn/sec] - * `config.inertia` [A/(turn/s^2))] + * `config.inertia` [Nm/(turn/s^2))] ### Valid inputs: * `input_vel` From b63983ef5cdfddfa51b4710eff61cd557fa8aaf2 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Wed, 29 Jul 2020 20:46:26 -0400 Subject: [PATCH 539/549] Fixed units for can_simple protocol and updated can HWIL test --- Firmware/communication/can_simple.cpp | 12 ++++++------ docs/can-protocol.md | 6 +++--- tools/odrive/tests/can_test.py | 24 ++++++++++++------------ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 027bfbea..77e8f416 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -279,19 +279,19 @@ void CANSimple::get_encoder_count_callback(Axis* axis, can_Message_t& msg) { } void CANSimple::set_input_pos_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.input_pos_ = can_getSignal(msg, 0, 32, true); - axis->controller_.input_vel_ = can_getSignal(msg, 32, 16, true, 0.1f, 0); - axis->controller_.input_torque_ = can_getSignal(msg, 48, 16, true, 0.01f, 0); + axis->controller_.input_pos_ = can_getSignal(msg, 0, 32, true); + axis->controller_.input_vel_ = can_getSignal(msg, 32, 16, true, 0.001f, 0); + axis->controller_.input_torque_ = can_getSignal(msg, 48, 16, true, 0.001f, 0); axis->controller_.input_pos_updated(); } void CANSimple::set_input_vel_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.input_vel_ = can_getSignal(msg, 0, 32, true, 0.01f, 0.0f); - axis->controller_.input_torque_ = can_getSignal(msg, 32, 16, true, 0.01f, 0.0f); + axis->controller_.input_vel_ = can_getSignal(msg, 0, 32, true); + axis->controller_.input_torque_ = can_getSignal(msg, 32, 32, true); } void CANSimple::set_input_torque_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.input_torque_ = can_getSignal(msg, 0, 32, true, 0.01f, 0); + axis->controller_.input_torque_ = can_getSignal(msg, 0, 32, true); } void CANSimple::set_controller_modes_callback(Axis* axis, can_Message_t& msg) { diff --git a/docs/can-protocol.md b/docs/can-protocol.md index 8368625a..0b3f0c84 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -47,9 +47,9 @@ CMD ID | Name | Sender | Signals | Start byte | Signal Type | Bits | Factor | Of 0x009 | Get Encoder Estimates\* | Master | Encoder Pos Estimate
    Encoder Vel Estimate | 0
    4 | IEEE 754 Float
    IEEE 754 Float | 32
    32 | 1
    1 | 0
    0 | Intel
    Intel 0x00A | Get Encoder Count\* | Master | Encoder Shadow Count
    Encoder Count in CPR | 0
    4 | Signed Int
    Signed Int | 32
    32 | 1
    1 | 0
    0 | Intel
    Intel 0x00B | Set Controller Modes | Master | Control Mode
    Input Mode | 0
    4 | Signed Int
    Signed Int | 32
    32 | 1
    1 | 0
    0 | Intel
    Intel -0x00C | Set Input Pos | Master | Input Pos
    Vel FF
    Current FF | 0
    4
    6 | Signed Int
    Signed Int
    Signed Int | 32
    16
    16 | 1
    0.1
    0.01 | 0
    0
    0 | Intel
    Intel
    Intel -0x00D | Set Input Vel | Master | Input Vel
    Current FF | 0
    4 | Signed Int
    Signed Int | 32
    32 | 0.01
    0.01 | 0
    0 | Intel
    Intel -0x00E | Set Input Current | Master | Input Current | 0 | Signed Int | 32 | 0.01 | 0 | Intel +0x00C | Set Input Pos | Master | Input Pos
    Vel FF
    Torque FF | 0
    4
    6 | IEEE 754 Float
    Signed Int
    Signed Int | 32
    16
    16 | 1
    0.001
    0.001 | 0
    0
    0 | Intel
    Intel
    Intel +0x00D | Set Input Vel | Master | Input Vel
    Torque FF | 0
    4 | IEEE 754 Float
    IEEE 754 Float | 32
    32 | 1
    1 | 0
    0 | Intel
    Intel +0x00E | Set Input Torque | Master | Input Torque | 0 | IEEE 754 Float | 32 | 1 | 0 | Intel 0x00F | Set Velocity Limit | Master | Velocity Limit | 0 | IEEE 754 Float | 32 | 1 | 0 | Intel 0x010 | Start Anticogging | Master | - | - | - | - | - | - | - 0x011 | Set Traj Vel Limit | Master | Traj Vel Limit | 0 | IEEE 754 Float | 32 | 1 | 0 | Intel diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index 512076e8..863eaff1 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -25,9 +25,9 @@ command_set = { 'get_encoder_estimates': (0x009, [('encoder_pos_estimate', 'f', 1), ('encoder_vel_estimate', 'f', 1)]), # partially tested 'get_encoder_count': (0x00a, [('encoder_shadow_count', 'i', 1), ('encoder_count', 'i', 1)]), # partially tested 'set_controller_modes': (0x00b, [('control_mode', 'i', 1), ('input_mode', 'i', 1)]), # tested - 'set_input_pos': (0x00c, [('input_pos', 'i', 1), ('vel_ff', 'h', 0.1), ('cur_ff', 'h', 0.01)]), # tested - 'set_input_vel': (0x00d, [('input_vel', 'i', 0.01), ('cur_ff', 'h', 0.01)]), # tested - 'set_input_torque': (0x00e, [('input_torque', 'i', 0.01)]), # tested + 'set_input_pos': (0x00c, [('input_pos', 'f', 1), ('vel_ff', 'h', 0.001), ('torque_ff', 'h', 0.001)]), # tested + 'set_input_vel': (0x00d, [('input_vel', 'f', 1), ('torque_ff', 'f', 1)]), # tested + 'set_input_torque': (0x00e, [('input_torque', 'f', 1)]), # tested 'set_velocity_limit': (0x00f, [('velocity_limit', 'f', 1)]), # tested 'start_anticogging': (0x010, []), # untested 'set_traj_vel_limit': (0x011, [('traj_vel_limit', 'f', 1)]), # tested @@ -176,26 +176,26 @@ class TestSimpleCAN(): axis.controller.input_pos = 1234 axis.controller.input_vel = 1234 axis.controller.input_torque = 1234 - my_cmd('set_input_pos', input_pos=1, vel_ff=2, cur_ff=3) + my_cmd('set_input_pos', input_pos=1.23, vel_ff=1.2, torque_ff=3.4) fence() - test_assert_eq(axis.controller.input_pos, 1.0, range=0.1) - test_assert_eq(axis.controller.input_vel, 2.0, range=0.01) - test_assert_eq(axis.controller.input_torque, 3.0, range=0.001) + test_assert_eq(axis.controller.input_pos, 1.23, range=0.1) + test_assert_eq(axis.controller.input_vel, 1.2, range=0.01) + test_assert_eq(axis.controller.input_torque, 3.4, range=0.001) axis.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL - my_cmd('set_input_vel', input_vel=-10.0, cur_ff=30.1234) + my_cmd('set_input_vel', input_vel=-10.5, torque_ff=0.1234) fence() - test_assert_eq(axis.controller.input_vel, -10.0, range=0.01) - test_assert_eq(axis.controller.input_torque, 30.1234, range=0.01) + test_assert_eq(axis.controller.input_vel, -10.5, range=0.01) + test_assert_eq(axis.controller.input_torque, 0.1234, range=0.01) axis.controller.config.control_mode = CONTROL_MODE_TORQUE_CONTROL my_cmd('set_input_torque', input_torque=0.1) fence() test_assert_eq(axis.controller.input_torque, 0.1, range=0.01) - my_cmd('set_velocity_limit', velocity_limit=23456.78) + my_cmd('set_velocity_limit', velocity_limit=2.345678) fence() - test_assert_eq(axis.controller.config.vel_limit, 23456.78, range=0.001) + test_assert_eq(axis.controller.config.vel_limit, 2.345678, range=0.001) my_cmd('set_traj_vel_limit', traj_vel_limit=123.456) fence() From a0bf270141389e33501cf23a55267e5654dd844d Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 30 Jul 2020 15:55:53 +0200 Subject: [PATCH 540/549] fix version.py version_str_to_tuple() was misbehaved if evaluated on a raw commit hash that contains only decimal digits. See https://github.com/madcowswe/ODrive/runs/920754951 --- tools/odrive/version.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/odrive/version.py b/tools/odrive/version.py index 45f81777..7a323899 100644 --- a/tools/odrive/version.py +++ b/tools/odrive/version.py @@ -11,8 +11,13 @@ def version_str_to_tuple(version_string): (major, minor, revision, prerelease) Example: "fw-v0.3.6-23" => (0, 3, 6, True) + + If version_string does not match the pattern above, this function throws an + Exception. """ - regex=r'.*v([0-9a-zA-Z]+).([0-9a-zA-Z]+).([0-9a-zA-Z]+)(.*)' + regex=r'.*v([0-9]+)\.([0-9]+)\.([0-9]+)(.*)' + if not re.match(regex, version_string): + raise Exception() return (int(re.sub(regex, r"\1", version_string)), int(re.sub(regex, r"\2", version_string)), int(re.sub(regex, r"\3", version_string)), From db41fe9a2904d2309402da42661b2e89212c96b7 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Thu, 30 Jul 2020 18:16:56 -0400 Subject: [PATCH 541/549] Added closed loop test to CAN HWIL test Fixed ascii protocol docs: current -> torque --- docs/ascii-protocol.md | 8 +-- tools/odrive/tests/can_test.py | 126 ++++++++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 5 deletions(-) diff --git a/docs/ascii-protocol.md b/docs/ascii-protocol.md index 948e7307..9625ec47 100644 --- a/docs/ascii-protocol.md +++ b/docs/ascii-protocol.md @@ -56,13 +56,13 @@ q motor position velocity_lim torque_lim Example: `q 0 -2 1 0.1` ``` -p motor position velocity_ff current_ff +p motor position velocity_ff torque_ff ``` * `p` for position * `motor` is the motor number, `0` or `1`. * `position` is the desired position, in [turns]. * `velocity_ff` is the velocity feed-forward term, in [turns/s] (optional). -* `torque_ff` is the current feed-forward term, in [Nm] (optional). +* `torque_ff` is the torque feed-forward term, in [Nm] (optional). Example: `p 0 -2 0 0` @@ -89,9 +89,9 @@ This command updates the watchdog timer for the motor. ``` c motor torque ``` -* `c` for current +* `c` for torque * `motor` is the motor number, `0` or `1`. -* `torque` is the desired current in [Nm]. +* `torque` is the desired torque in [Nm]. This command updates the watchdog timer for the motor. diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index 863eaff1..c3b85223 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -231,6 +231,130 @@ class TestSimpleCAN(): time.sleep(2.0) odrive.prepare(logger) +class TestSimpleCANClosedLoop(): + def prepare(self, odrive: ODriveComponent, canbus: CanInterfaceComponent, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, node_id: int, extended_id: bool, logger: Logger): + # Make sure there are no funny configurations active + logger.debug('Setting up clean configuration...') + axis_ctx.parent.erase_config_and_reboot() + + # run calibration + axis_ctx.handle.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE + while axis_ctx.handle.current_state != AXIS_STATE_IDLE: + time.sleep(1) + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_no_error(axis_ctx) + + # Return a context that can be used in a with-statement. + class safe_terminator(): + def __enter__(self): + pass + def __exit__(self, exc_type, exc_val, exc_tb): + logger.debug('clearing config...') + axis_ctx.handle.requested_state = AXIS_STATE_IDLE + time.sleep(0.005) + axis_ctx.parent.erase_config_and_reboot() + return safe_terminator() + + + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + can_interfaces = list(testrig.get_connected_components(odrive.can, CanInterfaceComponent)) + for num in range(2): + encoders = testrig.get_connected_components({ + 'a': (odrive.encoders[num].a, False), + 'b': (odrive.encoders[num].b, False) + }, EncoderComponent) + motors = testrig.get_connected_components(odrive.axes[num], MotorComponent) + for motor, encoder in itertools.product(motors, encoders): + if encoder.impl in testrig.get_connected_components(motor): + yield (odrive, can_interfaces, odrive.axes[num], motor, encoder, 0, False) + + def run_test(self, odrive: ODriveComponent, canbus: CanInterfaceComponent, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, node_id: int, extended_id: bool, logger: Logger): + # this test is a sanity check to make sure that closed loop operation works + # actual testing of closed loop functionality should be tested using closed_loop_test.py + + with self.prepare(odrive, canbus, axis_ctx, motor_ctx, enc_ctx, node_id, extended_id, logger): + def my_cmd(cmd_name, **kwargs): command(canbus.handle, node_id, extended_id, cmd_name, **kwargs) + def my_req(cmd_name, **kwargs): return asyncio.run(request(canbus.handle, node_id, extended_id, cmd_name, **kwargs)) + def fence(): my_req('get_vbus_voltage') # fence to ensure the CAN command was sent + + # make sure no gpio input is overwriting our values + odrive.unuse_gpios() + + axis_ctx.handle.config.enable_watchdog = False + axis_ctx.handle.clear_errors() + axis_ctx.handle.config.can_node_id = node_id + axis_ctx.handle.config.can_node_id_extended = extended_id + time.sleep(0.1) + + my_cmd('set_node_id', node_id=node_id+20) + asyncio.run(request(canbus.handle, node_id+20, extended_id, 'get_vbus_voltage')) + test_assert_eq(axis_ctx.handle.config.can_node_id, node_id+20) + + # Reset node ID to default value + command(canbus.handle, node_id+20, extended_id, 'set_node_id', node_id=node_id) + fence() + test_assert_eq(axis_ctx.handle.config.can_node_id, node_id) + + vel_limit = 15.0 + nominal_vel = 10.0 + axis_ctx.handle.controller.config.vel_limit = vel_limit + axis_ctx.handle.motor.config.current_lim = 30.0 + + my_cmd('set_requested_state', requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL) + fence() + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) + test_assert_no_error(axis_ctx) + + start_pos = axis_ctx.handle.encoder.pos_estimate + + # position test + logger.debug('Position control test') + my_cmd('set_controller_modes', control_mode=CONTROL_MODE_POSITION_CONTROL, input_mode=INPUT_MODE_PASSTHROUGH) # position control, passthrough + fence() + my_cmd('set_input_pos', input_pos=1.0, vel_ff=0, torque_ff=0) + fence() + test_assert_eq(axis_ctx.handle.controller.input_pos, 1.0, range=0.1) + time.sleep(2) + test_assert_eq(axis_ctx.handle.encoder.pos_estimate, start_pos + 1.0, range=0.1) + my_cmd('set_input_pos', input_pos=0, vel_ff=0, torque_ff=0) + fence() + time.sleep(2) + + test_assert_no_error(axis_ctx) + + # velocity test + logger.debug('Velocity control test') + my_cmd('set_controller_modes', control_mode=CONTROL_MODE_VELOCITY_CONTROL, input_mode=INPUT_MODE_PASSTHROUGH) # velocity control, passthrough + fence() + my_cmd('set_input_vel', input_vel = nominal_vel, torque_ff=0) + fence() + time.sleep(5) + test_assert_eq(axis_ctx.handle.encoder.vel_estimate, nominal_vel, range=nominal_vel * 0.05) # big range here due to cogging and other issues + my_cmd('set_input_vel', input_vel = 0, torque_ff=0) + fence() + time.sleep(2) + + test_assert_no_error(axis_ctx) + + # torque test + logger.debug('Torque control test') + my_cmd('set_controller_modes', control_mode=CONTROL_MODE_TORQUE_CONTROL, input_mode=INPUT_MODE_PASSTHROUGH) # torque control, passthrough + fence() + my_cmd('set_input_torque', input_torque=0.5) + fence() + time.sleep(5) + test_assert_eq(axis_ctx.handle.controller.input_torque, 0.5, range=0.1) + my_cmd('set_input_torque', input_torque = 0) + fence() + time.sleep(2) + + test_assert_no_error(axis_ctx) + + # go back to idle + my_cmd('set_requested_state', requested_state = AXIS_STATE_IDLE) + fence() + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) if __name__ == '__main__': - test_runner.run(TestSimpleCAN()) + test_runner.run([TestSimpleCAN(), TestSimpleCANClosedLoop()]) From d5861949556416d74025d5465a9fd7e11493eb01 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Thu, 30 Jul 2020 20:05:58 -0400 Subject: [PATCH 542/549] Split closed loop test from can_test.py into integration_test.py --- tools/odrive/tests/can_test.py | 127 +------------- tools/odrive/tests/integration_test.py | 230 +++++++++++++++++++++++++ 2 files changed, 231 insertions(+), 126 deletions(-) create mode 100644 tools/odrive/tests/integration_test.py diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index c3b85223..3195a806 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -231,130 +231,5 @@ class TestSimpleCAN(): time.sleep(2.0) odrive.prepare(logger) -class TestSimpleCANClosedLoop(): - def prepare(self, odrive: ODriveComponent, canbus: CanInterfaceComponent, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, node_id: int, extended_id: bool, logger: Logger): - # Make sure there are no funny configurations active - logger.debug('Setting up clean configuration...') - axis_ctx.parent.erase_config_and_reboot() - - # run calibration - axis_ctx.handle.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE - while axis_ctx.handle.current_state != AXIS_STATE_IDLE: - time.sleep(1) - test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) - test_assert_no_error(axis_ctx) - - # Return a context that can be used in a with-statement. - class safe_terminator(): - def __enter__(self): - pass - def __exit__(self, exc_type, exc_val, exc_tb): - logger.debug('clearing config...') - axis_ctx.handle.requested_state = AXIS_STATE_IDLE - time.sleep(0.005) - axis_ctx.parent.erase_config_and_reboot() - return safe_terminator() - - - def get_test_cases(self, testrig: TestRig): - for odrive in testrig.get_components(ODriveComponent): - can_interfaces = list(testrig.get_connected_components(odrive.can, CanInterfaceComponent)) - for num in range(2): - encoders = testrig.get_connected_components({ - 'a': (odrive.encoders[num].a, False), - 'b': (odrive.encoders[num].b, False) - }, EncoderComponent) - motors = testrig.get_connected_components(odrive.axes[num], MotorComponent) - for motor, encoder in itertools.product(motors, encoders): - if encoder.impl in testrig.get_connected_components(motor): - yield (odrive, can_interfaces, odrive.axes[num], motor, encoder, 0, False) - - def run_test(self, odrive: ODriveComponent, canbus: CanInterfaceComponent, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, node_id: int, extended_id: bool, logger: Logger): - # this test is a sanity check to make sure that closed loop operation works - # actual testing of closed loop functionality should be tested using closed_loop_test.py - - with self.prepare(odrive, canbus, axis_ctx, motor_ctx, enc_ctx, node_id, extended_id, logger): - def my_cmd(cmd_name, **kwargs): command(canbus.handle, node_id, extended_id, cmd_name, **kwargs) - def my_req(cmd_name, **kwargs): return asyncio.run(request(canbus.handle, node_id, extended_id, cmd_name, **kwargs)) - def fence(): my_req('get_vbus_voltage') # fence to ensure the CAN command was sent - - # make sure no gpio input is overwriting our values - odrive.unuse_gpios() - - axis_ctx.handle.config.enable_watchdog = False - axis_ctx.handle.clear_errors() - axis_ctx.handle.config.can_node_id = node_id - axis_ctx.handle.config.can_node_id_extended = extended_id - time.sleep(0.1) - - my_cmd('set_node_id', node_id=node_id+20) - asyncio.run(request(canbus.handle, node_id+20, extended_id, 'get_vbus_voltage')) - test_assert_eq(axis_ctx.handle.config.can_node_id, node_id+20) - - # Reset node ID to default value - command(canbus.handle, node_id+20, extended_id, 'set_node_id', node_id=node_id) - fence() - test_assert_eq(axis_ctx.handle.config.can_node_id, node_id) - - vel_limit = 15.0 - nominal_vel = 10.0 - axis_ctx.handle.controller.config.vel_limit = vel_limit - axis_ctx.handle.motor.config.current_lim = 30.0 - - my_cmd('set_requested_state', requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL) - fence() - test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) - test_assert_no_error(axis_ctx) - - start_pos = axis_ctx.handle.encoder.pos_estimate - - # position test - logger.debug('Position control test') - my_cmd('set_controller_modes', control_mode=CONTROL_MODE_POSITION_CONTROL, input_mode=INPUT_MODE_PASSTHROUGH) # position control, passthrough - fence() - my_cmd('set_input_pos', input_pos=1.0, vel_ff=0, torque_ff=0) - fence() - test_assert_eq(axis_ctx.handle.controller.input_pos, 1.0, range=0.1) - time.sleep(2) - test_assert_eq(axis_ctx.handle.encoder.pos_estimate, start_pos + 1.0, range=0.1) - my_cmd('set_input_pos', input_pos=0, vel_ff=0, torque_ff=0) - fence() - time.sleep(2) - - test_assert_no_error(axis_ctx) - - # velocity test - logger.debug('Velocity control test') - my_cmd('set_controller_modes', control_mode=CONTROL_MODE_VELOCITY_CONTROL, input_mode=INPUT_MODE_PASSTHROUGH) # velocity control, passthrough - fence() - my_cmd('set_input_vel', input_vel = nominal_vel, torque_ff=0) - fence() - time.sleep(5) - test_assert_eq(axis_ctx.handle.encoder.vel_estimate, nominal_vel, range=nominal_vel * 0.05) # big range here due to cogging and other issues - my_cmd('set_input_vel', input_vel = 0, torque_ff=0) - fence() - time.sleep(2) - - test_assert_no_error(axis_ctx) - - # torque test - logger.debug('Torque control test') - my_cmd('set_controller_modes', control_mode=CONTROL_MODE_TORQUE_CONTROL, input_mode=INPUT_MODE_PASSTHROUGH) # torque control, passthrough - fence() - my_cmd('set_input_torque', input_torque=0.5) - fence() - time.sleep(5) - test_assert_eq(axis_ctx.handle.controller.input_torque, 0.5, range=0.1) - my_cmd('set_input_torque', input_torque = 0) - fence() - time.sleep(2) - - test_assert_no_error(axis_ctx) - - # go back to idle - my_cmd('set_requested_state', requested_state = AXIS_STATE_IDLE) - fence() - test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) - if __name__ == '__main__': - test_runner.run([TestSimpleCAN(), TestSimpleCANClosedLoop()]) + test_runner.run(TestSimpleCAN()) diff --git a/tools/odrive/tests/integration_test.py b/tools/odrive/tests/integration_test.py new file mode 100644 index 00000000..0f66d8c6 --- /dev/null +++ b/tools/odrive/tests/integration_test.py @@ -0,0 +1,230 @@ +# this test runs the motor using CAN +# TODO - run a motor using all common use cases (uart, step/dir, pwm) + +import test_runner + +import struct +import can +import asyncio +import time +import math + +from fibre.utils import Logger +from odrive.enums import * +from test_runner import * + +# Each argument is described as tuple (name, format, scale). +# Struct format codes: https://docs.python.org/2/library/struct.html +command_set = { + 'heartbeat': (0x001, [('error', 'I', 1), ('current_state', 'I', 1)]), # tested + 'estop': (0x002, []), # tested + 'get_motor_error': (0x003, [('motor_error', 'I', 1)]), # untested + 'get_encoder_error': (0x004, [('encoder_error', 'I', 1)]), # untested + 'get_sensorless_error': (0x005, [('sensorless_error', 'I', 1)]), # untested + 'set_node_id': (0x006, [('node_id', 'I', 1)]), # tested + 'set_requested_state': (0x007, [('requested_state', 'I', 1)]), # tested + # 0x008 not yet implemented + 'get_encoder_estimates': (0x009, [('encoder_pos_estimate', 'f', 1), ('encoder_vel_estimate', 'f', 1)]), # partially tested + 'get_encoder_count': (0x00a, [('encoder_shadow_count', 'i', 1), ('encoder_count', 'i', 1)]), # partially tested + 'set_controller_modes': (0x00b, [('control_mode', 'i', 1), ('input_mode', 'i', 1)]), # tested + 'set_input_pos': (0x00c, [('input_pos', 'f', 1), ('vel_ff', 'h', 0.001), ('torque_ff', 'h', 0.001)]), # tested + 'set_input_vel': (0x00d, [('input_vel', 'f', 1), ('torque_ff', 'f', 1)]), # tested + 'set_input_torque': (0x00e, [('input_torque', 'f', 1)]), # tested + 'set_velocity_limit': (0x00f, [('velocity_limit', 'f', 1)]), # tested + 'start_anticogging': (0x010, []), # untested + 'set_traj_vel_limit': (0x011, [('traj_vel_limit', 'f', 1)]), # tested + 'set_traj_accel_limits': (0x012, [('traj_accel_limit', 'f', 1), ('traj_decel_limit', 'f', 1)]), # tested + 'set_traj_inertia': (0x013, [('inertia', 'f', 1)]), # tested + 'get_iq': (0x014, [('iq_setpoint', 'f', 1), ('iq_measured', 'f', 1)]), # untested + 'get_sensorless_estimates': (0x015, [('sensorless_pos_estimate', 'f', 1), ('sensorless_vel_estimate', 'f', 1)]), # untested + 'reboot': (0x016, []), # tested + 'get_vbus_voltage': (0x017, [('vbus_voltage', 'f', 1)]), # tested + 'clear_errors': (0x018, []), # partially tested +} + +def command(bus, node_id_, extended_id, cmd_name, **kwargs): + cmd_spec = command_set[cmd_name] + cmd_id = cmd_spec[0] + fmt = '<' + ''.join([f for (n, f, s) in cmd_spec[1]]) # all little endian + + if (sorted([n for (n, f, s) in cmd_spec[1]]) != sorted(kwargs.keys())): + raise Exception("expected arguments: " + str([n for (n, f, s) in cmd_spec[1]])) + + fields = [((kwargs[n] / s) if f == 'f' else int(kwargs[n] / s)) for (n, f, s) in cmd_spec[1]] + data = struct.pack(fmt, *fields) + msg = can.Message(arbitration_id=((node_id_ << 5) | cmd_id), extended_id=extended_id, data=data) + bus.send(msg) + +async def record_messages(bus, node_id, extended_id, cmd_name, timeout = 5.0): + """ + Returns an async generator that yields a dictionary for each CAN message that + is received, provided that the CAN ID matches the expected value. + """ + + cmd_spec = command_set[cmd_name] + cmd_id = cmd_spec[0] + fmt = '<' + ''.join([f for (n, f, s) in cmd_spec[1]]) # all little endian + + reader = can.AsyncBufferedReader() + notifier = can.Notifier(bus, [reader], timeout = timeout, loop = asyncio.get_event_loop()) + + try: + # The timeout in can.Notifier only triggers if no new messages are received at all, + # so we need a second monitoring method. + start = time.monotonic() + while True: + msg = await reader.get_message() + if ((msg.arbitration_id == ((node_id << 5) | cmd_id)) and (msg.is_extended_id == extended_id) and not msg.is_remote_frame): + fields = struct.unpack(fmt, msg.data[:(struct.calcsize(fmt))]) + res = {n: (fields[i] * s) for (i, (n, f, s)) in enumerate(cmd_spec[1])} + res['t'] = time.monotonic() + yield res + if (time.monotonic() - start) > timeout: + break + finally: + notifier.stop() + +async def request(bus, node_id, extended_id, cmd_name, timeout = 1.0): + cmd_spec = command_set[cmd_name] + cmd_id = cmd_spec[0] + + msg_generator = record_messages(bus, node_id, extended_id, cmd_name, timeout) + + msg = can.Message(arbitration_id=((node_id << 5) | cmd_id), extended_id=extended_id, data=[], is_remote_frame=True) + bus.send(msg) + + async for msg in msg_generator: + return msg + + raise TimeoutError() + +async def get_all(async_iterator): + return [x async for x in async_iterator] + +class TestSimpleCANClosedLoop(): + def prepare(self, odrive: ODriveComponent, canbus: CanInterfaceComponent, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, node_id: int, extended_id: bool, logger: Logger): + # Make sure there are no funny configurations active + logger.debug('Setting up clean configuration...') + axis_ctx.parent.erase_config_and_reboot() + + # run calibration + axis_ctx.handle.requested_state = AXIS_STATE_FULL_CALIBRATION_SEQUENCE + while axis_ctx.handle.current_state != AXIS_STATE_IDLE: + time.sleep(1) + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_no_error(axis_ctx) + + # Return a context that can be used in a with-statement. + class safe_terminator(): + def __enter__(self): + pass + def __exit__(self, exc_type, exc_val, exc_tb): + logger.debug('clearing config...') + axis_ctx.handle.requested_state = AXIS_STATE_IDLE + time.sleep(0.005) + axis_ctx.parent.erase_config_and_reboot() + return safe_terminator() + + + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + can_interfaces = list(testrig.get_connected_components(odrive.can, CanInterfaceComponent)) + for num in range(2): + encoders = testrig.get_connected_components({ + 'a': (odrive.encoders[num].a, False), + 'b': (odrive.encoders[num].b, False) + }, EncoderComponent) + motors = testrig.get_connected_components(odrive.axes[num], MotorComponent) + for motor, encoder in itertools.product(motors, encoders): + if encoder.impl in testrig.get_connected_components(motor): + yield (odrive, can_interfaces, odrive.axes[num], motor, encoder, 0, False) + + def run_test(self, odrive: ODriveComponent, canbus: CanInterfaceComponent, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, node_id: int, extended_id: bool, logger: Logger): + # this test is a sanity check to make sure that closed loop operation works + # actual testing of closed loop functionality should be tested using closed_loop_test.py + + with self.prepare(odrive, canbus, axis_ctx, motor_ctx, enc_ctx, node_id, extended_id, logger): + def my_cmd(cmd_name, **kwargs): command(canbus.handle, node_id, extended_id, cmd_name, **kwargs) + def my_req(cmd_name, **kwargs): return asyncio.run(request(canbus.handle, node_id, extended_id, cmd_name, **kwargs)) + def fence(): my_req('get_vbus_voltage') # fence to ensure the CAN command was sent + + # make sure no gpio input is overwriting our values + odrive.unuse_gpios() + + axis_ctx.handle.config.enable_watchdog = False + axis_ctx.handle.clear_errors() + axis_ctx.handle.config.can_node_id = node_id + axis_ctx.handle.config.can_node_id_extended = extended_id + time.sleep(0.1) + + my_cmd('set_node_id', node_id=node_id+20) + asyncio.run(request(canbus.handle, node_id+20, extended_id, 'get_vbus_voltage')) + test_assert_eq(axis_ctx.handle.config.can_node_id, node_id+20) + + # Reset node ID to default value + command(canbus.handle, node_id+20, extended_id, 'set_node_id', node_id=node_id) + fence() + test_assert_eq(axis_ctx.handle.config.can_node_id, node_id) + + vel_limit = 15.0 + nominal_vel = 10.0 + axis_ctx.handle.controller.config.vel_limit = vel_limit + axis_ctx.handle.motor.config.current_lim = 30.0 + + my_cmd('set_requested_state', requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL) + fence() + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) + test_assert_no_error(axis_ctx) + + start_pos = axis_ctx.handle.encoder.pos_estimate + + # position test + logger.debug('Position control test') + my_cmd('set_controller_modes', control_mode=CONTROL_MODE_POSITION_CONTROL, input_mode=INPUT_MODE_PASSTHROUGH) # position control, passthrough + fence() + my_cmd('set_input_pos', input_pos=1.0, vel_ff=0, torque_ff=0) + fence() + test_assert_eq(axis_ctx.handle.controller.input_pos, 1.0, range=0.1) + time.sleep(2) + test_assert_eq(axis_ctx.handle.encoder.pos_estimate, start_pos + 1.0, range=0.1) + my_cmd('set_input_pos', input_pos=0, vel_ff=0, torque_ff=0) + fence() + time.sleep(2) + + test_assert_no_error(axis_ctx) + + # velocity test + logger.debug('Velocity control test') + my_cmd('set_controller_modes', control_mode=CONTROL_MODE_VELOCITY_CONTROL, input_mode=INPUT_MODE_PASSTHROUGH) # velocity control, passthrough + fence() + my_cmd('set_input_vel', input_vel = nominal_vel, torque_ff=0) + fence() + time.sleep(5) + test_assert_eq(axis_ctx.handle.encoder.vel_estimate, nominal_vel, range=nominal_vel * 0.05) # big range here due to cogging and other issues + my_cmd('set_input_vel', input_vel = 0, torque_ff=0) + fence() + time.sleep(2) + + test_assert_no_error(axis_ctx) + + # torque test + logger.debug('Torque control test') + my_cmd('set_controller_modes', control_mode=CONTROL_MODE_TORQUE_CONTROL, input_mode=INPUT_MODE_PASSTHROUGH) # torque control, passthrough + fence() + my_cmd('set_input_torque', input_torque=0.5) + fence() + time.sleep(5) + test_assert_eq(axis_ctx.handle.controller.input_torque, 0.5, range=0.1) + my_cmd('set_input_torque', input_torque = 0) + fence() + time.sleep(2) + + test_assert_no_error(axis_ctx) + + # go back to idle + my_cmd('set_requested_state', requested_state = AXIS_STATE_IDLE) + fence() + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + +if __name__ == '__main__': + test_runner.run(TestSimpleCANClosedLoop()) \ No newline at end of file From e1ffdbd33dae42cc061fa453ecfaf823947cd399 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 31 Jul 2020 10:50:06 +0200 Subject: [PATCH 543/549] fix Anaconda Python detection on Windows Anaconda prints version information on stderr instead of stdout. --- Firmware/Tupfile.lua | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index bf077598..7f5380ae 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -6,9 +6,9 @@ tup.include('build.lua') -- command "python --version" does not open the Microsoft Store. -- On some systems this may return a python2 command if Python3 is not installed. function find_python3() - success, python_version = run_now("python --version") + success, python_version = run_now("python --version 2>&1") if success and string.match(python_version, "Python 3") then return "python -B" end - success, python_version = run_now("python3 --version") + success, python_version = run_now("python3 --version 2>&1") if success and string.match(python_version, "Python 3") then return "python3 -B" end error("Python 3 not found.") end @@ -16,8 +16,6 @@ end python_command = find_python3() print('Using python command "'..python_command..'"') -run_now("") - tup.frule{inputs={'fibre/cpp/interfaces_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/interfaces.hpp'} tup.frule{inputs={'fibre/cpp/function_stubs_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --template %f --output %o', outputs='autogen/function_stubs.hpp'} tup.frule{inputs={'fibre/cpp/endpoints_template.j2'}, command=python_command..' interface_generator_stub.py --definitions odrive-interface.yaml --generate-endpoints ODrive --template %f --output %o', outputs='autogen/endpoints.hpp'} From d996f02df10bbb4df492a91647c4b1b7c88ae78b Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 31 Jul 2020 15:35:05 -0400 Subject: [PATCH 544/549] Add missing input_pos_updated call on transition to closed loop --- Firmware/MotorControl/axis.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 6130f782..7775d97d 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -303,6 +303,7 @@ bool Axis::run_closed_loop_control_loop() { // To avoid any transient on startup, we intialize the setpoint to be the current position controller_.pos_setpoint_ = *controller_.pos_estimate_src_; controller_.input_pos_ = *controller_.pos_estimate_src_; + controller_.input_pos_updated(); // Avoid integrator windup issues controller_.vel_integrator_current_ = 0.0f; From 5beb3faa83d8a74cc96d4303e3d3a13ea1a139d7 Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Fri, 31 Jul 2020 17:52:43 -0400 Subject: [PATCH 545/549] Added null pointer checks to axis.cpp for pos_estimate_X Fixed formatting Updated CHANGELOG.md --- CHANGELOG.md | 2 ++ Firmware/MotorControl/axis.cpp | 37 ++++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29223f0e..58fde384 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * Added scripts for building via docker. * Added ability to change uart baudrate via fibre * Introduced `odrive-interface.yaml` as a root source for the ODrive's API. `odrivetool` connects much faster as a side effect. +* Added torque_constant and torque_lim to motor config ### Changed * Changed ratiometric `motor.config.current_lim_tolerance` to absolute `motor.config.current_lim_margin` @@ -45,6 +46,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * `axis.motor.thermal_current_lim` has been removed. Instead a new property is available `axis.motor.effective_current_lim` which contains the effective current limit including any thermal limits. * `axis.motor.get_inverter_temp()`, `axis.motor.inverter_temp_limit_lower` and `axis.motor.inverter_temp_limit_upper` have been moved to seperate fet thermistor object under `axis.fet_thermistor`. `get_inverter_temp()` function has been renamed to `temp` and is now a read-only property. * Fixed a numerical issue in the trajectory planner that could cause sudden jumps of the position setpoint +* `input_pos`, `input_vel`, `pos_estimate_linear`, `pos_estimate_circular`, are now in units of [turns] or [turns/s] instead of [counts] or [counts/s] # Releases ## [0.4.12] - 2020-05-06 diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 648a20a6..ae8ddb0b 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -318,13 +318,23 @@ bool Axis::run_closed_loop_control_loop() { } // To avoid any transient on startup, we intialize the setpoint to be the current position - if(controller_.config_.circular_setpoints == true) { - controller_.pos_setpoint_ = *controller_.pos_estimate_circular_src_; - controller_.input_pos_ = *controller_.pos_estimate_circular_src_; + if (controller_.config_.circular_setpoints) { + if (*controller_.pos_estimate_circular_src_) { + controller_.pos_setpoint_ = *controller_.pos_estimate_circular_src_; + controller_.input_pos_ = *controller_.pos_estimate_circular_src_; + } + else { + return error_ |= ERROR_CONTROLLER_FAILED, false; + } } else { - controller_.pos_setpoint_ = *controller_.pos_estimate_linear_src_; - controller_.input_pos_ = *controller_.pos_estimate_linear_src_; + if (*controller_.pos_estimate_linear_src_) { + controller_.pos_setpoint_ = *controller_.pos_estimate_linear_src_; + controller_.input_pos_ = *controller_.pos_estimate_linear_src_; + } + else { + return error_ |= ERROR_CONTROLLER_FAILED, false; + } } // Avoid integrator windup issues @@ -376,12 +386,23 @@ bool Axis::run_homing() { // To avoid any transient on startup, we intialize the setpoint to be the current position // note - input_pos_ is not set here. It is set to 0 earlier in this method and velocity control is used. - if(controller_.config_.circular_setpoints == true) { - controller_.pos_setpoint_ = *controller_.pos_estimate_circular_src_; + if (controller_.config_.circular_setpoints) { + if (*controller_.pos_estimate_circular_src_) { + controller_.pos_setpoint_ = *controller_.pos_estimate_circular_src_; + } + else { + return error_ |= ERROR_CONTROLLER_FAILED, false; + } } else { - controller_.pos_setpoint_ = *controller_.pos_estimate_linear_src_; + if (*controller_.pos_estimate_linear_src_) { + controller_.pos_setpoint_ = *controller_.pos_estimate_linear_src_; + } + else { + return error_ |= ERROR_CONTROLLER_FAILED, false; + } } + // Avoid integrator windup issues controller_.vel_integrator_torque_ = 0.0f; From 611d2d50215ba1c677bbbb941d216d1c96be175b Mon Sep 17 00:00:00 2001 From: PAJohnson Date: Fri, 31 Jul 2020 18:09:52 -0400 Subject: [PATCH 546/549] Better style for null pointer checks --- Firmware/MotorControl/axis.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index ae8ddb0b..f8fd2d15 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -319,21 +319,21 @@ bool Axis::run_closed_loop_control_loop() { // To avoid any transient on startup, we intialize the setpoint to be the current position if (controller_.config_.circular_setpoints) { - if (*controller_.pos_estimate_circular_src_) { + if (!controller_.pos_estimate_circular_src_) { + return error_ |= ERROR_CONTROLLER_FAILED, false; + } + else { controller_.pos_setpoint_ = *controller_.pos_estimate_circular_src_; controller_.input_pos_ = *controller_.pos_estimate_circular_src_; } - else { - return error_ |= ERROR_CONTROLLER_FAILED, false; - } } else { - if (*controller_.pos_estimate_linear_src_) { - controller_.pos_setpoint_ = *controller_.pos_estimate_linear_src_; - controller_.input_pos_ = *controller_.pos_estimate_linear_src_; + if (!controller_.pos_estimate_linear_src_) { + return error_ |= ERROR_CONTROLLER_FAILED, false; } else { - return error_ |= ERROR_CONTROLLER_FAILED, false; + controller_.pos_setpoint_ = *controller_.pos_estimate_linear_src_; + controller_.input_pos_ = *controller_.pos_estimate_linear_src_; } } @@ -387,19 +387,19 @@ bool Axis::run_homing() { // To avoid any transient on startup, we intialize the setpoint to be the current position // note - input_pos_ is not set here. It is set to 0 earlier in this method and velocity control is used. if (controller_.config_.circular_setpoints) { - if (*controller_.pos_estimate_circular_src_) { - controller_.pos_setpoint_ = *controller_.pos_estimate_circular_src_; + if (!controller_.pos_estimate_circular_src_) { + return error_ |= ERROR_CONTROLLER_FAILED, false; } else { - return error_ |= ERROR_CONTROLLER_FAILED, false; + controller_.pos_setpoint_ = *controller_.pos_estimate_circular_src_; } } else { - if (*controller_.pos_estimate_linear_src_) { - controller_.pos_setpoint_ = *controller_.pos_estimate_linear_src_; + if (!controller_.pos_estimate_linear_src_) { + return error_ |= ERROR_CONTROLLER_FAILED, false; } else { - return error_ |= ERROR_CONTROLLER_FAILED, false; + controller_.pos_setpoint_ = *controller_.pos_estimate_linear_src_; } } From 48449f23482b802f49fde04566148a5ae47ef196 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 3 Aug 2020 20:02:24 -0700 Subject: [PATCH 547/549] release v0.5.0 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86db8832..faa908de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Unreleased Features Please add a note of your changes below this heading if you make a Pull Request. +# Releases +## [0.5.0] - 2020-08-03 ### Added * AC Induction Motor support. * Tracking of rotor flux through rotor time constant @@ -42,7 +44,6 @@ Please add a note of your changes below this heading if you make a Pull Request. * Change `motor.get_inverter_temp()` to use a property which was already being sampled at `motor.inverter_temp` * Fixed a numerical issue in the trajectory planner that could cause sudden jumps of the position setpoint -# Releases ## [0.4.12] - 2020-05-06 ### Fixed * Fixed a numerical issue in the trajectory planner that could cause sudden jumps of the position setpoint From 0d018fc710c13514d2a4d192a689ad4e144b1f60 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 3 Aug 2020 20:36:50 -0700 Subject: [PATCH 548/549] pull out changelog items that are after v0.5.0 --- CHANGELOG.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8edcf215..31adaf34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ # Unreleased Features Please add a note of your changes below this heading if you make a Pull Request. +# Release Candidate +## [0.5.1] - Date TBD +### Added +* Added motor `torque_constant`: units of torque are now [Nm] instead of just motor current. +* [Motor thermistors support](docs/thermistors.md) +* Enable/disable of thermistor thermal limits according `setting axis..enabled`. +* Introduced `odrive-interface.yaml` as a root source for the ODrive's API. `odrivetool` connects much faster as a side effect. +* Added torque_constant and torque_lim to motor config + +### Changed +* **`input_pos`, `input_vel`, `pos_estimate_linear`, `pos_estimate_circular`, are now in units of [turns] or [turns/s] instead of [counts] or [counts/s]** +* `axis.motor.thermal_current_lim` has been removed. Instead a new property is available `axis.motor.effective_current_lim` which contains the effective current limit including any thermal limits. +* `axis.motor.get_inverter_temp()`, `axis.motor.inverter_temp_limit_lower` and `axis.motor.inverter_temp_limit_upper` have been moved to seperate fet thermistor object under `axis.fet_thermistor`. `get_inverter_temp()` function has been renamed to `temp` and is now a read-only property. + + # Releases ## [0.5.0] - 2020-08-03 ### Added @@ -19,8 +34,6 @@ Please add a note of your changes below this heading if you make a Pull Request. * [Preliminary support for Absolute Encoders](docs/encoders.md) * [Preliminary support for endstops and homing](docs/endstops.md) * [CAN Communication with CANSimple stack](can-protocol.md) -* [Motor thermistors support](docs/thermistors.md) -* Enable/disable of thermistor thermal limits according `setting axis..enabled`. * Gain scheduling for anti-hunt when close to 0 position error * Velocity Limiting in Current Control mode according to `vel_limit` and `vel_gain` * Regen current limiting according to `max_regen_current`, in Amps @@ -31,8 +44,6 @@ Please add a note of your changes below this heading if you make a Pull Request. * Using an STM32F405 .svd file allows CortexDebug to view registers during debugging * Added scripts for building via docker. * Added ability to change uart baudrate via fibre -* Introduced `odrive-interface.yaml` as a root source for the ODrive's API. `odrivetool` connects much faster as a side effect. -* Added torque_constant and torque_lim to motor config ### Changed * Changed ratiometric `motor.config.current_lim_tolerance` to absolute `motor.config.current_lim_margin` @@ -45,10 +56,8 @@ Please add a note of your changes below this heading if you make a Pull Request. * Added JSON caching to Fibre. This drastically reduces the time odrivetool needs to connect to an ODrive (except for the first time or after firmware updates). * Fix IPython `RuntimeWarning` that would occur every time `odrivetool` was started. * Reboot on `erase_configuration()`. This avoids unexpected behavior of a subsequent `save_configuration()` call, since the configuration is only erased from NVM, not from RAM. -* `axis.motor.thermal_current_lim` has been removed. Instead a new property is available `axis.motor.effective_current_lim` which contains the effective current limit including any thermal limits. -* `axis.motor.get_inverter_temp()`, `axis.motor.inverter_temp_limit_lower` and `axis.motor.inverter_temp_limit_upper` have been moved to seperate fet thermistor object under `axis.fet_thermistor`. `get_inverter_temp()` function has been renamed to `temp` and is now a read-only property. +* Change `motor.get_inverter_temp()` to use a property which was already being sampled at `motor.inverter_temp` * Fixed a numerical issue in the trajectory planner that could cause sudden jumps of the position setpoint -* `input_pos`, `input_vel`, `pos_estimate_linear`, `pos_estimate_circular`, are now in units of [turns] or [turns/s] instead of [counts] or [counts/s] ## [0.4.12] - 2020-05-06 ### Fixed From 30bc561231a36e4da78adda736aeebe9fa3fb5b2 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 28 Jul 2020 15:02:02 +0200 Subject: [PATCH 549/549] dump_errors: better handling of unknown errors --- tools/odrive/utils.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index dd51f5ac..571db8e9 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -89,13 +89,10 @@ def dump_errors(odrv, clear=False): if (remote_obj.error != 0): foundError = False print(prefix + _VT100Colors['red'] + "Error(s):" + _VT100Colors['default']) - errorcodes_tup = [(name, val) for name, val in errorcodes.items() if 'ERROR_' in name] - for codename, codeval in errorcodes_tup: - if remote_obj.error & codeval != 0: - foundError = True - print(" " + codename) - if not foundError: - print(" " + 'UNKNOWN ERROR!') + errorcodes_dict = {val: name for name, val in errorcodes.items() if 'ERROR_' in name} + for bit in range(64): + if remote_obj.error & (1 << bit) != 0: + print(" " + errorcodes_dict.get((1 << bit), 'UNKNOWN ERROR: 0x{:08X}'.format(1 << bit))) if clear: remote_obj.error = 0 else:

    ^ry8tE+sXl76#RyC|hi4ZbV8jC|`|MNP9_18V*PCLLN)+&E(9c)!5ShGh z5l?#f&XGMVZ4EJN9+OmPX=!K#Mn&y>py36C`u3jU@7UYeWW7tv6Oj>7%mr_b*Jy@R ze{tf6e`{;02BFU0{29c&*2UIwx^3ueyRZ(vWYg#srYx6&Mijd~r-1qdHnNz7!}*{Qf8?sdEWttnIa z3wnq8+1|9Z0~BvfARl10qb&$cTMnw%_8qxlv4}y;4KnRibmYF6T#4p?^IOc)LwaFj zQCD0as-2D}XFqX6tMci-<9P)i(3l>lQo8z<% zaE8^%69M8uYMtc|beucO<=jbF8c}qt2%yQp=HIe~Fr}QT&Yr0@o)NYCvh7#T<7(B7pZitcT5q46g)$9Dq=+o7a``JWyKLmIAmD%!EgO}>|r3! zij9XBLBvQd1^eXRvL*RZvcpDM&90Mw#QE znGA%hkT$%|foTGhly3OZqPoRedUE%=-8`34OweAvXw!MB@qw1Fc5^#a+=SotWbSRZU2nVzk3( zc$ntY&0&Db6xQW|*Ra~YFyn1Btaz~yZo{yr$|Rl-B+HP)pAQgmgK!rwhMg-V=;wG1 zmCLw78gVtSG+N48ob5@GeM3aZ`)O0s#}v^$>W!~zk!O>)mb25@j#e&T&>l9#%xHKV zc0#u6P3l7&H(8kq3JVij`o2&bIKu|kK7rDlU#bXT-R35qp7UqN3j~~IS^*T{WatXN z!d+XHO60)M*m}Xma@kEm^uH9FUz;<)&MpJns6Pg-OLyN5qk((S&jaSOO@sAwQl^bM&80>wCv0bL=CPhX<*FK)JUmV+DAo-TIJ3TUJwv zwDNSzwZgZkZQR@b$7%f1z@gk+u7QBR$`3Q8Nt?OGf&2QV;!32UipKK=wEGp*C9mh$ zPj5TDxxYS!fiHq=CkZ0QvMwqKHa~^Y^y07WPd1atWs#w|35l5Y zcY|W9H=w^bmQPGbztIducKk)oG0XfVhEjM%+s@S3KA7w9TN_GnZ*^AfQ(DfwC$FnZ z1OE|;|55vK@7IeVX(^su_RpggDwGosJHOckPo^t?OlB}AMO*>#?}sqq?s-aHHiXtz zDR#QMpOh6?3XIf_)l_7Ei|3UQkLILl@222L0gH?7tX4lqCa?iFo|6i2(#MDEqO5K~NcR=h4RgRnr0vwg1#ImfTp^4AOGk|_5rVnC%j0F7Z z#e{~U413<1qTX&>RY`UVzgPWx$)fygXn#k7qgSao^cRgia~_-xL(==w zhS9ZU!Z%7REJCYb<;VY|outRA{2u7HxM4*piuE2kd13GPCAg<1<1!G~`kRtAJHB8n zwea6*@31=w>omTk2gg~MK1Pk^Wc0D-UmFKNuubO&TT)9Oy0q&NPLw(yL2&&i%^4itwvTYO%-B{%<584L${de{}WzJ<$WAYPo+a zQ{?}>R)nVKz8!$qaz(54R%B&f^6KU$haKUH^t-&T5CfP5-?%LguZNxF++>`pWPOw< zK#d33Hq`jeSRnlaoaetT6mV@&K1(3ijr#ZV8Q@cy3*!?QwnQu~nCxd$EeOQ%^MT^z zQA=zI-4FRv8%?;#XVK1t%gpJq`G4Ib)X-@79`if}K&g2Nnv90?G5jZVnGF%Kgvr3<-pfUnbgkY@a!6H#!l?fb#n|C~{R8^t+j$pMAn@`tk^kwBZC zbTCVm(`HOYQQ=GpqYZrNI}m{YDaB|9Y7vGKG0hEM_q>{5&u`Ks@#EqxDg><*UsP98 zIKg+bp%kF34xLryN|F;2{_|kZ-+JG-;Q9=AuKN*$QMzhCpsq zGgsY3^ORAE5y%*^-sWiD(!!kBt&yMo^Uoo3@~!}PVR=*e!;9tAjOwp+JF-RBIRD~F zGc{Q&r4F@=r}lm?`VKNvgIxsAD=|`*sBvuRgMAdPd`RAS(|>2-;Y(93)fly)wJB(1 zw$GS6dse~9ZjyuRhR)USybb`hD3S2$C)joyp6SVa+Sz3AkwQy^FS}F=$)jU)B9uEY zUCY`#GnjZ|&Zn+fjtVKlo9)+xn0#&k5wuNp7Uzj?`mPz>oOF8XH{Z;Z08Q?!j+Va} z;AuiRs5L&*4BzF_Gk4%<_3R-t5|~asD9>1=ZmXP#M}I{3f4Esm^olkg<3if;w{+zXL!{#1%<bkUwWx?F~Spnjg(}w@%cGUnP8sgPkn?*&w{ZnCi{D08dbxeV{HriQ}%%pMer{^yl zdSSK$0?Tp|@5F_#%fCj?EU8oRC z_xPZH?#6l>#nL=iC8uc(U3RG)vAN@bK+OwM z8$n=*J>uW!k+fSHv50&o?rQ2XuZ>sLF-(uy%U&5l%UCX2g}-|^~)B)?w~ zL%*e|XQ1)oB!+*#PWWd0N4Rm;*K64A(&?2s`6%vkqTum%D6jM0B6+jCPD^i5u+CHk z(lz&f2aV>Oq0jS!iR|F5=sQxI$zJR&a+=LxUdNi=ljfZ> zu%96jikBhZJu&~Ftz8>|BuBePLAeoRlcXF++|#pqrDaIQm;9^(Kkd98`%&}IV3iiY zg@8<4q4C#kHtFmC0^4SRfjvDJ@b6N+{7MWg{vDKzxEO3lcY(pZdh+ihBx+Uf{_VXn z8icYaob=*OjQdX$D{$e}!OYhs^CU&iW`1%f?BHH8f1x-s$D&vB#%+EwE@U=73-)L7 zFuuL3!DZt9a#E>F#6q#Cp~XLa@k!<2Rj zO-@BjFzi;XW@0=6s*Ow)ZgR404*@c7Kx4@IP6oLEARhpFBEFULfM@}nj0}nW7vKdrmrEya zK@p4y%X0wA0uX%Wcwi*0!XGj%#{eCO314CdX?7&?HTgf$;1LKsDxn~E2vtUD$JnZcfVdWe~3y=-nZB;eC1lMQE#*fcz$oA2 zmo6=3c~VgL#@$;I%>Zl&9rV-7PTU6l_bmw9U#tkpM|(#{i7M`;z`QotrWBlj4=xos z5Kx5MY2FL}66dJ8=rcIL^f_9Qv7z0MHU&6LeKbj+sJ8vmK5hSFTC6PTxX<+&`V(VV zU2(I@5Wwete_a#|*H$dcLK0%Cyy57{!4P}hBAEssts zZwg-zN|Oo^@`xx3YoC{JfyJg@5(Vna$>_)6=SRhDNP~pxodJ-77XdYtVzY}e9}J-3hyEGCJP?^Xd2Sa5 z$9R@cP9*^M5v;2?fPZyB@C%v2!%bEccez3 z0OF}GW$0w z)4Ov%MPmuERZ`~Y#f^LXKmrXTb)r3NTGRsNKX1*LF_r8?L-T@PShuYdyYq8{lnSJ zhoJiB2$_WHOz}*ab8I9K?OmYWMP`-Nf+mX|D}Zy6h~gPcy(|#-jNo|YHpVcXwghcr zdt759`5N@!Yq=(or8uOtoYu9!d(D@^tM~iDtm%LhII#*E$wvVoBn-ndKol@Kbrc1~AK5HL8`k8f^i?DP7I1Zv=p}amgp18@E5SD- zik;omu?3V~{$Od;RVgDc;(Zo8@U_MmmM_sW^V>>7o2b&watug{IR@D22AdTjPSA`7 z4;SNS9-cbjM5HO0WjcGK=%yMe`pz!R=D~@=wHhWp)XHMQC1#8Ys5h#8`qa5Y} z2Ga~G=}ZYbn52Lo4p53}H`FuAJBrG<^+?$QVVbK;Dw+XoKcl1*2Ej(Dm*5lG{xac! zY8g)bzL9_BGaXG)f4vRqKkue7=GAEmTosz?!Z`pJ6`#v(V|)vwmGPC&wKvCgef#5Y zHK3|!$RJwRgOf4Qiw^;Cat#)r$2oBftZm=lz@a3k7=K_MU@4=+x78xDQ${qu%or4*D76rvBldlY!90zc zm)U|qZvs@fSfKCDg%mwpIPo2LPMG%uB9X@R4>hxq=g@24!n#toy=TJ0G!Zi@c1%ig zavQ0!8C5q=?psY>Pl8PRQDGK0FP+<>qZF;6Ljdp+02bN#UJ-PW2)xIsVkd+wj$L-~ ztWB_X=rC+mK~Gb7dqKPoPe~yin9v$Pa9+4LQ#>t03MzPe{aFK>vYqhb9QL^Irxoj( zj;95*HU2>X8T4QtXWb9^g9DGh@Q`{zyBs;iu1Us#t4Xw6AJ-8|-)0S36X{(Yz-yW~ z(m3(EDA}8&>OBd+u4u=<-V-nAWH=q0q9sjJLs?5Z8lidb_~&$PjhLse_2`bXU9!nU zHwO5!4sc^W5)ikJhYDVqaNq;NyilOa_K1bC0?t|aIctl1@@R#*RKfMYg4NW_2WYjy zk)65rp844kb)DJdf5bm8+He~fh}*3^osBv%now<9Y}6b%UQVCIBn7VpOKH((>Q_jQ zzheVfB-vZA;3uB_5@(`ZeQF+06dFAqUWL;IhH&gRS$>m1?=nt(*Hogad9MM}fzFVlOF>~#Fmx@$g>W3wYP= zesOBzQd)XiC0A1GdH^x?1aY~Bq@-wWnpFqsH?W<{Bdwxgt%uI7W zDbJ78)MeI4gYP@LlhLk{GZjER#rRxJor2IhP1_`kl{B8)SxloLxG45ufa}%x)!W|= zghtQLl+^oMu0Hsx_hu~Nhj*b0FRCIJWCcQ3 zTC?)^v^-YG5x%Zp<X-+seJ@IqCOS1c%=h(+}4x-N4VzmxKV>3L2c#II;V(%!wY>lD}~zes8;tu%)JQ z7*%~lvixab*E*K~FX(+Bgf5mXNe)mfNfwnY!JC46dopMQQI(8%JDJS|5Z+{8XL$bI zN8>pf-hl(jAD^{Ei_$|%j;FnpKULMt3MDDh`ip>{QI{Mmt*%$=tbF+Cy^HyNTISN? z)*o7vmD`FE-RkQ5AwPFSlFAvJOXMsa-~M_zw|N+O7yr1=jI&DGOteaIctkR3{Ea*E z*`&JNv7F^=ldWqzIIK4af0L0V%2{Q&l8;rlh;Rzm#ExAPV2OXR*$^ntACN)ezOnbW!4vH&fT#7C5|DHZ@Gn`T+>po{{H(Q4y5>q#uL466xvxQ z{-9=NB=smU}WvO)-+oQaREJ@6Z!s`+u6ck(-*1zddqd7)TTNx zRVG|2bKY3Z38FheVF1~+v`Xtq)Mew*$vgcJcw=<8h~&Gjox#o|t|Nk~NV>zwT8X4J zeqoOnxP0cfF?*folCoG)PR$oO>;V|v#QcrJLd03c{I^cDq6K6QPFFJR^qL;#g8La) zLm|VK0!#SeQ#&%wGWSwSX%Rk^*472M0U#qhkAk^C?Y9S%e7QkDm0$&nN1?p}zdqAc zDgg!4k1*O19OGZEBmnwYRviQi*)P?P5IGhYjH->Yzx%)1IQMWU(>{)iYa3DHI3sCN z6Dv)&1M!YtWJ*m=yN!%7gobs_X)qPls%$)5%OHy-t*yik+h81GL>Y`?^I{wtr*SNc zG&07i_kO6}_r2czXXcN&=eh3txt`~~f4|@N^ZjuX;UR96&^1L|UPVx|_-bD-Q%iS3 zeYm&?9^aa9Wg-^O?6m5a)!KsDbYB%noW>PbCN(yB*+m1oT|^ESg@@!k%FXhtLnx5g zNAUy3tI-BC_X@wac!5LL#h!E1r~)bQ?H55*mV80YH(}NwRbsq;CyyiS|0BVJ_sdsB z9#+J$2o=_&{>nV>{x|4G)Sf%tR&9Pf>BKz;$F7=cCwZsp6Z`i}cusSrkchz05Dje= zgFyhFK5Mg=$ss3l1cBA27+^FJfY@f{nsKYb?xWiKz*U*_z>X1?sm)`hkop~Don!^q z#|r@oIfZiUebpPN<<8Zdd(z{U;Hn2o?50-hKM10EWmSA_5P7<3IO_x*lr$`NLhqI9 z%=8O@b|{3zACX}GFiQ>xr=Pz-#_F;6Hq4j5-mVI->l1ViFzL7pY!R$}lZ6{CZ0aEx zMhEiBhF(UQGsFq_lm7xS4agmI00N<}+H0u*2d>QQcBu41f{u^jUA5Mq^{8keCFz>jRrQoit zW3zesvinl4!PeDm+k~hTP^WQ8u(%`2k}S#z>3*S3r$Ir(-(YQlI7i2AuH&YoC0*un z-I<)lkp*c-Cf@!?9XAclFn%cSswWWr(WGv{p&h!OyIWSM<`){QNBv={hPon+N6)+_ zjQfT%d~#!^%BU-=y%UbJb4?58322FlE~2unkcYA5tE|3+My;t{cd@#h0Kz&hC|6E!Opn@rEm#_(7;3P7E}Dx;XlSH$lEC4rLg>+=#zV zTi1W9!V%|_f&;u_kL9M=)vvdBn57)n99%3*k%#M*aNd8_<-ipD6xjRms^AA+6=ZJU zr|?&Qro?Gq(@M;|6!=A>1ySs^e`++v|CW9$*>Fc#r00>W1P9IMMl+=i$0k}Lt0P}m z`lrV|%BFwgZd4ToE*hBujsM3-}73Cea7t(-d{V(yTdKH} zgKCYKhiYdU$@7IfMDE$_A>>zH$<;RLtPF32c}d<<0WR|~_A)L=h&jgmv6zYCtnu^} z$vL_=g?5iiNJi#6UCydWrLz5X5%UeIjpXpww#Y!M)<(Jv8{1nGQ_diFQd&;qo(vAU z%T@lgD}EM<;oezkhj>cVNylTpx^Bz_+Yf?Nhz%h zX|kpO!e8P~Xa-{k5Aw$bC6RT&0a{%_Gwi(NW0m7t9pQ3<#tU$1|Gm&#&{tDRyJ!o1 zvAQ_4fZEMQC7$!Cz}_(PMdUz6`>3E(i$2E4+0?PaQo_Q|6~^{>M!p(rKs>W+b04V` z`&Q*#)TA6fDbj8$Ux|K(4Z)bL&KJi3uaz{UT=%2CYbk3o|Jrj~w+y+;+cuqcNG-}) zMK*(+2vUvF>KriCaYRMVM?3_byJdNxX|t@J^rTay&7 zz6~&Q&CQTS6>Vr*3OOk17kUd@->SbUepYyj%o<&Tp5}fl+FU%M7leHLtQq2H0;7)h z&i%C)d0|Wb)BHVW>&)EtGyg%4H_CT-f)aELKn3)8X+2RXBq#ak-q7)s$i-=6Zvztn zNt|j$ADF(^p*QkZdnZPr=N;m&0N4bIe~_Lzqg>MaW(rHFJ|Ni^#vtjIxnHtaJWW7*74_)_h*=a*AS75FX@aEl8KewThg@~vDB++EA8=Lz>@8f!1{=`EAl$|={9jlU z;Mi1DG8YNvtAwZ0FYra{FCZ-D9}HiK4HWZ&?>a8LW2I%09Nya9jo=`&lnKZIJBPpN l3Ab)e{k-CJ3F^>CZRNPEM|n=PD{!N_GgkH|iwPb#{tfjN)4l)z literal 0 HcmV?d00001 diff --git a/docs/endstops.md b/docs/endstops.md new file mode 100644 index 00000000..5b94cb97 --- /dev/null +++ b/docs/endstops.md @@ -0,0 +1,73 @@ +# Endstops + +Endstops are used for both "homing" the axis of a machine and for stopping the machine in the event it attempts to go "out of bounds". + +## Configuration Properties + +Each axis has two endstops: a "min" and a "max". Each endstop has the following properties: + +Name | Type | Default +--- | -- | -- +gpio_num | int | 0 +enabled | boolean | False +offset | int | 0 +debounce_ms | float | 100.0 +is_active_high | boolean | False + + +### gpio_num +The GPIO pin number, according to the silkscreen labels on ODrive + +### enabled +Enables/disables detection of the endstop. If disabled, homing and e-stop cannot take place. + +### offset +This is the location along the axis, in counts, that the endstop is positioned at. For example, if you want a position command of `0` to represent a position 100 counts away from the endstop, the offset would be -100 (because the endstop is at position -100) + +### debounce_ms +The debouncing time, in milliseconds, for this endstop. Most switches exhibit some sort of bounce, and this setting will help prevent the switch from triggering repeatedly. It works for both HIGH and LOW transitions, regardless of the setting of `is_active_high`. + +### is_active_high +This is how you configure the endstop to be either "NPN" or "PNP". An "NPN" configuration would be `is_active_high = False` whereas a PNP configuration is `is_active_high = True`. Refer to the following table for more information: + +![Endstop configuration](Endstop_configuration.png) + +3D printer endstops (like those that come with a RAMPS 1.4) are typically configuration **4**. + +### Configuring an endstop + +You can access these configuration properties through odrivetool. For example, if we want to configure a 3D printer-style minimum endstop on GPIO 5 for homing, and you want your motor to pull off the endstop about a quarter turn with a 8192 cpr encoder, you would set: + +``` +..min_endstop.config.gpio_num = 5 +..min_endstop.config.is_active_high = 4 +..min_endstop.config.offset = -2048; +..min_endstop.config.enabled = True + +.save_configuration() +.reboot() +``` + + +## Homing + +Homing is possible once the ODrive has closed loop control over the axis. To trigger homing, we use must first be in AXIS_STATE_CLOSED_LOOP_CONTROL, then we call`..controller.home_axis()` This starts the homing sequence. The homing sequence works as follows: + +1. Verify that the `min_endstop` is `enabled` +2. Drive towards the `min_endstop` in velocity control mode at `controller.config.homing_speed` +3. When the `min_endstop` is pressed, set the current position = `min_endstop.config.offset` +4. Request position control mode, and move to the positon = `0` + +### Homing Speed +Homing speed is configurable through the value +`..controller.config.homing_speed` in counts/second. It has the default value of 2000 counts/second. + +Note the assumption is made that `min_endstop` is in the negative direction, thus the velocity commanded is `-controller.config.homing_speed`, which will drive the axis towards the endstop. If you have an unusual setup and want to change this behaviour, simply use a negative value for `homing_speed`. + +### Homing at startup +It is possible to configure the odrive to enter homing immediately at startup. For safety reasons, we require the user to specifically enable closed loop control at startup, even if homing is requested. Thus, to enable homing at startup, the following must be configured: + +``` +..config.startup_closed_loop_control = True +..config.startup_homing = True +``` From 3c144749cd6e68766eb602171449a08350657e7a Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 13:55:37 +0200 Subject: [PATCH 133/549] Fix bad enum --- Firmware/MotorControl/axis.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 12ccea3b..402da78a 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -29,8 +29,8 @@ public: ERROR_POS_CTRL_DURING_SENSORLESS = 0x400, ERROR_WATCHDOG_TIMER_EXPIRED = 0x800, ERROR_MIN_ENDSTOP_PRESSED = 0x1000, - ERROR_MAX_ENDSTOP_PRESSED = 0x2000 - ERROR_ESTOP_REQUESTED = 0x1000 + ERROR_MAX_ENDSTOP_PRESSED = 0x2000, + ERROR_ESTOP_REQUESTED = 0x4000, }; enum State_t { From 104e4413c9d9e00274cf190c0e336ad3acfcc55d Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 14:07:44 +0200 Subject: [PATCH 134/549] Fix input_filter integration issues --- Firmware/MotorControl/axis.cpp | 5 ++++- Firmware/MotorControl/controller.cpp | 5 ++++- Firmware/MotorControl/controller.hpp | 2 +- Firmware/communication/can_simple.cpp | 11 +++++++---- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 6589532e..b2c10ff4 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -288,7 +288,10 @@ bool Axis::run_closed_loop_control_loop() { if (homing_state_ == HOMING_STATE_HOMING) { if (min_endstop_.getEndstopState()) { encoder_.set_linear_count(min_endstop_.config_.offset); - controller_.set_pos_setpoint(0.0f, 0.0f, 0.0f); + controller_.pos_setpoint_ = 0.0f; + controller_.vel_setpoint_ = 0.0f; + controller_.current_setpoint_ = 0.0f; + controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; homing_state_ = HOMING_STATE_MOVE_TO_ZERO; } } else if (homing_state_ == HOMING_STATE_MOVE_TO_ZERO) { diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 35eb3029..7063f081 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -59,7 +59,10 @@ void Controller::start_anticogging_calibration() { // When pressed, set the linear count to the offset (default 0), and then bool Controller::home_axis() { if (axis_->min_endstop_.config_.enabled) { - set_vel_setpoint(-config_.homing_speed, 0.0f); + config_.control_mode = CTRL_MODE_VELOCITY_CONTROL; + pos_setpoint_ = 0.0f; + vel_setpoint_ = -config_.homing_speed; + current_setpoint_ = 0.0f; axis_->homing_state_ = HOMING_STATE_HOMING; } else { return false; diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index d008adac..5673a9d4 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -133,7 +133,7 @@ public: make_protocol_property("vel_limit", &config_.vel_limit), make_protocol_property("vel_limit_tolerance", &config_.vel_limit_tolerance), make_protocol_property("vel_ramp_rate", &config_.vel_ramp_rate), - make_protocol_property("homing_speed", &config_.homing_speed) + make_protocol_property("homing_speed", &config_.homing_speed), make_protocol_property("inertia", &config_.inertia), make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth, [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 81502c8e..44f3844f 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -280,15 +280,18 @@ void CANSimple::move_to_pos_callback(Axis* axis, can_Message_t& msg) { } void CANSimple::set_pos_setpoint_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.set_pos_setpoint(can_getSignal(msg, 0, 32, true, 1, 0), can_getSignal(msg, 32, 16, true, 0.1f, 0), can_getSignal(msg, 48, 16, true, 0.01f, 0)); + axis->controller_.pos_setpoint_ = can_getSignal(msg, 0, 32, true, 1, 0); + axis->controller_.vel_setpoint_ = can_getSignal(msg, 32, 16, true, 0.1f, 0); + axis->controller_.current_setpoint_ = can_getSignal(msg, 48, 16, true, 0.01f, 0); } void CANSimple::set_vel_setpoint_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.set_vel_setpoint(can_getSignal(msg, 0, 32, true, 0.01f, 0.0f), can_getSignal(msg, 4, 32, true, 0.01f, 0.0f)); + axis->controller_.vel_setpoint_ = can_getSignal(msg, 0, 32, true, 0.01f, 0.0f); + axis->controller_.current_setpoint_ = can_getSignal(msg, 32, 16, true, 0.01f, 0.0f); } void CANSimple::set_current_setpoint_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.set_current_setpoint(can_getSignal(msg, 0, 32, true, 0.01f, 0)); + axis->controller_.current_setpoint_ = can_getSignal(msg, 0, 32, true, 0.01f, 0); } void CANSimple::set_vel_limit_callback(Axis* axis, can_Message_t& msg) { @@ -309,7 +312,7 @@ void CANSimple::set_traj_accel_limits_callback(Axis* axis, can_Message_t& msg) { } void CANSimple::set_traj_A_per_css_callback(Axis* axis, can_Message_t& msg) { - axis->trap_.config_.A_per_css = can_getSignal(msg, 0, 32, true, 1, 0); + axis->controller_.config_.inertia = can_getSignal(msg, 0, 32, true, 1, 0); } void CANSimple::get_iq_callback(Axis* axis, can_Message_t& msg) { From f5c7cd82e5215ca4a4503c7a7c9ca4a54c5e8c7f Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 14:14:31 +0200 Subject: [PATCH 135/549] Fix cppcheck issue --- ODrive_Workspace.code-workspace | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ODrive_Workspace.code-workspace b/ODrive_Workspace.code-workspace index a131f5cb..6e9385a0 100644 --- a/ODrive_Workspace.code-workspace +++ b/ODrive_Workspace.code-workspace @@ -11,7 +11,7 @@ } ], "settings": { - "c-cpp-flylint.cppcheck.standard": ["c99","c++17"], + "c-cpp-flylint.cppcheck.standard": ["c99","c++14"], "files.associations": { "memory": "cpp", "utility": "cpp", From eb385f0bb9bd30b074c4c2b2cbca9303f4af2f49 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 14:18:50 +0200 Subject: [PATCH 136/549] Remove set_pos_setpoint calls --- Firmware/MotorControl/controller.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index f668bff4..fc302219 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -86,11 +86,17 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_current_; } if (config_.anticogging.index < 3600) { - set_pos_setpoint(config_.anticogging.index * config_.anticogging.cogging_ratio, 0.0f, 0.0f); + config_.control_mode = CTRL_MODE_POSITION_CONTROL; + pos_setpoint_ = config_.anticogging.index * config_.anticogging.cogging_ratio; + vel_setpoint_ = 0.0f; + current_setpoint_ = 0.0f; return false; } else { config_.anticogging.index = 0; - set_pos_setpoint(0.0f, 0.0f, 0.0f); // Send the motor home + config_.control_mode = CTRL_MODE_POSITION_CONTROL; + pos_setpoint_ = 0.0f; // Send the motor home + vel_setpoint_ = 0.0f; + current_setpoint_ = 0.0f; config_.anticogging.use_anticogging = true; // We're good to go, enable anti-cogging config_.anticogging.calib_anticogging = false; return true; From 1e40897c2add38bd92cfb089af229b6ba0185ee6 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 14:52:01 +0200 Subject: [PATCH 137/549] Fix CANSimple to use input_pos, vel, current --- Firmware/communication/can_simple.cpp | 40 ++++++++++++++------------- Firmware/communication/can_simple.hpp | 20 +++++++------- docs/can-protocol.md | 17 ++++++------ 3 files changed, 40 insertions(+), 37 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 44f3844f..8efa2e13 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -76,17 +76,17 @@ void CANSimple::handle_can_message(can_Message_t& msg) { case MSG_GET_ENCODER_COUNT: get_encoder_count_callback(axis, msg); break; - case MSG_MOVE_TO_POS: - move_to_pos_callback(axis, msg); + case MSG_SET_INPUT_POS: + set_input_pos_callback(axis, msg); break; - case MSG_SET_POS_SETPOINT: - set_pos_setpoint_callback(axis, msg); + case MSG_SET_INPUT_VEL: + set_input_pos_callback(axis, msg); break; - case MSG_SET_VEL_SETPOINT: - set_vel_setpoint_callback(axis, msg); + case MSG_SET_INPUT_CURRENT: + set_input_pos_callback(axis, msg); break; - case MSG_SET_CUR_SETPOINT: - set_current_setpoint_callback(axis, msg); + case MSG_SET_CONTROLLER_MODES: + set_controller_modes_callback(axis, msg); break; case MSG_SET_VEL_LIMIT: set_vel_limit_callback(axis, msg); @@ -275,23 +275,25 @@ void CANSimple::get_encoder_count_callback(Axis* axis, can_Message_t& msg) { } } -void CANSimple::move_to_pos_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.move_to_pos(can_getSignal(msg, 0, 32, true, 1, 0)); +void CANSimple::set_input_pos_callback(Axis* axis, can_Message_t& msg) { + axis->controller_.input_pos_ = can_getSignal(msg, 0, 32, true, 1, 0); + axis->controller_.input_vel_ = can_getSignal(msg, 32, 16, true, 0.1f, 0); + axis->controller_.input_current_ = can_getSignal(msg, 48, 16, true, 0.01f, 0); + axis->controller_.input_pos_updated(); } -void CANSimple::set_pos_setpoint_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.pos_setpoint_ = can_getSignal(msg, 0, 32, true, 1, 0); - axis->controller_.vel_setpoint_ = can_getSignal(msg, 32, 16, true, 0.1f, 0); - axis->controller_.current_setpoint_ = can_getSignal(msg, 48, 16, true, 0.01f, 0); +void CANSimple::set_input_vel_callback(Axis* axis, can_Message_t& msg) { + axis->controller_.input_vel_ = can_getSignal(msg, 0, 32, true, 0.01f, 0.0f); + axis->controller_.input_current_ = can_getSignal(msg, 32, 16, true, 0.01f, 0.0f); } -void CANSimple::set_vel_setpoint_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.vel_setpoint_ = can_getSignal(msg, 0, 32, true, 0.01f, 0.0f); - axis->controller_.current_setpoint_ = can_getSignal(msg, 32, 16, true, 0.01f, 0.0f); +void CANSimple::set_input_current_callback(Axis* axis, can_Message_t& msg) { + axis->controller_.input_current_ = can_getSignal(msg, 0, 32, true, 0.01f, 0); } -void CANSimple::set_current_setpoint_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.current_setpoint_ = can_getSignal(msg, 0, 32, true, 0.01f, 0); +void CANSimple::set_controller_modes_callback(Axis* axis, can_Message_t& msg){ + axis->controller_.config_.control_mode = can_getSignal(msg, 0, 32, true, 1, 0); + axis->controller_.config_.input_mode = can_getSignal(msg, 32, 32, true, 1, 0); } void CANSimple::set_vel_limit_callback(Axis* axis, can_Message_t& msg) { diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp index 75553b87..c2e16a61 100644 --- a/Firmware/communication/can_simple.hpp +++ b/Firmware/communication/can_simple.hpp @@ -7,8 +7,7 @@ class CANSimple { public: enum { MSG_CO_NMT_CTRL = 0x000, // CANOpen NMT Message REC - MSG_CO_HEARTBEAT_CMD = 0x700, // CANOpen NMT Heartbeat SEND - MSG_ODRIVE_HEARTBEAT = 0x001, + MSG_ODRIVE_HEARTBEAT, MSG_ODRIVE_ESTOP, MSG_GET_MOTOR_ERROR, // Errors MSG_GET_ENCODER_ERROR, @@ -18,10 +17,10 @@ class CANSimple { MSG_SET_AXIS_STARTUP_CONFIG, MSG_GET_ENCODER_ESTIMATES, MSG_GET_ENCODER_COUNT, - MSG_MOVE_TO_POS, - MSG_SET_POS_SETPOINT, - MSG_SET_VEL_SETPOINT, - MSG_SET_CUR_SETPOINT, + MSG_SET_CONTROLLER_MODES, + MSG_SET_INPUT_POS, + MSG_SET_INPUT_VEL, + MSG_SET_INPUT_CURRENT, MSG_SET_VEL_LIMIT, MSG_START_ANTICOGGING, MSG_SET_TRAJ_VEL_LIMIT, @@ -31,6 +30,7 @@ class CANSimple { MSG_GET_SENSORLESS_ESTIMATES, MSG_RESET_ODRIVE, MSG_GET_VBUS_VOLTAGE, + MSG_CO_HEARTBEAT_CMD = 0x700, // CANOpen NMT Heartbeat SEND }; static void handle_can_message(can_Message_t& msg); @@ -48,10 +48,10 @@ class CANSimple { static void set_axis_startup_config_callback(Axis* axis, can_Message_t& msg); static void get_encoder_estimates_callback(Axis* axis, can_Message_t& msg); static void get_encoder_count_callback(Axis* axis, can_Message_t& msg); - static void move_to_pos_callback(Axis* axis, can_Message_t& msg); - static void set_pos_setpoint_callback(Axis* axis, can_Message_t& msg); - static void set_vel_setpoint_callback(Axis* axis, can_Message_t& msg); - static void set_current_setpoint_callback(Axis* axis, can_Message_t& msg); + static void set_input_pos_callback(Axis* axis, can_Message_t& msg); + static void set_input_vel_callback(Axis* axis, can_Message_t& msg); + static void set_input_current_callback(Axis* axis, can_Message_t& msg); + static void set_controller_modes_callback(Axis* axis, can_Message_t& msg); static void set_vel_limit_callback(Axis* axis, can_Message_t& msg); static void start_anticogging_callback(Axis* axis, can_Message_t& msg); static void set_traj_vel_limit_callback(Axis* axis, can_Message_t& msg); diff --git a/docs/can-protocol.md b/docs/can-protocol.md index fd1c49d1..fe24cab4 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -46,10 +46,10 @@ CMD ID | Name | Sender | Signals | Start byte 0x008 | Set Axis Startup Config | Master | - Not yet implemented - | - 0x009 | Get Encoder Estimates\* | Master | Encoder Pos Estimate
    Encoder Vel Estimate | 0
    4 0x00A | Get Encoder Count\* | Master | Encoder Shadow Count
    Encoder Count in CPR | 0
    4 -0x00B | Move To Pos | Master | Goal Position | 0 -0x00C | Set Pos Setpoint | Master | Pos Setpoint
    Vel FF
    Current FF | 0
    4
    6 -0x00D | Set Vel Setpoint | Master | Vel Setpoint
    Current FF | 0
    4 -0x00E | Set Current Setpoint | Master | Current Setpoint | 0 +0x00B | Set Controller Modes | Master | Control Mode
    Input Mode | 0
    4 +0x00C | Set Input Pos | Master | Input Pos
    Vel FF
    Current FF | 0
    4
    6 +0x00D | Set Input Vel | Master | Input Current
    Current FF | 0
    4 +0x00E | Set Input Current | Master | Input Current | 0 0x00F | Set Velocity Limit | Master | Velocity Limit | 0 0x010 | Start Anticogging | Master | - | - 0x011 | Set Traj Vel Limit | Master | Traj Vel Limit | 0 @@ -79,12 +79,13 @@ Encoder Pos Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel Encoder Vel Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel Encoder Shadow Count | Signed Int | 32 | 1 | 0 | Intel Encoder Count In CPR | Signed Int | 32 | 1 | 0 | Intel -Goal Position | Signed Int | 32 | 1 | 0 | Intel -Pos Setpoint | Signed Int | 32 | 1 | 0 | Intel +Control Mode | Signed Int | 32 | 1 | 0 | Intel +Input Mode | Signed Int | 32 | 1 | 0 | Intel +Input Pos | Signed Int | 32 | 1 | 0 | Intel Vel FF | Signed Int | 16 | 0.1 | 0 | Intel Current FF | Signed Int | 16 | 0.01 | 0 | Intel -Vel Setpoint | Signed Int | 32 | 0.01 | 0 | Intel -Current Setpoint | Signed Int | 32 | 0.01 | 0 | Intel +Input Vel | Signed Int | 32 | 0.01 | 0 | Intel +Input Current | Signed Int | 32 | 0.01 | 0 | Intel Velocity Limit | IEEE 754 Float | 32 | 1 | 0 | Intel Traj Vel Limit | IEEE 754 Float | 32 | 1 | 0 | Intel Traj Accel Limit | IEEE 754 Float | 32 | 1 | 0 | Intel From 263b209e20d270e23a1b3f270ab82b824a3860f3 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 14:54:13 +0200 Subject: [PATCH 138/549] Move CANopen heartbeat message --- docs/can-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/can-protocol.md b/docs/can-protocol.md index fe24cab4..7be636e8 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -35,7 +35,6 @@ Be careful that you don't assign too many nodeIDs per PDO group. Four CAN Simpl CMD ID | Name | Sender | Signals | Start byte --: | :-- | :-- | :-- | :-- 0x000 | CANOpen NMT Message\*\* | Master | - | - | - -0x700 | CANOpen Heartbeat Message\*\* | Slave | - | - | - 0x001 | ODrive Heartbeat Message | Axis | Axis Error
    Axis Current State | 0
    4 0x002 | ODrive Estop Message | Master | - | - | - 0x003 | Get Motor Error\* | Axis | Motor Error | 0 @@ -59,6 +58,7 @@ CMD ID | Name | Sender | Signals | Start byte 0x015 | Get Sensorless Estimates\* | Master | Sensorless Pos Estimate
    Sensorless Vel Estimate | 0
    4 0x016 | Reboot ODrive | Master\*\*\* | | 0x017 | Get Vbus Voltage | Master\*\*\* | Vbus Voltage | 0 +0x700 | CANOpen Heartbeat Message\*\* | Slave | - | - | - \* Note: These messages are call & response. The Master node sends a message with the RTR bit set, and the axis responds with the same ID and specified payload. \*\* Note: These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple. From 6bd14c51adc7f862f507cc4e1be9282c73097346 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 14:54:23 +0200 Subject: [PATCH 139/549] Add CAN tests for enums --- Firmware/Tests/test_runner.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index 3214f278..86d3d261 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -30,6 +30,15 @@ struct can_Signal_t { const float offset; }; +enum InputMode_t { + INPUT_MODE_INACTIVE, + INPUT_MODE_PASSTHROUGH, + INPUT_MODE_VEL_RAMP, + INPUT_MODE_POS_FILTER, + INPUT_MODE_MIX_CHANNELS, + INPUT_MODE_TRAP_TRAJ, +}; + // Fetch a specific signal from the message template T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { @@ -150,4 +159,12 @@ TEST_SUITE("CAN Functions") { can_setSignal(txmsg, 234981.0f, 12, 32, false, 2.0f, 1.1f); CHECK(can_getSignal(txmsg, 12, 32, false, 2.0f, 1.1f) == 234981.0f); } + + TEST_CASE("getSignal enums") { + can_Message_t rxmsg; + rxmsg.buf[0] = INPUT_MODE_MIX_CHANNELS; + rxmsg.buf[1] = INPUT_MODE_PASSTHROUGH; + CHECK(can_getSignal(rxmsg, 0, 8, true, 1, 0) == INPUT_MODE_MIX_CHANNELS); + CHECK(can_getSignal(rxmsg, 8, 8, true, 1, 0) == INPUT_MODE_PASSTHROUGH); + } } \ No newline at end of file From cf9857651e4a4b14b3eadfbd39a132cd78aae54c Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 14:54:41 +0200 Subject: [PATCH 140/549] Make Homing use input_xxx values --- Firmware/MotorControl/axis.cpp | 7 ++++--- Firmware/MotorControl/controller.cpp | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 7305e28e..6d16d914 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -287,10 +287,11 @@ bool Axis::run_closed_loop_control_loop() { if (homing_state_ == HOMING_STATE_HOMING) { if (min_endstop_.getEndstopState()) { encoder_.set_linear_count(min_endstop_.config_.offset); - controller_.pos_setpoint_ = 0.0f; - controller_.vel_setpoint_ = 0.0f; - controller_.current_setpoint_ = 0.0f; + controller_.input_pos_ = 0.0f; + controller_.input_vel_ = 0.0f; + controller_.input_current_ = 0.0f; controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; + controller_.input_pos_updated(); homing_state_ = HOMING_STATE_MOVE_TO_ZERO; } } else if (homing_state_ == HOMING_STATE_MOVE_TO_ZERO) { diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index fc302219..623f9845 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -61,9 +61,10 @@ void Controller::start_anticogging_calibration() { bool Controller::home_axis() { if (axis_->min_endstop_.config_.enabled) { config_.control_mode = CTRL_MODE_VELOCITY_CONTROL; - pos_setpoint_ = 0.0f; - vel_setpoint_ = -config_.homing_speed; - current_setpoint_ = 0.0f; + input_pos_ = 0.0f; + input_pos_updated(); + input_vel_ = -config_.homing_speed; + input_current_ = 0.0f; axis_->homing_state_ = HOMING_STATE_HOMING; } else { return false; @@ -87,16 +88,18 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) } if (config_.anticogging.index < 3600) { config_.control_mode = CTRL_MODE_POSITION_CONTROL; - pos_setpoint_ = config_.anticogging.index * config_.anticogging.cogging_ratio; - vel_setpoint_ = 0.0f; - current_setpoint_ = 0.0f; + input_pos_ = config_.anticogging.index * config_.anticogging.cogging_ratio; + input_vel_ = 0.0f; + input_current_ = 0.0f; + input_pos_updated(); return false; } else { config_.anticogging.index = 0; config_.control_mode = CTRL_MODE_POSITION_CONTROL; - pos_setpoint_ = 0.0f; // Send the motor home - vel_setpoint_ = 0.0f; - current_setpoint_ = 0.0f; + input_pos_ = 0.0f; // Send the motor home + input_vel_ = 0.0f; + input_current_ = 0.0f; + input_pos_updated(); config_.anticogging.use_anticogging = true; // We're good to go, enable anti-cogging config_.anticogging.calib_anticogging = false; return true; From dfca5561852385fbf1690e31293c6c95d2fa3664 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 17:04:55 +0200 Subject: [PATCH 141/549] Add anticogging config properties to protocol --- Firmware/MotorControl/controller.cpp | 6 +++--- Firmware/MotorControl/controller.hpp | 14 +++++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 5634d18b..65c9a2bb 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -87,7 +87,7 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) float pos_err = config_.anticogging.index - pos_estimate; if (fabsf(pos_err) <= config_.anticogging.calib_pos_threshold && fabsf(vel_estimate) < config_.anticogging.calib_vel_threshold) { - config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_current_; + config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_current_; } if (config_.anticogging.index < 3600) { set_pos_setpoint(config_.anticogging.index * config_.anticogging.cogging_ratio, 0.0f, 0.0f); @@ -95,7 +95,7 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) } else { config_.anticogging.index = 0; set_pos_setpoint(0.0f, 0.0f, 0.0f); // Send the motor home - config_.anticogging.use_anticogging = true; // We're good to go, enable anti-cogging + config_.anticogging.anticogging_valid = true; // We're good to go, enable anti-cogging config_.anticogging.calib_anticogging = false; return true; } @@ -180,7 +180,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // Anti-cogging is enabled after calibration // We get the current position and apply a current feed-forward // ensuring that we handle negative encoder positions properly (-1 == motor->encoder.encoder_cpr - 1) - if (config_.anticogging.use_anticogging) { + if (config_.anticogging.anticogging_valid) { Iq += config_.anticogging.cogging_map[std::clamp(mod(static_cast(anticogging_pos), axis_->encoder_.config_.cpr), 0, 3600)]; } diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 2e2886f8..8d271d88 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -23,9 +23,9 @@ public: }; typedef struct { - int index = 0; + uint32_t index = 0; float cogging_map[3600]; - bool use_anticogging = false; + bool anticogging_valid = false; bool calib_anticogging = false; float calib_pos_threshold = 1.0f; float calib_vel_threshold = 1.0f; @@ -104,7 +104,15 @@ public: make_protocol_property("vel_limit", &config_.vel_limit), make_protocol_property("vel_limit_tolerance", &config_.vel_limit_tolerance), make_protocol_property("vel_ramp_rate", &config_.vel_ramp_rate), - make_protocol_property("setpoints_in_cpr", &config_.setpoints_in_cpr) + make_protocol_property("setpoints_in_cpr", &config_.setpoints_in_cpr), + make_protocol_object("anticogging", + make_protocol_ro_property("index", &config_.anticogging.index), + make_protocol_property("anticogging_valid", &config_.anticogging.anticogging_valid), + make_protocol_ro_property("calib_anticogging", &config_.anticogging.calib_anticogging), + make_protocol_property("calib_pos_threshold", &config_.anticogging.calib_pos_threshold), + make_protocol_property("calib_vel_threshold", &config_.anticogging.calib_vel_threshold), + make_protocol_ro_property("cogging_ratio", &config_.anticogging.cogging_ratio) + ) ), make_protocol_function("set_pos_setpoint", *this, &Controller::set_pos_setpoint, "pos_setpoint", "vel_feed_forward", "current_feed_forward"), From 6654b3a37576aad6a96bcbfff87c25e54fb1037c Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 17:29:25 +0200 Subject: [PATCH 142/549] Just fetch the cogging ratio every time we need it --- Firmware/MotorControl/controller.cpp | 7 ++++--- Firmware/MotorControl/encoder.cpp | 4 ---- Firmware/MotorControl/encoder.hpp | 9 +++++---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 65c9a2bb..1199682a 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -5,7 +5,8 @@ Controller::Controller(Config_t& config) : config_(config) -{} +{ +} void Controller::reset() { pos_setpoint_ = 0.0f; @@ -90,7 +91,7 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_current_; } if (config_.anticogging.index < 3600) { - set_pos_setpoint(config_.anticogging.index * config_.anticogging.cogging_ratio, 0.0f, 0.0f); + set_pos_setpoint(config_.anticogging.index * axis_->encoder_.getCoggingRatio(), 0.0f, 0.0f); return false; } else { config_.anticogging.index = 0; @@ -106,7 +107,7 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) bool Controller::update(float pos_estimate, float vel_estimate, float* current_setpoint_output) { // Only runs if config_.anticogging.calib_anticogging is true; non-blocking anticogging_calibration(pos_estimate, vel_estimate); - float anticogging_pos = pos_estimate / config_.anticogging.cogging_ratio; + float anticogging_pos = pos_estimate / axis_->encoder_.getCoggingRatio(); // Trajectory control if (config_.control_mode == CTRL_MODE_TRAJECTORY_CONTROL) { diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 27679c33..95dc91f9 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -99,10 +99,6 @@ void Encoder::set_linear_count(int32_t count) { cpu_exit_critical(prim); } -void Encoder::cpr_changed_callback(){ - axis_->controller_.config_.anticogging.cogging_ratio = config_.cpr / 3600.0f; -} - // Function that sets the CPR circular tracking encoder count to a desired 32-bit value. // Note that this will get mod'ed down to [0, cpr) void Encoder::set_circular_count(int32_t count, bool update_offset) { diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index b6a3b3d8..9d2c272c 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -67,9 +67,6 @@ public: void sample_now(); bool update(); - void cpr_changed_callback(); - - const EncoderHardwareConfig_t& hw_config_; Config_t& config_; Axis* axis_ = nullptr; // set by Axis constructor @@ -94,6 +91,10 @@ public: float sincos_sample_s_ = 0.0f; float sincos_sample_c_ = 0.0f; + constexpr float getCoggingRatio(){ + return config_.cpr / 3600.0f; + } + // Communication protocol definitions auto make_protocol_definitions() { return make_protocol_member_list( @@ -120,7 +121,7 @@ public: 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, [](void* ctx) { static_cast(ctx)->cpr_changed_callback(); }, this), + make_protocol_property("cpr", &config_.cpr), make_protocol_property("offset", &config_.offset), make_protocol_property("offset_float", &config_.offset_float), make_protocol_property("enable_phase_interpolation", &config_.enable_phase_interpolation), From 5a812701a318f54cf74f3645587b27a4b0c5df8f Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 17:47:06 +0200 Subject: [PATCH 143/549] Fix some merging pains with the anticogging_saver --- Firmware/MotorControl/controller.cpp | 4 ++-- Firmware/MotorControl/controller.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 4f5aef0d..f844ed9c 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -88,7 +88,7 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) } if (config_.anticogging.index < 3600) { config_.control_mode = CTRL_MODE_POSITION_CONTROL; - input_pos_ = config_.anticogging.index * config_.anticogging.cogging_ratio; + input_pos_ = config_.anticogging.index * axis_->encoder_.getCoggingRatio(); input_vel_ = 0.0f; input_current_ = 0.0f; input_pos_updated(); @@ -100,7 +100,7 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) input_vel_ = 0.0f; input_current_ = 0.0f; input_pos_updated(); - config_.anticogging.use_anticogging = true; // We're good to go, enable anti-cogging + config_.anticogging.anticogging_valid = true; // We're good to go, enable anti-cogging config_.anticogging.calib_anticogging = false; return true; } diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index e1f899d7..c6f48bed 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -130,7 +130,7 @@ public: make_protocol_property("homing_speed", &config_.homing_speed), make_protocol_property("inertia", &config_.inertia), make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth, - [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this) + [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this), make_protocol_object("anticogging", make_protocol_ro_property("index", &config_.anticogging.index), make_protocol_property("anticogging_valid", &config_.anticogging.anticogging_valid), From 949ad66124e637b7d4d685290ecc0685e761447c Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 18:07:02 +0200 Subject: [PATCH 144/549] Fix anticogging_calibration where cpr != 3600 --- Firmware/MotorControl/controller.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index f844ed9c..47610e4a 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -81,7 +81,8 @@ bool Controller::home_axis() { */ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) { if (config_.anticogging.calib_anticogging) { - float pos_err = config_.anticogging.index - pos_estimate; + config_.anticogging.anticogging_valid = false; + float pos_err = input_pos_ - pos_estimate; if (fabsf(pos_err) <= config_.anticogging.calib_pos_threshold && fabsf(vel_estimate) < config_.anticogging.calib_vel_threshold) { config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_current_; From 9e2476ed6510a5b04d675242e810537a26231484 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 18:20:09 +0200 Subject: [PATCH 145/549] Remove anticogging_valid from config for now --- Firmware/MotorControl/controller.cpp | 5 ++--- Firmware/MotorControl/controller.hpp | 6 ++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 47610e4a..bbbe6af9 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -81,7 +81,6 @@ bool Controller::home_axis() { */ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) { if (config_.anticogging.calib_anticogging) { - config_.anticogging.anticogging_valid = false; float pos_err = input_pos_ - pos_estimate; if (fabsf(pos_err) <= config_.anticogging.calib_pos_threshold && fabsf(vel_estimate) < config_.anticogging.calib_vel_threshold) { @@ -101,7 +100,7 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) input_vel_ = 0.0f; input_current_ = 0.0f; input_pos_updated(); - config_.anticogging.anticogging_valid = true; // We're good to go, enable anti-cogging + anticogging_valid_ = true; config_.anticogging.calib_anticogging = false; return true; } @@ -219,7 +218,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // Anti-cogging is enabled after calibration // We get the current position and apply a current feed-forward // ensuring that we handle negative encoder positions properly (-1 == motor->encoder.encoder_cpr - 1) - if (config_.anticogging.anticogging_valid) { + if (anticogging_valid_) { Iq += config_.anticogging.cogging_map[std::clamp(mod(static_cast(anticogging_pos), axis_->encoder_.config_.cpr), 0, 3600)]; } diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index c6f48bed..5b26940c 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -35,7 +35,6 @@ public: typedef struct { uint32_t index = 0; float cogging_map[3600]; - bool anticogging_valid = false; bool calib_anticogging = false; float calib_pos_threshold = 1.0f; float calib_vel_threshold = 1.0f; @@ -105,6 +104,8 @@ public: float goal_point_ = 0.0f; bool trajectory_done_ = true; + bool anticogging_valid_ = false; + // Communication protocol definitions auto make_protocol_definitions() { return make_protocol_member_list( @@ -118,6 +119,7 @@ public: make_protocol_ro_property("current_setpoint", ¤t_setpoint_), make_protocol_ro_property("trajectory_done", &trajectory_done_), make_protocol_property("vel_integrator_current", &vel_integrator_current_), + make_protocol_property("anticogging_valid", &anticogging_valid_), make_protocol_object("config", make_protocol_property("control_mode", &config_.control_mode), make_protocol_property("input_mode", &config_.input_mode), @@ -133,7 +135,7 @@ public: [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this), make_protocol_object("anticogging", make_protocol_ro_property("index", &config_.anticogging.index), - make_protocol_property("anticogging_valid", &config_.anticogging.anticogging_valid), + make_protocol_ro_property("calib_anticogging", &config_.anticogging.calib_anticogging), make_protocol_property("calib_pos_threshold", &config_.anticogging.calib_pos_threshold), make_protocol_property("calib_vel_threshold", &config_.anticogging.calib_vel_threshold), From 05711d1eba19bf9df16d4f0498e784c4ae4046ae Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 19:35:15 +0200 Subject: [PATCH 146/549] Bring the anti-hunt functionality back --- Firmware/MotorControl/controller.cpp | 10 ++++++++-- Firmware/MotorControl/controller.hpp | 4 ++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index d295246c..83c96fc9 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -142,6 +142,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // Position control // TODO Decide if we want to use encoder or pll position here + float gain_scheduling_multiplier = 1.0f; float vel_des = vel_setpoint_; if (config_.control_mode >= CTRL_MODE_POSITION_CONTROL) { float pos_err; @@ -158,6 +159,11 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s pos_err = pos_setpoint_ - pos_estimate; } vel_des += config_.pos_gain * pos_err; + // V-shaped gain shedule based on position error + float abs_pos_err = fabsf(pos_err); + if (config_.enable_gain_scheduling && abs_pos_err <= config_.gain_scheduling_width) { + gain_scheduling_multiplier = abs_pos_err / config_.gain_scheduling_width; + } } // Velocity limiting @@ -185,7 +191,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s float v_err = vel_des - vel_estimate; if (config_.control_mode >= CTRL_MODE_VELOCITY_CONTROL) { - Iq += config_.vel_gain * v_err; + Iq += (config_.vel_gain * gain_scheduling_multiplier) * v_err; } // Velocity integral action before limiting @@ -212,7 +218,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // TODO make decayfactor configurable vel_integrator_current_ *= 0.99f; } else { - vel_integrator_current_ += (config_.vel_integrator_gain * current_meas_period) * v_err; + vel_integrator_current_ += ((config_.vel_integrator_gain * gain_scheduling_multiplier) * current_meas_period) * v_err; } } diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 020f34d0..16674d90 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -32,6 +32,8 @@ public: float vel_limit_tolerance = 1.2f; // ratio to vel_lim. 0.0f to disable float vel_ramp_rate = 10000.0f; // [(counts/s) / s] bool setpoints_in_cpr = false; + float gain_scheduling_width = 10.0f; + bool enable_gain_scheduling = false; }; explicit Controller(Config_t& config); @@ -102,6 +104,8 @@ public: make_protocol_property("current_setpoint", ¤t_setpoint_), make_protocol_property("vel_ramp_target", &vel_ramp_target_), make_protocol_property("vel_ramp_enable", &vel_ramp_enable_), + make_protocol_property("gain_scheduling_width", &config_.gain_scheduling_width), + make_protocol_property("enable_gain_scheduling", &config_.enable_gain_scheduling), make_protocol_object("config", make_protocol_property("control_mode", &config_.control_mode), make_protocol_property("pos_gain", &config_.pos_gain), From 59d7bb405c2d778ba026f5821256d559f6cf4942 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 20:42:26 +0200 Subject: [PATCH 147/549] Add missing encoder modes for coherent sampling --- Firmware/MotorControl/encoder.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index b268028a..704ead7f 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -293,6 +293,12 @@ void Encoder::sample_now() { sincos_sample_c_ = (get_adc_voltage(GPIO_4_GPIO_Port, GPIO_4_Pin) / 3.3f) - 0.5f; } break; + case MODE_SPI_ABS_AMS: + case MODE_SPI_ABS_CUI: + { + // Do nothing + } break; + default: { set_error(ERROR_UNSUPPORTED_ENCODER_MODE); } break; From 983d09c491efec69b1aa69d3085b79847d790ed9 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 20:50:21 +0200 Subject: [PATCH 148/549] Add missing encoder modes in odrivetool enum list --- tools/odrive/enums.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 81f32570..17d26f9f 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -73,5 +73,8 @@ INPUT_MODE_POS_FILTER = 3 INPUT_MODE_MIX_CHANNELS = 4 INPUT_MODE_TRAP_TRAJ = 5 -ENCODER_MODE_INCREMENTAL = 0 -ENCODER_MODE_HALL = 1 +ENCODER_MODE_INCREMENTAL = 0x00 +ENCODER_MODE_HALL = 0x01 +ENCODER_MODE_SINCOS = 0x02 +ENCODER_MODE_SPI_ABS_CUI = 0x100 +ENCODER_MODE_SPI_ABS_AMS = 0x101 From 73fbd0d54d808b8f5d334bfb47f67a2275a629b9 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 21:11:43 +0200 Subject: [PATCH 149/549] Try to delay SPI error counting during initialization --- Firmware/MotorControl/encoder.cpp | 5 ++++- Firmware/MotorControl/encoder.hpp | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 704ead7f..316032da 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -442,7 +442,7 @@ bool Encoder::update() { case MODE_SPI_ABS_AMS: case MODE_SPI_ABS_CUI:{ - if(abs_spi_pos_updated_ == false){ + if(abs_spi_pos_updated_ == false && abs_spi_pos_init_once_){ // Low pass filter the error spi_error_rate_ += current_meas_period * (1.0f - spi_error_rate_); if (spi_error_rate_ > 0.005f) @@ -457,6 +457,9 @@ bool Encoder::update() { delta_enc = mod(delta_enc, config_.cpr); if (delta_enc > config_.cpr/2) delta_enc -= config_.cpr; + if(!abs_spi_pos_init_once_ && delta_enc != 0){ + abs_spi_pos_init_once_ = true; + } }break; default: { diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 35d93d95..acc0abc3 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -106,7 +106,8 @@ public: void abs_spi_cs_pin_init(); uint16_t abs_spi_dma_tx_[2] = {0xFFFF, 0x0000}; uint16_t abs_spi_dma_rx_[2]; - bool abs_spi_pos_updated_; + bool abs_spi_pos_updated_ = false; + bool abs_spi_pos_init_once_ = false; GPIO_TypeDef* abs_spi_cs_port_; uint16_t abs_spi_cs_pin_; uint32_t abs_spi_cr1; From 0c270eaa615cee358e7ef68695f7504682708da3 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 21:22:07 +0200 Subject: [PATCH 150/549] Remove SPI_COM_FAIL error, add spi_error_rate_ to protocol --- Firmware/MotorControl/encoder.cpp | 4 ++-- Firmware/MotorControl/encoder.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 316032da..d79d2b2e 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -445,8 +445,8 @@ bool Encoder::update() { if(abs_spi_pos_updated_ == false && abs_spi_pos_init_once_){ // Low pass filter the error spi_error_rate_ += current_meas_period * (1.0f - spi_error_rate_); - if (spi_error_rate_ > 0.005f) - set_error(ERROR_ABS_SPI_COM_FAIL); + // if (spi_error_rate_ > 0.005f) + // set_error(ERROR_ABS_SPI_COM_FAIL); } else // Low pass filter the error diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index acc0abc3..c93c08d8 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -133,8 +133,8 @@ public: make_protocol_ro_property("vel_estimate", &vel_estimate_), make_protocol_ro_property("calib_scan_response", &calib_scan_response_), make_protocol_property("pos_abs", &pos_abs_), - // make_protocol_property("pll_kp", &pll_kp_), - // make_protocol_property("pll_ki", &pll_ki_), + make_protocol_ro_property("spi_error_rate", &spi_error_rate_), + make_protocol_object("config", make_protocol_property("mode", &config_.mode, [](void* ctx) { static_cast(ctx)->abs_spi_init(); }, this), From 5db152b10385787cb47f8f9dae520e28ccb77dda Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 21:58:33 +0200 Subject: [PATCH 151/549] dump_errors() should tell you about unknown errors --- tools/odrive/utils.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index f5ce0c7f..8cbb704a 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -53,8 +53,12 @@ def dump_errors(odrv, clear=False): print(prefix + _VT100Colors['red'] + "Error(s):" + _VT100Colors['default']) errorcodes_tup = [(name, val) for name, val in errorcodes.__dict__.items() if 'ERROR_' in name] for codename, codeval in errorcodes_tup: - if remote_obj.error & codeval != 0: - print(" " + codename) + if remote_obj.error: + print(" ", end='') + if codeval != 0: + print(codename) + else: + print("UNKNOWN_ERROR!") if clear: remote_obj.error = errorcodes.ERROR_NONE else: From 0d6be2869babdec0e50424ce27398560808ee696 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 26 May 2019 19:52:27 +0200 Subject: [PATCH 152/549] step callback needs to increment input_pos, not pos_setpoint --- Firmware/MotorControl/axis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 6d16d914..367c464a 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -79,7 +79,7 @@ void Axis::step_cb() { if (step_dir_active_) { GPIO_PinState dir_pin = HAL_GPIO_ReadPin(dir_port_, dir_pin_); float dir = (dir_pin == GPIO_PIN_SET) ? 1.0f : -1.0f; - controller_.pos_setpoint_ += dir * config_.counts_per_step; + controller_.input_pos_ += dir * config_.counts_per_step; } }; From d95d0bc283dd8ab9517c7af54c3b2336cc123bae Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 26 May 2019 22:39:37 +0200 Subject: [PATCH 153/549] Fix call to input_pos_updated() --- Firmware/MotorControl/axis.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 367c464a..b32adedb 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -80,6 +80,7 @@ void Axis::step_cb() { GPIO_PinState dir_pin = HAL_GPIO_ReadPin(dir_port_, dir_pin_); float dir = (dir_pin == GPIO_PIN_SET) ? 1.0f : -1.0f; controller_.input_pos_ += dir * config_.counts_per_step; + controller_.input_pos_updated(); } }; From a4cdac52b8292f02e039c68bab02b2271c68ac83 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 26 May 2019 22:39:54 +0200 Subject: [PATCH 154/549] Change move_incremental to use input_pos --- Firmware/MotorControl/controller.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 5062b695..9c5d5608 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -41,12 +41,14 @@ void Controller::move_to_pos(float goal_point) { goal_point_ = goal_point; } -void Controller::move_incremental(float displacement, bool from_goal_point = true){ - if(from_goal_point){ - move_to_pos(goal_point_ + displacement); +void Controller::move_incremental(float displacement, bool from_input_pos = true){ + if(from_input_pos){ + input_pos_ += displacement; } else{ - move_to_pos(pos_setpoint_ + displacement); + input_pos_ = pos_setpoint_ + displacement; } + + input_pos_updated(); } void Controller::start_anticogging_calibration() { From 22c6eadc4374d4b95e7c82646b0a331ac4253a81 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Mon, 27 May 2019 17:16:51 +0200 Subject: [PATCH 155/549] Update endstops.md With help from @owhite --- docs/endstops.md | 96 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 27 deletions(-) diff --git a/docs/endstops.md b/docs/endstops.md index 5b94cb97..cdf530d5 100644 --- a/docs/endstops.md +++ b/docs/endstops.md @@ -1,10 +1,13 @@ -# Endstops +# Endstops and Homing -Endstops are used for both "homing" the axis of a machine and for stopping the machine in the event it attempts to go "out of bounds". +By default, the ODrive assumes that your motor encoder's zero position is the same as your machine's zero position, but in real life this is rarely the case. In these systems it is useful to allow your motor to move until a physical or electronic device orders the system to stop. That `endstop` can be used as a known reference point. Once the ODrive has hit that position it may then want to move to a final zero, or `home`, position. The process of finding your machine's zero position is known as `homing`. -## Configuration Properties +ODrive supports the use of its GPIO pins to connect to phyiscal limit switches or other sensors that can serve as endstops. Before you can home your machine, you must be able to adequately control your motor in `AXIS_STATE_CLOSED_LOOP_CONTROL`. -Each axis has two endstops: a "min" and a "max". Each endstop has the following properties: +--- + +## Endstop Configuration +Each axis supports two endstops: `min_endstop` and `max_endstop`. For each endstop, the following properties are accessible through `odrivetool`: Name | Type | Default --- | -- | -- @@ -14,60 +17,99 @@ offset | int | 0 debounce_ms | float | 100.0 is_active_high | boolean | False - ### gpio_num -The GPIO pin number, according to the silkscreen labels on ODrive +The GPIO pin number, according to the silkscreen labels on ODrive. Set with these commands: +``` +..max_endstop.config.gpio_num = <1, 2, 3, 4, 5, 6, 7, 8> +..min_endstop.config.gpio_num = <1, 2, 3, 4, 5, 6, 7, 8> +``` ### enabled -Enables/disables detection of the endstop. If disabled, homing and e-stop cannot take place. +Enables/disables detection of the endstop. If disabled, homing and e-stop cannot take place. Set with: +``` +..max_endstop.config.enabled = +..min_endstop.config.enabled = +``` ### offset -This is the location along the axis, in counts, that the endstop is positioned at. For example, if you want a position command of `0` to represent a position 100 counts away from the endstop, the offset would be -100 (because the endstop is at position -100) +This is the position of the endstops on the relevant axis, in counts. For example, if you want a position command of `0` to represent a position 100 counts away from the endstop, the offset would be `-100.0` (because the endstop is located at axis position `-100.0`). + +``` +..max_endstop.config.offset = +..min_endstop.config.offset = +``` ### debounce_ms -The debouncing time, in milliseconds, for this endstop. Most switches exhibit some sort of bounce, and this setting will help prevent the switch from triggering repeatedly. It works for both HIGH and LOW transitions, regardless of the setting of `is_active_high`. +The debouncing time for this endstop. Most switches exhibit some sort of bounce, and this setting will help prevent the switch from triggering repeatedly. It works for both HIGH and LOW transitions, regardless of the setting of `is_active_high`. Debouncing is a good practice for digital inputs, read up on it [here](https://en.wikipedia.org/wiki/Switch). `debounce_ms` has units of miliseconds. + +``` +..max_endstop.config.debounce_ms = +..min_endstop.config.debounce_ms = +``` ### is_active_high This is how you configure the endstop to be either "NPN" or "PNP". An "NPN" configuration would be `is_active_high = False` whereas a PNP configuration is `is_active_high = True`. Refer to the following table for more information: -![Endstop configuration](Endstop_configuration.png) - 3D printer endstops (like those that come with a RAMPS 1.4) are typically configuration **4**. -### Configuring an endstop +![Endstop configuration](Endstop_configuration.png) -You can access these configuration properties through odrivetool. For example, if we want to configure a 3D printer-style minimum endstop on GPIO 5 for homing, and you want your motor to pull off the endstop about a quarter turn with a 8192 cpr encoder, you would set: + +### Example + +If we want to configure a 3D printer-style minimum endstop for homing on GPIO 5 and we want our motor to move away from the endstop about a quarter turn with a 8192 cpr encoder, we would set: ``` ..min_endstop.config.gpio_num = 5 -..min_endstop.config.is_active_high = 4 -..min_endstop.config.offset = -2048; +..min_endstop.config.is_active_high = False ..min_endstop.config.enabled = True +``` +### Testing The Endstops +Once the endstops are configured you can test your endstops for correct functionality. Try activating your endstops and check the states of these variables through odrivetool: + +``` +..max_endstop.endstop_state +..min_endstop.endstop_state +``` + +A state of `True` means the switch is pressed. A state of `False` means the switch is NOT pressed. As simple as that. Give it a try. Click your switches, or put a magnet on your hall switch and see if the states change. + +After testing, don't forget to save and reboot: +``` .save_configuration() .reboot() ``` +--- -## Homing +## Homing Procedure Configuration +There is one additional configuration parameter specifically for the homing process: -Homing is possible once the ODrive has closed loop control over the axis. To trigger homing, we use must first be in AXIS_STATE_CLOSED_LOOP_CONTROL, then we call`..controller.home_axis()` This starts the homing sequence. The homing sequence works as follows: +Name | Type | Default +--- | -- | -- +homing_speed | float | 2000.0f -1. Verify that the `min_endstop` is `enabled` -2. Drive towards the `min_endstop` in velocity control mode at `controller.config.homing_speed` -3. When the `min_endstop` is pressed, set the current position = `min_endstop.config.offset` -4. Request position control mode, and move to the positon = `0` +`homing_speed` is the axis travel speed during homing, in counts/second. -### Homing Speed -Homing speed is configurable through the value -`..controller.config.homing_speed` in counts/second. It has the default value of 2000 counts/second. -Note the assumption is made that `min_endstop` is in the negative direction, thus the velocity commanded is `-controller.config.homing_speed`, which will drive the axis towards the endstop. If you have an unusual setup and want to change this behaviour, simply use a negative value for `homing_speed`. +## Performing the Homing Sequence +Homing is possible once the ODrive has closed-loop control over the axis. To trigger homing, we must first be in `AXIS_STATE_CLOSED_LOOP_CONTROL`, then call `..controller.home_axis()` This starts the homing sequence, which works as follows: -### Homing at startup -It is possible to configure the odrive to enter homing immediately at startup. For safety reasons, we require the user to specifically enable closed loop control at startup, even if homing is requested. Thus, to enable homing at startup, the following must be configured: +1. The axis moves towards the `min_endstop` at `homing_speed` +2. The axis presses the `min_endstop` +3. The axis moves away from the `min_endstop` to the home position + +### Homing at Startup +It is possible to configure the odrive to enter homing immediately after startup. For safety reasons, we require the user to specifically enable closed loop control at startup, even if homing is requested. Thus, to enable homing at startup, the following must be configured: ``` ..config.startup_closed_loop_control = True ..config.startup_homing = True ``` + +## Additional endstop devices + +In addition to phyiscal switches there are other options for wiring up your endstops - you will have to work out the details of connecting your device but here are some suggested approaches: + +![endstop figure](https://github.com/owhite/ODrive/blob/master/docs/endstop_figure.png) \ No newline at end of file From 12664d141d263cc2fb52460cff20298b68971833 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Mon, 27 May 2019 17:17:20 +0200 Subject: [PATCH 156/549] Remove the ability to home the axis with requested_state = AXIS_HOMING --- Firmware/MotorControl/axis.cpp | 4 ---- Firmware/MotorControl/controller.cpp | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index b32adedb..b8995b3c 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -348,10 +348,6 @@ void Axis::run_state_machine_loop() { else if (config_.startup_sensorless_control) task_chain_[pos++] = AXIS_STATE_SENSORLESS_CONTROL; task_chain_[pos++] = AXIS_STATE_IDLE; - } else if (requested_state_ == AXIS_STATE_HOMING){ - task_chain_[pos++] = AXIS_STATE_HOMING; - task_chain_[pos++] = AXIS_STATE_CLOSED_LOOP_CONTROL; - task_chain_[pos++] = AXIS_STATE_IDLE; } else if (requested_state_ == AXIS_STATE_FULL_CALIBRATION_SEQUENCE) { task_chain_[pos++] = AXIS_STATE_MOTOR_CALIBRATION; if (encoder_.config_.use_index) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 9c5d5608..ebf6a9a1 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -60,6 +60,8 @@ void Controller::start_anticogging_calibration() { // Slowly drive in the negative direction at homing_speed until the min endstop is pressed // When pressed, set the linear count to the offset (default 0), and then + +//TODO: This needs to be upgraded to use its own run_control_loop! bool Controller::home_axis() { if (axis_->min_endstop_.config_.enabled) { config_.control_mode = CTRL_MODE_VELOCITY_CONTROL; From b18e98f03066b9fd93d35f064494c5d8592a3699 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Mon, 27 May 2019 17:19:00 +0200 Subject: [PATCH 157/549] Add endstop_figure --- docs/endstop_figure.png | Bin 0 -> 78173 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/endstop_figure.png diff --git a/docs/endstop_figure.png b/docs/endstop_figure.png new file mode 100644 index 0000000000000000000000000000000000000000..aaec20dd8460c20687c42578933d6644344bed3e GIT binary patch literal 78173 zcmcG#WmKF^vo1V?y9al79o!+fy9b9E+}$;VV1b~)Ed)Y<;O-WDa0u?M!Qqhi-SX^j zf9L0+7Hjo%cePYicXfANcZ|BK96Aay3IG5=SCD_J2>`(80{}o}B!t%-f}!pa002!1 zA}y`1AT3R)?&fR*aj*seepw^JTCSndgxdDD!*M zvECjH)}U0Hyp=GS)WMdSgBdi`m}_SAnyX-Ob)* z%g7Lh4N*l`V;+_91P&%|{o0_B(#O?=>KCbo0PrS4@m)60w>7-t$3R*?J63Tp-=YbW#(x>iIuM-JYTTEHDLfCRHn3{t zw$*RQ5`-3{q$9W{(9+Q3TLiveje%$Of3NXP9y_C`UQXrC4Woj{OzMbvEmBs}0mZeE zmY?3RaC{Ii+MOzj2aXMI*BzJFk^p(z9GQ5Gc%k-oUh)gmW)-g`i4XT@)8Z!u8C4vk zo?tE7EsTo}b{x8rua^B^;WxGj-`7#u47=kLO!K9yB)!Mw0n=#pvTQbl&Z{xTAZc7$ zYH?eypA-8VL}fJl1%OKMVKrQXt~*T+a$GuLxf9_dI%NupNRtWGvI%W6{++ z-6cm4?*^hW!Ev%&gTJPTYH6derDKwMT?%NN!Vou}eLw*SJv;FaYwh8aLDdW*)U~+e zSe4j9NDU<)Vy#!VdSEss5U;Q*hXgkdzqkqy9>hei0TU_QHkFs*8rKT*8TiQI0{x}f zl9m_j1IF3`{TdQOV^==aqz|6wr z{oVW6>*?#sF9Lk#8@bL5`fMg#+n5UjhsH}(17qaiEu_?z@+=O(@acQMEWbYOKnkp# zCXQx{Msk>s2nYxmfs-qBR`7OYl>u0QH+!9LtkFh7 zd0j&88euusFvBDX2~jO1spa9d%s$Jwi2dqB<0N4P6z7q$BHBy5nMdLaKQ<$nN6qY{ zav^=h7z;-n>xk<(1_phZoh zM6u$l!D5orrPdjwFT%KcBlw(pd^xuOKAlDvP(dpH8slGg4=;zH%iB> zP9Js_=4F>fA8|IkZ6HPf)^?rU1wn2@L4YDRe!MSjP3P9D6HSKdkDrONa|fWeIyX33bvS?ILd5x}7qe4X!PA8rB@HH*Ny^hDu97H_(}pOTXqWH@rxSO(eOQ)2#z+1P^O~-$6oqk@@b%}O zyPjWBr)1BiOm+uW#Lhy$Jbv!OL&bR9ezBIdZxSZLM8);!9#1F-{ zh@HWgW6P;QGjPylFaPBEX~rI6M+7OWTDDrSS#Y*?{%ob=P`7kY$vd++AO!WR^DOuL z5f?cskhQ>f%@@3==@953Q*ZC6w$xttvq7o)VX|`QB&ID%bgq4{edReG_BYj_d9(EQ z9v!)Y!UW6sO}_yp)vw8=$%AE!`vp7)N87Ak?@pJ+!Z^FVBdp#Yzb9b_5!5S%Dz*K3 zN*YO;q=}W|iwh7Nw$Wbv`le7d;r{4gKl8A;@47y#eM!H;q5;1l?L_q?;KuHzoZKV9 zCBc_`l$=9WH~~L?h4a3Ip~wBpZbHuYoZQydHmi5&?|!fFt?aMxuJ8w-1#ASQ-hMf! zyN$XXyA<7eSQ-DtvFiL|Rs5O#S@2o-G52FN1r6y>Yh~LI9RP=!mV_K73#aJWZf88@ zFUoi7juYW#hh~yzvugtma$bD?3=h8V?ccL*NemjrQ`lnc>NDaKV;kZx;+IgQ;_1He z;#g)DVQCd>E>1J}VlroX>nTZTnRwbQhJ>Z+dH-S3AxRlf{^}OGmE#`Z9 zaa-tpRhhFr0&AYOx`%8_)0Ky^-F&`{X|GHN?nhjuT#@Zl_*S~2AhwDRLt+_AX2~Db9**w2nILcXjSYHZddH$LUtiaJRDaR;VF|joK z*|>A;*LDAkB#a;A09w*-Kkpge$aQx5Fh8wtuV+-9(b!~mS;#hIr24XPsq|Yn`TMq! zxMQ-z;XHZAlU262$!`oO*{_31KbgRrF$H0cW0c=B%WG3r5Po;>JMt>I>fQYx`yKCA zN$MIrbI04Gp6Va$F*q?&c&X!fNc^+js*sJAjUFQokUKb@I}4sI4X)Oc)re>Z&Yzvt z&`i_N$sxq!lN}4wi_W@>9QxQEPR}ke(0^(4b9)@B*U~5*dnA8;h=CqK<;LN@2ztz( z9(B2k?PiYoXFIp9`L1_TM}0o(5PiW|S6wjq-SXx}`E=%a`*!XG<41hYjr6Ih zPvY5{7emM8g7jTaf(c#1UIH_@kw~Tg#`BiCx6e&+DQ>QpXtLPhDbthD>1pOJ#`w(( z?&Emm3UZqXhZP6HJ9b~|N4urN$sa}!8}}z|L;ld0`lIagmgCdFmE7mVi*?tRj|oKy zpL0pY>jL8M18gas%_N4C&$Z=%XnY_Z+>o>-Kwc?KXCvG=Chp9Q$Y;ufmNJyk7u538 zFy?cZJYajufae$&;7_N`kJe9cuz4ci7!Y8mdn~^~-sbGDSMC&akzlU>?(AgY0m!of z0zRRmUHzaSysd>t@J9`?gwEV36=SXcUbA(kYp$_=3OI{8+0Kz5Ng za0dYJ>Hhu$6*OtjUN0Z;5N&-AeHCRPOJ_%Rb1P>HYjz(;m)F_=fQXOKYtqr$!<^E` z(ZR`G$VZg=Up0hY(|@x$s44$d#lv2dT3~5I5@n$z1h8a*qz;MIk*G`1vxmmIk>smUTd(q`#O1;`>;8=)BL-U z|7_>2wY#Mo#Ki;R>_qvuU2_X(PY+RQ>c1WR*WbU#Y3&2~@1C68|6^LO6Xf_?!okJP z$?^Zk<^i$!zp(u+`8V6Y=JoIHME*7=qz>`1b})DgakO@Fe;t|_zW}$$zqwmERqv#*3e}^KZ<_58TmC|2Xh;fN<{I9(Ks4v3tS5W^D+`qN*uiRI) zh@psZ{FjQwP~>Wwt^fcDfWlizZ6DxKHe$$h-}S&Qiix_PmVm^E0_uJkT{ugAuRh|6 z>a1QICFS>YF)?(RZ*eV;g5!8od*4+xm^o9Td_bYrWW8#6>KK1GUdno?_HXcR@Ne+v zhxPARbso;*cV6^fsr?i|NT8f3@jq#dRy~tPF;xA3<|c+HQUWT}vBKc~Np7s6fGRiE z2?=(2%70Lv2zyu+Eem_n85u3sKS;d=PEk?O&PN~JtWR|Mj7;pGZT(Fjq)4EL)VXad zHkft6=ouL7D=PIInP*R-lK-=&;di)i-@cK4D62GSY5sG(G?y3-!&(A=&S}|?#t)gI zy%}ctF@1*jzgnMCS}QBfaog;VbB`)L(}6W(Gpv>Gnn{u)-^UQ#!qU?;+`!SnzExD? zo<~52`v=vMBhMRN*IaPl%oip!#j-aKj^>I7^8%;SscRu!*fg?@=bHmRb_DhR$@Dk< z7AO5h2d-jxcvu%#0X@XJ@>d#%ce(C&H@K=h8F$oi%Fut(EOTfIf)eLcjrA{WG@IRD zv}~OgqWFufFZ+lnr;;%Lr0*IqV(B#*9Ug8-M(|Jtx_S723>OX=Bn1OB4f z3L(RVd{$-d`v0g48wUwcj@^wa8U8=CJEicdat}>puYdL|2@{tn0nm@bdWS^(57lDP zCaS_f5+L%sT(4sR6~j4@RERz`3W~v5s;#oMA|lt)BJ1BZl;-kvJU*PZ=bn+jGEZFL z#iTT@t6$iUz!V>GDD<==L>>}p<^g~1tqgZqm3NR5lmya5@lZ}07iJpC)M zM}BtPy4g41kpF7u02~Wi7kU^Urlf_taU(l-O^L2o>Bwc>x;v=1$9xwvp^uo}W? z7^2T=^qe!%3!SMc{$-rS=Q!<*Ws)eN^a+s3)ImOy^OWI#Tvui|+tgoCP|)pIJ1JWh zif~=r$_mp@T#4xB9TB_#$+KC?X6Tv&5d=f5;2>aFcw%IhV z(v}|js)ks?;85TthcQ*H6NVThcJdvalTRkS#H1;oXW@uxUY&* zpT|L}tF5|Za7jxEiCYVocva0$xOBc=xHuhqc6h4eKI+$v-~(MLOzvM>`Th>3WXSG1 z3<(AahY{v)CvX{f`e-;v|3!@Ke{+rR-ZY~BSKj~Qx^x-ZXbe;Y+zF8*M%dF`ofiMO zG*D9{Bv)m?Wc;=rMZzz|>^BaVEM&(HJ+s?Cn0#slKBg(ZE_@HQ!pX#{yA)HQcj2!V z@Sl{gl$Xb=@3Cx*oKy*mh&8?Qj-rdhw0)WFU8{g+wOwHM>^47v$S2)fqiTyjyU9B@ z30-=QqeIJ$030zR;$Nxk?yCWpDzndrJ$hQVDrvJGUMgKmBJm|VG)p20m;1rHS-Vje z>CWu|L=mrQ{7Yd7`aCuxE~gzY?M5|L3MpC&BNEccl=_V>`TkGWbH58@Vs(EFEfSmC zgOBe&H>?INJ)L-WxUUAkv`r1+&31ps&#*N&KSmyXc{&X~fvSmL2gv-`8St(WIEq=) zGY#CXZ2Mz)b5Q={{@pd1V1;qhRNLj4@X2$>VT2{`X#BF5*tFnQw7LRjoJ zY^4aiN?LOGIlBCGvzkk7<+Q`UP04|esCJ1M()#pg={F)pK;ng+)8KydVWm5XQ)4~} zkIhFCLARoNnWZulDUw89_~1{ISrzd47r8w2nt>MOeaw zxo(0D=#+)pX41v=dNhxwb@!Cr$+>MasPt!Sbop@>uyZ7Xr>3>0YEH0 z`03CT105A=I;$V_XTkPFujBr6Gc>QTuS?(3{@E^o;SG1=IZwmI1(+#Ql!vzyUzO(K zS>ITHLAcc-3XP~Bvo!GLw_eAcbZgz@2WW_Hu2`V)!=Ga<&=I_g@3QMalmAK6z^i*( z6}?>x$C&thqB-yL<@p9P_*Qz`y$zct<;pTabcen5OzPD?m#yCN`Wgr*SuWK({uVl{ z*nIVL)6VpFMB|Ycv}De$=TQ$!tIs=?trv#{Hnhi8oZM!tTVZvdvVpi;(|8m$xBbky)Sz%okWZRLNF0{Pum}ynA-)=3n?1- z7blvprj&knyxK4^h&!@p1>E;E-|#|fI+02oQW#+9vk82$v4!-;OZGPH<{awk$P&;S zHM^pgSSxl8hZc`<(AcCM^QgwU>>t$>6Fk-hAmbfTdkbBvW(ze@JnhMS%fCBM2tK<1 zb6idt%c}+U_%m;bd3<$lpZ#>S@birWr%sd?su{FU4)a_j}O4d$}|B2wSHm2|qCpxBM$z6``4$;k752ih0>nPt+qlwo--}ky%LlnD{*J;gY zPD+YiO-MV9i{5w$AJ-9UY`8h6j`24W? zVi#cK+*VhTAn>)!hU_J9VO&bP zr{BZT&xD}lcN@a5Ga|c?umLoc=J*Z&_JFEXS`NgY5Omx|ULYob`%08E3Lva39PWrm z!6E&q!C=I-ZSj}81Xxmvpxbq)w(D7_56Us-u## zWwU$51g2B9o#N&d9}WBlF^>kbR$rcPGEEER5_D}JVRiX_duTNr*DsYE`A|8n5{mz+ z^*Qq%AoDKj#_fOXU5(2*B~KW@ggp(u?V(t{J>S%Ek0Hr}=alo6#6@A-?Av<)1qi=n z@7o<1FW1Osy?rVQ?394D1Rcd>L&BWf?`*`h zAU}O(xF4Aak$W$v2!B~QpFBgygO4^>cjmw)ET|DNOIa_gV{RYt!_BT%YBqS4;C~^ZVl_>i@^X z_KRzjou~_}a-QUU7iLf%dT>Y3)ABq2<86RRdXxKZb9rU+@RRd__7lR|B=S#s)Xv*i z4KKG{T__O~P?i`qI5~BO08!M_?|amfl#`3_S17CiDz%&*VgNdjtK(v=Yv~ua4=}Q= z1UzrMZ=z$Yzqn;*V$2-v=k&>zPRqbYd?S(-kXHG}~7;mGrl~ zx&anjaNrk4f|-?!%8|)d6lh#Cyt1x1_rlmkHphgYV`$R5KLPG6th;wvliOaci-~61 z2+caxEvb@?H!}s>zk$Krx0F;v71`M-#XKmEOV^ZVvNQX3ZFH9-`Nhms0w-}qNj`Kv zDUk`sFe5@4z4)jIsM%0*Z+WiJlb+ZQq6(p;`WCpY1bRE%|3=R^DG}RV3M3YTt0uxl zwHO}ZL5-Y|DL<&KCne-_&9F#>F6RR@Mc%K#86b66mJ&knB$!A|j1s0IE5FD6p1v6T zcrJJUbi0{w*btH*p{#Hq9Q^z$_;13kQUnYUC2OcWFY+k`(g#$)$Zs6b00rO4LvIradU)n@;6g364C_cBbT58?59NR zrV9YYppsBL+#fk|U#pD>Tvd5_*hH++h@pbs;;^Z05nnqJd7s)=ZdeNU=4rqVoSVc^%Tr$a>7-Xs_0?dp%1+J9c zXn#0SLK;*Z3Kx;mJ?lJe=y>|RlLdalff1Tf1)0Y1&g#=pNr+K(8XPma-|`y_UKvS+ zq%96Did?g-$!;=?!=*DYGc)59C}=mKAgL|8^q?PLoc4>43#h?};PBXvX?JX$ItN{n zuz7D0iM%i2mVAUM!7XUh5uViD*Q%{hR70Re9;S_I^|{|K#l>Y%)fS%?1fCn_SS^ZG zpEMtSYXiIE7FDOkjCv)Bh6G&dDMgbvrf&2O%Qh^&#-4x zR!8a;?P#jg;S0Eh%Vyla%dSe|=;Kw3*YQCVCI$O(k(MJa4HDCy&~;d9#3aWe0+Wjn zvF*2Lh|nyg^>!ncIKIwUQ$xF_bNY=TwL(k$d`4I@g6!9Pwd`_* z`=@xl&1xpR+g?tSp+S?allnzF7BbgDL5WMCv&bZlyiH?ag4iNDsermKA+KFZrRl*+ zTb3PhdVTZjR_E#Z(*tEVt1lm$?j@b~<-J>*-?e5OMy9&a!=UybFYZ3HQwN_tHt0YK4@hsCZT5RUTM60_K!P_lMQN{; z2P~J7Dt^3MDhRxDnx8Odw!eMYR|`IvXAKRdUo@)KA}pDQR~959{aS*e0y2a>Al=6P z&V5)usCG2jo8viaa1x40uKt~O(P~)K8V8mQlnSo$em%EL`>}hX1!tPT=)O!a7Frim zbf!P?&KVa5hH%;anWfJZRbwe_Erib&@pVJ@n$z<9 z=ls2JQZpv z#)tR_Pf5@RZH!LjLt%Yy?ZP~HDS)D~UNv91cL+8a7SvlA*=$dKL?2iWV__BsBUehV zuf3U9z{6Yp;MCI6cC%mvFNZHl++mv&Bntsd zY|6?m2-PVpfXqOv?3VPkG1wQ~YMXk?gc z2>e1o1RQ{B4k3U)Zvuh&yrw8;E5VEM0xpF|=k*MzMu?ykV$g{d1;jv*hz9Ph%aU`S zQOBXF?|lK(5DIBey0EycyxwYN;!Ro+xU0>c=*bwZDeeFPi0H&AIwo3S>uM{sYiZfO zrlZiVyt?nR@1|0@+MN1-KwcWR2SouqDLRZf18jus7?KaF-_V8o+S1Um8xAOAZ&6$u3L}L83h4Z3MP~Sv^ zpjOK@ic!1}JV(G`K3GIQt3L?^Y{=er^=aC|%13&|CXs9ruw`580$Y($lHe%_Lh={{ zM+7{}XT3Ojpzbf!9U3lz0~I0zjT z8Rdl9`Qe!0O@zcw&W&j2jZ{eM^oiC5lJstG6cQTb3LQe%F{zJ(Xg1yr{5X;qT8IIt zafqW4nYLkE1rUSE!Qo6|8BFqPoz{1gk9af|6Aikq=BcBN;;ZwjFEPZ;u&_r&ewqQMcjH*&M0vkmu`x2zSob zCG{7OW8QG>*~v`c8SEhBhLHm;415W=7_pl#A(y~EW-v2NwFI{ebq*ZRUV+sh-b9TP zdeuaVC6Bf2oiqa&>$x@dhhfzXbrrSn(@TZ%BFwz$k{GpAwPb^#L6MS_o*ypaMfa(W z9l!;TJZE`<{fP(wa*|rYiivutocT)7p(FqHKGUMyWGOd~G>ODRPiK5UelXzn>Bv@G zsc&;P4#%!9nELuH5G4dgvbeg8jTsG!O6av>{#Ar@HN@*fe;QNT>kCKMP&3Uj=OkX0 zG)5eHp(#iJ%mS{k&dy-xF4Z*bOE%CcRsvcmT9s+DqebGs;eslB)Xz(xA`RjSqN~To z|BZf91WVcXH7h$1AXC4}^I0-dN?K~SXTzlyOB+;+pd5#lL-7VG>Au3fTF+_} z@J7AEjk+RIo3MOcd%|@3dBKeQfJKF5`X-)W*OHdIk}RDuPzeYx$gT$_!i_%&BK3Hr`b&ZPKpYIZ%%>r*?-F%C2+1ZU9^?K+()AQd1HEs3A0 zyJ)_L#h|r?W~Yp+Y*136HO}e1;#qnjnZXC>mu2O1GoX+rbvS_;G2c^KgFP0hu~5vG zBt?LSf1-SXb;PYn=50JcAY1}Z%ig1Ru==juzPT+T2MQ694jF2Hj=6>s3p^?)YAs5r3E?WDH zTO$ox$V}klMOiJH33&B<$q@E9(MR>N39NOSY^1p7AJ&K&DxwzSgOMF zRIF(5YH5xd;qtieCFny$HY%}H$fGb`Wt>Fm40Yh7Q{1+yjTGMhLQ5GDkO!p7Zq&L% zR$>5WX8^?{Eb~3z&E|hf3z5FHtsTIa-iMYP?qQmRif<_{aH1pG`=)pI{U)YfX8pYd zbd&ir7%fX6T+;X+_LvGC4?Xoe3talAn+Z4{T*N#QK^`F+7qbu;1L7CuH)eymC_{3? zB!>?OnB)bX2%VTJ#3~Y8cf(*JX~C)s)L=CAs-ICLLGrM!oPlfe%{4pGfu z*GXi_v`6W3@PPEh`V&S7z*0oGk?4%|uBYLPQ?X23e1*m@-MuOV%Zs+`)vCT8@Z; zt)PCELSWIU83dRY8=eK!uAnF(lUaHz-W!qznlR<^DW%{F`fglv6=G=?FMi_l>^@Iplm89%6ORY;$c(YoTvl0csuUEzJ>kS&f z07W8DS~|Kv?hhs##Wi8LxYzPhXTOi`z?mE`RT&vFj51@)XmhMOw*U(bkzu(w5!c=r z)#cWMX|n@Fs+MI(dvpJxe4UbF%Dn%(UBDcEeU#iC<+I0Ckwt%-&9#c(CS*7&*CQxE zc$2}2A=|~UJs=n&I&B`KY*qC6#w6lMs<7fRS;OSu>3c(=Ny^mwGO}k*m~Yoco%T}1 z+JOU@ZXvc_G{mAn8RC8~9E)*wDM$)Vvb8P6>XA*|?}!=%rKg|axDBfI9*RhNsz{Vj zXW-~Tyn{-6?@*<|W2_eg6)bX~Eh4#1>XGJWnH#%kib=i$;TC21Mtg&#vOg)pEqO&e z<&xpQqw1yA@PCzOfipY^s6oQs`xU}mzPq-<>b~NaBRpCnmoTd}>UvKxf3)tX0;kf; z#euCcL4glt!rXF|N30R}!gk+Dhu3JgqeG4=>TZiS)eQ$xg}v4lcZ*L8GUNvVva8=$ zhO~=Q)DGaThynH z4b>s!*9oRxYwNO2{pu8V=5Jw(oVahNO}0~zhY=BS=81ldne)Ij;&1~dP3q^7C8o~O z6tW{shnoRRb-0wp$B5h6BG>4(*82(@12xim2~xqaoB|a+KW@rNlMlDabgXs*V-`HM z3=?z|i5cJxoC1B$UELd6Q&wlYB^muHV%DLNnZ(vD>RCH9rYw`kC6E9$99jN`Q_xaF ztm@kaXALX`t4Vob)lyjj$%`^hoZg$*)S;KCA&=FF-GRN2xoQcV;12=9dlvKXVO*K_ zRKX+8%~u<9%QP=%`eX>gg}V=zJzWWRJmNM34X0_|FH7A_p)hx_!Gs`UaJ2qvl}ne@ zz=@J%aKNDw}oJbA7>`3F146aVZ3J}NpZG25+~ zxtJEtAZ8D-wCr|!s51yMVH@$0FT`i|9Ro_w;C+%Dg@z;XIkldb1h=6quDNymq`;lU zT`!m{b1*qc3?YO28@;o^Ph`=(LQ#6wyJ*SwiojYC-Ct*=X0()KO?G@kmY@oo^beN9 z=;43f91HSbf#Vso;x2|E&WV$T@p{zleZ3<@VSWy5%F@ zUuJCt58xS3kBQk zLRD;SYp`%L!DZoTKH3#}P+zQA>QET^?t3sE4Q#6BCK$BJ4F#DYa+F}>xHaP+Dx zo|u&|^zGqT|C)u*;MIDl?FpIwsu{!;;)|dY5vm4D)yR`QZf2brLOIp#26MpqSRI6R zX3@Vf5brml&G(#+Kr0fCcmNar27!VUyQee8<#8>cY8p1z`8JFo8GbEcl%5o4RHCN@ zbv+#ZAw=Nu6C&YbBE3Y>B(LEC)nggeU*(*C#QjQJ+=lQ{7Phg}-($UrkEu21?Fc$V z$zms)cn!0$#XJ0-njFPbTNZoHj!(VFU(n>SoQk~mbvp63f4`QT8)#J6)sWHHa#vS% z+Y|5n@*Ax012)o+jgFcv?P)k22QO;TB#O)8`n3lyyB1;>0ZVYJqQuDczKFl7*5}@Q z<20zXKlgkax<-bZFz$2Hv!(`#VH^z}9xF8W+8F@fiWK`dxa{OQf9xk}i|T-<1`2tO zMfD_#Z!sV^e)GI7S+F|BwkI?A5?8Gv2OtPRkMheK|F%~~F(kgz-_IxkxB56?%p-4M zTP_X=5%VZ#rWt*+s9!4yhU9m3zW3Atkb;&f%q_^?9HKD{5wqeC7aC-W_|{-tOu$IP zj#eJSMDR)w8iGOE71vUQql=8QPw|W36m~z`WBNmf$+y*nxhl#P8kUrZc{~$Hq7np@ zD2b8vq(W|#buc2!KsRu7{y-+nSh5vmceCZ}ADBVT3JRqLdVR8<+?2mp*WyMrjylB< z)qWvjktxENS@Y?+;O|KUWrZ9xTkFADXir6VC(kW+U1J~HmT>+3cOj?qMx|ZC{q392 z)N`4^f#T1??R$9h+3zz~WhZ5qzTO7hp6-l6yD4e>Z;m(5H&SC#7i?)xl|4QpoDjY& zt@s}JYliPE$>kSx*qpxN7^Gs$(x%vw(~*NDO@J9OB)mRm-?tNW-#m@N3@L7TcFDnU@@o`+UQZN3Nke`gyF_>W z>BKvJ?dn2I%He6s5L}}4Sq#S8a79=}`1fz1)ZVuk^q)fTZfD*mjbW(mA3+zs%T~p(T3Kbz z$(*R9e8f^olqjT~FeGRih)k__G;C{vs$kV%aFUCZpVqqiP|@7X(7aLqbg>P^QZJdS zzVq6_Zh=R|FS$4Vxyy7|t4Z?nj37Y+U>X;v;d;+%NPw=xI|O=yi9dHi-CE={^=fY+ z)zR+xp6PQK1btK(ZU7TBl}1N&@v#GCjaUUV^2YnhBF1_d_*ZeKD`HJUL*AID3rerh z78{Q8uq!5c!*TJAFSxVFX6eL2+bLR_2llzH+U+By?wm|Etw$c_H|VjXj?6;cg=Y|(@wb;qCzW@d#wI8GK3B&zar`Di8(#fNT4EU}Y;a=S#e#qerdBgf6Pcd7h z9DQoGd5nTnpU+0x;09qA(|z7;iIqrj$>%-eRMpvBV=jQGU^?xH1$Ra84@DNlMo#cp zzz!<58Z=rVfWk)I5AN^utHA3(>n~qbXol}(jsn(6sisGb$*vAqejFgLy~d#vzSTp( zYz||%Gs8%Rr8;RMn1|x0D4<5sCYY}c14KOI+lI;EwpkJ
    q z+V2h|GqG-upMYpY&!rXENR8GPQpb`_48QooKMA}9Sk}ZWfun8z4YO&d!vg5G(|wV< zpPh916VqXx%GT{ z(s2w5O(zIpT!8f%s=4uFi@pP|5Q>KVS*NCY*;DA22q_?Xn`y)a`MdFCzEbQj zQR7qO&tZ>?y)an@N?719wJl!tld#x~aw6uo)KZp=<^3y4fl=K`SfEsX3Fe&=?$Tyc zT9Q9hpg0={>mBBXuR$u`*TGYTUqM0{O6%n)F^EO`7au9q#5S0}KqoQqX+JGS@K9f1 zHS$nu;+^X}85yoP06Qqp9^n{)0o}a>g&@+6lf2=_PXyHOZwOSOf`x)>Qd;cd+claW z`)q5wf4-VZE1;Fyy9t)fb5`{Wacu1F_=_S37lSE~%FQ{$j`7_74yYo5g!tmx#F`cd zTVsoq-UZmvl;y)eS6LYRTn#$kVkIxB0A3KfZ)OYZ4%dl&f%eeH{tY3_y|_Z0mh-A< zRK7rZi#HtB68-V6J7vK4kC!T$kBu8w-QGR4W?B*GsAN*`$hA($aOk?YyW-gZ@ecP0 z_@6uyR@sq(S;3F@rvZhdRXsNV=@6QeGRxkCsNS1|l40?r^mydNz73zIM}GK{iMN2o zboQ<1W-~V$;4j;f%lY)qp)u0ruF&_cahSdXWn+AFj0E_HQ3%3)i@WcC=P6Uc#LmN8HR}SZkR4YwaqOO0y zV3)87#HW9kvmr9Qn??4m0)Fdm-ZG)Ae#nPM2=EAjmXt+Z{|J66xBJPr8sP3(UwalR z&VFTmpLr;B(suLC`>YF*WmVZcS=DM#dn1nj_nuh#A4!z=hQ{j)KFGBm==itQo=;Y? zJqj(<-eWRqE6|rM^5}koJY7{Pa8+g=K%Zep+_zM#7#4<*i%e?wrp$O_!36)ZU^0(p zTM_nBE;=^&@Z;Uil-fyixA-D`rMcN}6eu$ow0AVG;}bhiMu|G}G$wR7HLWr z)4hHwr64a6T0#nx3)n0_VBM1jpfDN+vv%#3jJrlPGnpiEogmt3E zrmO6mh76v_feh^s3Lyz0n%Q!*^eGmWxRUEU*pM0;yb1ht@kbmEG7|pstbFLU94VmE zr&N{@Unx3kO2wkdpE!M;5S?B1_iOlGE3K@c4GzhbJ%eZ%@i2MNQsU2Am$b({fjWl# zW=!fQCUcMpz+-qkkxCkr(D8h4&n6CLm}Cunjo(-LC!8Fpl5W+VTe;5ODEJ_tEn}U% z!i($-YzS!bUhhB}Zbt*iD7SgB;`ld(pW(qt>>;#fxi!j$cffSUTyTru&B=FReFa=Y zu1@^S8CXmK3GW`wm89b1UIz!igRJMZjx#zl>7K2f`e%WtP11r?$T;GVeUL3BCAnfJB2bdHLe znXF)o37d%ptX$78vkQcn0p%?qY7Zwu4BW`dJ6`bv#^8~a_MV^SI8`D*IMblJ?RZZ) zaOnhf!Lhjs&LYdvpw)xaB*n`tezrlDkp^4a%Quh_0xA3<%eI^kc$sYTj?ut?m;16QC&t@;VR(QIM4 zF{6if05iF`(2J2Px=h`8Y~Yzm@YA&)9?~M)tmFBpAJ3HAf^}M%O#=5{l*9gFbto(g zNCjLmzx|=<74Ro5sH3p;oG=%#o#Vr!RpFEoDEk39q)06^H}l}eLQ#)*uJ2h_bU^)x ziOKBw&9uCd%z%PzFO0hczP)n$mxtn$Wy=qU-bcO#pCw==5Jet-E(pERG!Gt{s@e=J)s2*nyvE@Goh(n5V>^SGojr{t-z!&Fbxn{ zf#3IkT#zbpddY)Y9WOO#P4p%qHWuDVu|(|P0{Wo%355$5D!;AMOiNmDCjB5QN(O_G zlb*UT7{PSVaBwN3xUyr%%QGShX+}Sd*akcrOt-$4jG&EQKxwwu6erlo;3Zm|4`P-u zi*oiCn|TPbn5&%Zs|D+yHXpLX=P47tqO;X0kSE+0E2N}rb+1GDuGh;;Rvj#3?qUOH zixeJko)ou-englqYu%2;z=a{OFj!P%zxPb{$V9$7-x|PRM5753q7xzwHbKvRL@`s8G?An*^H zAsBC-6wF#7a89~nkppKj>RP``2%{OCbOx{}f2NunPM*v1gmm0|-94#ef)d@i?K;(8 zrjsgfcTD(n+D*ZaJm5@nri!Z=aj)m)JvNQ@FNEBH2d!yRuI^@*sTgy|%;Kc0ThH*a z3DKKnttW%bE1E}SoHN?I?*#xP-q$BL#R- z_KQ&?EfRqWa(xzt?p*ESJ)ODx3-W8sA*+Li_T&I6jRHoc$0S`FF2!WHOb+vIMQmR2gvpelG(2 z4Dg|a0Pye2%(|Pb3sh`6%fZNqosp?CUn}SWOBd&uJ5f)D08=P(SFA9nWksJ>|D0G7 zSh6_T1{LOEYt49Rj9__L6e`c^ zR;zcS<)%cXxMpr<9~1-67rGt+X`K-Q7sT9o~EI2k`L>GqY#! zwb%Ont#}VYMrh5*zv%sdZ3w18fzM4N?dI+!yx;>;IUZY5RBc)AD^jCS+PFwwNG;4l zr~9>wg)&Yn1}jWwPVKI)b+tg(Qo^uv@D0Kl334Xq#NSMj+3U(Q264k|{x;Pd;c11f*K>CzP;2+Tn7!8pw}H$JFAF$&+SWs3C zvpltB3&gnkA3G~x7cugBnmtZnjb9g3)T#W1RdwQ7y!uhu&GmATg%3}jk-GB1MjW`u z{z>_vk2m#;XaCyIT)&Ts$4#?Bkd-Ydr{)t_r|+V^PAMtDjGB>5m6{YwCaj!@1kHcr zHIfffPr7I@K13+>ky$N+3RlzcVG$~dNO1h)JG8KJdpb2rNY3lnylkJTBf9j-Qmm+r zL0UUR0+K=z-7w7;Uy>fGoCX3?9VW!l%vH58>Mxlb7B4KfdC-^P70y9p^FLD(#FZNe zJzqRCM4~Ol@7i$vkb81Wvt<0RFQb_1@rJ3dSP{5j5_98CFFDtfoA=boI=@^Pc|pH* zT$B5ODvae+K7Qk`(PV-b6N+f^h&{3QJ4{MhJtp?wcpNsgNcuxNq>Y%D45JBm-`6Di zgpz-#RVYOFRsS}UZ!kMXA$y8-R>_dFx_p962 zLADih6AllPf!K)HyLd+J0}ZNC)d$m1zmge2aIkhuvyduwd+O2f z!5-6KOH&QfbARtB7P%mc5gWvK5mrKKEA?R{@NQ%H$+R6=@mq={K0)tyPvf6cnHBDC zW~WO$S`Fl|AM4CZxh1?VEPeAkBiGFS>`5;pfD%{x3zhFoQ_1YhXPE* (gTC=*;R zi_aOl{iq2X9U;e2WZG1aCpWb7l0#OPmxTR98DtGI#r)*VvU$A|F6ge}#15$vXge`a z=iFf>glN*>^*9rBxZ>Bn*;zy0W444d?u;j3+fre&^r<*%GJ~v9*2+bJ1U8fPbgXJX zkuigeN+gpK;v&4M%kY!_eKK)*Me;CfOcR({J>s7PcH=-`f>jxuI-}hs?d3oW!I)hc zL^efauR9ch#)x@|bk-+~Z-tPT*8WnQuOnu`7zpi!`D0NxF2?7bpFJM6m7hODsJrPm z6tHvHXdf6#f1$`An55#(gwtF!HqS09G~RLOspz0z;vX13{g}0|wXYdGNy}4@r7W(c z5pGmDJ!vEwMWh>_>^6tH0XR1so}K3~!|GOOM+%FYc3<*D?WMx5hUCx03dxZgQQHrs zWrNk&3Xu$dUeUdZ-EE^<+C18hFa`TzStJS@(s-I#*My*cQ4tdLkMAXz<{gjCQ-}CW`7qT4c{9q2zE9EZ5VKnF5-Qd9C;a8Y;80lQf9&3{L4;Ak4nuGAvU{#3hNzF@0-AmvD zEBB38!RIb08G><~_Ywjt5Yc2}sTIB|lmz1H`%iA)7&Fp~zc@tHgE%Ah%nYJn+{dGQ z=}goD3>_(#!(~i0V^)@OJ)uT-x`aKJLx%k6t&lh=t}zQ@kUEi>LtuFE3la`Q@`3JQ zwBvRUFQghl)ezW&I1YCb9LQq!Eo17B2^#p2PYN_SmiIILJHyTgyb-d2K=o`f>>-Xk zC|0*wxginx{Ht0h5}A4Phh*-E!iNa4&vD(jXyGc;TGhjsnz4P}SeuPg?>WS&PZb$~ z5Twwu;MlT}vgUepyh*We8&G-WiKbCJ@2i}RnF6%tR!+U_AI(AY^-ZmNG?MPt$<);l z-C)(Vy!-Ne!rW1|z=}Sm;6=xS&uojHOqPN|u}C#Pb04a(UEIh&Sg0o&fiz(_j^VJN z;R22sAQIdE1utW{DR^RV)F08 zikb;UZZVsXx>#I618Ev{y}kPQbx~{VIi?T?7Ayp@7S9Ac9@-9~NX;2Rt30Dw(%@BW zO+?VEDDt1_Gk_RSyAxApN3HdPzr>O&KL6Syfd6D*dcc0D3gi+|Nf1oj%cXz%z(6SN zf-1;2`m%^$A83Ok6pI&yk8{A04mkA`8OA5?IhEHRji zVqpl{mjn-Q;r&*kM*~s(HP7Fpx)Of-aYg4ReZ=3fBMl@_ccp*e9%vRzg`1F!_Iy-` zwcVgcgH&2*q;dE;6EugrtbZft039 ziV#O&0bY+ORm_s5`4>d+R($v^vHzb;7t9w^$}f*Zx8bm*?a$>5{yFlV6Y{`+YDQHI z^He8d4~mfgM!XK9np;a8X_kXrN;njWe)#+*4$HX~Ov4DT>3_AOo%V>=mv(u*e&WY_ zJ)fwsO}5On{TdUnz6QjCaG#;!cVxhcUq&eRCB9??SOr_n{KR>w!xK&};Gd*e)O|9; z*T^a2IQ*-|XT43zXjJ~gNdm*B>5V3x=M5GDw;^M279Gaaaa=Huf4_?2WZf#%*d zU0!M9g6uPkoN9Os{v*|PvqKzbT)Bw*Y@e7J}+hPKPpW!b{tFlj%Ao^&UoJ&}ouo z655}AQbaxaVd}r4^Q0*s-V8RE-nr`fSJ$3Vr%&T?pG0ZOP?71 z=rFGzGzp}2nAVfL%}Lq4-A{|$ToxmD?W<0j^hh@(mpz5$`P=Rs{k-p{kTRckb?=f^ zc8ATW8cc1f{{Fo+Xzxyb<969^>0kH%c}LpBV6**LJu~liRu`sI;}~?Z@}m8W$Rq+C zjd@G{*9=UgUSHup$LXpK;ocuIJv0HT!K3NXg(>b+3PRS!J@wp2d*cY$)JjFWxVBkp zutKQ#w;mASF{>oe%FtBOwUg$nU}4#!RnF`{xu?atzdi2@+B{rt%SzPOx(oOTVFq6QkYRKKqIc=-n_>8FDlkq3G6}^0 zfWV9!s^D{teq}w7dTa+Wd~^ThI)PkY@b*IZdmb&zu@BxG`a5w|?Q{5rFnCe2hjOZH z=dABG?7L%dIFb{we9oD@Gq z9==^e?eRiuf=;7x(%LN$VsmJl5P0jmJ(!uXH`w4Hq?oy#9X=Zsos3d83@2OE(4O$dn_~;Hrd%ey+6eP9-O(cpJtKm zq@nLsm_6eT+^;rdjuu%D33GJ84)C8?t}VE`VB+VVNryoVoCN}Qpi)i_!Vv8nEC(qVqHxf{?qA0o`xTMdMOT>bOif_4&@^cFYf zhBMy`kKHs532)9(LGY>LSaVvRz0RN=IzR1`Y`D9!f*D?|bc3qjt|q$BVtIAP(cW0j ze$%444yWs?o7!lj{F`O?WrjoNAs|nlG!ZseK0O{o+mC0qDsv`FuRh4SpSn-!2rdLw zc|nW+*!#2=^}8Dazzagsh}~f}vQ`$Fa?0`8jH`W?%Op+qmL#bf`((*eDa0=cQ$0ok z!7l&yFcIL|FwDO20r%ATH7U*ZD)I0O-n7^Co)j7HATmCOY&Qf+IH9|yw2K-0^=*SN zNX1J-&n?MESA6G!!*VjWqBrSsK%3oNCBJgXJi`v7q?QA@=CffB=oZEismO~wPPlZ5 z?+?371(;t94#<8=$(TN7+^WimK{m<;fJ?fxPP|8Feb2KyS4MW}R(#I8_p=j%1~VMG zNC#^h5;E}ATNEq;wglczT>O}lmid_Jr?i`V)8r|@4Pi)l^smc^tA*Yns0xZIZ(T1d zZNad|ZB=HY$<)k`XHVxV;cNn~28fO`qLIR~k2!)@+rQYX$s#64vl9MIkqXD_$*t@6 zTr5$)*Kq2~T>(cGD-!1kuiR95Fihs0whe>Fl2vglERkmqKM=9x;lBsucbV$Z%jzVS ziyyIA?lOf_o$uLI(r|+Obmp@PQx&Kp(ss1ZhWu~$R>sy>z!2rq>2v1}1gi>U(e?AG zTAvOt)?Z3;eXoO)0P;gCJ!3YAymhEd{DI}G^>BqhgldbM;lat@U1R0 zj^;^nIe1NC&Y;%ZaZLlEMLZg3)1S)-mXOcy?(`0v|H%=in!;>)Re8_MzNVr=SAP_` z!H;RPe1(TK)$lPsXe2*0iab{OE-R4x(-GB7Ab}(j^OJs{+9##b3wR!Nstt+Hnqmb^ zQeh9VX`muw@bI=p>Zd?=f*5i9SDa8PMZR zQt0{Dup;@F9LomURVJy*Y0KRNUBYg?Qzo zVIE_7&n8!c-z9zCy;PHxr}J*6W7aPB34y1Is$=i%7*1u_RP%iF;^hSg@zo65x(G&b zSRA8C@l}9han?AmFLRnGJ2-)D;G4HZEB;Eq5O7$S@}7;a-axmT1A^{53;fWjh`>yu zMNZ=4MbKKKTl6R`7B#Ep{(_7hGm~!P9#4jme3-aNnGu}~eTd)sP?vF@R9crUeYU+b zgjRF|!P?>CBkJ?O^1*t&suEOxa1h$G)uarXZeI5**DiBz9fpkiom)}S@u$*IT6s>_ z2>1}vMQw9W7wcNWRmKvUR%%EL$}c-#%5b@Jz^LG$!Bwgo?TBvVV@iQ?dwIx;X5Yi- z9(SuAbdx;Wf7n(!@79j7=rwbvnT3q;s`5*{akU*&=g$Os{69tF%Mjgkp+`YecQL9CK2{o z{Me;Qpj}PWp-5k}s(Uc4)%9KzpTkAbwa7II^4%Y|X%S|1%w=TryMMsW8U6aN)qbeP zZM9_T=jwbERtqkh;N0U^O3xqyldoZ(Ehm~gp5Y2&^`S)wV;A0JVeQ$<25omIn_HoYaZQi?;lJU2za_&Zl}ueo_+zm*Gc_d4s8t0 zE6T-8V>y?7s#GvfZf18F-LjOkQL;ba|2-sv%+QgKnw2?=Bi*e}Uv1jm!LJrFSueUA z8T2Xjm1}Q8?8AtAY&ule_JwCM5GDpA_JT@O!|*3+`9a#Bf^%@s&><}8V+|OiHuFX| z4j%S%=08Wv22Ws*&8HanQ>2%%-j&iv8q}647eV`!szz?5wf306x-rm{`d$Fxc+S$f z$U~X&dSe6(JB)#6Tmymif3X>F@~NI&h{V}I-6adMG@IdE_dQ;FQWuI5!fE}Yj@==% ziIMwR12kp9(_bt>30Gt6{jDkUR@nvmn;+#8ZG$?&r;$bXsdSqCuDBY5Z@gwi9^)N9 zJGKQh#(x<{W_rY+`*=a>%4NJ7u~SIuqJ`;&XOc_^m*jPQi$mxEK0hmILIJ5zTCVa+ zgJ=W9V_6Ij-*Uvim)2FACcYPfHUJnS{9Ts|sJ*L90S}|IfMa)yvSZy5Z?do3A&16S z;3Og?w4Fe-x6jWV%K74;Kq_GcNSGF@q{y0>vkI)It+9n7{{t?L3Ig;Ixe-L-CFUpt zBdq*d_&52H>bP{I6&#K@q2)=`xVd0rxqLb~t(Kp}8qDTTqW3ACsaNW;>TD~J*GY-< zz86wc#+U^SuZSnEKhqh7X^a}c#iUE!t74Qhs|w8J)|5=~$hi!T(Q2^O;^mJ8L4njW z4f^}ei*x+7JunD?aI$@02z*=Y$hdc%?}Tuo7nN{2S9fH>!uz^DYEkQ8bP9pr%mV|_ zhowKUr~{EfOtAnBbR=d-#Z4sde@ymJTol!CA(cuoo=eTrh1%G|D54?7Y}Pb|{`PT5 z`my{__gaQebR;^o@%+Np`~A~?!#Y2OG^Jd74^hROX#$lCw#2=k9-*@Lg;nuIBY7KY z4~@b)!q@;p+nfIX>T?dcHGi2I(R#&9`6kmdhWR$JI|)&zbGi%F5k+Uk+p=-Ss8P4~ zYv@A`rcvYyNhn6GQbPtR@%;=?M-6Wz+-q$sUzjYS`WXMWwBfa`hMcj}SGzsJ67S;} zHVG=adwnTR`m+MCD5ZBUmz;aZtulQh`*F)=aQ z(3z8p_g!oahqK=p_TrL@Umb@d3w{jk95nvsywG>UB0U+RWq!D|0iEW!ZcJis6! zULY{7g4o2JC6G5Eh+$5{*3f3pXlUVHNFHAG_`XTe9&_smZX6IwB|>UU-0Y$~j)* zV}9CJ2idsL*7VBzkMUL{3zu+Sg(FmTrJU|FrTUTtU!+MP0dO^)gDh!d)!$VEKDT^Xuod`>=Yf$X#f9waq}C`zCD@#_Qy0VjuHTwzz~pW=2E zGqjnc4G{!f%|N@;%BdglYwTk#rtD}O=wTey4ohPjl&kgY_g_3qzj2;p`nzadv{8QF zKx{gy&`j^`c=p<&lXvb!^QwD!_Oa+=3rzTG=(*Bjqi} z^|F&Pw{Z!ct$TrbT5UfS*L+6CPReOp8#~)f5N1MJ$D)dg#MiFL zzCSqWj|0e!)$@QipTWPJ^ZGuOYwLj1=A6a`Mn64$;_* zpuXVI^@@k=rzci6FO96aAKN*@ z>L@f{4&Wb;f1ls|x&o*Ydg8A>ZLcpac+mVBI2r6Bv(V@mlfQd2t2-VeGp=Sa^bY)i zt?QwdvMj#ym)JC82+aYi^k%?0p~K|828_@*w)BiaXF!gdqSR8zBy#ft?LQ*gq|kc> z0gOVFscw?Gcx}MnZx|wGJ?h^UU-P1Q#&N?``?S(&?clh*$6-OseR6^i_bzAZRx(J% zGB6H^FHdKAAC*3e`h0dhP~-%rBEW_fToH}o9ho+lE#TMY1JricaPs0=msHw28g*6G zf@xLyLmF2v&d&i1R$5?DRez&)MU|BNIeY_<=2+aFzr`=)V_>XkST@f%T?O*qv^uK} zP8tR-P0In@ow|2bHnc4WOXcQ61q5HLRE%Yk>8H?Fxc6-VW6c@y+kLn^EUTZm7qj7) z2LFelkN94szl?nuUHuPw!wEcS&Kmc#?TfQK)E)b8I0h6n^i$!)u4y4?(i!^hhpAa+ z4|JNosfM-2ii{3`Y7j^y+T2dh24W94XQluSSqDpo*v+$uQvSJ@s-CF6jDn(q3&6 z)b0>TqbIPw*|{s38#X6Bt3jaK9$ckEg`icbWbDPD8S9JEXijsh?;A05e4kKQQj?4$&mPBJl9}$@xnRS6#*n zqd8HFq=Fx%1ko#+{5*U`^%W(*3fns;Ue3e^Ts7lh95WQ%jXr|>)+BU^F*EZ5Sfr-1yV+m+j|05@8qV z9S?P&bETjbp<5a3u@|J$I}0A&Q6TDN*L;T$YPxbGNOCixe^VP+Y=1oBAmCmhDk<#@(zP*so_*c1G5z?8l@8THhm_k0H0;NJSuX~? z>9M-8%kv28J_a8LaE-kbB*;$_*3fBG&5~o{Thh<@4q>me)AiflobjUx@jyv3Z%j3* zlgCC4wbJctX=nQ~&789?s;W}@drh-mr<^N4MP#*V2wo-}!p5`({B78}Ipy7w z@>239pdR_azf~!l(-&M&s%tK|B-t&(vpnpxJk9#jNJBT%(1hI{(!l@uxCDaE2g~J* zQ}EmfS^aun_y;7dBzwN~a?y$%w>w)W&+eYixf7r&I_eC2q^ZK4Mi&Ao8N;<58Y zGCbgDeMBvZmCJzkr{g9|D0_c>LEg{VzZA~@jKkdtXPMIZDG(eQB@|qHO=*BKTZf_g z+M$$AhwCBP8%v8A+Nn~EV&8|dy`tMXdb)J{Mxp(+ait_iT#_O#Peb{$-bj1MjsbH& zDFh@@G#>Xfs8YQafBLtB+AeLgGEcej+z_UZ{!V%T&sDM^2B&1!_CU07FzrgVGv;Et zv+Vc}3K_L!Q99dp#M1cDWF|Kmvwa7(q_!4k+?ytw_1>gNammRf5dIVlu(G-N?Nga6 zrsJ9BH_6LQVY|cceH8wj_v%;=8Js8^_5o>pQoj;y5{iF(mW=*Xsu<-|mvf4;JXsEA z+w8~OXVadaupk?rXy=IEDo0^e?N!Uh*JHX8TGu*-qZne+Xi|v7&N8<30&M%{^>u%j zKEZ0LVcT>ac>1`VAN^Z%@o2sG%5L8*T3#)sBHo2T)#WX_iB<+_WBtjllym)(u8;Qx ziA>RlX}X|bVQwG?g*p|WRsIl?_9AnOy_NLyzIA;XVqCLpUK9_UjZ}S+nJhwBuZVb8 zsMEUM7Aik{_{nK0sUwrL%O}z!8;)`cU4rbSx66Ujh538-$6kLF_F*M5pde&DMjr7& zCCIh#!B(;LAji2yQ-JexOFurc*Zw#P<*+@RrjY(FtX*n3fCkP@J%c0w*NhYpxgW2M zArtY5L12u^Nb-jzY08pTHL7cD22^5zWH_Y$B9)$B6tlc<>sY2or8E)zR4-J1kvz;6 z3DM5>xIMOuqO7g!U8dNq>Ujum`1{@mRW%dr3Yq0MeL;w`T$obkIm-Oj*b_mI?)JLU z>f#19LY)|2{y7kO+*|yg`fwtY1Sh?A12fP!P#11F=jJfE|KgfOa*Li|%xZ7>Jin9K zYmMwIGHwqt1=X}wOifX!GkVCRFnoNQ!Zec>pJ(`j-Biu#TXOujJVpMv5K{A7EOTHR zv;mW25?xB`gFyg8V3DqH+!gspG^*Dyq*BLU;m8_2kUi26M7KCEN*fd*+!_jSZU5{F zS3z-Jps4!;MDM%9(=cS*9iGuPg%vVU*^d@5SkPk;g#UVXw81fW_CFTWH?3M&eI}j# zWbFV1#H<7V;h+`9Z_ZEoaVg9ik+(CvE)U<$X1~8I?}_4k2FQbWwQ#yv8R%A+#Hr zkYeSc!<}U--@c4Vg`w{jM^yGnToZZcug4TTd;2wBGszm8x(=%vkD-J z7mg)H4!Kgr$d1inx4dD}P8bM!NCo!nG(5%n?*oWCr3SMa3PG(;>|^$B>+vyU$xd_!Q}rQJKfcBE$7eVQED=)hBA!3qi8{|sBHD9f!XaZB(w8p9(#q5q zQ!>YeLh-ntlytRxuT#ho;89EcQuFoSbfu04H(z7>8vi$UkV=DFT`mP->L}orZKBLj zA0c~zL7dle`2w4I^)0crA!Lv}FrId0jWcuX#y9~qM8!j7e0_&CG@h|BGC77?APA zJ8&lEG)5CN^Zl`C=GK_D$x_2)a)nhwOi>1~>c!JA7mXB12gYWLoIDUM&O%C~DE)nZ zI!X36>S17otL1tUEL>l2hY-EeS15FQt(E#?c$(s3`g#VRe>NQt2)|q&X_(eHtJYZm zgvdVcoZ-MDJuTW6XHYO;Pt>L3P_Yx^P(kqYuZ!@XuSJ>J&f$6X!$)>FI@IkL5V@S)&&et5^WGg5uoh&6 z=^PDH1)&!}>{(@-;3GR`z#69L&p0HFTq)O}TuKI1UM{!5Ph?u-FSr1r^Xy0NWhc-j zxXq5=;?0%%0{_e#L^f|fx!};ctI289hvJ>}?SVzuabO$(omTP*eIK{R*Q#mhg~8{V zL2|6Sd49NF{T+dhDaL*N{56cZBA-3@AS;9h!V{*U^#uPG9^Q zF}!JO&*%%lVmdy!$Zk3=$j$piya3i$y%oq?jm9#h1v?mIBf*9mrfY*?WVbGh>IcV9 zXl4RG@R}xG?3pX%m^ZltX`>uQTb_WJ#&Z3A(n5_98~g|C@4SD9r?jAf*){|%mtOmU zYzYqQVVAiq$1>B=Buwbl5?8))x-{h*_)IY}5Z%2?YC0D0UqNucg}w2Z4RV_R^4n^k zxbUY*RlAf1IW*ZY6_Wj#8V|wD#ZZeX3Xtmfh^9M`bnrf|+;!Xkz+weFyGW%A~_CsvYfl$O~|h%7#&Qz)dO%7-5A8Kr+85-xafD723dW6GxwXX{0ac zuO|E#f$yJN8f6knbUS%q1S{viu!%~eO~ZZglz6G zszAOo9zq!c=d+bFyyB~Cir$sg?o{?boc}1S0?kNc;&Me?eEe|EzlwVQm?Omk z3K?wbeB#Ft*E%bn_7ir^our+80C8KjM0=*n3k3LAzkPIOOu)fv0I1M;RRGX&Nm7OS z9je=&18`oJYFt7GY6pqR=dE+?z!-Ul`N4U4T^D_byRz|3XTW8u+o7?0EEvIZn*YH8 z5+w8Qv~o!gttmqjI0-7}_f7M5EU&zk>&c{2HR+<$AmJ)yhHd^$#3<6dx8Vk2%+dh9 zLq(GLP#mfB#PjEV6<(6~_#C3jA*|LR0Mo>$XHb~LSEg84$5<}2FTcmemmv&J4iI|* zc)@MJ%2ZK!NL|Nn_nnqH)(%i3#j&e@vJeXTcYrej^q&;&o)IV7S3TDCU@?MWQRjsF z28jbI@K%AnkmT_IaE-)+4huC#-T|cPMq9$mkq>|ZRJ^nSXe0`aEwjad4n2Jepd+;< zT4TfA^fD_@kQ>I{wVDhni?UOI^IB&2dQ|Rp7D-p-Y8hB+!F%3OKNk*+MStXS1XfSm zC%Ww4ApgyK!!TJ!113iu4}fHM{J;y~AK&)ZypI_)l$7$cH{Et+39CjQMh#xKQ1~{4 z3ZX$y)ggW=&k(aM>fyauI-^gw$5fCZCGyt!eK{mS`H4cajGU>rrt3=^7p{LA(@I7w z#w5g>#p{#fosXS{Pz|TvTNX6p^@Z1T5fhgs8G?@O8A`{{9IIlOvs^%LT9@VqmHB+kZ4c@f4K}4 zgo6^8OGLt=4tNI=sDME9hNQ`_lq3ke&mU03SlIx5CXL7boM))_8%|%o6%n6hO;!9S z`eLD9oVTz%;^+ascZbUozm!uTJl~PEE1$W99tNsJ0x_M0GN_6YGUNE2cVN7!$=2_W zW!g!W=A{?Ee8ICA{wov!GcN<{?hcQ+{#-arafYMf|eJ5{93cL4_i zF^0{f!s!yy8ZNP|M!mQc4HIFE=^yBMBx$=MtdF7fkAv{&U*Bjz+T`24y|c(#~?y%Ivf5t zkEmvi3H`F$zSWbG6oA&)brnn~*n_h48R^m(w%anz(>NSrheaqK$rx4;#M5AAZs9NS zeNFSkLsbr!eUjumdQG@(0kjgsEwK4ob(!EV4n~L}0o?Y~B=9)nxz9(pEXKV)BL#}m zBb3M3g=7uB&RD6BBi+9EX`l$%B8x}}rZJlK3uOcNSwuZ%hCvbM24xH^p)oZLy~3Ca zAlX%DVr z_)PO%;IX9ruqfl^Y8|j(SXzYW0z#Ab=}qdiFbWnUDn0ZM$X$ql{2-S_6ZUmFw0EVGEsP)yD) z9ad_#C02Wg&9IR%1dpKX7j!gpvP17q>j1}ng~K54z}ELJbp^zKmp2dorOT5feghf& z4D{*j9IZHnK6qId^z?vBy{yl;4AbRjrEFc65R(6kG*xn3z+u6(tGgB?J)T?l; z!v*_>$#HeXwh-OzOwOyS2iib_NbpF1+FT6^9=l{GAHXW*>4!r`S|W_qHUzpjCD?~x z;@C*8=Zjx%@$HLG3Nch(*R6{g2+1_4MH27O##QLsgq&y7#_|5Xokw^Bb(#ew7-}<8 z){RIM1dp|FWPHX!L%wp}Hs>+X+<8ZM%~t4pfM3M@W$0w=(`yXBd-5Hh*2?%}VaouG zo+hHh&5U1$PTR%eLg94+4beK2)usg={hHrc(m6IV$0$sj$GQHm@0Uct@(-FUD1H0- z8^gc=i%+-jQ-=zwvmL$n)*p;&VhbY8i{bD)9`X-)X*_&d!bW$5>9p=Ns{wB>-t>@z z3^l*tw-u0?X@K+M>fSJi?>vVVhpD>N^yWrVWG7 zzGX@74zxj%$Z*7n*bVTr+?{5+4Me8mFw0@Eqnju|#Iwv=tTD2o%L^vH9t^{bRq4%X zJ(Rgui9>@UbfwpE{+?$1arV{fG=%mOyGwdn-qNDoZIu=T{V~R*rk+zjX4#98-G}2! z_m#_V6y!;At;8`WBp4jcpFf5>-d>;Do+A7D;Ii53LCW-*W~4k(&RQYkK?U@ZgE*)i z=qMis@RD6&x?M4d$L>qQ>VsJ5CG44%@x9k@y`FArZ4iEarlDVxPzmSw<++h{D9o;N za)3|-KudyQt_}c6Q;k`PYvu2EbV&Y_kR58+qOqwG<3E**k%?OVCu_UW+zox78#HP| z+bxUIu&OKlO6bNn)?CrzD(pHb^_FH98};Es6X!{TDvIIqL`6p~AuKaMLiFAeGhn!l z5-1(!@m6&XJ;TjtRIJu60EN(o4ljr^p1<{+bv+?yfNzcE_Oo$opXv8!&{tkw9Ib9M z_CLtp>=%~(TpHgzidGIjY@&+pD^V`50*#8~-ys$~qk&=*_f}t#9CZz z?%(6Hf$FIKb0?F@-e#hyev~37Nc|KIL;|YO4AvwT)mMgD=nvm)K^Nk1-%}^{ZoL>C z7e@h`1)8edG)~<<6uh$GY|t~is|f(h-q_E1q zLNx;iNF)gL(nMQJ_)b?q%|xS>VE24gz>@|c3SzYlA`?RMKC|NDU8rtn+(60opmuv| zcC&D%A6lkn@cWISlGrFZS7M)Cd^-d_5Xe4_wqFJ3($Fw%{(vehYYz&zs*h`ap{O{1 zgNbU;-k*i42&Jw|1d+S}eCSEmM6(aK=4Rh50057*?^K03r0o&9g5NVZu!O8AtDK?Y zuY{*LPY&A&4nghUA_joUztj7~0WH#Q9A1-6*bhT?Zj>75UV6Ma7d*~fbC(Q=Qe zsncf1WrqX>uhU;Ufu09uipST6;P^3?itbEMer5&U!fE!)Peu>R0V^|fg-rXZE`~8z zar#vA*$KaI1f0E}eA)3U)PBzVa>%^Xz5IAEs{l?$J&855<`WG9#Y2?Z{@V%xYa z;j_IhM$I;38QR|rsOifb1tQ4P8?+kV8k&=vuDbfq+KUhN0tV{hn<9*!f{2`Zfb+(LhrpZi9$ikr^${TG5blHU~T!hj+p*8LSp8a$nL6<*T^cU5^5z zxO?t?vwBRZ1Q+K5kF&9X1FmY>d~Ui=@5_`}iiV;IK*VN5tDGtSZ_VU$Jl1?8uY4xm za457e+LWo>wK!{>p_0yGMS4UMv_ugH)Bby&eKVAUbQwbY^rJW=%1^V2w!YatqWxx$ z?p}sFG&sD9mC=S?j6>f8PubwVh9abfYZN_~pWe^UIm+J-6H8^Fwt_ni3#%1h^)#93 z=ibj`7_2=hQ37*~M4vSj`p z5NVX*NCcE*-zHoxlYJB?MxH-%hvy%rBVre?~9X%fua1MO3Gcqc;lni zEH=r2PSdyDDEs z-r;ya4GVGJe%@7;pD*G8=Uj;q^;p?;C7aT;PN^4f=)-pg$SlH)kZ_BZ=F zxQV@Z5HBw`(7zkBSZ(w`QjNsp{n9Xjk;5#`fYkIA)o-$!pYyxy$Bq^C&^iGQL^z4w zSA=K-ao&#=)6m4g@DgB*d~&V&%>$G!Czov$Qz8+=MS!`3&}0OXXPZ}aT)nKgObl;bxagj*b>7NLF@}m~d?C87 z5Bd$rWe4xhxOH*FfEBSoeC!`U6Z~kOYB|i~c9K|zj%C4^v$76PE#|XpK$kRn70wY$ zm-~EN>YNrPMAwXF`4=zls4Pn-&s?;UoO@(sz=6TPW3Lp;pqIt~ZHTTX|JOxtQp~i} zpT1DUTa$w2f}=uIG9^i+*UC6AA9*L4n-zPd*=Xk9uf%AYX~QwO8)tH zC`5^hHuSM;9MVEg2skMVTV<2+f%l=Y%7PmZQ(QNhum@S8gEc;83pyGK)U*k1(Ww)RT?Rn&8x`f~PoTs?nf>rXQSQ{XiicOGJP$#B)LAPVjAF&8tUBUG=Hk`)coZUC&t^TTk6iG&OA>_{Zi_=QeTm8-}b&IFZvw7U4DbWHuox)+!zH~8*qbKuwESh0so=^b~0wVDWBs3U0 zvRKHPnpVINP6-Vm5!&z}SRg@Ec@~8^?E=Zt8$dn~<|2$^BH$x)u?&$p^s)A@=6i(O zUC^gosI8KFBrAPr`)rSe-sjjtjcU;$oC25-lyV^QyWr8|6=II~b@(shYk;wnb|`Xx zAQ06qp|I~=Ry|YQ6eFEH!(VEjpx7b|x)Vj=7UOPpk7K{wXj-;!Z#8(mO7W(?2N>qX zN*~u41<#DLl`@B}IfeL|8l*{5#OHs)7y^``X2Sey?4zQu4+-(|C z!>+_Voxg?4mIXLFQn@c#QO(aaWdjasezgUhCFGPVEL{r<+bHN_tq_SNMpo5J4qGKf zg9eM_hq&4;vae6?GVZ&~cRL^v?x4xa2RvG3)vS63;lS4g@b^VA z&!-LnGpLw7L}8*Sb`cZbIoH`PJ1_d8>YyH^7nc`r4rd5;t$7K&e>WccCVd2M5<)?s z-^uN{pXZ;1mz3Inx@M>HO_vn9ls&Xcdd!wfP$r{#>@YeAM#m4pl3;s5%M9iXMAem_ zW_d4S@fAYT$Z`8Bhk>w>AW~4o!IhR&=XxUGRYfL{Zyc}+mwW#JT>9xx3xfjc|D>uZ zr8M%JG>_`0_Lu{+-d&RRr;YRDX(6{k0Mp|5ijy_WQequ&Of)k5$}2S3K#lVQmJIFh zz5f%85lEnU)jQ)|wY`noeOECBxlp(^K&wWcfNiM-5ITYV=c-#-^${fTeIS@Vip^rc z80Cg8xp;I~jkWFa5~`)Hi(pjR6B(lf9)X!#J7a7p&NaYIrPB#AF*s3xnA2}sCWg%^ zphKstWt+h!Kq34*L%oSBSkH<^$j_J+LLGi4cls1;df7Ps<)@{M4LWW`)C!<3oXqLE z$m#tfvwE(Tb$>UnUOWM3s#K!G2#qGg;G0I|=_k+H2L8j>fv-3CS0dDsXf)LxwenZP z;FRkc*)jCPMF>2)Yw3sf%BCghQwehH@{>kIU+~--NHY-HCj6y8gxGyNuO&e*=zU8t zLi#cLUX#clKnbljcil300OXEiR&Kv=_khV>*uyIlUaU(Qf|K7*z)>_+qp|OS`F|e+ zlXok-J4{{3vTo;e6t=Po{WvmmdY2Z-1J9~~BBu+)Vj9QVHt*NlGn< zMn{7#QD6;`gX+0hCX9ih<=4@RoO(^$$5LmUXh~8D&Prp z2lVqS+55ik_j77&8Ubtl0qZA%isWRa$51;VL~fL-yfw>IPi#ir10tUjmff?wO8hDq zc7!4h8bd-@#$({KPo=%Wscf&_w6cZ$E2{}nv+E!~N|h4v^q^w~Ca&G&j-H?lh&wP6 z!Zb59FHC>t5R*70aGO;^xuqB~p0Wp-??6Dp{+U*Z`V;_XqY*D0brrk!h{)K|13WW5 z2$YJsSTsWGCjJm5LrW2+wJGScWZP^euAL0*M0Thju91JCp7p9`gT?QJA?eFayW!?G=s((Ukyyuh$jH=>L@^)g zdM}!_g=Ssg55>|1V#5BZ8_S|V!$TFh3YoEhuwwU*UVVSXJ_(9*jcVd?ZvHOvz6mRD0p%9XvY zH9opDuE)3X8w{L?Yql5oFD*le(-ZCOJEk8=8AxDar^K|Z0?AH~qedmXejCI6VzIQn zsS{WtJO4QlJ=d@inK29&}v1O>3xFb&T`^_F516=-YygqL%3p5vfP2RB+|NBFw z_}Ow)mdov%EoEi8WB=VdcS3_>%H97^=YlpqsDm7VZM?315#U5i{e%5(5$T8o*(*f8 zOZ1(H1$xn|{>B+cTu~o~3yJB#bg{y_v3J_WyBNlOq+JNuq=u=O*F*#44@V8wwj+>q zJm&fe(nj)o#v}EH|8-tdf1`y>3saFRaw6i)QljAIYqGJy1tO{)7 z+iY!Z719Q)X`0oC2tVro^n&ZFNih9g`0mINwfWGOMm#F7Ep$sdgpMy2d&&I6FT&lw zi;O)8NWKFBdcxb(=PtppGn3b-Ctp? zUTYXnfmrmh=T)tI40sXSu@g>kWj6xqYKyQOu^^p`!4oYDqaT0X*EdH8ziVAYLy%s& zj|@}*liUMMW~!N};dN+D+`3si70?bj`yn2QlUZsr@cLnuiE<9IHv;)wPUT;B0A&@5TznpkGaV1MS|= zsH|GX;6kr;+C0n2m{SB8%)z>-eJ`QRF!aPBFn`%k)OM>m?_gz?Kn4s(NX<+`*#3|j zyTUq;QH9jwJ_jSxlg8a8RCDz2{EWt@xF)ASN<%NL*}i3TlFC`5ara;aHhpuVFf-UZ zY^mO|d`kF~tjTWR0c5}m#S7z?B(x=Qo5b(H6j>(O%w3Bp^=}5EnX6mj6jeu5wn38l zFF;IePKn5Fj8dYXxVhe{^c8?R7Buj9;giIj=Lt5CK~aii{T=QA$nHi{RNK8DE8F#F zKhN740r&bQ_osvF@D1ZKnZTR$6_I~7&m||%59F@s-br#!`44dksSc7AN;ZvNZBSvD zRfWt2*RibvOk|nq?a({Ye2RbsG+IU6fRgV6ax@}FLvEK}$PJ5JVYt2NvDjgC`%Es; zI2HIvqMg;mg+r7Y7vVKTaBI4e*~jD=p5wAO!uc(E8gy*iUe?4}OD&HVk5?ZcXO~gU zoU33X6NUR)!EY!-vq(KEH>9 z;GZHbzYte}aU5w{{|d>*g~!#=mWtH-tSz;XEj8rhgZqP5wzi-`7dRBQlsP(gjbEcY zVZ{Og(L}RuAggVh+Lp8%cEd#O-;=s8k8e`#!(qp5`?+4sBNh^=ogJ2t0*a7Pp#q7OR?O?-91}_x$?m|2GLq~RA@U7Ta(kkO-JJv$l7SY=PdCl5%N?c>mHC1{fwM# zp(8!>WP)OhL+q{~74Zo0Jzz7fugjqfGb?Q<7)c8Z!LY$8#6NDlAR;Y`LMGpppIql< z6--by`E{}8A<__USlvY|hS8YbFBb(q$!QVYuQ2?Xq27O)jP2vhyET-B-pw*s=xKgu z^%2E9%_}NUT?m*G7OoORAH;PYcuw2N5r+&FlGXJM%S^^3!=-Om8~V6S&{^ti zEGL-`uuhIn)sn3058_v>*Nto zVZOZx9+F6}&hhibhuS1TXR7raQ*5YbHOBXA>1}F+GZDcm^xT z$5#ZMO@-HYb*y)Xyzf{g$tv8#hP=4?U{@2Jge4V-@(uYGRs;g?cox;|x9TFe->9=OJOW39nRq`p z*b2V)dz?o^exf3~k|PZ$Qt{cwDT^okN#Gbc9ItM{|p#URXJP6=U14GAKJ2?5e=i?_o;~IcV`Q^ zXiAGAn+_8UQYBD4CGv}iBcY^St<9pVL&+TQDFU22hW4$AdR>t`Z3hTv*>0mvk8X4> zP$g2S+HMIro3*lG%wcttLjr@Ns`&$gor*xJkU~_=B{Jjw`Ufn%%CG^%WUSn%X4dsc zjdDdKb#jQ-2;^>eiJo%nG=*sn^VFEJ%PgW5UVooN&S19f^QHmS%{sk;JYodC{rCT8 z0d&Edoi*>EuW21WmiN`~p-H~z%Sey}9kHI89A<$yK%0Q{OCHE#{T-rSw>P;8VhfP% zF&PurPq%a1r+|h=F9Mll+gqUj8QA2k*1loYv~(QPMZff3k&kA9KH(C=2Auu6R!-rn zzx-yd8TGFXu#>XfA`$Vrsn>-Je!1b2Z3FTwOQ%h*FUVt0J~Ng?3#B~M&|*kFI;?RI zl;df!d5&??aca}&Egg9rPO<&zq&LK`+N?B#;?Iv_k{GsuF=~sZ)4@1%xqVb?;|%g% z{W&I8u7Li{?f{EXztxnalx^)|a2n$$8AS0@l~^Pb$GYx#$;f6qm{MVQg?Mll@7Kt> zPpHO$)zg#5*nVa5;Bu7IYd2ul7n80_z6UornSh*bqHG8s2nYEK49UMBx1F_?!{9?< zoj(Ogz-aHC|JzeB$gIcM(q8k}i8<;(K5Pcsib8OB zRq%GNVo|ZfUhIr}x9ddeYy-HB5LZ{P)+*y_Di9FLfBw{x=tFb|MqOKS29?Z{O*!8K zkqxy?ctNv4hBj(&7%NucGx-++h{YzcS{&^rsfrV7Oa0c3Y31au2eNO#;JP4F<-^OEg2Igog5agGC1x^2T8ft6!LuNYHIgtx!a;9hhoeTo=M zZF%z1%Mc{~Gei@oD3am3T-b&1fRxR{vx~RHBp4-e*Ns7HDF#zjTJm;gcx@5fvzQ%# zB$9_!BuE&)p>r;+&Qk3`x`N8XB_XJxm8?qBag{3NrpE~J{oI_R5pQ-aEdpYNzSP`v z0a2Yim|%FFIS$DoNj^qZw*d3dPn0`#;E*=4EQ(i$m+=V-#Bqn_)lhEh3>1jz$j3_g zFU?a8275Jbi}UmQ9XG`s<~gD$@>#=V?5Lw3S(TguP9CXYZ{FYnf#4*LQ5VOLiy7L= z(h{^10bmx2qqj&w74S7a{`maavpe=zQ7Xv9Clz%GKB7k4n1-arSb*ZgM{ruBqUGgY zTN1e^yxzmJV;xWPVpsoW${%ij;6AwE*iiHfwPv7uTP%IK2t7bCpoMpbx+A z#M?XoO6_MVGCJR^dFE9ho~gy-$txAV7y4rYkVO)`(rk|JiHyu7fr2~YxDmB*qi)&W zA!V0FqhP#>bN~h2_W+h@0a(L=gx}bX@?QU9yF?TsS%tFl+vEAtp@GmqD&@d3mrkJRSqrYtr0UaOI6TP&u1}qWT{J&#^Ig=E=k_80yjPsVz>xVoqCQ$srIXQua zKJQSxsv2MY)BjwKtu~=SOZHS~i>!qbQ0}33Xv=E;a;(#-p07}cc!L9=Y1c|fKQ!w* z;(!fjlTzlcpuh0LoqFU3r?=4&4;(y=ZXE&ZNetGCLeD z@nq7jI{?qCC$=>>-6-Oek25B)Z}Eavc8j*kzoB|=eXxv@g6zLVmz?)ZB7cz>i7SN z`XKKc|LFN+w|?V4mW--A?Y_hd`?Blk6Pwj30k9c7vly#xw`|Lz*p^+ujeP%GqhDDHC~31Zxn9Cb6=aRE zdwEo#5jVjW*Ux_5mqSKtU@}1uw)XlPHqMj;Qp7$%U@Z%p(!H+jXb^q=mWW+WIp_*H?SRfW;vr zR3sEjm4V{xDi8a~*m~}>$jj+k&$5X72K$TM*?K$vVIV3Usb>}muL_eRp`h+hRp}q1 zs*;XniukbL@kKt{MOC4GsYGoQa8;R|U|R8B&a^YH#yl(uGx4*VDzi+d_^Sdpa@RVc3gjf3Vuxr4ACjh3Q zy>xJ`+W$7fhcoDbRcTjMZK zmzue(xzv^uq*6G#hZM0|0gLBEZ11p;!bCGSpo6zbi~j+Z690p$Xfp4lBumAVP+h7RY)CCnc{2; z((N)5`J;Ha@4#C!?s5spkFBZq!J;WR#%T9n?I76I2m7_x`gK zf4nJ9HR&pT=WB;PV-h{Wa^dMCkbqdAfi5z~ifU)E()2ucRco>0jdZr(e#g)KAP+9$ z$PaZn1wjGlh9)TFB-_aXG-xF_*vTOgUPZqyq!MRn^9Kmah0(>0vyG2rP@{tSpajO~ z(%E;i`hpNjWrGG-x3&xk5U~Mu^b%E#(tr2M+#=g1M8wkG)Q0RJ((J}srv9wth`M+R z78Gj@{GM(lI^`?Lr*Cm2=XW$@x`%i0`Sz_x-!m=Pp3QFPhTV(NTrucXDI0>QW+8<1 z&YFaq$T*a=Odd<+Ow?wVJ~W-xn%yXHvXdM6a@#GixNa5@ zg9-E zOyK9<8u=Cm%c$YZ9lGs>N_jMqpl`{=s}Nyz9gyyHR2 zj0WYLG0@CnD%Ou+wcavCK90^CEAI)=;r@QbP0awFFSd+m@qA|;tAwz&E;#suo@R7E zH+#-~hsh3a4|WXr{GW5hzjNHZC-xm20xY`VSId8XuPj!uAGq&9^Ka>SwEpO`ct3y< z<=c4oR3m=C(<`o(YjF7vb%xRYY7>@zWMD5CH&497Na8%j@85NU)o<$C$BozZwb!oa zx7voO4$FXA(tGabk0|)8j<>5W0HqP&Z@)Y0fbtVN$P&NR_adfeB2WzE&%<0SbH z_R@^B<3mZ&4a%f3#1vEgExxDe`#|53Y7eI>+2Z~qKT9bF-Q51^@~3BTy7f47JjbVU z+AylJMETS9==RU<*5IY)Hs--Jd8lDx2yrtOH3R% z1y(2h{_%*p-WQ^9OcyJ6q83lk0B(=#+~22bFSSyB9UpS`q1#CXvM~}b$w}<6!=`jS z-w3gNbj%3rCu>NzIOestPia>MvsIWoPj8X3Y`%sr<9uG_aCV;2Ys=bzN;ZE^4UCE7n0k=@Jaa7@XjsUu40Q7hhu%l?TIP6SdfqI;+ZMBNmHW=sa>qe1TR_e{=ibc;~>|N zYh7#7XpMTG!)FCpJwWq?6c2uTw@kQgAuB*lkxK44Na4bLWS$PgECH%OjZAq7jV#2* zdN7wISY;;0;1*=XsA-E_Gc3vVD$C-CeAjsq%28@J8b;z!+E$N9pfe2hF_=gEkoO1E zG{38h3A$1NzjM(05c!wn?9B|n_GV#TOLqh|IW`+`ig#NhYUm_5gkw6RaGNET_bjrl zun{<3cN9Lmll0AjeFB=jiv~8g1s`Lb_g9wLPcw6RPt?T$L6(erV36Fq;*9k@JeKjF zH_G8zDa7gx(0A7P@~3lkQ2Za)+8Qe31JF`mR+j~MS2`zLILW^z^{zU>vO3|jw*3%T zGE2IV4cP0RJ*=_Kx>+)iCO!$UsK@AvSeqW}-FQB8$NT#Huc>p6Y^HDYzL;>|=0@H5 zj7j7dkD|nh0?FrNZkUj?I#tl85+rt5{CsO5t^t%B7(GmGCQPuN;Wq3K=#UU-)kZB4}Dfh3yQ=2KP9Y{C2#w`?qi5?m}|7w$0FC3CpM?hK6z*dEz`1E0ZqgImy^LnjlmDY?Zfr^)LmPNo0XS}=gut?vHvw-p;&7U{`a z6E5J(+$K0rdS{~+5XmVKN8 zkar)rc7n@495l@uU={fm>@m*!7hFR8X~JRV!&F&Z0VUh^RTwau|J;xa1!P)fA>G3s z*wv{GkoK6nLB;eYg#$LJhM*kl!EYnIBOxGVlY;WEm*R!9Qu_1EIR6~k;^CU2?+jl@nR;+!4yKD-$*RSy)ZE}P4H8nt}Yf*?4{}|2~~kuw_O>UMl;8DaSaIM^OcpjpN3x^3M4d%tEZ zgR3;C5Q>cOtT={d4#FeG_AvIYb2w|z)2z2MF`Ixqg`i2$30E) zQ#8#K6~VLK@s2{?Fe35Yj@)k~jBAj#v|H2mz{a>A?ONqIDErNg53gM6wN(TWL4BT> z?E|@TMq^k^B^{9L=Gs+BDr4uwbCRbML?kr|1E^DY^NOdjv^aV~^bRUN1=!c`B=Jrl z=_gjPPlq($dZ+0kQ%oFdY-Bc!(Z5Ic6Y|%-{T+tc1Yi8o!Av>A@X{1i@UOs|^#9V~ zePyIlMt^bpZi^9QGcZOZOk@X8G2hl8B<#TqF<}tn0hQpuBuGf2+;vi^f>rP*<9?1{ zj-Xq@D`?SxL{RW<+?{p)Zm@`FK@l7dBW4Nc^aw!aX`EhLrCH_|D$PKi8_6+rj@_>$ zy&CM@;6F`$jr)%sqS3YS=AB?~*-gBuqx5VLS3iEj#W!*acK;{1XQyyf#G1AWL@$L? z^QZ9aABOUbqxn}e;&t6T&sq*{P;%ng~*OyFG%~Vg3p*hUTcN&(G z3Y>>KNBr(UA)>oAx*dhv+bL490-69&n2h4?Y z(lzE|89$AfQGV;e$`5`y~yA53GX6@Z2|8W--dbGg%DGiRfx z{p&wToF32bm`U$Fnn*wQB+`ITn}E=QUV_+Y0)c^JAb-`Q&pywHa6iV2|KD<>7C;mH zgT=rru8?#S{J+b4TMYlQ90CWV5!ff|A&lG^>mk`B5I~$BFhN0dFsU0vn&3dz(T$4* z5?82Yz<`8(rZ?Z1@eJMh-yZ9I*chjf;Zm&!CQy?6NxEsQJja}>_~FG{HQS;YQmA~w zgD`L(NkPFZdH2*#%v+YiyTeBNBEIN@{^cW&3PM)c{a@C!NRDhkAQsmR_7Ph-lZ5zn+?d1^4&TR1V`#CR z{^}ipyVT#I(%Y_!a&K09I8Z`%M1?-8i2ZE<)HL>Fj_JyqC~&~LB6F4poz4d>GDpkE z2{t#@Tp(sv78PFY-Jm=3RX+R{h&gKT4Z&7nO3{8!MX-(ErAvo`8KGIm#$-HRiWA`4 z#N=(*pizMnL8<1PD02{hy5l@eSfgQ#n^J2q*KP(=eIiBCB! ztI}PZg)Q6zMBrqR)|~C_?S(&cy5}nr1em)JD~18yEB$Va)95`hK?931it`~$E_ZB( zF^GL|kX-@ZNUQaz9qBci!&pnUH!=Z1*T5j|Xyh}|Bjs)poH8keP4>!1yS64TR8YBu z4aW5NSH-=6Xvs^`m>z8n9w0{Q^Prnz*W;?$Y}A5ZQoW1neq!>I+i5DEOXO^Ebp0C@ zwKq)A>+(Wu5=3A`DMPgX4VyX(LG*wz=w>nvF#iDt8&3hq&sSeO{BOULQaA1@NrA%? zy0UqEeD$0 ziuQRq`ggE7Nz%I=GbA%*LjUx!P1kG5Fb^0~&H)f~UXH(o6YIjz=1X5Zy^egPAvp8y z>+6V4M`2xD0|OMbPB$Uqf!07vTgw$ERmNs3CcZ%)O8AfUx9_?4#te#kmv}kf*H%#7 zRH!)%$9yN^;IFVu_-!sq^RqtKrd0DUs4rXt-JjOQG__NVb&baijUw-kM7#;G1U=ZI zVYGiB#<|p1jn(%c+P@&X?I!SoQ~Qg|Iw{S7Bm0HO9Z!Woviz5nm-7R^at{4m(jn#;%Y+5|k5bJr1jCmPM=>~fdI~Gy{FPo7mTmJbab9&-wBIygO zh`d`US5#j#Q_15?rZqiI8d2LZUOzzPY&iBg9hUbpF}IirY4{2IlzrPauzISNIx4{}?N`l!-TA8Iv*R-Vl9}n7 zyRp1SP`6`uVS7O;Bo7LXgE^Tks{GG$E~S-qPw20wnaki#oJ17`st+8e=e4Ay#8>}_ ze@F%}D9b|j84Z}t@A6a7C>;|M=DIp?asI2SNE9b{_CA_6KY9uGryrh8&SCM;_TJv< zMeObCf=>%5o}xSFQ1c!c@{lIl0&#ZNur5wbl_rsKvs2u4A*>>}-39pVcDD!ZOdwEM znEid7?aON&J}4fmkb*sqMQGoZ)?rF&`J+_n{A|Um_IQ1X*+BL+;l%3kw;PRdoNk3L z8f}MEZ@l63goQC2sq+vO^NvDRu!f9&QC^b3*f2xR$uWu77ljNl2Tm%0yW11(%;`>5 z5vz#jL@k0ib=8YCzwXp_Q5eO&%_;DdK#=TXT7v-PnuKW><2)#M&S~MX&np_kr}x7E zu_z~Sx9jhHl&u4=t~*(7?tCiTLIF+S2Xn4e-7|9r$6~oYI?58@hh3yX&WWoRoh*Cu z1E*D|5`uH?RorBQrKOT(xR+{kl|Cv40i#_u;^?cg5Q@(*_C!zOdrq1xmLA77L5Mg$q9Arfx z1)4TkN!)Bow?>bcg#}uCOMyTX`Gt$hUG_2lrN7XBGw^#7e74*k{d>0ZIN8!;e15=& zPw$L9p{~@-*Dq@|RtE-X*NbF;Ok7S=K~zZ3>)c%7PEYpIMa;_fdKu z{hki&obJv|$2_|h*`K%zyZci0__G|*4BW6Dt$fLigMkTeqj-~y5{?Zf6OK=ONiI+jalaW4Mp1 zyOU|oo!acSM$BcGeWoZE#|i5G#yJ;TUM8X^l{3lh`chi=wo{S|BHZYUN9Ie6h6XUF z{D{xoOFI)E{|<_cT+62em%+UI;XR48c5DU&vcsh5tYC6hL<26G6!34L z22N>mLBfC;gf|*=*b~Ip3Cw~&qVY$+)LiOS18tpR>mVXUu=G4*!a-)+sDYSw1lt1P zd>U8@#N3m`ir;e?AUyp7Y%3~B*-|-8(qLv;CnA5d(Q0bo)_{>FAUze1#>OkX*FTAswL!`F1v}X*bah1Q@ZP#nPMyKu>EN`_yeehK9=l!pju?K{rXDbZ0XO- z39)5k>zBsm=}IAA!v#7J%P<_fe<-09(%Q=Mazm$q(_Y>xUYbz>w{wHgP9pTc&bR0@ zF_uSq>%QiKcm)3;tcZ7NMVVG1>cZ_tu?zW?}l%ox) z@fnWbc$}HKc1LBsZ4I2jbbZNePrPiJWuXnzs>Yh}-qrK4S2GMJ^ zegw=kBA24iy<0uTgWf*(5bx1&Fi+Ir*~nqQPcK2)UOzPydP2xgFyS<@yAvuG65gUxJ zcDA-DU`}Cw%vX!Az2_cF%rj5UgAzvhjHG6md!x7_!d#JSQ|OcUcBtG8tSX$8uoobA zQJV0;A8FORyL^k!fr=2L?%mKpYGCeJ#j%Ss!g#46H^ESbV;<^ILkq!M4hkV@=1rE%((x zoeqs|N?jGdYPMpM#((@$syNiH`OOxYQ14J@9=aKapHtvcm(f7?N zIbUD`Lhy%`6S}3?8Aj`fxh#1yGxx=8&c!RoJE!p_;0ft3WZDhtB&nFZNxY6jrROO< z@oNkJ<+3xh;NDtg)4w-P2urZ+O3M2nrAm#fh)f-|{1wDFZi~yFlhcsX_j~OJCyK!K zJYr195opif_-U9`>3$}GVfqgr4 zrRz$0wX2n%2y41=bUN}82!`dhPdk{^sdU2MPW{pLYPIR!g{doC99UdAS#LcHnW^HU~0CtOc`udR+ z2QjQ>?sVL1?i!s4R6(h>KUh#LhJZ3Asu34?g=YOPFgDm%mk|-1@Es^Tcb32QB1wuo zSH%}ZNPjpCn-YNJLE663O@FM;T+1^>47`2Yf~pw(u};&`HpSjqeKi_w-<8jOxBB09 zQFJ@gKStk6hExj<>kqPSg-_5^rrPxYqti(tPGbUCr%?n(kM(Y7r=X(^a8x*AHZ zB&PVsIKz#+j*43^$4*ZEJL>p!6#G?#EG%jSWKE+uVP1#f=7SD|Ls-#=-c2GHqj)l; zqGqITl6J2PXgmA&U9+qs=!6t#BvF8}(8&|4QD6Tns`b(TcvpXwZrF@~3J6^HTl zsA&-frJ4cXhC`M`YLg{!)NC3!B;#9)FdT@ZX$EQ_f^L~K)mnP^Em^e{mfq(Yq_vo; z@)sc$u=J^{ZiXoTePN4Hs>E+b^Z4f&6m1v_F?s>D3d-(xNn;{xb7pTo9xmN zX6$y|97`yDpT`zl(y-a$nL@K*OF{g|I@tV4<(if$(t|NqZ*BQGsIc`kY%xa`y6nn)uIW>tT#t3Yg@!gVVWm!I{Z#u?Ot9gkCe}-M z7FAm;kl!e9p6tEQk#UeCs9ubrQ*h6z!*BdJJLc-vyj$v2DUnBzdDQsr<;u_bE^mkZ*d>jo+`qZ)|{WE79i-x^%u%|@&Y4c3bV z=8)WLI}~Pl8&Hy*LjRn}HM{S3ZdUo+t2GrC86ILYeBI@zk-!ox!#Oj^VfFv8^(~$C z6`8dwZ`K_bwlg`dm0*N+dGAXotK^3(ZiMb}1-CCBX5wqM`4T|d$>bm7Q9y>$YcfsE z-Ua$i)+|jMk_fF9gu2fH^p~pQrlv-XxF1wP?S4cXiwMUNbyC+fp}5myYG4kDh&%{J z6D_e#e0f-2=m`7i+%**#`U^#NsiCp)dm6&i-LqxSW$r|jXnj%N`uOK6^Dt0pzAP=uE(yxxjnn z2=;^)e-UR6`T+w!b$?SWU^qGNe2yJjY{=b`I&~8po00G#X?mC}Ke$Xhfz2IfK-r}z z9wQ~gS8I^E>12%o@0=@ws7p#`ZWE0Q*$Q*2R$Wk}obD-dI%>jLcgsr8)#LEJf{O9-b57P zAFAbD(^R7|{MLHJ&wFD}nR!0vcTtbEHNbKT1i$$Ureg8k5Y-OrDLC z^iRe6z}RW|{*r;TI*UzL`Jf1fj~a0}i~bUNIil9}@LYK}Z`D>LLDcLm%@JUab0s0Je4 z4%54AjEA7<^S55H2d{ykEUOQB?9&B4 zqbe_ae2$buytK{|ZTA3j@|U_I&UNwAH5E7!^iSqO7vHlSoZqZ|jh;~AEGWoG)TI#Y zOO=kSP6*JSmScku&LcMjq5m_~RjSkV- zMT~a1M0dKG*sA&2Y3hpB|LLWm(9Q_hV0_Fp(9sA!p--+r@|CGFfGYOYAJr4}O?T-GM=B3yy@YCAlOr|kpk*+lpfI4{ z4qc?iV-)T62zh_Gv^{e&;h0A%&EfO%ZTfpxx!T-EIOmgh_1{C58U2V_@&QCQi_gwR z#GOyE`4bTM1{;7&1u0PR&<;DKYM$R9@FUrvQo*N;V66kdAgV8(QHqH_Us?JC9M^pE zDQKJ9IHjEjFKK2|KsDbHIlp!1s3dP5c0}kp)--jh(#(E(neKcE^%=bBx*fq#_90y2 z`ADU8Gz9~Rw~Qt(R0R1mz$^@R6`91>sbRr^56N2O(bjBFrt^xLTShp)s@Mb@)4`mw z-bW~^7)A|A4qq9UG!(Og#l)!n_Xbp)#9%zkC~_KvhzR;AgexGHL$GQ23>$L%GPDZr zCtHz2*s_N*pZ+Djt7RQN{+4GUF`23mD_Bm@QWG{ozd$X>!=&(K*JukXPRJHrc+`9< zLY9kI=H%{L$t>@Riq@g}kL!4kq=pR&S!R-Rql0@A37Z!++zyk~b#L0P9(RF_5H0sr zQH$)JzJ<0zqUH~3Bv83#@(BZZ7hV$9C}TD|dfG9N@}9wDwA?MvTT^EZ&2JhJfPD86 z$+e%ZlaFr8Bu&!WCG>-Z{~!yV=$7HfoTF&4pY=BYI^-S-K!bb}wd1MW+03YAP{gQA zgj9khr6Dps*4JO-c200@S84$!?F*O-0}W}NLz z15dCJWxL+#v@sq%~v=29e00S(PNqqU19_UY;RhCrU3Hj_lLE+fV(XW z|5BKiEz~Vh%B|ClN(5v=wqlLbhrotkg|1;ZH;;QH&pDtzRC=`<+Shwew3OEuQl zD}F7kdy`<0doVxc@mR(x8~Dt@j;IPBbKrdBUV^5!p#5z_wOopd?-g!4FxGcK2Gwoy z*Mfs{{7OkBKa()O*M-@&6Kz)Y(d?7N7O?~yTPPN=Q4qTDXPVCY_7s!#NG~x#-Tx?q z)9V=a=>iHv*b``i)aWiGp)zoe#I(}#45O;t1+et4+3}Vjy#@o>0CiOCvGo^JbhF=M zRwtVosFt3m8}SSh9WTBF+cp%WL%~d-)We^uLjE0+(pq^vB zmrOBe)U_42m^Qw+&B_Ds92Ikqke2noD{P{;3{&mrRqyL>`Lz2yIxBIWqxWS293{5a zWYmHwkvv+g0S-6mF1zgZ^kH&#U}LTP!b4FI%bA}nw|A;+vuYb?Q>xVcXuA09FMM^I z=j2!tT_60&UMr9z*X(AjW53(_L%|LTYwnTJ3>-m~H!B96Vt{A8tPgxZHLODsaD=vL zOUtD#_b+t7OFahX9 zohzgHX@bvRT8$C$jP-th>|HH>=~b+l0)yyPUv;e^tOP+ZaSev{w!dD%GXFCEF8ulE zt&*z2mP@VlQBx*IGr^W)Mr%k7q>D${istIZAP+%=rincUlX5e;RZKLl*p-W*13+bb zJQ#>UfpM~Y^o4Y#sZC(-WKBVUvMYDPNB#GCcsB0^KjZwR&Xylaq#F4A+8YMq)5Ii^ ze&XuR-Enwe=P78>f4+OE31%7A)j;c&sdSWw%D00mwCYS9tqr3X$?ctoYn7_3UF*x! zekEACiWlTkejG3@D{*wznU7uFvSXtDu%wYsYL};J-1|B);1Z6@;?H=xkAN z$l!yk?_VqOdpDvgFPZuD+Z0z0qnXyUIt1AyROykh1p^o_TFzXQn3qnTC*C){5shyF zp|~_Q|II`Nq@5;QDDH=3HN{6I(m3@E%-OZSimO5ad(uwaTEL{Z$RyB?>h3!IULck1 zzgXEExGy(oAuI&&PnW#|a7Er#?pM=Bf;Ttqmtj`UmMv9*cSKh)%+9Dm@+Ph)h9PzD&kkDz@=UBYpExWwhdHMX0YNaXgdTDH5}TB-Lara0Ou^<;?{xQ?s{FDC$H`CaF0dzrU4@x_=0AHJ zKT5qLCXoH_uvb6_HVni(kmwPDgE~cHf&KtK2v@+o+u*DaE3{7tuL0yKYI6U6WrUez z$aV}mb9_k(s03jBOV7I{5>9~dO^nc0sg25@&Odx9c>Q{xF6xA4*t;uY`Vd{}&!{^L zXUeC?-L1~_U#=O3F`Z}XuU@e}@*-6yMBH~g`ulmHni0iKslrAdUba4q2>{#VJ4CMYY!xbazCP`!*?ivE;7qiHY-GOh))Y4kLzRb{Kywz8S@*!EjJ zEQRfF6$yK);^7Ic41somA}(h@q_q=A9V3%fLBWa8ikN=GxBl=e-6h1HGe`C6Cv3aM zY&_$VkWWYT6dJ5tj}bPdBJcrP(tJak{hM#CSmjdcqnT*m_=hrlf$fN95=^#m&7^qV z)J~3I&Suue)Ax}yQjbl`RRF+|p`EHfE*cxsfW=@egozpICpN+N?(_N^!vv|OzFwn6 z|0@NhTNijanhugGc>Oz97KzaGmcix|2u$i5;_p%oC$<&fxQmAT&RotlrtW5eRyLXq zYvQos@ys=cCABR^U}aSaA`|Q2M3%@j%Yzw|6CO&)831<#ud7QaRfmZBj?nmGeK_so zsLI1qkEgrD$PG_J+7yzSz;9IsP%U#CZ@VJ?v)mbtDJ_$5Bo;R1@&LaSvIuUZwebkW z6if{C#_fp2cybQ`S-YnXcnzM4N!p_QM!hd=q36uQA z31C%EImTd`ugcVo&VAi8 zRekfmUbN1c5`V;*kO`4u_6510y-^BhPw=^U<#k&T#1JCY*7~2cc)v>Dag-r>;n5FH zY@m98vcD1<=YomVA|;Rj59V8#bNDw+z?lmpDuuvwJHv?aTKvY`Pc1{)N}H8P}l*}+N?vvUCEmVPGUH{-9XFI5Rr%{STCKq z>?xKboVhG$vaz>Kai`}fp1eQSCZ6am;U}dL8H-FKjZJ*bpx_~+dvb?|-rskt70PF{ zs^XXjR2jBGL*$rHghc{&&0Fp>Ztna)og+R=B1#i6%6=gvm9vAK5~n^xt_Lul6by;XBxd)(*2 z096nPa6gJdtwMXaEVv^^qs>S2=bY2UL|Y;XldGJrW;dldBbo(%Ej5L&fWU=85b-`( zT%9YToeyD9tM&Pd%C}jWca&e!BG>p+A=V!}h|Idq=MmxFei5_UPb-+zo&2G?QZTw-njVTAt;;1sUpjyH zIpt_ZGi@%kw7?!ao6Hatvn;|`+hoW#k9V>QDp#=BuRVzSa`i~w?abs?DL|S$kjFBW1{x4($ zn#w*QmvCug+PQwHc@F%&#B{$w zbl!Z+dDDQv(}B=x@!V-FB)Bw0II6DySCHX3AU2*8tKWIeJ6jSm*j_Ve{TG4Hr_V_; z>NvBIbhZS&Q{Pab4n0GYq~GN9tG_FjiP|F-fWkJLjyN%GJbt*fgYg|P|IHE!?CdI@ z^Fr_~RJYEjc&rwQg^wAUT$4lhOdvCI`d+<<=*JyjAZtqCPz?jd08s)7U_w@^YTTXg zdy-NX=jQb~<0GT4$VdLb?>d234)a+&u_x7R+RWK~n%Am55Ry$F6d)!3eBK00>>ar zZqMwqtVd2wmI(vR_*A&u2;AE!BS%&~$bthxZ;q!XbER?Yd2Eh$95vtKEMZa971R;M zB%0wDEKtPiVMM|4b8YcoVDN1=<^h)2jVV_&w>4$(tNtb!m@t@(xX8Crz^GAeJ{TU{ zPMQU%g0LOwU~sLeaX*_!IbD2txNCne78rqAGe^+~v(Spp>vDoD5j4Z$y+)f_!St1n z{`IE_lkt=hTk-5FZ)9a>D6UWGX9^vw)Am3_Vg!`E4EIpOPDb7hbOp16|NHX#D*C3) z=|v#n3a@oox|gH(eVW8sUZQ~t54WNu*f-mzUG?E4QyFv=oLfxTe%~uCNyK6jHDoh+ zvjZtYu=&Y~p07VhrNKew+B!PBCk-I>&qaFyS_tT@hyT=^F+;i`q}Amsvw>|*q0+zGIp8Y9P1+K!GTSO__gw^W!{^x~A{LpQw5 z&0C%y7x$YB(mjWLcS{Vg^qh#@On#+|opn=zo5A&wd(Y9z8n#fh18(mJ=v$4BQ~iHK zf6CH(AK6i;)_%s~+aqrk4XhN`%sK5e-(0Z326{jQ{3sE8Hb@5+O>GP+PhMA^q6>)_ z;Ts}D$wu?&4$adyY~^Ia&d4qcT;5@P1mV60ItsHR<~UMj1gh*^HNn^$bi+JW!!4LQ zYs@UyovO)b=08KOweF3(*&#$mZt7HcJ7Ph*Hkd(C!PgDQ-PCcbNWH|08vVAcSfGFi$A0q*%I zMrtVb3DbRqk?VN+2?ZjZMogt$>HD~i>$QRBn83@05VS+tc6x$IBRo?6C;8}Y)Ss;gmh~`2Yl^CZM>n?4+!~Q^2Hs7cT`Hm-OG{Q8b;i| z0&(Ii28<2hBi7}7o>47xPM)G`)n7CBp73#xvI`~~cOZ0!C^x9AtbLbtTai(2^k(8_ zr)B~4N=3^D;P6A)`|a!N+Vi=_tGZq`j&<VBewy0ITlKCS zG=so#y02ThwY9A)@#BG_-S7_W*$vxxJmd_qQ;B-{bYlfJ>HUI%znB{yGl(!OY!#># z+&^nPMR9+J{1l3$UIBV#;84e+x1tmOw$u3E~P{qV-c`8br0tQ73BY zX~W~POZ5XMZwM0EzW&Opj@^>-lCWAQfAx!abTUtvgNJglQ1X6Z2=P1GQ-vwf%qI+^ zk_(OTP10nxoDd8YJ{Jc3mHS{9F_-SWMBE?H^( zvc1v;>%TUJCbT^eJy}9y0+dc$ee{1gREFCI+4472Qp9tVkWdDXu@Yg=-r3Ci zKsgMiR>Us^S-2jCYQ+Je9u3<}X?L z6Iu)mw1J8fU>tc`;_yQO!pDH{P-AgLcoC90|AcQO*(6jNnc&ND$+fn-k(Y46@`68h z+T=K$T-?H(l)=BPo*FMq?Iz>t==KzTk#1^Xf?rykVf2iH1=l4jqm)`7G!le}Mq z^CLuqhaV6OU;{wTKnj}bp$kkr9m8lI_-Wt_U|y916wHG8GX~#OKzeru6u)GPapMRG z`Lty?bi*gB4fjItBiPMf4iKNW-o*Z3952aJ^x~|EKHIY5K-~znlB(cl4u_-?WUh-pMqNNK;B#F zQmc5@<4$~|t79A$O_8ZFG<>hQLUORt4Dbx-t7^z#bVH`1Tb2iCw}{yk91vQB-K$;; zlzeLrX(f9tJH^ltcq{j zRq5S6z~MhA*{99{pcEZ_ETet=A2*?5}ns(xzcpz-ISwP1?$E;HHB{4V;AHc>Opk= z$zo+e-M{ri2H&W5V=c!KlPEWYko)0YcRKVBAPb@SRH!BOR(>4j0eptGTAFRucJ*LB z1Iq8CLhjy*Tt?TJXIS~dIyyRzv?{JDwm|eM-O1|%nRr;egeFBWMNMRy^pSyf2e6aP zK+%BiC%0cC(6EJ|aO9&@0MC5xr-4b~Cl2L0&{E-hYVGv*bg3Eh4>1!+q@v}FSIV=Z z{mF{U{cJ^>PIgcZ98>y>2I7Sl4|70C5JMHuCe+YYjp7;2fp{<`LDrkX%IexFlb-t4 zVIX<`kJOt@xLMbhyU2U9#C81g7V`vaR4v-bgCd+W^&DN3enm%ti0;E9uL(x|o9dRW zEBckb#PtKWe^#)EII63D84uAZ&sjW7O*dE#C$|N9#(m!9jxR0?t#>vH0#>O=?}XhK z#zkL;p&c+bG&R-_dE+1k#naRibJZ5Y$=0ccaAr7Hm;)4;4YLn8bxd;to8c2ZSzsPy zmI#!;WnkjLSGrmhB-Rdnl1rRq{`&{8?@efL6CUFVvvC}7>aw*x}t41g8O117T-zZqtI?y)QvEyO>p+$li7#m z*o_T4w<`m9Bq5(V^y4wc?QjI7E}`@27j6AeG!Tq+Evq%BPIy$Cq@<*lRop!axOxe% zUR(ew)eDsL*AoGKS$3bN9{MR$$%SKBuI`ZphhEymO7qxabkUu>BlVW?yZc_Nq+vPQ zNz}^N=qQp!AsRzSMLZrYNCb@$rgRwllrH(G1dduu8yCjke^>eAXB%X_&il5?FL;you>s&EODkDoO@#RU%mp9_*n^ zH&S;sQj$fycbsr?xf$IxbnG<2kdnY7#%L^NR<8I@ha{+I(v(>ezIPVu3XZulY!sXp z1bkBqSn&u@RX~n2-Y*@sL@>Ip5J;*JrV6O6Uwl-Ffh|ObO2Tc5)C0S4gW72}*q;*t zGsNaetBR(no2yqaq#>tG((Fars|*BsGrbs0=wgBM=4O}>g@YhYR*3@B%)f)zW*&c9 zfXtaQmh?4qKH^`0dTEPQ$q?-({6XWUC_TV+&n<~5jr8GlD~y@0u^Et7uD0E;98)PI z=Z@Ia)w6#t$OEV3)))-W`mqH>B%D8rC@refxBLKExJ5KzueEARu{;+6gUv)_0F;m( z$bko9fzdfmx{-@+%kZpM$kk?c+-~UEU3M5aoVs=kLBNG<&cxGu_2g@c4bp&U6tt2t zKF{!!ca_~|MXYG+j5)04NSw*}6g4(iI%V&Q3;u!Dg_T1=G>4&V7-A z;o64Tu!9JLsrKPQB<{#@`1JmCiRF%T&LIL`;=B{j)O91C~$^!`;y&MN&@hs{xpJNdo_66xcoxZ`U32(Q^#{iAzPXWV@6LhJB9q zZ<@1oe2OoE)67Gf<4WJag|&)8-;p`LN2_*I9~a^CpfDd>z!6)jA5?)2PH&4sCq{F<#XpKp{fHs=3s8X zu$tDgWh`YK^7qCe*wgc)hGliovDZq_x8)I}gfG&e7?9sr{t^R$ixxx;N$gXoE%<;~ z6Hje)yY5uW@{%mAHLZSf0uwt-B2?f;)`ea!#Vu=9uy#aXdrWh&JS`CkHHMrh@1K=w zCHA7bMT1{8tqDd}kRoD?152HUgVlqOY`dh-7yWJR@jG z<}?V&46d=e%QL(Ws{=zB%O+B5V5$*h7@XziwfL)S!yIlL(u+ghOUHkFD;=3J z457Q@Yej?=I|YNEWJj*4@T%l-(?9p(iPvZ9{Ofipw1b~rq*Tx(n+%=g0!Y`iJDn({V9iL*O{{!C7UvWl#1nRyA&& zZe8H_EnQpmSF;mkQRB8~R}-X?w}gz>qnW|+XfGNr((c()PSNx6nbGYM82H^Nm-XbF zLLKsxp-V5o$ER}~DyJNQL_-RP$S|GT>J?ML4qUKzEB+kKfI<^|b@(5A!${G%??#X& zM(q^M@uY6+h1@M9^a`O^uCg5*WHc$~ zHewh5C1=&H#Rda@0!N~iPh2@@eJKl!FzRWqF%>9OUQq0pjm8-MrHlL99N$zH z)%8!dEN0n*gl4ldim&MrdwV z#6b~SZ@CdunG&(uMrI7g6KV6ItE`9kDa~c?C?ApFR1nWsK1mJ;<-kAKrIX`e-1rj&@oF#Rs2UT*Z{z!@_ClJ|a7=S94T5rI zB+^fXV!6Wu2t=*5W4z?vl!uwM{%Au-P}5h^ClTdNVbCYh-;kfHoJ3MLzov_*U9rG3 zh$+p}gZBrWC)@#9fl=9rk!I+YYkVEW4HIsiUO{TN`%)Ek9G5_ecs zK_#x`TE52vWu2*RM`khl28q6z&`T1() zp0gBOR^9pK<{Zs+K_-+Y9#SeE+Js?2a`Uv73YcfO1h!LAyZs^Bp_gPTEE40~sZ>$~ z5Em58tRdn@1W+#r{A6YgO=L=sVI@i=y7?i+6i>fchlfl&Rm0D|&pr(yvBjmGNzpIS4kFb0A_dj+&phOR8q9U*O+(-%1vF@-Yw$(hxQ3k2xN>&cYE9wqW!%u` zBhWl(Ou#gL+ThExmu@!mQHOCpz?Zj}*}^X~n4{K*lFeCOp$X`NaDI*SeP;HmwnF1h27kw%4$Cv{70SJ zWPI}Ms+|{CgD!40cb;=Hdlvf%jTd|hvq!nbPhS9{2efdADJg?dC#ZJ7vWeA@?63X; zi)?Qvlug#D9@qponsEk?uiU~U#Xi(%US(A1VdRbI6ZZCw6HXwXcl89~Q z0RfeV+IE*8MD*~yWQ}M_n6XL=XMLsC&rJ0s8kU}OGU+}W>@w@P-QVPDUcTKQksv1O z+Twtz*p(1a&nrWJWUY4Os0`|{|IY{MyvPpl8y#*eHerx6?oLAwfHpPGQEp4c82nor z5K8E`r6lF7#X7=?mjY}?9d)$uFOd`;aLS!# z)XPKVjXwT|@5^0-M6=zXS>n_3AU!-UwNpQ7i4`&M8o%0aYYgIMr%i9!71P3u&i+#i zJ9ZAn$!$?@923etjJyfv2pU-Pkj7os_fI-+G-tDX$1}5UU3__R`}KSyhJ609d(!M+ zZ@dYuy-O!**nDDJJrsJeKa2(|!5pAf8kt355K`#=kPPwI0!^L|n}|Jm6HA8K&?2; zuka;5><=fR#5ul)}!C}{{|@LO9yn=~oWLVmA26Yf(?E@&vew8)?dZG*?q%oPBUg10i^5GuQ2T^sEQ6aG(>4DJAR^JNB zyVf|H@b?&{Z2h^$PPSQwH!Z`y9J3x3{Ph;%t_C@b*;S994O<@OB2Y?Dau{_ivc zRt!tpXr7}x9H4>&|M>YDf8{qVw8|JIh9UAy^}I@zi>;m@qQc_A)A5*-(DUW}s*+;d zD%p*^Nc)mVpOUjlj!q8>I8&?tf{LTq|LRsr3h3@=0kRT-oJnv)v*;XFW|(p?9TSON z2n{>(KVfNp{uVNzg-<{(@o?J^PmjD0-4%y zOoU3@0%cQ?oTc+pMT~Bzn%)F9#QJ&KeQwyV0{uCmWmyc2-D6H0n@BgA%DG% zn&tUj7dsan^)a(zM8E~X@bS9Yzpz^I#aGW_!U+`2ViJD!xucab6w$uI+hly?oDvhP zk}4DPr$G|xf&%{Q|Fl^MXnAjK#vRHXQbq7C)k;6e(ohVl){hJOZ=23>^v2CyXV z7ARk!{qI8`zxav?e6w_+x?Rryy{r7ss~-YXKthIOm(>3p`0u;&$1Q-y33}rHdm(`k zz@>nx`{6+o|Njq5C?wY0Q^HX+CW z!mwZnJw3hTio|{b(EkmK{+20h`_-^N9QQ-E;>>_{HB|*b>zjW6hQTqdw#xAnW%>_s4GQh);s3$84OE#BZ_(-0PE* zZ-4gpOD4)wr9IL*tBc-z3_l5#k6RNwc57lHggiC_WVvqBG^strBp!#=H(q5bOpu%= z8N9){Wpz-nktFyrY>7sNx*}Fz*gfQvjJJ2o@yQ7iiS^$(f12@&OkkY$kP|%g=OW@ zNeTvf5=%a_(;)}!6mO0~wLsAoAW#o)=qsx~LHZrQ>ShrmHDIF-vjLc6uz82VKBI3i zopFrd$-*sx3@b<$xc~4ZB|b?2A(Kq7%n-p&taC_@o9BS{R5TfnkM+9{4;U`Rc!y%$ zk2HpHzgI8Mh5l>#_nQ~Gv~4-<>S02x&4=_o5TYjuSZLHL-Cipu5vWQGI%v+s8@_!` z9g%vpnzJXREZ%L!ihb{TD%!nTa9V?ryGIrLYDl5_(PvicF5|r-2#^#wcaB^_Pj4$e z{&_x%`}BE@HJFY%o{UWNiK}C4FGj@fY(k4?xGi~uT<_??xM7L+*<0AV7ehq$)au}l zI(Q&AfA^7`z{R&0^`)^Z^+OlAnDn`O!=3P9y*CdcPiFQp(bt3@AkxT>MSNJ9L{*fV zV0Mjp7k2{}373EDpK$Wn*vmiPzG1B5h!t4WJ@v?zU1UApiq@-8oq8KBnb<&>_ywjE zlBXQGFYr38vNO$asah-QRM#e!IG7!V?1&l`E5G`c+Oe7SPe9BDOqGl^5t#7}tlrwy zrXf1bE|&4K_ScNg#eGtrxa*p!Y7N%!^uLuBRcCA10%HB*r<}qIXphMKsUHu!EG;(f zOUN259|ODRC&c_c8;@q2{13oG20gwTMhR0#5CG}f-JLAj0SQ7GB?$RWK>U;L4P;8ts`jxMji&96F* zFxmlPRu>?B(|R^tLnplgXw7Eq zUIj9_FM+(Gl2VY3h*Ve|{1ZB})Sj??0QsREK$FGG+odMzSX2#G;=c?!8|=2*>J7o; zYN;IkWR}8ZpMGp{?oei1_TJIP7r1m*cqHy$S@&z%DTNe=*Kh;T1F~7S)3p=MLh#3+ z1dIgqk5CFYSqiv+WPeb9ghGh)1$M>2!G#2bH^0+BSck>Rjx0TD(+OVzp52tEgabt7cHgycEA+&cBU2v*35S zz?q@?Ud7N|AK2Pe|FF8HvbMT4g@_{fdT0NBmz&mp&DT+5KqgJlDs17orjEVYwa7>W zcuYU9I{b|DnDF)gqwoYP@sJ(HWR=u-tarYY!34L)>brl8G$JOeCWNDqapeKf&3FW! z7bnvF9uxhWgnUu)9E2^JgtGY|5ZwSspRK+3a&E=y$xPki|(dHN@ClInXhjr zB$0zA5PO56wZ95Xys`~LOl#W`?QxaiIvltNDqBc^q=!Y>@66Bm7MyZS?#2XQ5nsNG z&AhM)U9+WB;_dLB9X%yJEgUs?s>U7YVz}0j`*`=@uT46dQ7$DY+}>V3y8G?B@2Rlw zxpzFTwroY94SDPQt8d(haO}AE?pQE;?h$(Fv0K*$qN$a~fA=c?hM~le6ZOIPEtjm}vpWKWAr^tIkPv|$lqXNHTGc^@{ze70# z-uGRDx82{d{Pnx4puWd1QJ$kOov?xqZvO25Buy&VyCz(F348jV(ahTncq*MXq)`r- z580SKx)8m5yoTiTr~km;J3L@-Zp!|9Vt44QV$5Tyy&Y1AtZZ(-nU*WS0TkpjpI$mY z)!xcB=FpeQze--L<^#^RZr94f!Szqj#YRsDh#JC(Zc!*BirvMloQ|ceTS1Inpy&jS zFx-)0E}tH*+FOM5?eBt@6TvfBZ$+rQ1**UxPMytkK|AhryagzSG5vxn*p z&2!&mr*^E*OErXU#Dv(}_65!lA8dW3*NzbVuDRSTYWC)KINP?cDh?cK&;2G^ls8#11o zGE&y=+wWaJ@16p+HYvl#GqU1Z!(LY`pRaCd$2_0v?Vsxp1@D_CZp{7f%+siT7Kv1q z(zbDk5AnX<%y@^XF3B-9nbOhEx-{6gEZ<@Ebd#>OVQikc;B``plZq&?zWdoeT?@TmA67;A z+U&ne`QJ#Tz@xra$8u(z0c1L*{q~EMyN<1O_HG*bcq6mQh!^^fEBdn5{PH8o`N3+& ze&Y8_VtWaT+yrkYsVQ{>9WN!X>9~AE=GMxbDeu=~gY{un?E(Pxdh@YXGWw(1Jf{Qt z9aXK*EuRE{T$bJLJgBlA9e3_s|DhX4@xFPXEgv@hu2bD~l4=24~yU@%iVZk)fJo#)qNc-`e^i67 zdHQAW@Jq46J}2>4f3V<9)?tI+aYOUO2Q+vL$2J1wv_$uke*TzV-r@+A8(!{k36}0g zGva9f+7s8|69F0Jy|Ud*xT)Nmo5LQn8_d5$Is<4o?u$wf+vb=UJ56j~2-hzO+cbTS zb=H?QJ&a-;pO;tto~nqStBM##3ESVw2sC*dTu@38=BGSoAu-tvzH!2JyzDZcgDY!N zecIm&G#5D5YJ=bixLRQShhK{9ge#9<^|p2eYLKt+yDhoq#tpFME29=G`BwnQOH^&! z6zY6gd~Y@UZ+k_r2K-+JBHoKN+KRCW22YxkZhuMcEVJ`HZdXw}^PKRm z?c1zQ_KDxU>VAITfA&^+o)LPTN&jl+7^cGxUu%3?iLlS?IxR!(x_rX*DGrAICp;Q4 zN_;HbBv!e8JkIbGV0p7TPkebb9wZgW<8fiARKD~J{RdGPJTH}LXaT2RFFDcFRrjX| zp%^FC&$RuR|Gd|((_cC4hRMx7OS>O$QR^pT(k@`4oc8QT8T+&>ua+}rf511DmX085 zLqd|fMR@#gt@QQ_0qx&*YP>YaC?ckcd8HpyZsFM5oF=T2Z9?JKDI5x{GtTgRr2G=I5kz(;IUx7?Z);#PKo zUr;~!6beXFAcoeRk(Zop`zWpcvpJ&uRb+YPzqS3#O+l7zogl#NAd$^yU|WKgG3B9s z@k%4qa63_yBT;4go+Hr4gGNS+l~=djFK?1FZzrWyZs^QI&?K||=TlM!A;HVGSidT@3aj=w)a*{n5c6woDQUz=#ldpN>+*gz%gr@3 zj_zdRCHT`Z0Co4<x7C9EM#YA)REHkxiRH+j7);xCcZq-{8HMj zU7vdDet$TApLz`orxDA<485bil^5I%(;&HBh_d$fXOdf@LW+7Jdf9-TFblMLi4XM8 zZ2vP(L%bus4zpz?!j{-QH}ST1iNjYdWe0{!Q=&Svb*+u~2e(3)c1D4(I;OfAB+;_n z{T$Bskk6lI%89%kc$G*tZ!#41b`a$zG{Jq@((&99QBm5@U|EOEQl0>9%jpMSkqF7V zVz2{jR@X`i8u6S+t^uwXj*n>sYU$*(XdZ)28qM zu;HuA6d25Wctjx;k$<;|VtwyBwW(=n-5qO_=1n=#mebJr_Bg0#lg zkNM8)RQrehnc-|poPrP5UWwe#pB9gh3N>iQ#t^abmt&SvDVbXh>nhcZy+N)J@#s_P ziPI9#T8~Uj^a*!0V{3{no63@+=htLwghDRaq>l=N)IisWA6ZQ3B(Erw+k2gOSN zEWE8(tX7Nn&k%2v)HqcSR>^PT(2p0@VI3puE}K~-ur|sJ(1p7Rl4jdET#&v<;%_Xj zx0{6 zJ$$1?{9LfsTqjY1Y(+Y`lR)k(WuWS2TP_e}@KBy+tMUT&l zF&rA{hbU`gg_!1Ne}eW&lOwRbL}4z&3_(cCTdQn#MkgV_EF$$s>lb?Bt8(3X_Q%An zz*fzF!dG$`N1eC!yPd;a z@aae<4YGDIUG`sPn@xY^ioE_Lu$^^kFGl;N3pQJ~D2|cm{2r9rT|?)RTlER2XU?BNLRWnU5>*F2~}dRq{S zEp;lZF#NnC6_p>eStu0mAk}l(sUHlE_jSSS6yse6?L! zp7`TG)zF?JuK4RaF%G-%7CNjLeiSRN@fAB2sW~>DL^H{Q10MGt(KqC;{;xZVX+Mbx z5R|lrmMRbhP8%5}q^WPw`whanv)-57YSUn7^p}DvQf+%@h&;6I2d-PTAP4RvW1-{8 z*`3v3cQFg(VNnPQy-rIlRqZT)Rv>o`_Mm)zqnWE08H&e|8x&f~+9w*s$@tKpa`~Q` zmqth0EU$etXI}l<4%UZA!||^P;68cK4s$;&x9Rl&5JYrup>#Kp!)MizW+FEzCC38Lde< zxgKN{{B%&SaWHIvzqGI~(w&Tb<^A=jNWxh#K4D6e0GO_L0vw0 zVYvD=c_V?`*`F`WKhnG%hs2N2Ebs4eL@~00APYxj-8-g_;I}&Cbl}!{&M>#J(yn0E zY;Qo!{St3=VcTrU=f~ocX=Ruy_vNq=Z+010E;%`RFZqFQJ=u^?kQG&^YEpKUG)qgz z5?=`a_U)SSR7H(eeWF5BC3ncVXU z-s93c{5V+RrsPq4V&?9s_-o-u=Q(QHJ(X}?Mm&d$%zAU)a049ALYey46RWMI9m-FB zg5`NYB=J0e6=7y?sXaCPxkc~A_^7NllD@Cf@%7S|F3dn^L0oV_4kx0=zZf&Hy~1{3 zjBBMkF_~mv<%UrECv12Z)=)A9D)$cR-5m>lcURX zWja<0hNn&}Z_n!&LWx(aKW2FOB6RupF*b}j$=bX8a?qFdlXKg@sR^Iw9Wm^>nEKer zjzs>|jK>GI6g#b~r=-#!RfKYZe|-l+{ZrX}=z2?3cf|DKg$B0T4vz!YKA30J==ttXS7aZWUVRZy=b_?=^$;$V(qcNVn(AT{*Sg;q-NWOc1hpqZ zrFIj46%xm_!kPpVtj`&E_CKn`XLDEVH!CeJqN=7#MQSxR)MM56OHmk1)d$q?OGOz} z)niLx8rpAdt&R_{U+MTeus2I7)pOOehrvrdw4$v7PQ9_?A1r8LM}D+EWc~}KgD$!( z{r#BW2`PuGT2ZxWpkc4B)l|)3W~Lh_t%LU9qbV?ws2iez{SqDJ^n?_^h@eAQk2k=d zYi49pH!^=Lal1!IAAG8EbLIMw43*-hzNmh*_InGm0HqX}+}6jmsLNlSh>kUb2pgxJ z-eQ}py%NrY4b$MlJ$I(Hu-5-Fvg4q-Fr&UCq|!70HR!U*crLc0J@-}f(}VwG(C-Fi zTeeN)u=poWdPRM|qr+Qo5bCKol?_%+^bEBBxw^JhD z%+ykbD`WWS@+EP;7@dIn=1V0AhPwKap>J2`f1cv>+bt|{=CSQ>2n$cYTpe74eFX0c zRF!-?T^0wJQp!Ectm}V?ulAVKCIq)$Ep!vBOWKbgQtwFeRXJ5_ARvX=<6?;IvyvneC@-oqluM%=7lgiFN+V({EC5aTXi6o;3Tn zc96EiR}`O4)7_>LZ@%1$@oht--?=@!KfBM=y){_7YJ)so6JVG%TQ$ipCq4&rF^rnB zy@V5#0?}j*Gn8m|!_ke7lQD~V=N7Wy+h1P)O82S}v>7!UogTAbx)riZqq)GtQqbI6 zKP`R!4N7?C{M<9ZzUqzSq0T2)V-^W1`o&xb)Zt&Ps(`2XMml2 z&4YPN+FmEw)`&b}CCX!@y0a^I)leSM{4v!Ns zUqPc|*WFIFBuG<1r;bEu>w%_R*Q#6l4g#7k9;5_pMBT7M-@4E-->rTZ2Z854l>@(A zj{fUgxV9bgRn}HS)|af97coAUE#`REPW)>mKa9_BlF2_dBaE`XP#k)IFtAzexg4Kh z90q(urF6RoDsvr?|45~n{gR+jPwn03on8!cD@ET_br7Ro_#$|}wedVO!Tk2cp%&L+ z0=A5*WvnKpmzt6HrCPY4!d@I}&hIXj0%p?veV8L2Z#3zzjztvwq0S( z#E)zeE3x)$NaIQ^-Awxsw2KOZu1J-8oyy-UmA-?s^2PpMhW<6Sz+^Gj4&PqIkK2#F z^dtU50jmyuES*lkK>_)xcXuNedP1EA*{}Go>~#Ja)s`S!bGRKDJ&!UY z3`x7=r>%CYsQWA-N($=dU}o&{$2tAhPfwWGC$VYjJJi$NQZ1*_w2i-^RWbscsUE1F zjMDK(KrStFl%CAj@a+%1dH+vY=N-;g__uMYr8Qeht46d|6;(4frM0(O4MJ5>Ble8Z zqFQQ?P$N~VYQ%`WsUT*p*jsHPR${*C@Av-ozURMlt}~wVoO4~zInVvMzqeCX*SYG< zLdPUumrildG}BaDDDTm+qrKop?)DeD9rHzAI_pAkyFqpQt}j5kRAv!g3bwz&E0!of znCWeWzt3Kej<4YAGrF&4sU5)kv*Y5{7FN4^K-;%6cd;Mi)4ZJ7bOhtd-jg~s;IVG# zjCC6pJfMeHtUhrH|6i@ukZ9ilGMNJG}Z{$+F$j z;UtnO(^+!5Jq7Q#^<;6nbb8&1Y71#pH3|WCzVEYP$rN^K1dQwuMTYa)rGt{fq1jJ0 z>5Xb0*DvF>p_4F8;P!&|cjgty%A)~p2i+W*sA$a*5lpUDFh~ zTb4cXz0i{QX*tMRcymU$;eR$ALEz_q1x>Bs4wUbTBF%!>S;HF>jw zE{WE&P94l0GX;IkiOc-MX>s)O!yXFxfMt~H$@Id-Ucl8WfZ&eM+L?4MZ8HXvV?UZ19vEl5N zFgC${5qSdFg^L($<0E`4_36nM7Q`11wvO4@vai#}vP)~1_P!0PoEMf3FgGmU_*5@2 zw>4<0`3~KMa}M8oY5?)&zteSbv$xh(*4%m{xVdDF#Yg`ThS&5DSoM05aRTB;oX30y zETs_Iwd->_xr4s!O`eQEy)3U@4^s!JvYu*A__8h6%tpYNnwxQZ=>e@KhneJx%oLp3 zCz_@x3cHrY?3%Xu_ABifeH>lMk^ibYv8RuF25%}9#VN<=mT-!*&M)Pp#I$|aYh{jQ z?Ro#i!FE-Cv5rR`6SgJ@7^elqcxv^17G;uK#BLVNkMLXWM_#7d#TY;5_p}I&Mcv;G zy4A+Ezi{6^jJcQ%qkN~2e>O>vIIB~m{nYE0ph-*oyLPVo9kUb(a}fpr_fzFlPRLZ7 z9f0-rPDeae_4TlU9)LL0b)w42_kB*n)BLuYWwzWW@%C(m{k3s@HM8hQ~g^B8?x;aBeKKDo@V13I%u92#8=2x~B8I{qQ;!2On7A?+oY+wir( zu4@?8m=CUizem|itY=8j)R2x@m*dj6bu0{v9qtvEXJXAu51*QhyDRi;*i)gs+Wm%I z+Q5Otd6HddOZZi~q!?{6;)qQzu#Po=9V0m=`D+i+CV56 zt{d<=96CT->A;^%vQA%2AHF;?BjeCb{Aq&!r_On&%I5ATyR2T0wgkSfzdS0PCo|?8 z1xR~|X#{V!Vp@r1+NA=Gr#JzGock}Q82zA*Y${3+n!5%eK}Tgarg%K z0SJj}`KLtYCZXaa;O(9_wk88jySyxU{+OSv7e^HW13LUTYRJNGhfnl=p6Im9 zNYH|B{_X&yt+!9Ruco9nNJ|H8aK{ntQ#+RF#pA1!SA0paw^^I3G z!SqjQZu10(v}{mek7z2mCZG>bxqW6N=`|)~ zhNRp&FXid>)QbIrx@6{s=l}5<9^DzMhXm)m*QLSSdJm4w7UAVk?KWP&4^EpjMV9X+Mh5#|>#i|}mXd;h~ zDI(2A^tNPtFwq#y@_p1xIVtPTWJA+x~bJ3&^Lf4%M&=Pis{^_ybV52xOc)BC{oJyoMt7^o1Jibt+-LTI)52E zDit+Q-{G;pGDk?r>l+Z)uYslnIRZR^I`H!hA*rDF=9rj`%4?k) zhyfz}+OTaCXlcu)H)kgsc@+_R-cg)6Hd_f7y~jxw?}6LU{NpvFzr4bk+R*?i zc-lbseR&cOR`vF}oJubq6IN#{V09=O^)ehfddE+Jp-bMH{2 zD4-wrRLs1|DXMtq@O7tHOq=XipdP7`69=$6^|6=Y;eg!UObo&(K;fTrG>hg?I%(it znd`G6^I?U)ZOHw<~qXO;eu+x+5j3XTJk zRHQYk|4#eku68N*$8DM7$x7vFZ807OO3FF?pIbTk2ckho%>59roRyy-S}X5$(Mw{& z@W8{_6>}-R!F7Ya9@V4+Y&bpVTys-uU4D?RrOmH6C(JgmftzNlV?e{MaE$nxQH@-uIkD z>ai>#51@ZHNgYrkN(zeSP|$ymtu3YujH^M?>KLm~$jES9EsT};1KH~z}IE`9yAMNc_g0@LE5CK~>7y7h>KQGl>PaatqU*rpKKh+|O=Q#dY< z-8t^H-Z4vB!MaIKMP=0LUa5ocy;~Y=8^KevKGAuY=ezuJcxxJeC+{ot0UaMFiVZ4O z9nV<74LGb)zUyckCv;8;p*b*^z7*wUqQw&g`?`jq=Ag=*&hOqAg_F@rL>Bo(CV;Y z9kxaq5y;I@-F=_EwFM9BT{xlG&O5lVmT5bSZNhnb?5caC4+QZ^$&W8(o;ikx^L^Y zR@PP5?lQJSy z!$R(y@elE4E`)-AZg~vnLIFrpqLnlh;eS2d~D-9(7(9T4fCH&Ao0~ zU`lB~&X9V}_u?&DE?kkFw1WG*hEAKl{a+xsSE~98DZcQ#)(pot9n$~NHv4>RA8;Oy zMIR+?#nWzxJWMtH^RweyK*hr?(nRW23OcfNF5Scw_w?@}$Skwe5Z6Fq%7kZ3FA{63 zMWysx56y@kj=AeJ*ohyg-&C6=Gcg6{q^qqAjcwCfZ%)(Pzb$N){WY#Vx`*@rn`Y-y zEPwj#_zewvN$GHmVwP&No^O(T%f#3x5m^agtKd)hv+YWs0nM3sTydCP@@ zjL4nIv4ZGHff(Q1XgCq2tdV_^7|sw>`HtLRP!@q{l2X(eF*z|PGX@kVAe%VOhL0-| z!F@+8;+M*u3v)HJI^ybQ)uT+a@4%UZ+S>F@x|Sd3H;5#KlwpaQw6j;~obDQj7LLdBN|#D+ydtwc6Z3p=;YTih z`*^Ow9dmt1{?V=>Koa*e*{wy+ZV)VgeBgLPVvJ<$l1n_UD)^@V65(zbIiP@K{`2O- z4%I_vnnuHtZw0YQa3zP+Xr3!YZAWIZt-3LnO8S^$vT$wM>O@1W&?oXg*h&vwzSm@;ZlOo@1Lo>)(bJD=y zmP_O5;0uO_Er%vvzcqKX23DdlSp<7#L(S~>b*+$EqFH3-RV1Wkg9g?}RQ0N*SpY*i z3JF3p6N{MXGQV`uDt{Z_=9HrJmy6uOoJf$G>NEKG zTO9=z_m8P4(L``W9n@A*WpcoKzNQ;)m$gjyj`dj(iHCt1C&)!(BlA6|&QIwb@vU4o zVgOwoueAQM+Ud#4?55tm&H%+K12SB^57M2J(rIPIIOq-7q!#2imXmymxMmH-{ZOSH zLX8IxYv{+eKVH|b3H41!$=}iA&-+4eM-u0s&b#Es(z91&q)sv5m!&=&D*TSoU zdvvofbX3<2eSMBWgRJbxy-X+pV+Khzs?_P`2C8pkgU4+L-zf{ou4e&>H&%S z6pCoIlJoqEXIfRWLuBu8Nyd}ZS*@+F#&b7D{TYzeF9VjI^iIAS&&<+$j3m!=ZY4-C zm@M&C;q{DT*`LwWS5fZDCi8ulou2ak>H}Eld#Ysl$~0(}V#hE0m8%Bzy%>2*Lg~(_)DbU=_I5Bb#^gDRaYN4$NB$TK^K9qo_(S z((%QIFr8PZ__PT5By3DvxjV%g;!_&)ZVfX0Wgh5`#iq!b#UGoFO~3#mW=_<(t5VKoVAPQDaEW zx$ITJYrjoJ<7dWfRe>1F__k>cH09X$H@BOPN?c|mKMXwY(OA2sZ9*V*3t4T~n9|Tw z^%kOoiM7Vnz9#`Xph_&qcHLE7WvN{DZKdR2kLuME9I zquLtJ??^YEtI9mdXsauJ2c+Kp1GCpJx-kD^f0Vu zBbOUc6+Og&|4>nC-=Zap)B7X~+@a)*ZPyksK3FNOru832F);Lh;mw*D7bLs~Cz6I} zn&DSkpzb3)?S_C84%uIVoz)NGgb{%m&-o=aLkvyvmkBcZ;|V6eY3U-4R2z#Qt$4Z2IUkrj4-oa9G1h@6Ss^@st|!aQ;)G(JPb{<&GAa@|_(bWNWNxFe&3 zgoj`8oRi(sy$BDl*>ots`85yq0jpF0JKjj6!M}0iBk-s;oSh)P8z*&P{>}838JU?I z8d9{}6Mt!QE^w6}jU%?OKhOLz3oDbV44pt}64In`X;pCgEn{vL{`qkTB*skbJ=Nm+ z`tsF<&{Y7aAiAR}b85Uy|IY}7Y^vQHOXP2T>$k1~F@JAK5tgp74wp~6TnYF*ty29b z(SP24a3cP3Y-G|%MfGvdANg~;eow96cU(|3;hL`b{y=Z^9lRV+iVDsv^W4!Knl7M( zFR|{j@8LaaV-NDL?=$>LhU{f`wS5!1$!mgl`&d%|PJ9>P7I(J`v6>L&bIyt9GUA<; z)a`<+%R3+>D^L#cbegQ)QpPx+=P&^E!uCcQywm0Tfa|%2-+ECEm*cfikrhehq0@|sY+Tzz@S@47CsAo|^_ZCxKIy#a7znAYw3$vYe0@B(oXpR{(0 zOLZvE&_Qpy-fq)PJ<4~>2i#IsQ1P&XqV_dK+~}tzg4%U7Zp<2%L>JY#?3q!cw%7#> zCu_9ROMErIET|TQ&eq90M8c_{&C2LjpHR`VXFA7F^O=AEno*9aW~~doU~w~VC=F2- zrZ=Z;o8$2q6uRzee{8O?`Uy67ZN+pqTFeb!7lzb?IxUQ?)cScu8b13RoGHC;^I=Vu zXUG*UfBRboB($rX1+0j_`|TbKeuu;EEvQa2%_+ECbimhiWTD^{Xe@9~Q1)mEVUoC~ z^66?Ci3Cy8eTHo^86V8WWSsqu`{xUcgp65Zri*qOlqx@7ao!i zTs#AWVg*Hvk802{pV5$|wrup`_6^_n*9Ap4dpC8nBrdibQ% zuJ~~2=9R2$T$aFxM*HVX-FcE1;<5)?mh@|rnvA7Erw_YX=9$yGp>wZ~uSM0+E+$ee z%K|2Zo7~g__8GMa2kkU#XOcZEvE6WFUat4t*8HVp2x4)^Qr;A!Sb96a%P6zXq>_7k z$3rq}aw2hF?E4THA%13K#!|mQOs%)cu^SF;&QOYRO!w z@zypk1BX1Vtgo7CCYa1LnkW`O6=1@d#N1J;l=g-7L{2{$Mq?Gt^h|j_b{JUMI<9sG zhm0v^@dhn_84+(e{MJS(v*m9>$E1uQfIS#h><}+d>4^0w>a${K!f>}i~vB6nG*>6c_ zPq~y4xA#9&uN$J@RDCm&l3xa1#)TSO`bWjY8g72Z*iT5mNi*b?f5aDiKH48UrpUfj z#h(?g%0!`v8dVP*9YKTqaX)DAv$srCK)TIebwB;#vMc^&+2NfDYo@8=?I^!vP#W3}F|02h?n zt2|(1TyZt(>5+*<#p6Ds+Unr}0W#lkyA^f07jeL899Elh4#$ z7wrk>=Y|T3-YbYc0r$6RuNRLVp%3AR19-#Jw*Wx`iL5QDh~**;2U*bMC65fg^dd%R zOU5sbRsmU3{@DGOAof`htaxlH4*D~(nlqwYayipYcd|-uF1z6CR5ai|T+ucjIOnK| zQ`Z?~5ZuX|?WsO30-wsWR&WW{zg20te?mLzOFjx0h`Zx1j8m=abQE&d*9k z>)Mv1o;KUpU+lQddl|l~MLRi(dN0X5D1Kc{o&0jv@y^&$^-CuwD_h{FB2JFr6~o$! zrA)~kCoO$d86L774zF!0LcL z^INs8!r3tnU@dgYnRS-4NYvtT!SZ_%#=|Zh7Q-OaNmvai6@ znLQGP-w#qg80vR%YY30sfJye+)lRo7Ek~j(Eb9YSJ;Yw0ebEgxcNRS$tD05&^5c~| z|KqLAS4<=Q#+JjZB~EP~7bop(2c~;I_^))R<)*vEx&hEq^JyT3mOv}zM@0z3`>!XO zvNNlq59|fq4l*QtKg6qGt2>XJ)kaad3IV`Y618m{W zEN`|6$_84hHdBI!HO^CE0Z=oPx{v@ScO#?|jK0Z>e||>-RiKp(gLd!^exZt9;DY8- z@QT&^k)*DPgh@vu^Qbac%0XE@oEwXf&|;{WfFim!7vRrlvP8#!l1*)ppL-|c+=2n{ z;KKa1tvcpY0q1PFRz$^wxsniRgWR6cZtjQ5=q?+xgu(j~bG&onN7R7hC(78wAhUd| zVxweBE4>NcG(XoUW}n>>y&^BxFf+!kx&=`e9XFgvJ-H8@E9m2-Ir;Egb9oMuO9dfN z14$Iha=B$|v*l8pr<=s^K2Z!Ve}sTi6;V}5FB6Q;h!!;;eiO}09>~Tl@7x8w%FIGT zzO4GPUlQNq7tpXd93e?TYgqP-Ems(|#W=Y1D_!IxdsehKjSz#x5+w_~V{IX3P*ZyO z;4=(aS22oYuf4U-eoh^e(I|VxTX*r!?*^f3{Z}%@T>)x=f1N4V~REcZIeD8XToo!?Lzx4(WMZluaYE78efQ9?Y z!C6}K_3El=Yf2`X6dSmlHKLf|+UwowF!;QYrDn}PbD~`$87jsr1MlPx?cbtGBbzRM z>3BtGcld*Z4@Ud8k#RW{cUn`X+=L|H@jvD%`6>P((xyZ+EAiNW7lgsdN~~Ub9HZR> zRrA0U{rrC#;#anaPO65N;8N`1@#YCy;B2*UeeKD7k}zTI8&)W6E7{`j?7D@e8Fi+4 zNAIdfyU?JxZJmg{MHUp{QVZ8T*EVyX)diWe&p03YzPFI#BuH#FMDPLeT7TmQk-H7B zT@OHIF$fiE+mqtki$5YndRPIoa_%<=)OFbf@47}z1LF0A^R20mO6cYvei;@=m7M&) zcFVAS4|6Ms6dLb_ao#N!cK?q-_kZu{zdP5Svr4v8P~Cbk`Ty@)b*;79qNRj#T;g|5 zB3StU^a=5ySY254KcV Lc~Pol_3?iILA3P^ literal 0 HcmV?d00001 From 6d0154ebc440bd31d58874b441b01e5363a9484a Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Mon, 27 May 2019 17:20:42 +0200 Subject: [PATCH 158/549] Update endstops.md --- docs/endstops.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/endstops.md b/docs/endstops.md index cdf530d5..88009b27 100644 --- a/docs/endstops.md +++ b/docs/endstops.md @@ -83,7 +83,7 @@ After testing, don't forget to save and reboot: --- -## Homing Procedure Configuration +## Homing Configuration There is one additional configuration parameter specifically for the homing process: Name | Type | Default @@ -93,7 +93,7 @@ homing_speed | float | 2000.0f `homing_speed` is the axis travel speed during homing, in counts/second. -## Performing the Homing Sequence +### Performing the Homing Sequence Homing is possible once the ODrive has closed-loop control over the axis. To trigger homing, we must first be in `AXIS_STATE_CLOSED_LOOP_CONTROL`, then call `..controller.home_axis()` This starts the homing sequence, which works as follows: 1. The axis moves towards the `min_endstop` at `homing_speed` @@ -112,4 +112,4 @@ It is possible to configure the odrive to enter homing immediately after startup In addition to phyiscal switches there are other options for wiring up your endstops - you will have to work out the details of connecting your device but here are some suggested approaches: -![endstop figure](https://github.com/owhite/ODrive/blob/master/docs/endstop_figure.png) \ No newline at end of file +![endstop figure](https://github.com/owhite/ODrive/blob/master/docs/endstop_figure.png) From 3df4d483cd28269d8e1dc757a02f04c841dd62d3 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Mon, 27 May 2019 17:54:53 +0200 Subject: [PATCH 159/549] Update endstops.md --- docs/endstops.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/endstops.md b/docs/endstops.md index 88009b27..ca63bd19 100644 --- a/docs/endstops.md +++ b/docs/endstops.md @@ -112,4 +112,4 @@ It is possible to configure the odrive to enter homing immediately after startup In addition to phyiscal switches there are other options for wiring up your endstops - you will have to work out the details of connecting your device but here are some suggested approaches: -![endstop figure](https://github.com/owhite/ODrive/blob/master/docs/endstop_figure.png) +![endstop figure](endstop_figure.png) From 3097de703fe960ec48e25f71b6a33459ef096ace Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Tue, 28 May 2019 21:50:17 +0200 Subject: [PATCH 160/549] can_getSignal should always return a float --- Firmware/communication/can_simple.cpp | 6 +++--- Firmware/communication/interface_can.hpp | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 8efa2e13..d4d68aa2 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -291,9 +291,9 @@ void CANSimple::set_input_current_callback(Axis* axis, can_Message_t& msg) { axis->controller_.input_current_ = can_getSignal(msg, 0, 32, true, 0.01f, 0); } -void CANSimple::set_controller_modes_callback(Axis* axis, can_Message_t& msg){ - axis->controller_.config_.control_mode = can_getSignal(msg, 0, 32, true, 1, 0); - axis->controller_.config_.input_mode = can_getSignal(msg, 32, 32, true, 1, 0); +void CANSimple::set_controller_modes_callback(Axis* axis, can_Message_t& msg) { + axis->controller_.config_.control_mode = static_cast(can_getSignal(msg, 0, 32, true, 1, 0)); + axis->controller_.config_.input_mode = static_cast(can_getSignal(msg, 32, 32, true, 1, 0)); } void CANSimple::set_vel_limit_callback(Axis* axis, can_Message_t& msg) { diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 28e3987b..7b8ad661 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -88,9 +88,9 @@ class ODriveCAN { #include template -T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { +float can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { uint64_t tempVal = 0; - uint64_t mask = (1ULL << length) - 1; + uint64_t mask = (1ULL << length) - 1; if (isIntel) { std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); @@ -103,12 +103,12 @@ T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, T retVal; std::memcpy(&retVal, &tempVal, sizeof(T)); - return static_cast((retVal * factor) + offset); + return (retVal * factor) + offset; } template void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { - T scaledVal = (val - offset) / factor; + T scaledVal = (val - offset) / factor; uint64_t valAsBits = 0; std::memcpy(&valAsBits, &scaledVal, sizeof(scaledVal)); @@ -136,7 +136,7 @@ void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, con } template -T can_getSignal(can_Message_t msg, const can_Signal_t& signal) { +float can_getSignal(can_Message_t msg, const can_Signal_t& signal) { return can_getSignal(msg, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); } From 4bd3326261db936c233f26af867e5af87976465f Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Tue, 28 May 2019 21:51:04 +0200 Subject: [PATCH 161/549] Add tests for ints with factors and offsets --- Firmware/Tests/test_runner.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index 86d3d261..b7f7f7c1 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -41,7 +41,7 @@ enum InputMode_t { // Fetch a specific signal from the message template -T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { +float can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { uint64_t tempVal = 0; uint64_t mask = (1ULL << length) - 1; @@ -56,7 +56,7 @@ T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, T retVal; std::memcpy(&retVal, &tempVal, sizeof(T)); - return static_cast((retVal * factor) + offset); + return (retVal * factor) + offset; } template @@ -89,7 +89,7 @@ void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, con } template -T can_getSignal(can_Message_t msg, const can_Signal_t& signal) { +float can_getSignal(can_Message_t msg, const can_Signal_t& signal) { return can_getSignal(msg, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); } @@ -134,6 +134,14 @@ TEST_SUITE("CAN Functions") { std::memcpy(rxmsg.buf, &myFloat, sizeof(myFloat)); auto floatVal = can_getSignal(rxmsg, 0, 32, true, 1, 0); CHECK(floatVal == 1234.6789f); + + can_Message_t msg; + msg.id = 0x00E; + msg.buf[0] = 0x96; + msg.buf[1] = 0x00; + msg.buf[2] = 0x00; + msg.buf[3] = 0x00; + CHECK(can_getSignal(msg, 0, 32, true, 0.01f, 0.0f) == 1.50f); } TEST_CASE("setSignal") { @@ -164,7 +172,7 @@ TEST_SUITE("CAN Functions") { can_Message_t rxmsg; rxmsg.buf[0] = INPUT_MODE_MIX_CHANNELS; rxmsg.buf[1] = INPUT_MODE_PASSTHROUGH; - CHECK(can_getSignal(rxmsg, 0, 8, true, 1, 0) == INPUT_MODE_MIX_CHANNELS); - CHECK(can_getSignal(rxmsg, 8, 8, true, 1, 0) == INPUT_MODE_PASSTHROUGH); + CHECK(static_cast(can_getSignal(rxmsg, 0, 8, true, 1, 0)) == INPUT_MODE_MIX_CHANNELS); + CHECK(static_cast(can_getSignal(rxmsg, 8, 8, true, 1, 0)) == INPUT_MODE_PASSTHROUGH); } } \ No newline at end of file From 7d92f5b8622055b24b849594ddaf4e7c2229e6c4 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Tue, 28 May 2019 23:04:06 +0200 Subject: [PATCH 162/549] Create new functions without float or offset --- Firmware/Tests/test_runner.cpp | 25 +++++++++++++++++------- Firmware/communication/can_simple.cpp | 18 ++++++++--------- Firmware/communication/interface_can.hpp | 8 +++++++- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index b7f7f7c1..fa3995da 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -41,7 +41,7 @@ enum InputMode_t { // Fetch a specific signal from the message template -float can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { +T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel) { uint64_t tempVal = 0; uint64_t mask = (1ULL << length) - 1; @@ -56,16 +56,21 @@ float can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t len T retVal; std::memcpy(&retVal, &tempVal, sizeof(T)); + return retVal; +} + +template +float can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { + T retVal = can_getSignal(msg, startBit, length, isIntel); return (retVal * factor) + offset; } -template -void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { - T scaledVal = (val - offset) / factor; - uint64_t valAsBits = 0; - std::memcpy(&valAsBits, &scaledVal, sizeof(scaledVal)); - +template +void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, const uint8_t length, const bool isIntel){ uint64_t mask = (1ULL << length) - 1; + uint64_t valAsBits = 0; + std::memcpy(&valAsBits, &val, sizeof(T)); + if (isIntel) { uint64_t data = 0; @@ -88,6 +93,12 @@ void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, con } } +template +void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { + T scaledVal = (val - offset) / factor; + can_setSignal(msg, scaledVal, startBit, length, isIntel); +} + template float can_getSignal(can_Message_t msg, const can_Signal_t& signal) { return can_getSignal(msg, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index d4d68aa2..a0ecf937 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -185,7 +185,7 @@ void CANSimple::set_axis_nodeid_callback(Axis* axis, can_Message_t& msg) { } void CANSimple::set_axis_requested_state_callback(Axis* axis, can_Message_t& msg) { - axis->requested_state_ = static_cast(can_getSignal(msg, 0, 16, true, 1, 0)); + axis->requested_state_ = static_cast(can_getSignal(msg, 0, 16, true)); } void CANSimple::set_axis_startup_config_callback(Axis* axis, can_Message_t& msg) { // Not Implemented @@ -276,7 +276,7 @@ void CANSimple::get_encoder_count_callback(Axis* axis, can_Message_t& msg) { } void CANSimple::set_input_pos_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.input_pos_ = can_getSignal(msg, 0, 32, true, 1, 0); + axis->controller_.input_pos_ = can_getSignal(msg, 0, 32, true); axis->controller_.input_vel_ = can_getSignal(msg, 32, 16, true, 0.1f, 0); axis->controller_.input_current_ = can_getSignal(msg, 48, 16, true, 0.01f, 0); axis->controller_.input_pos_updated(); @@ -292,12 +292,12 @@ void CANSimple::set_input_current_callback(Axis* axis, can_Message_t& msg) { } void CANSimple::set_controller_modes_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.config_.control_mode = static_cast(can_getSignal(msg, 0, 32, true, 1, 0)); - axis->controller_.config_.input_mode = static_cast(can_getSignal(msg, 32, 32, true, 1, 0)); + axis->controller_.config_.control_mode = static_cast(can_getSignal(msg, 0, 32, true)); + axis->controller_.config_.input_mode = static_cast(can_getSignal(msg, 32, 32, true)); } void CANSimple::set_vel_limit_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.config_.vel_limit = can_getSignal(msg, 0, 32, true, 1, 0); + axis->controller_.config_.vel_limit = can_getSignal(msg, 0, 32, true); } void CANSimple::start_anticogging_callback(Axis* axis, can_Message_t& msg) { @@ -305,16 +305,16 @@ void CANSimple::start_anticogging_callback(Axis* axis, can_Message_t& msg) { } void CANSimple::set_traj_vel_limit_callback(Axis* axis, can_Message_t& msg) { - axis->trap_.config_.vel_limit = can_getSignal(msg, 0, 32, true, 1, 0); + axis->trap_.config_.vel_limit = can_getSignal(msg, 0, 32, true); } void CANSimple::set_traj_accel_limits_callback(Axis* axis, can_Message_t& msg) { - axis->trap_.config_.accel_limit = can_getSignal(msg, 0, 32, true, 1, 0); - axis->trap_.config_.decel_limit = can_getSignal(msg, 32, 32, true, 1, 0); + axis->trap_.config_.accel_limit = can_getSignal(msg, 0, 32, true); + axis->trap_.config_.decel_limit = can_getSignal(msg, 32, 32, true); } void CANSimple::set_traj_A_per_css_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.config_.inertia = can_getSignal(msg, 0, 32, true, 1, 0); + axis->controller_.config_.inertia = can_getSignal(msg, 0, 32, true); } void CANSimple::get_iq_callback(Axis* axis, can_Message_t& msg) { diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 7b8ad661..dcd06232 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -88,7 +88,7 @@ class ODriveCAN { #include template -float can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { +T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel) { uint64_t tempVal = 0; uint64_t mask = (1ULL << length) - 1; @@ -103,6 +103,12 @@ float can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t len T retVal; std::memcpy(&retVal, &tempVal, sizeof(T)); + return retVal; +} + +template +float can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { + T retVal = can_getSignal(msg, startBit, length, isIntel); return (retVal * factor) + offset; } From 0f7337743c73511bc31fb9048aabd026ad5eb641 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 1 Jun 2019 14:08:16 +0200 Subject: [PATCH 163/549] Add input_pos_updated var to avoid race conditions with comms thread --- Firmware/MotorControl/controller.cpp | 7 ++++--- Firmware/MotorControl/controller.hpp | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index ebf6a9a1..68c05906 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -26,9 +26,7 @@ void Controller::set_error(Error_t error) { //-------------------------------- void Controller::input_pos_updated() { - if (config_.input_mode == INPUT_MODE_TRAP_TRAJ) { - move_to_pos(input_pos_); - } + input_pos_updated_ = true; } void Controller::move_to_pos(float goal_point) { @@ -157,6 +155,8 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // // NOT YET IMPLEMENTED // } break; case INPUT_MODE_TRAP_TRAJ: { + if(input_pos_updated_) + move_to_pos(input_pos_); // Avoid updating uninitialized trajectory if (trajectory_done_) break; @@ -181,6 +181,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s set_error(ERROR_INVALID_INPUT_MODE); return false; } + input_pos_updated_ = false; } // Position control diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 4c954a9a..9f90629a 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -102,6 +102,8 @@ public: float input_filter_kp_ = 0.0f; float input_filter_ki_ = 0.0f; + bool input_pos_updated_ = false; + uint32_t traj_start_loop_count_ = 0; float goal_point_ = 0.0f; bool trajectory_done_ = true; From ec05f7e45a68fd70a5ac021a75c4e942d2fa2213 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Mon, 3 Jun 2019 21:07:49 +0200 Subject: [PATCH 164/549] Make sure the trajectory mode exits to avoid rollover issues --- Firmware/MotorControl/controller.cpp | 8 +++++--- Firmware/MotorControl/controller.hpp | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 68c05906..064cb733 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -36,7 +36,6 @@ void Controller::move_to_pos(float goal_point) { axis_->trap_.config_.decel_limit); traj_start_loop_count_ = axis_->loop_counter_; trajectory_done_ = false; - goal_point_ = goal_point; } void Controller::move_incremental(float displacement, bool from_input_pos = true){ @@ -155,8 +154,10 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // // NOT YET IMPLEMENTED // } break; case INPUT_MODE_TRAP_TRAJ: { - if(input_pos_updated_) + if(input_pos_updated_){ move_to_pos(input_pos_); + input_pos_updated_ = false; + } // Avoid updating uninitialized trajectory if (trajectory_done_) break; @@ -169,6 +170,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s pos_setpoint_ = input_pos_; vel_setpoint_ = 0.0f; current_setpoint_ = 0.0f; + trajectory_done_ = true; } else { TrapezoidalTrajectory::Step_t traj_step = axis_->trap_.eval(t); pos_setpoint_ = traj_step.Y; @@ -181,7 +183,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s set_error(ERROR_INVALID_INPUT_MODE); return false; } - input_pos_updated_ = false; + } // Position control diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 9f90629a..097188d5 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -105,7 +105,6 @@ public: bool input_pos_updated_ = false; uint32_t traj_start_loop_count_ = 0; - float goal_point_ = 0.0f; bool trajectory_done_ = true; bool anticogging_valid_ = false; From d716ec9b95f45ffc1c31346c541731889c07c804 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Tue, 4 Jun 2019 20:49:45 +0200 Subject: [PATCH 165/549] Add Clear Errors function to CAN --- Firmware/communication/can_simple.cpp | 12 ++++++++++++ Firmware/communication/can_simple.hpp | 2 ++ docs/can-protocol.md | 9 +++++---- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index a0ecf937..e043920e 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -115,6 +115,9 @@ void CANSimple::handle_can_message(can_Message_t& msg) { case MSG_GET_VBUS_VOLTAGE: get_vbus_voltage_callback(axis, msg); break; + case MSG_CLEAR_ERRORS: + clear_errors_callback(axis, msg); + break; default: break; } @@ -375,6 +378,15 @@ void CANSimple::get_vbus_voltage_callback(Axis* axis, can_Message_t& msg) { } } +void CANSimple::clear_errors_callback(Axis* axis, can_Message_t& msg) { + axis->motor_.error_ = Motor::ERROR_NONE; + axis->controller_.error_ = Controller::ERROR_NONE; + axis->sensorless_estimator_.error_ = SensorlessEstimator::ERROR_NONE; + axis->encoder_.error_ = Encoder::ERROR_NONE; + + axis->error_ = Axis::ERROR_NONE; +} + void CANSimple::send_heartbeat(Axis* axis) { can_Message_t txmsg; txmsg.id = axis->config_.can_node_id << NUM_CMD_ID_BITS; diff --git a/Firmware/communication/can_simple.hpp b/Firmware/communication/can_simple.hpp index c2e16a61..4f98f0a8 100644 --- a/Firmware/communication/can_simple.hpp +++ b/Firmware/communication/can_simple.hpp @@ -30,6 +30,7 @@ class CANSimple { MSG_GET_SENSORLESS_ESTIMATES, MSG_RESET_ODRIVE, MSG_GET_VBUS_VOLTAGE, + MSG_CLEAR_ERRORS, MSG_CO_HEARTBEAT_CMD = 0x700, // CANOpen NMT Heartbeat SEND }; @@ -60,6 +61,7 @@ class CANSimple { static void get_iq_callback(Axis* axis, can_Message_t& msg); static void get_sensorless_estimates_callback(Axis* axis, can_Message_t& msg); static void get_vbus_voltage_callback(Axis* axis, can_Message_t& msg); + static void clear_errors_callback(Axis* axis, can_Message_t& msg); // Utility functions static uint8_t get_node_id(uint32_t msgID); diff --git a/docs/can-protocol.md b/docs/can-protocol.md index 7be636e8..97a3669b 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -34,9 +34,9 @@ Be careful that you don't assign too many nodeIDs per PDO group. Four CAN Simpl ### Messages CMD ID | Name | Sender | Signals | Start byte --: | :-- | :-- | :-- | :-- -0x000 | CANOpen NMT Message\*\* | Master | - | - | - +0x000 | CANOpen NMT Message\*\* | Master | - | - 0x001 | ODrive Heartbeat Message | Axis | Axis Error
    Axis Current State | 0
    4 -0x002 | ODrive Estop Message | Master | - | - | - +0x002 | ODrive Estop Message | Master | - | - 0x003 | Get Motor Error\* | Axis | Motor Error | 0 0x004 | Get Encoder Error\* | Axis | Encoder Error | 0 0x005 | Get Sensorless Error\* | Axis | Sensorless Error | 0 @@ -56,9 +56,10 @@ CMD ID | Name | Sender | Signals | Start byte 0x013 | Set Traj A per Count / s^2 | Master | Traj A per CSS | 0 0x014 | Get IQ\* | Axis | Iq Setpoint
    Iq Measured | 0
    4 0x015 | Get Sensorless Estimates\* | Master | Sensorless Pos Estimate
    Sensorless Vel Estimate | 0
    4 -0x016 | Reboot ODrive | Master\*\*\* | | +0x016 | Reboot ODrive | Master\*\*\* | - | - 0x017 | Get Vbus Voltage | Master\*\*\* | Vbus Voltage | 0 -0x700 | CANOpen Heartbeat Message\*\* | Slave | - | - | - +0x018 | Clear Errors | Master | - | - +0x700 | CANOpen Heartbeat Message\*\* | Slave | - | - \* Note: These messages are call & response. The Master node sends a message with the RTR bit set, and the axis responds with the same ID and specified payload. \*\* Note: These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple. From bee16cf9e23f333fb88f832cbc69ed138a73b0db Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Wed, 5 Jun 2019 20:11:36 +0200 Subject: [PATCH 166/549] Add some tests for delta_enc modulo handling --- Firmware/Tests/test_runner.cpp | 43 ++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index fa3995da..9be49eae 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -186,4 +186,47 @@ TEST_SUITE("CAN Functions") { CHECK(static_cast(can_getSignal(rxmsg, 0, 8, true, 1, 0)) == INPUT_MODE_MIX_CHANNELS); CHECK(static_cast(can_getSignal(rxmsg, 8, 8, true, 1, 0)) == INPUT_MODE_PASSTHROUGH); } +} + + +TEST_SUITE("delta_enc"){ + // Modulo (as opposed to remainder), per https://stackoverflow.com/a/19288271 + int mod(int dividend, int divisor){ + int r = dividend % divisor; + return (r < 0) ? (r + divisor) : r; + } + + int getDelta(int pos_abs, int count_in_cpr, int cpr) { + int delta_enc = pos_abs - count_in_cpr; + delta_enc = mod(delta_enc, cpr); + if (delta_enc > (cpr / 2)) + delta_enc -= cpr; + return delta_enc; + } + + TEST_CASE("mod"){ + + int cpr = 1000; + + // Check moves around 0 + CHECK(getDelta(1, 0, cpr) == 1); + CHECK(getDelta(0, 1, cpr) == -1); + CHECK(getDelta(999, 0, cpr) == -1); + CHECK(getDelta(50, 650, cpr) == 400); + CHECK(getDelta(650, 50, cpr) == -400); + CHECK(getDelta(50, 500, cpr) == -450); + CHECK(getDelta(500, 50, cpr) == 450); + + + // Test moving a distance larger than cpr / 2 + CHECK(getDelta(950, 450, cpr) == 500); + CHECK(getDelta(451, 950, cpr) == -499); + CHECK(getDelta(450, 950, cpr) == 500); + + // Test handling around mid-point + CHECK(getDelta(501, 499, cpr) == 2); + CHECK(getDelta(499, 501, cpr) == -2); + CHECK(getDelta(550, 450, cpr) == 100); + CHECK(getDelta(450, 550, cpr) == -100); + } } \ No newline at end of file From fc5022fde0f65eb8d242ff85b85193c285dc49eb Mon Sep 17 00:00:00 2001 From: Ioannis Chatzikonstantinou Date: Thu, 13 Jun 2019 10:27:07 +0300 Subject: [PATCH 167/549] velocity limiting in current control mode --- Firmware/MotorControl/controller.cpp | 39 ++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index d295246c..f8c88512 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -1,10 +1,7 @@ #include "odrive_main.h" - -Controller::Controller(Config_t& config) : - config_(config) -{} +Controller::Controller(Config_t& config) : config_(config) {} void Controller::reset() { pos_setpoint_ = 0.0f; @@ -59,10 +56,10 @@ void Controller::move_to_pos(float goal_point) { goal_point_ = goal_point; } -void Controller::move_incremental(float displacement, bool from_goal_point = true){ - if(from_goal_point){ +void Controller::move_incremental(float displacement, bool from_goal_point = true) { + if (from_goal_point) { move_to_pos(goal_point_ + displacement); - } else{ + } else { move_to_pos(pos_setpoint_ + displacement); } } @@ -88,12 +85,12 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) fabsf(vel_estimate) < anticogging_.calib_vel_threshold) { anticogging_.cogging_map[anticogging_.index++] = vel_integrator_current_; } - if (anticogging_.index < axis_->encoder_.config_.cpr) { // TODO: remove the dependency on encoder CPR + if (anticogging_.index < axis_->encoder_.config_.cpr) { // TODO: remove the dependency on encoder CPR set_pos_setpoint(anticogging_.index, 0.0f, 0.0f); return false; } else { anticogging_.index = 0; - set_pos_setpoint(0.0f, 0.0f, 0.0f); // Send the motor home + set_pos_setpoint(0.0f, 0.0f, 0.0f); // Send the motor home anticogging_.use_anticogging = true; // We're good to go, enable anti-cogging anticogging_.calib_anticogging = false; return true; @@ -124,7 +121,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s vel_setpoint_ = traj_step.Yd; current_setpoint_ = traj_step.Ydd * axis_->trap_.config_.A_per_css; } - anticogging_pos = pos_setpoint_; // FF the position setpoint instead of the pos_estimate + anticogging_pos = pos_setpoint_; // FF the position setpoint instead of the pos_estimate } // Ramp rate limited velocity setpoint @@ -166,7 +163,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s if (vel_des < -vel_lim) vel_des = -vel_lim; // Check for overspeed fault (done in this module (controller) for cohesion with vel_lim) - if (config_.vel_limit_tolerance > 0.0f) { // 0.0f to disable + if (config_.vel_limit_tolerance > 0.0f) { // 0.0f to disable if (fabsf(vel_estimate) > config_.vel_limit_tolerance * vel_lim) { set_error(ERROR_OVERSPEED); return false; @@ -203,6 +200,26 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s Iq = -Ilim; } + // Velocity limiting in current mode + if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL) { + float vmax = (config_.vel_limit - fabsf(vel_estimate)) * config_.vel_gain; + if (Iq > 0 && Iq > vmax) { + limited = true; + if (vmax > 0) { + Iq = vmax; + } else { + Iq = 0; + } + } else if (Iq < 0 && Iq < -vmax) { + limited = true; + if (vmax > 0) { + Iq = -vmax; + } else { + Iq = 0; + } + } + } + // Velocity integrator (behaviour dependent on limiting) if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL) { // reset integral if not in use From 7b232b4f8a2670c48e9ac2959e85e040a0056a60 Mon Sep 17 00:00:00 2001 From: Ioannis Chatzikonstantinou Date: Thu, 13 Jun 2019 10:31:26 +0300 Subject: [PATCH 168/549] better naming --- Firmware/MotorControl/controller.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index f8c88512..8fb179be 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -202,18 +202,18 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // Velocity limiting in current mode if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL) { - float vmax = (config_.vel_limit - fabsf(vel_estimate)) * config_.vel_gain; - if (Iq > 0 && Iq > vmax) { + float Imax = (config_.vel_limit - fabsf(vel_estimate)) * config_.vel_gain; + if (Iq > 0 && Iq > Imax) { limited = true; - if (vmax > 0) { - Iq = vmax; + if (Imax > 0) { + Iq = Imax; } else { Iq = 0; } - } else if (Iq < 0 && Iq < -vmax) { + } else if (Iq < 0 && Iq < -Imax) { limited = true; - if (vmax > 0) { - Iq = -vmax; + if (Imax > 0) { + Iq = -Imax; } else { Iq = 0; } From cfb0e1127220b769f759a8b4186b3f345e4338f3 Mon Sep 17 00:00:00 2001 From: Yannis Chatzikonstantinou Date: Fri, 14 Jun 2019 22:48:57 +0300 Subject: [PATCH 169/549] add check for vel_limt == 0 --- Firmware/MotorControl/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 8fb179be..642e1785 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -201,7 +201,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s } // Velocity limiting in current mode - if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL) { + if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL && config_.vel_limit > 0) { float Imax = (config_.vel_limit - fabsf(vel_estimate)) * config_.vel_gain; if (Iq > 0 && Iq > Imax) { limited = true; From 313bd9fe9eebbaf648eb7d9fdc27f4b74e870498 Mon Sep 17 00:00:00 2001 From: Yannis Chatzikonstantinou Date: Fri, 14 Jun 2019 22:53:04 +0300 Subject: [PATCH 170/549] add check for vel_gain > 0 --- Firmware/MotorControl/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 642e1785..cb5b8642 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -201,7 +201,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s } // Velocity limiting in current mode - if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL && config_.vel_limit > 0) { + if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL && config_.vel_limit > 0 && config_.vel_gain > 0) { float Imax = (config_.vel_limit - fabsf(vel_estimate)) * config_.vel_gain; if (Iq > 0 && Iq > Imax) { limited = true; From 544933bb4421bbc02d1b14ea1fa3b021d5806f83 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Wed, 19 Jun 2019 23:13:20 +0200 Subject: [PATCH 171/549] Add a lot more velocity limiting tests, implement it --- Firmware/MotorControl/controller.cpp | 30 ++++++++--------- Firmware/Tests/test_runner.cpp | 49 ++++++++++++++++------------ 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 99f62b44..61a17a38 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -114,6 +114,17 @@ void Controller::update_filter_gains() { input_filter_kp_ = 0.25f * (input_filter_ki_ * input_filter_ki_); // Critically damped } +namespace { +float limitVel(float vel_limit, float vel_estimate, float vel_gain, float Iq, float current_lim) { + float Imax = (vel_limit - std::abs(vel_estimate)) * vel_gain; + if (vel_estimate > 0.0f) { + return std::clamp(Iq, -current_lim, Imax); + } else { + return std::clamp(Iq, -Imax, current_lim); + } +} +} // namespace + bool Controller::update(float pos_estimate, float vel_estimate, float* current_setpoint_output) { // Only runs if config_.anticogging.calib_anticogging is true; non-blocking anticogging_calibration(pos_estimate, vel_estimate); @@ -256,23 +267,8 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s } // Velocity limiting in current mode - if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL && config_.vel_limit > 0.0f && config_.vel_gain > 0.0f) { - float Imax = (config_.vel_limit - fabsf(vel_estimate)) * config_.vel_gain; - if (Iq > 0 && Iq > Imax) { - limited = true; - if (Imax > 0) { - Iq = Imax; - } else { - Iq = 0; - } - } else if (Iq < 0 && Iq < -Imax) { - limited = true; - if (Imax > 0) { - Iq = -Imax; - } else { - Iq = 0; - } - } + if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL && config_.vel_limit > 0.0f) { + Iq = limitVel(config_.vel_limit, vel_estimate, config_.vel_gain, Iq, Ilim); } // Velocity integrator (behaviour dependent on limiting) diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index 4b8521ca..d340ca74 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -9,6 +9,7 @@ #define DOCTEST_CONFIG_NO_POSIX_SIGNALS // #define DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS + #include using std::cout; @@ -232,34 +233,42 @@ TEST_SUITE("delta_enc"){ } TEST_SUITE("velLimiter") { - // Velocity limiting in current mode - auto limitVel(float vel_limit, float vel_estimate, float vel_gain, float Iq) { - float Imax = (vel_limit - fabsf(vel_estimate)) * vel_gain; - // bool limited = false; - if (Iq > 0 && Iq > Imax) { - // limited = true; - if (Imax > 0) { - Iq = Imax; - } else { - Iq = 0; - } - } else if (Iq < 0 && Iq < -Imax) { - // limited = true; - if (Imax > 0) { - Iq = -Imax; - } else { - Iq = 0; - } +// Velocity limiting in current mode +#include + + auto limitVel(float vel_limit, float vel_estimate, float vel_gain, float Iq, float current_lim = 50.0f) { + float Imax = (vel_limit - std::abs(vel_estimate)) * vel_gain; + if (vel_estimate > 0.0f) { + return std::clamp(Iq, -current_lim, Imax); + } else { + return std::clamp(Iq, -Imax, current_lim); } - return Iq; } - TEST_CASE("limit Vel"){ + TEST_CASE("limit Vel") { CHECK(limitVel(0, 0, 0, 0) == 0.0f); CHECK(limitVel(1000.0f, 1.0f, 0.0f, 0.0f) == 0.0f); CHECK(limitVel(1000.0f, 500.0f, 1.0f, 1.0f) == 1.0f); CHECK(limitVel(1000.0f, 500.0f, 1.0f, -20.0f) == -20.0f); CHECK(limitVel(1000.0f, 999.0f, 1.0f, 2.0f) == 1.0f); CHECK(limitVel(1000.0f, 999.0f, 1.0f, -5.0f) == -5.0f); + CHECK(limitVel(1000.0f, -999.0f, 1.0f, -5.0f) == -1.0f); + CHECK(limitVel(1000.0f, -999.0f, 1.0f, 5.0f) == 5.0f); + CHECK(limitVel(1000.0f, 0.0f, 1.0f, 1.0f) == 1.0f); + CHECK(limitVel(1000.0f, 0.0f, 1.0f, -1.0f) == -1.0f); + } + + TEST_CASE("Accelerating"){ + CHECK(limitVel(200000.0f, 195000.0f, 5.0E-4f, 30.0f) == 2.5f); + CHECK(limitVel(200000.0f, 205000.0f, 5.0E-4f, 30.0f) == -2.5f); + CHECK(limitVel(200000.0f, -195000.0f, 5.0E-4, -30.0f) == -2.5f); + CHECK(limitVel(200000.0f, -205000.0f, 5.0E-4f, -30.0f) == 2.5f); + } + + TEST_CASE("Decelerating"){ + CHECK(limitVel(200000.0f, 195000.0f, 5.0E-4f, -30.0f) == -30.0f); + CHECK(limitVel(200000.0f, 205000.0f, 5.0E-4f, -30.0f) == -30.0f); + CHECK(limitVel(200000.0f, -195000.0f, 5.0E-4, 30.0f) == 30.0f); + CHECK(limitVel(200000.0f, -205000.0f, 5.0E-4f, 30.0f) == 30.0f); } } \ No newline at end of file From 0481241a8b2b98f5b376191e7edc211b644b7293 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Fri, 21 Jun 2019 16:54:22 +0200 Subject: [PATCH 172/549] Use a better algorithm for velocity limiting in current mode --- Firmware/Tests/test_runner.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index d340ca74..5f921645 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -237,12 +237,9 @@ TEST_SUITE("velLimiter") { #include auto limitVel(float vel_limit, float vel_estimate, float vel_gain, float Iq, float current_lim = 50.0f) { - float Imax = (vel_limit - std::abs(vel_estimate)) * vel_gain; - if (vel_estimate > 0.0f) { - return std::clamp(Iq, -current_lim, Imax); - } else { - return std::clamp(Iq, -Imax, current_lim); - } + float Imax = std::min((vel_limit - vel_estimate) * vel_gain, current_lim); + float Imin = std::max((-vel_limit - vel_estimate) * vel_gain, -current_lim); + return std::clamp(Iq, Imin, Imax); } TEST_CASE("limit Vel") { From 4c623f206896f11c1269b98254a619a2268dc329 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Fri, 21 Jun 2019 16:55:15 +0200 Subject: [PATCH 173/549] Implement better algorithm --- Firmware/MotorControl/controller.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 61a17a38..56e2257d 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -116,12 +116,9 @@ void Controller::update_filter_gains() { namespace { float limitVel(float vel_limit, float vel_estimate, float vel_gain, float Iq, float current_lim) { - float Imax = (vel_limit - std::abs(vel_estimate)) * vel_gain; - if (vel_estimate > 0.0f) { - return std::clamp(Iq, -current_lim, Imax); - } else { - return std::clamp(Iq, -Imax, current_lim); - } + float Imax = std::min((vel_limit - vel_estimate) * vel_gain, current_lim); + float Imin = std::max((-vel_limit - vel_estimate) * vel_gain, -current_lim); + return std::clamp(Iq, Imin, Imax); } } // namespace From ee3c3adfdff89a5eb1334f1b5c42dea68492aaf7 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Fri, 21 Jun 2019 17:00:36 +0200 Subject: [PATCH 174/549] Tests that were failing are now behaving as expected --- Firmware/Tests/test_runner.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index 5f921645..0b3cc5e2 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -235,6 +235,7 @@ TEST_SUITE("delta_enc"){ TEST_SUITE("velLimiter") { // Velocity limiting in current mode #include +using doctest::Approx; auto limitVel(float vel_limit, float vel_estimate, float vel_gain, float Iq, float current_lim = 50.0f) { float Imax = std::min((vel_limit - vel_estimate) * vel_gain, current_lim); @@ -268,4 +269,9 @@ TEST_SUITE("velLimiter") { CHECK(limitVel(200000.0f, -195000.0f, 5.0E-4, 30.0f) == 30.0f); CHECK(limitVel(200000.0f, -205000.0f, 5.0E-4f, 30.0f) == 30.0f); } + + TEST_CASE("Over-Center"){ + CHECK(limitVel(20000.0f, 1000.0f, 5.0E-4f, 30.0f) == 9.5f); + CHECK(limitVel(20000.0f, -1000.0f, 5.0E-4f, 30.0f) == Approx(10.5f)); + } } \ No newline at end of file From c861cc0b4658a7f14ef078bc8aa17dbb19da8fda Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Fri, 21 Jun 2019 17:20:16 +0200 Subject: [PATCH 175/549] Make inputs to limitVel function const --- Firmware/MotorControl/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 56e2257d..59c7164c 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -115,7 +115,7 @@ void Controller::update_filter_gains() { } namespace { -float limitVel(float vel_limit, float vel_estimate, float vel_gain, float Iq, float current_lim) { +float limitVel(const float vel_limit, const float vel_estimate, const float vel_gain, const float Iq, const float current_lim) { float Imax = std::min((vel_limit - vel_estimate) * vel_gain, current_lim); float Imin = std::max((-vel_limit - vel_estimate) * vel_gain, -current_lim); return std::clamp(Iq, Imin, Imax); From 1cbbf4127c722c5920ffec8d15f8fe387d7944a4 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Fri, 21 Jun 2019 18:26:41 +0200 Subject: [PATCH 176/549] Add changes to Changelog --- CHANGELOG.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9e8c24e..2d5c62f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,31 @@ Please add a note of your changes below this heading if you make a Pull Request. ### Added -* Check current limit violation: added `ERROR_CURRENT_UNSTABLE`, `motor.config.current_lim_tolerance`. +* Simplified control interface ("Input Filter" branch) + * New input variables: `input_pos`, `input_vel`, and `input_current` + * New setting `input_mode` to switch between different input behaviours + * Passthrough + * Velocity Ramp + * 2nd Order Position Filter + * Trapezoidal Trajectory Planner + * Removed `set_xxx_setpoint()` functions and made `xxx_setpoint` variables read-only +* [Preliminary support for Absolute Encoders](docs/encoders.md) +* [Preliminary support for endstops and homing](docs/endstops.md) +* [CAN Communication with CANSimple stack](can-protocol.md) +* Gain scheduling for anti-hunt when close to 0 position error +* Velocity Limiting in Current Control mode according to `vel_limit` and `vel_gain` +* Current limit violation added `ERROR_CURRENT_UNSTABLE`, `motor.config.current_lim_tolerance`. +* Regen current limiting according to `max_regen_limit`, in Amps +* DC Bus Power limiting according to `power_supply_wattage` +* Unit Testing with Doctest has been started for select algorithms, see [Firmware/Tests/test_runner.cpp](Firmware/Tests/test_runner.cpp) +* Added support for Flylint VSCode Extension for static code analysis +* Using an STM32F405 .svd file allows CortexDebug to view registers during debugging + +### Changed +* Anticogging map is temporarily forced to 0.1 deg precision, but saves with the config +* Some Encoder settings have been made read-only +* Cleaned up VSCode C/C++ Configuration settings on Windows with recursive includePath +* Now compiling with C++17 # Releases ## [0.4.10] - 2019-04-24 From 7e569a2e1dfac7947fa15caf9ef6ce6d62acd770 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Fri, 21 Jun 2019 21:52:10 +0200 Subject: [PATCH 177/549] Further reduce the complexity of the limitVel algorithm --- Firmware/MotorControl/controller.cpp | 16 ++++++++-------- Firmware/Tests/test_runner.cpp | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 59c7164c..2dcd90a4 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -115,9 +115,9 @@ void Controller::update_filter_gains() { } namespace { -float limitVel(const float vel_limit, const float vel_estimate, const float vel_gain, const float Iq, const float current_lim) { - float Imax = std::min((vel_limit - vel_estimate) * vel_gain, current_lim); - float Imin = std::max((-vel_limit - vel_estimate) * vel_gain, -current_lim); +float limitVel(const float vel_limit, const float vel_estimate, const float vel_gain, const float Iq) { + float Imax = (vel_limit - vel_estimate) * vel_gain; + float Imin = (-vel_limit - vel_estimate) * vel_gain; return std::clamp(Iq, Imin, Imax); } } // namespace @@ -251,6 +251,11 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // Velocity integral action before limiting Iq += vel_integrator_current_; + // Velocity limiting in current mode + if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL && config_.vel_limit > 0.0f) { + Iq = limitVel(config_.vel_limit, vel_estimate, config_.vel_gain, Iq); + } + // Current limiting bool limited = false; float Ilim = axis_->motor_.effective_current_lim(); @@ -263,11 +268,6 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s Iq = -Ilim; } - // Velocity limiting in current mode - if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL && config_.vel_limit > 0.0f) { - Iq = limitVel(config_.vel_limit, vel_estimate, config_.vel_gain, Iq, Ilim); - } - // Velocity integrator (behaviour dependent on limiting) if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL) { // reset integral if not in use diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index 0b3cc5e2..71450787 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -237,9 +237,9 @@ TEST_SUITE("velLimiter") { #include using doctest::Approx; - auto limitVel(float vel_limit, float vel_estimate, float vel_gain, float Iq, float current_lim = 50.0f) { - float Imax = std::min((vel_limit - vel_estimate) * vel_gain, current_lim); - float Imin = std::max((-vel_limit - vel_estimate) * vel_gain, -current_lim); + auto limitVel(float vel_limit, float vel_estimate, float vel_gain, float Iq) { + float Imax = (vel_limit - vel_estimate) * vel_gain; + float Imin = (-vel_limit - vel_estimate) * vel_gain; return std::clamp(Iq, Imin, Imax); } From cbc23de7b7f456778fd0909d27b5776dc8abbbab Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Mon, 8 Jul 2019 23:07:47 +0200 Subject: [PATCH 178/549] Add axis.clear_errors() function --- Firmware/MotorControl/axis.hpp | 11 ++++++++++- Firmware/communication/can_simple.cpp | 7 +------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 2f100d83..776e4e02 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -134,6 +134,14 @@ public: void watchdog_feed(); bool watchdog_check(); + void clear_errors() { + motor_.error_ = Motor::ERROR_NONE; + controller_.error_ = Controller::ERROR_NONE; + sensorless_estimator_.error_ = SensorlessEstimator::ERROR_NONE; + encoder_.error_ = Encoder::ERROR_NONE; + + error_ = Axis::ERROR_NONE; + } // True if there are no errors bool inline check_for_errors() { @@ -311,7 +319,8 @@ public: make_protocol_object("trap_traj", trap_.make_protocol_definitions()), make_protocol_object("min_endstop", min_endstop_.make_protocol_definitions()), make_protocol_object("max_endstop", max_endstop_.make_protocol_definitions()), - make_protocol_function("watchdog_feed", *this, &Axis::watchdog_feed) + make_protocol_function("watchdog_feed", *this, &Axis::watchdog_feed), + make_protocol_function("clear_errors", *this, &Axis::clear_errors) ); } }; diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index e043920e..62d9306f 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -379,12 +379,7 @@ void CANSimple::get_vbus_voltage_callback(Axis* axis, can_Message_t& msg) { } void CANSimple::clear_errors_callback(Axis* axis, can_Message_t& msg) { - axis->motor_.error_ = Motor::ERROR_NONE; - axis->controller_.error_ = Controller::ERROR_NONE; - axis->sensorless_estimator_.error_ = SensorlessEstimator::ERROR_NONE; - axis->encoder_.error_ = Encoder::ERROR_NONE; - - axis->error_ = Axis::ERROR_NONE; + axis->clear_errors(); } void CANSimple::send_heartbeat(Axis* axis) { From 1163691a4c4a98fef698bc5b81238981f0ec4c5f Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Tue, 9 Jul 2019 18:18:56 +0200 Subject: [PATCH 179/549] Fix bad CAN callback for input vel and input current --- Firmware/communication/can_simple.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Firmware/communication/can_simple.cpp b/Firmware/communication/can_simple.cpp index 62d9306f..da22bb70 100644 --- a/Firmware/communication/can_simple.cpp +++ b/Firmware/communication/can_simple.cpp @@ -80,10 +80,10 @@ void CANSimple::handle_can_message(can_Message_t& msg) { set_input_pos_callback(axis, msg); break; case MSG_SET_INPUT_VEL: - set_input_pos_callback(axis, msg); + set_input_vel_callback(axis, msg); break; case MSG_SET_INPUT_CURRENT: - set_input_pos_callback(axis, msg); + set_input_current_callback(axis, msg); break; case MSG_SET_CONTROLLER_MODES: set_controller_modes_callback(axis, msg); @@ -279,14 +279,14 @@ void CANSimple::get_encoder_count_callback(Axis* axis, can_Message_t& msg) { } void CANSimple::set_input_pos_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.input_pos_ = can_getSignal(msg, 0, 32, true); - axis->controller_.input_vel_ = can_getSignal(msg, 32, 16, true, 0.1f, 0); + axis->controller_.input_pos_ = can_getSignal(msg, 0, 32, true); + axis->controller_.input_vel_ = can_getSignal(msg, 32, 16, true, 0.1f, 0); axis->controller_.input_current_ = can_getSignal(msg, 48, 16, true, 0.01f, 0); axis->controller_.input_pos_updated(); } void CANSimple::set_input_vel_callback(Axis* axis, can_Message_t& msg) { - axis->controller_.input_vel_ = can_getSignal(msg, 0, 32, true, 0.01f, 0.0f); + axis->controller_.input_vel_ = can_getSignal(msg, 0, 32, true, 0.01f, 0.0f); axis->controller_.input_current_ = can_getSignal(msg, 32, 16, true, 0.01f, 0.0f); } From 59cbbbb8f9017b97d7ab63f201464879b23df81f Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Tue, 9 Jul 2019 18:22:20 +0200 Subject: [PATCH 180/549] Fix Input Vel documentation in CAN-protocol.md --- docs/can-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/can-protocol.md b/docs/can-protocol.md index 97a3669b..7fb0c312 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -47,7 +47,7 @@ CMD ID | Name | Sender | Signals | Start byte 0x00A | Get Encoder Count\* | Master | Encoder Shadow Count
    Encoder Count in CPR | 0
    4 0x00B | Set Controller Modes | Master | Control Mode
    Input Mode | 0
    4 0x00C | Set Input Pos | Master | Input Pos
    Vel FF
    Current FF | 0
    4
    6 -0x00D | Set Input Vel | Master | Input Current
    Current FF | 0
    4 +0x00D | Set Input Vel | Master | Input Vel
    Current FF | 0
    4 0x00E | Set Input Current | Master | Input Current | 0 0x00F | Set Velocity Limit | Master | Velocity Limit | 0 0x010 | Start Anticogging | Master | - | - From 0ab104346f7b1c26b6f683000f85f75e07de37ab Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Tue, 9 Jul 2019 21:31:04 +0200 Subject: [PATCH 181/549] Modify vel_ramp logic slightly to be more STL-based --- Firmware/MotorControl/controller.cpp | 10 ++----- Firmware/Tests/test_runner.cpp | 42 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 2dcd90a4..0080c789 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -138,14 +138,10 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s current_setpoint_ = input_current_; } break; case INPUT_MODE_VEL_RAMP: { - float max_step_size = current_meas_period * config_.vel_ramp_rate; + float max_step_size = std::abs(current_meas_period * config_.vel_ramp_rate); float full_step = input_vel_ - vel_setpoint_; - float step; - if (fabsf(full_step) > max_step_size) { - step = std::copysignf(max_step_size, full_step); - } else { - step = full_step; - } + float step = std::clamp(full_step, -max_step_size, max_step_size); + vel_setpoint_ += step; current_setpoint_ = step / current_meas_period * config_.inertia; } break; diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index 71450787..0b116fc6 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -274,4 +274,46 @@ using doctest::Approx; CHECK(limitVel(20000.0f, 1000.0f, 5.0E-4f, 30.0f) == 9.5f); CHECK(limitVel(20000.0f, -1000.0f, 5.0E-4f, 30.0f) == Approx(10.5f)); } +} + +TEST_SUITE("vel_ramp") { + float vel_ramp_old(float input_vel_, float vel_setpoint_, float vel_ramp_rate) { + float max_step_size = 0.000125f * vel_ramp_rate; + float full_step = input_vel_ - vel_setpoint_; + float step; + if (fabsf(full_step) > max_step_size) { + step = std::copysignf(max_step_size, full_step); + } else { + step = full_step; + } + return step; + } + + float vel_ramp_new(float input_vel_, float vel_setpoint_, float vel_ramp_rate){ + float max_step_size = 0.000125f * vel_ramp_rate; + float full_step = input_vel_ - vel_setpoint_; + return std::clamp(full_step, -max_step_size, max_step_size); + } + + TEST_CASE("Blah") { + float vel_setpoint = 0.0f; + float vel_ramp_rate = 8000; + float input_vel = 0.0f; + CHECK(vel_ramp_old(input_vel, vel_setpoint, vel_ramp_rate) == vel_ramp_new(input_vel, vel_setpoint, vel_ramp_rate)); + + input_vel = 10.0f; + CHECK(vel_ramp_old(input_vel, vel_setpoint, vel_ramp_rate) == vel_ramp_new(input_vel, vel_setpoint, vel_ramp_rate)); + + input_vel = 10000.0f; + CHECK(vel_ramp_old(input_vel, vel_setpoint, vel_ramp_rate) == vel_ramp_new(input_vel, vel_setpoint, vel_ramp_rate)); + + input_vel = -10000.0f; + CHECK(vel_ramp_old(input_vel, vel_setpoint, vel_ramp_rate) == vel_ramp_new(input_vel, vel_setpoint, vel_ramp_rate)); + + input_vel = -0.1234f; + CHECK(vel_ramp_old(input_vel, vel_setpoint, vel_ramp_rate) == vel_ramp_new(input_vel, vel_setpoint, vel_ramp_rate)); + + input_vel = 0.1234f; + CHECK(vel_ramp_old(input_vel, vel_setpoint, vel_ramp_rate) == vel_ramp_new(input_vel, vel_setpoint, vel_ramp_rate)); + } } \ No newline at end of file From fbd7ab27385561ebb5f3480058b5e4913198df3f Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Thu, 18 Jul 2019 17:29:21 +0200 Subject: [PATCH 182/549] Improve the behaviour of the HOMING sequence --- Firmware/MotorControl/axis.cpp | 20 +++++++++++++------- Firmware/MotorControl/axis.hpp | 10 ++++++++-- Firmware/MotorControl/controller.cpp | 8 +++++++- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 2afb00b3..f5e64724 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -315,19 +315,25 @@ bool Axis::run_closed_loop_control_loop() { return false; // set_error should update axis.error_ // Handle the homing case - if (homing_state_ == HOMING_STATE_HOMING) { + if (homing_.homing_state == HOMING_STATE_HOMING) { if (min_endstop_.getEndstopState()) { encoder_.set_linear_count(min_endstop_.config_.offset); + + controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; + controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ; + controller_.input_pos_ = 0.0f; + controller_.input_pos_updated(); controller_.input_vel_ = 0.0f; controller_.input_current_ = 0.0f; - controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; - controller_.input_pos_updated(); - homing_state_ = HOMING_STATE_MOVE_TO_ZERO; + + homing_.homing_state = HOMING_STATE_MOVE_TO_ZERO; } - } else if (homing_state_ == HOMING_STATE_MOVE_TO_ZERO) { - if(!min_endstop_.getEndstopState()){ - homing_state_ = HOMING_STATE_IDLE; + } else if (homing_.homing_state == HOMING_STATE_MOVE_TO_ZERO) { + if(!min_endstop_.getEndstopState() && controller_.trajectory_done_){ + controller_.config_.control_mode = homing_.storedControlMode; + controller_.config_.input_mode = homing_.storedInputMode; + homing_.homing_state = HOMING_STATE_IDLE; } } else { // Check for endstop presses diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 776e4e02..f53ae0c1 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -90,6 +90,12 @@ public: uint32_t can_heartbeat_rate_ms = 100; }; + struct Homing_t { + HomingState_t homing_state = HOMING_STATE_IDLE; + Controller::ControlMode_t storedControlMode = Controller::CTRL_MODE_POSITION_CONTROL; + Controller::InputMode_t storedInputMode = Controller::INPUT_MODE_PASSTHROUGH; + }; + enum thread_signals { M_SIGNAL_PH_CURRENT_MEAS = 1u << 0 }; @@ -250,7 +256,7 @@ public: State_t& current_state_ = task_chain_[0]; uint32_t loop_counter_ = 0; LockinState_t lockin_state_ = LOCKIN_STATE_INACTIVE; - HomingState_t homing_state_ = HOMING_STATE_IDLE; + Homing_t homing_; uint32_t last_heartbeat_ = 0; // watchdog @@ -265,7 +271,7 @@ public: make_protocol_property("requested_state", &requested_state_), make_protocol_ro_property("loop_counter", &loop_counter_), make_protocol_ro_property("lockin_state", &lockin_state_), - make_protocol_ro_property("homing_state", &homing_state_), + make_protocol_ro_property("homing_state", &homing_.homing_state), make_protocol_object("config", make_protocol_property("startup_motor_calibration", &config_.startup_motor_calibration), make_protocol_property("startup_encoder_index_search", &config_.startup_encoder_index_search), diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 0080c789..b77d6ed1 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -61,12 +61,18 @@ void Controller::start_anticogging_calibration() { //TODO: This needs to be upgraded to use its own run_control_loop! bool Controller::home_axis() { if (axis_->min_endstop_.config_.enabled) { + axis_->homing_.storedControlMode = config_.control_mode; + axis_->homing_.storedInputMode = config_.input_mode; + config_.control_mode = CTRL_MODE_VELOCITY_CONTROL; + config_.input_mode = INPUT_MODE_VEL_RAMP; + input_pos_ = 0.0f; input_pos_updated(); input_vel_ = -config_.homing_speed; input_current_ = 0.0f; - axis_->homing_state_ = HOMING_STATE_HOMING; + + axis_->homing_.homing_state = HOMING_STATE_HOMING; } else { return false; } From d2ef949a48560b96507d85e8f287ac43c5aef17b Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 4 Aug 2019 18:00:57 -0400 Subject: [PATCH 183/549] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e2b2e21..685845fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased Features Please add a note of your changes below this heading if you make a Pull Request. +### Added * Simplified control interface ("Input Filter" branch) * New input variables: `input_pos`, `input_vel`, and `input_current` * New setting `input_mode` to switch between different input behaviours From 87e83bd4ba8120d0fe15cb285abec5174a12284c Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 9 Aug 2019 20:37:07 -0700 Subject: [PATCH 184/549] Change ERROR_CURRENT_UNSTABLE to ERROR_CURRENT_LIMIT_VIOLATION --- Firmware/MotorControl/motor.cpp | 2 +- Firmware/MotorControl/motor.hpp | 2 +- tools/odrive/enums.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 2a9cd703..fd628c89 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -360,7 +360,7 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha // Check for violation of current limit float I_trip = config_.current_lim_tolerance * effective_current_lim(); if (SQ(Id) + SQ(Iq) > SQ(I_trip)) { - set_error(ERROR_CURRENT_UNSTABLE); + set_error(ERROR_CURRENT_LIMIT_VIOLATION); return false; } diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index d31985ee..f0783584 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -23,7 +23,7 @@ public: ERROR_UNEXPECTED_TIMER_CALLBACK = 0x0200, ERROR_CURRENT_SENSE_SATURATION = 0x0400, ERROR_INVERTER_OVER_TEMP = 0x0800, - ERROR_CURRENT_UNSTABLE = 0x1000 + ERROR_CURRENT_LIMIT_VIOLATION = 0x1000 }; enum MotorType_t { diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index c5f6812c..030ac154 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -42,7 +42,7 @@ class errors: ERROR_BRAKE_DEADTIME_VIOLATION = 0x0100 ERROR_UNEXPECTED_TIMER_CALLBACK = 0x0200 ERROR_CURRENT_SENSE_SATURATION = 0x0400 - ERROR_CURRENT_UNSTABLE = 0x1000 + ERROR_CURRENT_LIMIT_VIOLATION = 0x1000 class encoder: ERROR_NONE = 0 From 0e9dbc36bcdf20b803957a752a14b1747e9bb5fd Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 9 Aug 2019 21:50:55 -0700 Subject: [PATCH 185/549] change ERROR_CPR_OUT_OF_RANGE to ERROR_CPR_POLEPAIRS_MISMATCH --- Firmware/MotorControl/encoder.cpp | 2 +- Firmware/MotorControl/encoder.hpp | 2 +- docs/troubleshooting.md | 2 +- tools/odrive/enums.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 7dcdd57a..b2d72646 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -232,7 +232,7 @@ bool Encoder::run_offset_calibration() { calib_scan_response_ = fabsf(shadow_count_-init_enc_val); if(fabsf(calib_scan_response_ - expected_encoder_delta)/expected_encoder_delta > config_.calib_range) { - set_error(ERROR_CPR_OUT_OF_RANGE); + set_error(ERROR_CPR_POLEPAIRS_MISMATCH); return false; } diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index c2d32841..02e991bb 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -10,7 +10,7 @@ public: enum Error_t { ERROR_NONE = 0, ERROR_UNSTABLE_GAIN = 0x01, - ERROR_CPR_OUT_OF_RANGE = 0x02, + ERROR_CPR_POLEPAIRS_MISMATCH = 0x02, ERROR_NO_RESPONSE = 0x04, ERROR_UNSUPPORTED_ENCODER_MODE = 0x08, ERROR_ILLEGAL_HALL_STATE = 0x10, diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 5b2ff466..79c0eee8 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -84,7 +84,7 @@ For gimbal motors, it is recommended to set the `motor.config.calibration_curren ## Common Encoder Errors -* `ERROR_CPR_OUT_OF_RANGE = 0x02` +* `ERROR_CPR_POLEPAIRS_MISMATCH = 0x02` Confirm you have entered the correct count per rotation (CPR) for [your encoder](https://docs.odriverobotics.com/encoders). Note that the AMT encoders are configurable using the micro-switches on the encoder PCB and so you may need to check that these are in the right positions. If your encoder lists its pulse per rotation (PPR) multiply that number by four to get CPR. diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 030ac154..be8061e6 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -47,7 +47,7 @@ class errors: class encoder: ERROR_NONE = 0 ERROR_UNSTABLE_GAIN = 0x01 - ERROR_CPR_OUT_OF_RANGE = 0x02 + ERROR_CPR_POLEPAIRS_MISMATCH = 0x02 ERROR_NO_RESPONSE = 0x04 ERROR_UNSUPPORTED_ENCODER_MODE = 0x08 ERROR_ILLEGAL_HALL_STATE = 0x10 From ca93c0e2d21a1a692e6523cef1a0344ea38c130c Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 11 Aug 2019 12:30:24 -0400 Subject: [PATCH 186/549] Add isHomed indicator --- Firmware/MotorControl/axis.cpp | 1 + Firmware/MotorControl/axis.hpp | 2 ++ Firmware/MotorControl/controller.cpp | 1 + 3 files changed, 4 insertions(+) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index f5e64724..dcd21b55 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -334,6 +334,7 @@ bool Axis::run_closed_loop_control_loop() { controller_.config_.control_mode = homing_.storedControlMode; controller_.config_.input_mode = homing_.storedInputMode; homing_.homing_state = HOMING_STATE_IDLE; + homing_.isHomed = true; } } else { // Check for endstop presses diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index f53ae0c1..060e3005 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -94,6 +94,7 @@ public: HomingState_t homing_state = HOMING_STATE_IDLE; Controller::ControlMode_t storedControlMode = Controller::CTRL_MODE_POSITION_CONTROL; Controller::InputMode_t storedInputMode = Controller::INPUT_MODE_PASSTHROUGH; + bool isHomed = false; }; enum thread_signals { @@ -272,6 +273,7 @@ public: make_protocol_ro_property("loop_counter", &loop_counter_), make_protocol_ro_property("lockin_state", &lockin_state_), make_protocol_ro_property("homing_state", &homing_.homing_state), + make_protocol_property("is_homed", &homing_.isHomed), make_protocol_object("config", make_protocol_property("startup_motor_calibration", &config_.startup_motor_calibration), make_protocol_property("startup_encoder_index_search", &config_.startup_encoder_index_search), diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index b77d6ed1..8837bae1 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -72,6 +72,7 @@ bool Controller::home_axis() { input_vel_ = -config_.homing_speed; input_current_ = 0.0f; + axis_->homing_.isHomed = false; axis_->homing_.homing_state = HOMING_STATE_HOMING; } else { return false; From 82beb16ae56a0dbd6f19d3d4475ff1da66a9694a Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sun, 11 Aug 2019 15:24:35 -0400 Subject: [PATCH 187/549] Fix odd behaviour during homing Because the accelerations and velocities are so low during this phase, we were overshooting quite a bit. Also, the pos_setpoint was wrong during the calculation of the trap_traj, so it was all sorts of screwed up. --- Firmware/MotorControl/axis.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index dcd21b55..76420adf 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -317,6 +317,11 @@ bool Axis::run_closed_loop_control_loop() { // Handle the homing case if (homing_.homing_state == HOMING_STATE_HOMING) { if (min_endstop_.getEndstopState()) { + // pos_setpoint is the starting position for the trap_traj so we need to set it. + controller_.pos_setpoint_ = min_endstop_.config_.offset; + controller_.vel_setpoint_ = 0.0f; // Change directions without decelerating + + // Set our current position in encoder counts to make control more logical encoder_.set_linear_count(min_endstop_.config_.offset); controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; From 2658e9f053fc0fc43e853b94e589ee6d40669f57 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 13 Aug 2019 14:02:57 +0200 Subject: [PATCH 188/549] fix NVM bug This bug prevented the user from saving the configuration more than twice without rebooting. --- Firmware/MotorControl/nvm.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/nvm.c b/Firmware/MotorControl/nvm.c index e581beee..5a1b4a71 100644 --- a/Firmware/MotorControl/nvm.c +++ b/Firmware/MotorControl/nvm.c @@ -362,10 +362,12 @@ int NVM_commit(void) { read_sector_ = 1 - read_sector_; // invalidate the other sector - if (read_sector->index < read_sector->n_data) + if (read_sector->index < read_sector->n_data) { status = set_allocation_state(read_sector, read_sector->index, 1, INVALID); - else + read_sector->index += 1; + } else { status = erase(read_sector); + } return status; } From 6d520d93162fb68603c82b04e46ee8772a859db9 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 13 Aug 2019 14:09:39 +0200 Subject: [PATCH 189/549] remove workaround note for NVM bug --- docs/getting-started.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 8060bbb3..5dac6b3e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -222,8 +222,6 @@ This is 4x the Pulse Per Revolution (PPR) value. Usually this is indicated in th You can save all `.config` parameters to persistent memory so the ODrive remembers them between power cycles. * `odrv0.save_configuration()` Enter. -Due to a [known issue](https://github.com/madcowswe/ODrive/issues/183) it is strongly recommended that you reboot following every save of your configuration using `odrv0.reboot()`. - ## Position control of M0 Let's get motor 0 up and running. The procedure for motor 1 is exactly the same, so feel free to substitute `axis0` wherever it says `axis0`. From 2cc54f980de5a4d452578252496a167c87ff0e1d Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 18 Aug 2019 11:02:28 -0400 Subject: [PATCH 190/549] Cleanup intellisense a bit --- Firmware/.vscode/c_cpp_properties.json | 61 +++----------------------- 1 file changed, 6 insertions(+), 55 deletions(-) diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index 4790c165..9e671e1b 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -3,8 +3,7 @@ { "name": "Win32", "includePath": [ - "${workspaceRoot}/**", - "C:/Tools/doctest/doctest" + "${workspaceRoot}/**" ], "defines": [ "STM32F405xx", @@ -25,23 +24,7 @@ { "name": "Linux", "includePath": [ - "${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", - "${workspaceRoot}/Board/v3/Middlewares/Third_Party/FreeRTOS/Source/include", - "${workspaceRoot}/Board/v3/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS", - "${workspaceRoot}/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Core/Inc", - "${workspaceRoot}/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc", - "${workspaceRoot}/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc", - "${workspaceRoot}/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy", - "${workspaceRoot}/Board/v3/Drivers/CMSIS/Device/ST/STM32F4xx/Include", - "${workspaceRoot}/Board/v3/Drivers/CMSIS/Include", - "${ARM_GCC_ROOT}/arm-none-eabi/include/**", - "${ARM_GCC_ROOT}/lib/gcc/arm-none-eabi/**" + "${workspaceRoot}/**" ], "defines": [ "STM32F405xx", @@ -53,39 +36,15 @@ "__packed=\"__attribute__((__packed__))\"", "__GNUC__" ], - "intelliSenseMode": "clang-x64", - "browse": { - "path": [ - "${workspaceRoot}", - "${ARM_GCC_ROOT}" - ], - "limitSymbolsToIncludedHeaders": true, - "databaseFilename": "" - }, + "intelliSenseMode": "gcc-x64", "compilerPath": "arm-none-eabi-gcc -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" + "cppStandard": "c++17" }, { "name": "Mac", "includePath": [ - "${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", - "${workspaceRoot}/Board/v3/Middlewares/Third_Party/FreeRTOS/Source/include", - "${workspaceRoot}/Board/v3/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS", - "${workspaceRoot}/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Core/Inc", - "${workspaceRoot}/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc", - "${workspaceRoot}/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc", - "${workspaceRoot}/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy", - "${workspaceRoot}/Board/v3/Drivers/CMSIS/Device/ST/STM32F4xx/Include", - "${workspaceRoot}/Board/v3/Drivers/CMSIS/Include", - "${ARM_GCC_ROOT}/arm-none-eabi/include/**", - "${ARM_GCC_ROOT}/lib/gcc/arm-none-eabi/**" + "${workspaceRoot}/**" ], "defines": [ "STM32F405xx", @@ -97,15 +56,7 @@ "__packed=\"__attribute__((__packed__))\"", "__GNUC__" ], - "intelliSenseMode": "clang-x64", - "browse": { - "path": [ - "${workspaceRoot}", - "${ARM_GCC_ROOT}" - ], - "limitSymbolsToIncludedHeaders": true, - "databaseFilename": "" - }, + "intelliSenseMode": "gcc-x64", "cStandard": "c11", "cppStandard": "c++17" } From 9d378f1b67e8e273d4701e537bbeb93f0a08d62e Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 18 Aug 2019 11:13:24 -0400 Subject: [PATCH 191/549] Fix bug in anticogging due to improper scaling --- Firmware/MotorControl/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 8837bae1..5016f7dc 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -243,7 +243,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // We get the current position and apply a current feed-forward // ensuring that we handle negative encoder positions properly (-1 == motor->encoder.encoder_cpr - 1) if (anticogging_valid_) { - Iq += config_.anticogging.cogging_map[std::clamp(mod(static_cast(anticogging_pos), axis_->encoder_.config_.cpr), 0, 3600)]; + Iq += config_.anticogging.cogging_map[std::clamp(mod(static_cast(anticogging_pos), 3600), 0, 3600)]; } float v_err = vel_des - vel_estimate; From 4ba522b139afb3f1ef19fb9ba52634e887cbf4ac Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 18 Aug 2019 11:43:24 -0400 Subject: [PATCH 192/549] Add pre_calibrated, enable anticogging at index search if trrue --- Firmware/MotorControl/controller.hpp | 3 ++- Firmware/MotorControl/encoder.cpp | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 097188d5..d3cee06a 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -35,6 +35,7 @@ public: typedef struct { uint32_t index = 0; float cogging_map[3600]; + bool pre_calibrated = false; bool calib_anticogging = false; float calib_pos_threshold = 1.0f; float calib_vel_threshold = 1.0f; @@ -140,7 +141,7 @@ public: [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this), make_protocol_object("anticogging", make_protocol_ro_property("index", &config_.anticogging.index), - + make_protocol_property("pre_calibrated", &config_.anticogging.pre_calibrated), make_protocol_ro_property("calib_anticogging", &config_.anticogging.calib_anticogging), make_protocol_property("calib_pos_threshold", &config_.anticogging.calib_pos_threshold), make_protocol_property("calib_vel_threshold", &config_.anticogging.calib_vel_threshold), diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 0cc64979..b21c8cef 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -51,6 +51,9 @@ void Encoder::enc_index_cb() { set_linear_count(0); // Avoid position control transient after search if (config_.pre_calibrated) { is_ready_ = true; + if(axis_->controller_.config_.anticogging.pre_calibrated){ + axis_->controller_.anticogging_valid_ = true; + } } else { // We can't use the update_offset facility in set_circular_count because // we also set the linear count before there is a chance to update. Therefore: From 51e81cc8f1becc90b0429e4116221fe7c3dddf40 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 20 Aug 2019 21:13:21 -0400 Subject: [PATCH 193/549] Change taskname to label --- Firmware/.vscode/tasks.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Firmware/.vscode/tasks.json b/Firmware/.vscode/tasks.json index 2376b650..d5b7d969 100644 --- a/Firmware/.vscode/tasks.json +++ b/Firmware/.vscode/tasks.json @@ -4,7 +4,7 @@ "version": "2.0.0", "tasks": [ { - "taskName": "build", + "label": "build", "type": "shell", "command": "make", "group": { @@ -19,13 +19,13 @@ ] }, { - "taskName": "flash", + "label": "flash", "type": "shell", "command": "make flash", "problemMatcher": [] }, { - "taskName": "openocd", + "label": "openocd", "type": "shell", "command": "openocd -f \"interface/stlink-v2.cfg\" -f \"target/stm32f4x_stlink.cfg\" -c \"gdb_port 3333; log_output openocd.log\"", "problemMatcher": [] From 2b58d15d8d42db762d568bff8b58f0a2af0c1cbf Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 20 Aug 2019 23:13:54 -0400 Subject: [PATCH 194/549] Formatting pass --- Firmware/MotorControl/axis.cpp | 133 +++++----- Firmware/MotorControl/axis.hpp | 237 +++++++++--------- Firmware/MotorControl/controller.cpp | 95 ++++--- Firmware/MotorControl/controller.hpp | 107 ++++---- Firmware/MotorControl/encoder.cpp | 229 +++++++++-------- Firmware/MotorControl/encoder.hpp | 152 ++++++----- Firmware/MotorControl/endstop.cpp | 18 +- Firmware/MotorControl/endstop.hpp | 8 +- Firmware/MotorControl/low_level.cpp | 172 ++++++------- Firmware/MotorControl/low_level.h | 4 +- Firmware/MotorControl/main.cpp | 89 +++---- Firmware/MotorControl/motor.cpp | 147 ++++++----- Firmware/MotorControl/motor.hpp | 221 ++++++++-------- Firmware/MotorControl/nvm_config.hpp | 28 +-- .../MotorControl/sensorless_estimator.cpp | 14 +- .../MotorControl/sensorless_estimator.hpp | 34 ++- Firmware/MotorControl/trapTraj.cpp | 40 +-- Firmware/MotorControl/trapTraj.hpp | 18 +- Firmware/MotorControl/utils.c | 42 ++-- Firmware/MotorControl/utils.h | 6 +- 20 files changed, 877 insertions(+), 917 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 76420adf..d85489dd 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -3,9 +3,9 @@ #include #include "gpio.h" +#include "communication/interface_can.hpp" #include "odrive_main.h" #include "utils.h" -#include "communication/interface_can.hpp" Axis::Axis(int axis_num, const AxisHardwareConfig_t& hw_config, @@ -26,13 +26,12 @@ Axis::Axis(int axis_num, motor_(motor), trap_(trap), min_endstop_(min_endstop), - max_endstop_(max_endstop) -{ - encoder_.axis_ = this; + max_endstop_(max_endstop) { + encoder_.axis_ = this; sensorless_estimator_.axis_ = this; - controller_.axis_ = this; - motor_.axis_ = this; - trap_.axis_ = this; + controller_.axis_ = this; + motor_.axis_ = this; + trap_.axis_ = this; decode_step_dir_pins(); watchdog_feed(); min_endstop_.axis_ = this; @@ -41,29 +40,29 @@ Axis::Axis(int axis_num, Axis::LockinConfig_t Axis::default_calibration() { Axis::LockinConfig_t config; - config.current = 10.0f; // [A] - config.ramp_time = 0.4f; // [s] - config.ramp_distance = 1 * M_PI; // [rad] - config.accel = 20.0f; // [rad/s^2] - config.vel = 40.0f; // [rad/s] - config.finish_distance = 100.0f * 2.0f * M_PI; // [rad] - config.finish_on_vel = false; + config.current = 10.0f; // [A] + config.ramp_time = 0.4f; // [s] + config.ramp_distance = 1 * M_PI; // [rad] + config.accel = 20.0f; // [rad/s^2] + config.vel = 40.0f; // [rad/s] + config.finish_distance = 100.0f * 2.0f * M_PI; // [rad] + config.finish_on_vel = false; config.finish_on_distance = true; - config.finish_on_enc_idx = true; + config.finish_on_enc_idx = true; return config; } Axis::LockinConfig_t Axis::default_sensorless() { Axis::LockinConfig_t config; - config.current = 10.0f; // [A] - config.ramp_time = 0.4f; // [s] - config.ramp_distance = 1 * M_PI; // [rad] - config.accel = 200.0f; // [rad/s^2] - config.vel = 400.0f; // [rad/s] - config.finish_distance = 100.0f; // [rad] - config.finish_on_vel = true; + config.current = 10.0f; // [A] + config.ramp_time = 0.4f; // [s] + config.ramp_distance = 1 * M_PI; // [rad] + config.accel = 200.0f; // [rad/s^2] + config.vel = 400.0f; // [rad/s] + config.finish_distance = 100.0f; // [rad] + config.finish_on_vel = true; config.finish_on_distance = false; - config.finish_on_enc_idx = false; + config.finish_on_enc_idx = false; return config; } @@ -71,7 +70,6 @@ static void step_cb_wrapper(void* ctx) { reinterpret_cast(ctx)->step_cb(); } - // @brief Sets up all components of the axis, // such as gate driver and encoder hardware. void Axis::setup() { @@ -87,7 +85,7 @@ static void run_state_machine_loop_wrapper(void* ctx) { // @brief Starts run_state_machine_loop in a new thread void Axis::start_thread() { osThreadDef(thread_def, run_state_machine_loop_wrapper, hw_config_.thread_priority, 0, 4 * 512); - thread_id_ = osThreadCreate(osThread(thread_def), this); + thread_id_ = osThreadCreate(osThread(thread_def), this); thread_id_valid_ = true; } @@ -108,27 +106,27 @@ bool Axis::wait_for_current_meas() { void Axis::step_cb() { if (step_dir_active_) { GPIO_PinState dir_pin = HAL_GPIO_ReadPin(dir_port_, dir_pin_); - float dir = (dir_pin == GPIO_PIN_SET) ? 1.0f : -1.0f; + float dir = (dir_pin == GPIO_PIN_SET) ? 1.0f : -1.0f; controller_.input_pos_ += dir * config_.counts_per_step; controller_.input_pos_updated(); } }; void Axis::load_default_step_dir_pin_config( - const AxisHardwareConfig_t& hw_config, Config_t* config) { + const AxisHardwareConfig_t& hw_config, Config_t* config) { config->step_gpio_pin = hw_config.step_gpio_pin; - config->dir_gpio_pin = hw_config.dir_gpio_pin; + config->dir_gpio_pin = hw_config.dir_gpio_pin; } -void Axis::load_default_can_id(const int& id, Config_t& config){ +void Axis::load_default_can_id(const int& id, Config_t& config) { config.can_node_id = id; } void Axis::decode_step_dir_pins() { step_port_ = get_gpio_port_by_pin(config_.step_gpio_pin); - step_pin_ = get_gpio_pin_by_pin(config_.step_gpio_pin); - dir_port_ = get_gpio_port_by_pin(config_.dir_gpio_pin); - dir_pin_ = get_gpio_pin_by_pin(config_.dir_gpio_pin); + step_pin_ = get_gpio_pin_by_pin(config_.step_gpio_pin); + dir_port_ = get_gpio_port_by_pin(config_.dir_gpio_pin); + dir_pin_ = get_gpio_pin_by_pin(config_.dir_gpio_pin); } // @brief (de)activates step/dir input @@ -136,7 +134,7 @@ void Axis::set_step_dir_active(bool active) { if (active) { // Set up the direction GPIO as input GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Pin = dir_pin_; + GPIO_InitStruct.Pin = dir_pin_; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(dir_port_, &GPIO_InitStruct); @@ -174,9 +172,8 @@ bool Axis::do_checks() { } } - if(board_config.power_supply_wattage > 0.0f && - (Ibus_sum * vbus_voltage) > board_config.power_supply_wattage) - { + if (board_config.power_supply_wattage > 0.0f && + (Ibus_sum * vbus_voltage) > board_config.power_supply_wattage) { error_ |= ERROR_DC_BUS_OVER_POWER; } @@ -221,10 +218,10 @@ bool Axis::watchdog_check() { } } -bool Axis::run_lockin_spin(const LockinConfig_t &lockin_config) { +bool Axis::run_lockin_spin(const LockinConfig_t& lockin_config) { // Spiral up current for softer rotor lock-in lockin_state_ = LOCKIN_STATE_RAMP; - float x = 0.0f; + float x = 0.0f; run_control_loop([&]() { float phase = wrap_pm_pi(lockin_config.ramp_distance * x); float I_mag = lockin_config.current * x; @@ -233,11 +230,11 @@ bool Axis::run_lockin_spin(const LockinConfig_t &lockin_config) { return false; return x < 1.0f; }); - + // Spin states float distance = lockin_config.ramp_distance; - float phase = wrap_pm_pi(distance); - float vel = distance / lockin_config.ramp_time; + float phase = wrap_pm_pi(distance); + float vel = distance / lockin_config.ramp_time; // Function of states to check if we are done auto spin_done = [&](bool vel_override = false) -> bool { @@ -260,7 +257,7 @@ bool Axis::run_lockin_spin(const LockinConfig_t &lockin_config) { if (!motor_.update(lockin_config.current, phase, vel)) return false; - return !spin_done(true); //vel_override to go to next phase + return !spin_done(true); //vel_override to go to next phase }); if (!encoder_.index_found_) @@ -269,7 +266,7 @@ bool Axis::run_lockin_spin(const LockinConfig_t &lockin_config) { // Constant speed if (!spin_done()) { lockin_state_ = LOCKIN_STATE_CONST_VEL; - vel = lockin_config.vel; // reset to actual specified vel to avoid small integration error + vel = lockin_config.vel; // reset to actual specified vel to avoid small integration error run_control_loop([&]() { distance += vel * current_meas_period; phase = wrap_pm_pi(phase + vel * current_meas_period); @@ -286,7 +283,7 @@ bool Axis::run_lockin_spin(const LockinConfig_t &lockin_config) { // Note run_sensorless_control_loop and run_closed_loop_control_loop are very similar and differ only in where we get the estimate from. bool Axis::run_sensorless_control_loop() { - run_control_loop([this](){ + run_control_loop([this]() { if (controller_.config_.control_mode >= Controller::CTRL_MODE_POSITION_CONTROL) return error_ |= ERROR_POS_CTRL_DURING_SENSORLESS, false; @@ -295,7 +292,7 @@ bool Axis::run_sensorless_control_loop() { if (!controller_.update(sensorless_estimator_.pll_pos_, sensorless_estimator_.vel_estimate_, ¤t_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; if (!motor_.update(current_setpoint, sensorless_estimator_.phase_, sensorless_estimator_.vel_estimate_)) - return false; // set_error should update axis.error_ + return false; // set_error should update axis.error_ return true; }); return check_for_errors(); @@ -305,14 +302,14 @@ bool Axis::run_closed_loop_control_loop() { // To avoid any transient on startup, we intialize the setpoint to be the current position controller_.pos_setpoint_ = encoder_.pos_estimate_; set_step_dir_active(config_.enable_step_dir); - run_control_loop([this](){ + run_control_loop([this]() { // Note that all estimators are updated in the loop prefix in run_control_loop float current_setpoint; if (!controller_.update(encoder_.pos_estimate_, encoder_.vel_estimate_, ¤t_setpoint)) - return error_ |= ERROR_CONTROLLER_FAILED, false; //TODO: Make controller.set_error - float phase_vel = 2*M_PI * encoder_.vel_estimate_ / (float)encoder_.config_.cpr * motor_.config_.pole_pairs; + return error_ |= ERROR_CONTROLLER_FAILED, false; //TODO: Make controller.set_error + float phase_vel = 2 * M_PI * encoder_.vel_estimate_ / (float)encoder_.config_.cpr * motor_.config_.pole_pairs; if (!motor_.update(current_setpoint, encoder_.phase_, phase_vel)) - return false; // set_error should update axis.error_ + return false; // set_error should update axis.error_ // Handle the homing case if (homing_.homing_state == HOMING_STATE_HOMING) { @@ -325,21 +322,21 @@ bool Axis::run_closed_loop_control_loop() { encoder_.set_linear_count(min_endstop_.config_.offset); controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; - controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ; + controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ; controller_.input_pos_ = 0.0f; controller_.input_pos_updated(); - controller_.input_vel_ = 0.0f; + controller_.input_vel_ = 0.0f; controller_.input_current_ = 0.0f; homing_.homing_state = HOMING_STATE_MOVE_TO_ZERO; } } else if (homing_.homing_state == HOMING_STATE_MOVE_TO_ZERO) { - if(!min_endstop_.getEndstopState() && controller_.trajectory_done_){ + if (!min_endstop_.getEndstopState() && controller_.trajectory_done_) { controller_.config_.control_mode = homing_.storedControlMode; - controller_.config_.input_mode = homing_.storedInputMode; - homing_.homing_state = HOMING_STATE_IDLE; - homing_.isHomed = true; + controller_.config_.input_mode = homing_.storedInputMode; + homing_.homing_state = HOMING_STATE_IDLE; + homing_.isHomed = true; } } else { // Check for endstop presses @@ -367,7 +364,6 @@ bool Axis::run_idle_loop() { // Infinite loop that does calibration and enters main control loop as appropriate void Axis::run_state_machine_loop() { - // arm! motor_.arm(); @@ -382,12 +378,11 @@ void Axis::run_state_machine_loop() { task_chain_[pos++] = AXIS_STATE_ENCODER_INDEX_SEARCH; if (config_.startup_encoder_offset_calibration) task_chain_[pos++] = AXIS_STATE_ENCODER_OFFSET_CALIBRATION; - if (config_.startup_closed_loop_control){ - if(config_.startup_homing) + if (config_.startup_closed_loop_control) { + if (config_.startup_homing) task_chain_[pos++] = AXIS_STATE_HOMING; task_chain_[pos++] = AXIS_STATE_CLOSED_LOOP_CONTROL; - } - else if (config_.startup_sensorless_control) + } else if (config_.startup_sensorless_control) task_chain_[pos++] = AXIS_STATE_SENSORLESS_CONTROL; task_chain_[pos++] = AXIS_STATE_IDLE; } else if (requested_state_ == AXIS_STATE_FULL_CALIBRATION_SEQUENCE) { @@ -401,7 +396,7 @@ void Axis::run_state_machine_loop() { task_chain_[pos++] = AXIS_STATE_IDLE; } task_chain_[pos++] = AXIS_STATE_UNDEFINED; // TODO: bounds checking - requested_state_ = AXIS_STATE_UNDEFINED; + requested_state_ = AXIS_STATE_UNDEFINED; // Auto-clear any invalid state error error_ &= ~ERROR_INVALID_STATE; } @@ -419,7 +414,7 @@ void Axis::run_state_machine_loop() { case AXIS_STATE_ENCODER_INDEX_SEARCH: { if (!motor_.is_calibrated_) goto invalid_state_label; - if (encoder_.config_.idx_search_unidirectional && motor_.config_.direction==0) + if (encoder_.config_.idx_search_unidirectional && motor_.config_.direction == 0) goto invalid_state_label; status = encoder_.run_index_search(); @@ -443,25 +438,25 @@ void Axis::run_state_machine_loop() { } break; case AXIS_STATE_LOCKIN_SPIN: { - if (!motor_.is_calibrated_ || motor_.config_.direction==0) + if (!motor_.is_calibrated_ || motor_.config_.direction == 0) goto invalid_state_label; status = run_lockin_spin(config_.lockin); } break; case AXIS_STATE_SENSORLESS_CONTROL: { - if (!motor_.is_calibrated_ || motor_.config_.direction==0) - goto invalid_state_label; - status = run_lockin_spin(config_.sensorless_ramp); // TODO: restart if desired + if (!motor_.is_calibrated_ || motor_.config_.direction == 0) + goto invalid_state_label; + status = run_lockin_spin(config_.sensorless_ramp); // TODO: restart if desired if (status) { // call to controller.reset() that happend when arming means that vel_setpoint // is zeroed. So we make the setpoint the spinup target for smooth transition. controller_.vel_setpoint_ = config_.sensorless_ramp.vel; - status = run_sensorless_control_loop(); + status = run_sensorless_control_loop(); } } break; case AXIS_STATE_CLOSED_LOOP_CONTROL: { - if (!motor_.is_calibrated_ || motor_.config_.direction==0) + if (!motor_.is_calibrated_ || motor_.config_.direction == 0) goto invalid_state_label; if (!encoder_.is_ready_) goto invalid_state_label; @@ -470,7 +465,7 @@ void Axis::run_state_machine_loop() { case AXIS_STATE_IDLE: { run_idle_loop(); - status = motor_.arm(); // done with idling - try to arm the motor + status = motor_.arm(); // done with idling - try to arm the motor } break; default: diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 060e3005..af305a43 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -5,7 +5,6 @@ #error "This file should not be included directly. Include odrive_main.h instead." #endif - enum HomingState_t { HOMING_STATE_IDLE, HOMING_STATE_HOMING, @@ -13,52 +12,52 @@ enum HomingState_t { }; class Axis { -public: + public: enum Error_t { - ERROR_NONE = 0x00, - ERROR_INVALID_STATE = 0x01, // + template void run_control_loop(const T& update_handler) { while (requested_state_ == AXIS_STATE_UNDEFINED) { // look for errors at axis level and also all subcomponents bool checks_ok = do_checks(); // Update all estimators // Note: updates run even if checks fail - bool updates_ok = do_updates(); + bool updates_ok = do_updates(); - // make sure the watchdog is being fed. + // make sure the watchdog is being fed. bool watchdog_ok = watchdog_check(); - + 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 @@ -216,7 +215,7 @@ public: } } - bool run_lockin_spin(const LockinConfig_t &lockin_config); + bool run_lockin_spin(const LockinConfig_t& lockin_config); bool run_sensorless_control_loop(); bool run_closed_loop_control_loop(); bool run_idle_loop(); @@ -243,8 +242,8 @@ public: volatile bool thread_id_valid_ = false; // variables exposed on protocol - Error_t error_ = ERROR_NONE; - bool step_dir_active_ = false; // auto enabled after calibration, based on config.enable_step_dir + Error_t error_ = ERROR_NONE; + bool step_dir_active_ = false; // auto enabled after calibration, based on config.enable_step_dir // updated from config in constructor, and on protocol hook GPIO_TypeDef* step_port_; @@ -252,16 +251,16 @@ public: GPIO_TypeDef* dir_port_; uint16_t dir_pin_; - State_t requested_state_ = AXIS_STATE_STARTUP_SEQUENCE; - State_t task_chain_[10] = { AXIS_STATE_UNDEFINED }; - State_t& current_state_ = task_chain_[0]; - uint32_t loop_counter_ = 0; + State_t requested_state_ = AXIS_STATE_STARTUP_SEQUENCE; + State_t task_chain_[10] = {AXIS_STATE_UNDEFINED}; + State_t& current_state_ = task_chain_[0]; + uint32_t loop_counter_ = 0; LockinState_t lockin_state_ = LOCKIN_STATE_INACTIVE; Homing_t homing_; uint32_t last_heartbeat_ = 0; // watchdog - uint32_t watchdog_current_value_= 0; + uint32_t watchdog_current_value_ = 0; // Communication protocol definitions auto make_protocol_definitions() { @@ -275,51 +274,47 @@ public: make_protocol_ro_property("homing_state", &homing_.homing_state), make_protocol_property("is_homed", &homing_.isHomed), make_protocol_object("config", - make_protocol_property("startup_motor_calibration", &config_.startup_motor_calibration), - make_protocol_property("startup_encoder_index_search", &config_.startup_encoder_index_search), - make_protocol_property("startup_encoder_offset_calibration", &config_.startup_encoder_offset_calibration), - make_protocol_property("startup_closed_loop_control", &config_.startup_closed_loop_control), - make_protocol_property("startup_sensorless_control", &config_.startup_sensorless_control), - make_protocol_property("startup_homing", &config_.startup_homing), - 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), - 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, - [](void* ctx) { static_cast(ctx)->decode_step_dir_pins(); }, this), - make_protocol_object("calibration_lockin", - make_protocol_property("current", &config_.calibration_lockin.current), - make_protocol_property("ramp_time", &config_.calibration_lockin.ramp_time), - make_protocol_property("ramp_distance", &config_.calibration_lockin.ramp_distance), - make_protocol_property("accel", &config_.calibration_lockin.accel), - make_protocol_property("vel", &config_.calibration_lockin.vel) - ), - make_protocol_object("sensorless_ramp", - make_protocol_property("current", &config_.sensorless_ramp.current), - make_protocol_property("ramp_time", &config_.sensorless_ramp.ramp_time), - make_protocol_property("ramp_distance", &config_.sensorless_ramp.ramp_distance), - make_protocol_property("accel", &config_.sensorless_ramp.accel), - make_protocol_property("vel", &config_.sensorless_ramp.vel), - make_protocol_property("finish_distance", &config_.sensorless_ramp.finish_distance), - make_protocol_property("finish_on_vel", &config_.sensorless_ramp.finish_on_vel), - make_protocol_property("finish_on_distance", &config_.sensorless_ramp.finish_on_distance), - make_protocol_property("finish_on_enc_idx", &config_.sensorless_ramp.finish_on_enc_idx) - ), - make_protocol_object("general_lockin", - make_protocol_property("current", &config_.lockin.current), - make_protocol_property("ramp_time", &config_.lockin.ramp_time), - make_protocol_property("ramp_distance", &config_.lockin.ramp_distance), - make_protocol_property("accel", &config_.lockin.accel), - make_protocol_property("vel", &config_.lockin.vel), - make_protocol_property("finish_distance", &config_.lockin.finish_distance), - make_protocol_property("finish_on_vel", &config_.lockin.finish_on_vel), - make_protocol_property("finish_on_distance", &config_.lockin.finish_on_distance), - make_protocol_property("finish_on_enc_idx", &config_.lockin.finish_on_enc_idx) - ), - make_protocol_property("can_node_id", &config_.can_node_id), - make_protocol_property("can_heartbeat_rate_ms", &config_.can_heartbeat_rate_ms) - ), + make_protocol_property("startup_motor_calibration", &config_.startup_motor_calibration), + make_protocol_property("startup_encoder_index_search", &config_.startup_encoder_index_search), + make_protocol_property("startup_encoder_offset_calibration", &config_.startup_encoder_offset_calibration), + make_protocol_property("startup_closed_loop_control", &config_.startup_closed_loop_control), + make_protocol_property("startup_sensorless_control", &config_.startup_sensorless_control), + make_protocol_property("startup_homing", &config_.startup_homing), + 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), + 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, + [](void* ctx) { static_cast(ctx)->decode_step_dir_pins(); }, this), + make_protocol_object("calibration_lockin", + make_protocol_property("current", &config_.calibration_lockin.current), + make_protocol_property("ramp_time", &config_.calibration_lockin.ramp_time), + make_protocol_property("ramp_distance", &config_.calibration_lockin.ramp_distance), + make_protocol_property("accel", &config_.calibration_lockin.accel), + make_protocol_property("vel", &config_.calibration_lockin.vel)), + make_protocol_object("sensorless_ramp", + make_protocol_property("current", &config_.sensorless_ramp.current), + make_protocol_property("ramp_time", &config_.sensorless_ramp.ramp_time), + make_protocol_property("ramp_distance", &config_.sensorless_ramp.ramp_distance), + make_protocol_property("accel", &config_.sensorless_ramp.accel), + make_protocol_property("vel", &config_.sensorless_ramp.vel), + make_protocol_property("finish_distance", &config_.sensorless_ramp.finish_distance), + make_protocol_property("finish_on_vel", &config_.sensorless_ramp.finish_on_vel), + make_protocol_property("finish_on_distance", &config_.sensorless_ramp.finish_on_distance), + make_protocol_property("finish_on_enc_idx", &config_.sensorless_ramp.finish_on_enc_idx)), + make_protocol_object("general_lockin", + make_protocol_property("current", &config_.lockin.current), + make_protocol_property("ramp_time", &config_.lockin.ramp_time), + make_protocol_property("ramp_distance", &config_.lockin.ramp_distance), + make_protocol_property("accel", &config_.lockin.accel), + make_protocol_property("vel", &config_.lockin.vel), + make_protocol_property("finish_distance", &config_.lockin.finish_distance), + make_protocol_property("finish_on_vel", &config_.lockin.finish_on_vel), + make_protocol_property("finish_on_distance", &config_.lockin.finish_on_distance), + make_protocol_property("finish_on_enc_idx", &config_.lockin.finish_on_enc_idx)), + make_protocol_property("can_node_id", &config_.can_node_id), + make_protocol_property("can_heartbeat_rate_ms", &config_.can_heartbeat_rate_ms)), make_protocol_object("motor", motor_.make_protocol_definitions()), make_protocol_object("controller", controller_.make_protocol_definitions()), make_protocol_object("encoder", encoder_.make_protocol_definitions()), @@ -328,12 +323,10 @@ public: make_protocol_object("min_endstop", min_endstop_.make_protocol_definitions()), make_protocol_object("max_endstop", max_endstop_.make_protocol_definitions()), make_protocol_function("watchdog_feed", *this, &Axis::watchdog_feed), - make_protocol_function("clear_errors", *this, &Axis::clear_errors) - ); + make_protocol_function("clear_errors", *this, &Axis::clear_errors)); } }; - DEFINE_ENUM_FLAG_OPERATORS(Axis::Error_t) #endif /* __AXIS_HPP */ diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 5016f7dc..a4d1355b 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -3,17 +3,15 @@ #include -Controller::Controller(Config_t& config) : - config_(config) -{ +Controller::Controller(Config_t& config) : config_(config) { update_filter_gains(); } void Controller::reset() { - pos_setpoint_ = 0.0f; - vel_setpoint_ = 0.0f; + pos_setpoint_ = 0.0f; + vel_setpoint_ = 0.0f; vel_integrator_current_ = 0.0f; - current_setpoint_ = 0.0f; + current_setpoint_ = 0.0f; } void Controller::set_error(Error_t error) { @@ -35,13 +33,13 @@ void Controller::move_to_pos(float goal_point) { axis_->trap_.config_.accel_limit, axis_->trap_.config_.decel_limit); traj_start_loop_count_ = axis_->loop_counter_; - trajectory_done_ = false; + trajectory_done_ = false; } -void Controller::move_incremental(float displacement, bool from_input_pos = true){ - if(from_input_pos){ +void Controller::move_incremental(float displacement, bool from_input_pos = true) { + if (from_input_pos) { input_pos_ += displacement; - } else{ + } else { input_pos_ = pos_setpoint_ + displacement; } @@ -62,17 +60,17 @@ void Controller::start_anticogging_calibration() { bool Controller::home_axis() { if (axis_->min_endstop_.config_.enabled) { axis_->homing_.storedControlMode = config_.control_mode; - axis_->homing_.storedInputMode = config_.input_mode; + axis_->homing_.storedInputMode = config_.input_mode; config_.control_mode = CTRL_MODE_VELOCITY_CONTROL; - config_.input_mode = INPUT_MODE_VEL_RAMP; + config_.input_mode = INPUT_MODE_VEL_RAMP; input_pos_ = 0.0f; input_pos_updated(); - input_vel_ = -config_.homing_speed; + input_vel_ = -config_.homing_speed; input_current_ = 0.0f; - axis_->homing_.isHomed = false; + axis_->homing_.isHomed = false; axis_->homing_.homing_state = HOMING_STATE_HOMING; } else { return false; @@ -96,19 +94,19 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) } if (config_.anticogging.index < 3600) { config_.control_mode = CTRL_MODE_POSITION_CONTROL; - input_pos_ = config_.anticogging.index * axis_->encoder_.getCoggingRatio(); - input_vel_ = 0.0f; - input_current_ = 0.0f; + input_pos_ = config_.anticogging.index * axis_->encoder_.getCoggingRatio(); + input_vel_ = 0.0f; + input_current_ = 0.0f; input_pos_updated(); return false; } else { config_.anticogging.index = 0; - config_.control_mode = CTRL_MODE_POSITION_CONTROL; - input_pos_ = 0.0f; // Send the motor home - input_vel_ = 0.0f; - input_current_ = 0.0f; + config_.control_mode = CTRL_MODE_POSITION_CONTROL; + input_pos_ = 0.0f; // Send the motor home + input_vel_ = 0.0f; + input_current_ = 0.0f; input_pos_updated(); - anticogging_valid_ = true; + anticogging_valid_ = true; config_.anticogging.calib_anticogging = false; return true; } @@ -117,8 +115,8 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) } void Controller::update_filter_gains() { - input_filter_ki_ = 2.0f * config_.input_filter_bandwidth; // basic conversion to discrete time - input_filter_kp_ = 0.25f * (input_filter_ki_ * input_filter_ki_); // Critically damped + input_filter_ki_ = 2.0f * config_.input_filter_bandwidth; // basic conversion to discrete time + input_filter_kp_ = 0.25f * (input_filter_ki_ * input_filter_ki_); // Critically damped } namespace { @@ -140,32 +138,32 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // do nothing } break; case INPUT_MODE_PASSTHROUGH: { - pos_setpoint_ = input_pos_; - vel_setpoint_ = input_vel_; + pos_setpoint_ = input_pos_; + vel_setpoint_ = input_vel_; current_setpoint_ = input_current_; } break; case INPUT_MODE_VEL_RAMP: { float max_step_size = std::abs(current_meas_period * config_.vel_ramp_rate); - float full_step = input_vel_ - vel_setpoint_; - float step = std::clamp(full_step, -max_step_size, max_step_size); + float full_step = input_vel_ - vel_setpoint_; + float step = std::clamp(full_step, -max_step_size, max_step_size); vel_setpoint_ += step; current_setpoint_ = step / current_meas_period * config_.inertia; } break; case INPUT_MODE_POS_FILTER: { // 2nd order pos tracking filter - float delta_pos = input_pos_ - pos_setpoint_; // Pos error - float delta_vel = input_vel_ - vel_setpoint_; // Vel error - float accel = input_filter_kp_*delta_pos + input_filter_ki_*delta_vel; // Feedback - current_setpoint_ = accel * config_.inertia; // Accel - vel_setpoint_ += current_meas_period * accel; // delta vel - pos_setpoint_ += current_meas_period * vel_setpoint_; // Delta pos + float delta_pos = input_pos_ - pos_setpoint_; // Pos error + float delta_vel = input_vel_ - vel_setpoint_; // Vel error + float accel = input_filter_kp_ * delta_pos + input_filter_ki_ * delta_vel; // Feedback + current_setpoint_ = accel * config_.inertia; // Accel + vel_setpoint_ += current_meas_period * accel; // delta vel + pos_setpoint_ += current_meas_period * vel_setpoint_; // Delta pos } break; // case INPUT_MODE_MIX_CHANNELS: { // // NOT YET IMPLEMENTED // } break; case INPUT_MODE_TRAP_TRAJ: { - if(input_pos_updated_){ + if (input_pos_updated_) { move_to_pos(input_pos_); input_pos_updated_ = false; } @@ -178,29 +176,28 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s if (t > axis_->trap_.Tf_) { // Drop into position control mode when done to avoid problems on loop counter delta overflow config_.control_mode = CTRL_MODE_POSITION_CONTROL; - pos_setpoint_ = input_pos_; - vel_setpoint_ = 0.0f; - current_setpoint_ = 0.0f; - trajectory_done_ = true; + pos_setpoint_ = input_pos_; + vel_setpoint_ = 0.0f; + current_setpoint_ = 0.0f; + trajectory_done_ = true; } else { TrapezoidalTrajectory::Step_t traj_step = axis_->trap_.eval(t); - pos_setpoint_ = traj_step.Y; - vel_setpoint_ = traj_step.Yd; - current_setpoint_ = traj_step.Ydd * config_.inertia; + pos_setpoint_ = traj_step.Y; + vel_setpoint_ = traj_step.Yd; + current_setpoint_ = traj_step.Ydd * config_.inertia; } - anticogging_pos = pos_setpoint_; // FF the position setpoint instead of the pos_estimate + anticogging_pos = pos_setpoint_; // FF the position setpoint instead of the pos_estimate } break; default: { set_error(ERROR_INVALID_INPUT_MODE); return false; } - } // Position control // TODO Decide if we want to use encoder or pll position here float gain_scheduling_multiplier = 1.0f; - float vel_des = vel_setpoint_; + float vel_des = vel_setpoint_; if (config_.control_mode >= CTRL_MODE_POSITION_CONTROL) { float pos_err; if (config_.setpoints_in_cpr) { @@ -216,7 +213,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s pos_err = pos_setpoint_ - pos_estimate; } vel_des += config_.pos_gain * pos_err; - // V-shaped gain shedule based on position error + // V-shaped gain shedule based on position error float abs_pos_err = fabsf(pos_err); if (config_.enable_gain_scheduling && abs_pos_err <= config_.gain_scheduling_width) { gain_scheduling_multiplier = abs_pos_err / config_.gain_scheduling_width; @@ -261,14 +258,14 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // Current limiting bool limited = false; - float Ilim = axis_->motor_.effective_current_lim(); + float Ilim = axis_->motor_.effective_current_lim(); if (Iq > Ilim) { limited = true; - Iq = Ilim; + Iq = Ilim; } if (Iq < -Ilim) { limited = true; - Iq = -Ilim; + Iq = -Ilim; } // Velocity integrator (behaviour dependent on limiting) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index d3cee06a..502b097a 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -6,24 +6,24 @@ #endif class Controller { -public: + public: enum Error_t { - ERROR_NONE = 0, - ERROR_OVERSPEED = 0x01, + ERROR_NONE = 0, + ERROR_OVERSPEED = 0x01, ERROR_INVALID_INPUT_MODE = 0x02, - ERROR_UNSTABLE_GAIN = 0x04, + ERROR_UNSTABLE_GAIN = 0x04, }; // Note: these should be sorted from lowest level of control to // highest level of control, to allow "<" style comparisons. - enum ControlMode_t{ - CTRL_MODE_VOLTAGE_CONTROL = 0, - CTRL_MODE_CURRENT_CONTROL = 1, + enum ControlMode_t { + CTRL_MODE_VOLTAGE_CONTROL = 0, + CTRL_MODE_CURRENT_CONTROL = 1, CTRL_MODE_VELOCITY_CONTROL = 2, CTRL_MODE_POSITION_CONTROL = 3 }; - enum InputMode_t{ + enum InputMode_t { INPUT_MODE_INACTIVE, INPUT_MODE_PASSTHROUGH, INPUT_MODE_VEL_RAMP, @@ -35,27 +35,27 @@ public: typedef struct { uint32_t index = 0; float cogging_map[3600]; - bool pre_calibrated = false; - bool calib_anticogging = false; + bool pre_calibrated = false; + bool calib_anticogging = false; float calib_pos_threshold = 1.0f; float calib_vel_threshold = 1.0f; - float cogging_ratio = 1.0f; + float cogging_ratio = 1.0f; } Anticogging_t; struct Config_t { ControlMode_t control_mode = CTRL_MODE_POSITION_CONTROL; //see: ControlMode_t - InputMode_t input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t - float pos_gain = 20.0f; // [(counts/s) / counts] - float vel_gain = 5.0f / 10000.0f; // [A/(counts/s)] + InputMode_t input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t + float pos_gain = 20.0f; // [(counts/s) / counts] + float vel_gain = 5.0f / 10000.0f; // [A/(counts/s)] // float vel_gain = 5.0f / 200.0f, // [A/(rad/s)] - float vel_integrator_gain = 10.0f / 10000.0f; // [A/(counts/s * s)] - float vel_limit = 20000.0f; // [counts/s] - float vel_limit_tolerance = 1.2f; // ratio to vel_lim. 0.0f to disable - float vel_ramp_rate = 10000.0f; // [(counts/s) / s] - bool setpoints_in_cpr = false; - float inertia = 0.0f; // [A/(count/s^2)] - float input_filter_bandwidth = 2.0f; // [1/s] - float homing_speed = 2000.0f; // [counts/s] + float vel_integrator_gain = 10.0f / 10000.0f; // [A/(counts/s * s)] + float vel_limit = 20000.0f; // [counts/s] + float vel_limit_tolerance = 1.2f; // ratio to vel_lim. 0.0f to disable + float vel_ramp_rate = 10000.0f; // [(counts/s) / s] + bool setpoints_in_cpr = false; + float inertia = 0.0f; // [A/(count/s^2)] + float input_filter_bandwidth = 2.0f; // [1/s] + float homing_speed = 2000.0f; // [counts/s] Anticogging_t anticogging; float gain_scheduling_width = 10.0f; bool enable_gain_scheduling = false; @@ -72,7 +72,7 @@ public: void move_incremental(float displacement, bool from_goal_point); bool home_axis(); - + // TODO: make this more similar to other calibration loops void start_anticogging_calibration(); bool anticogging_calibration(float pos_estimate, float vel_estimate); @@ -81,7 +81,7 @@ public: bool update(float pos_estimate, float vel_estimate, float* current_setpoint); Config_t& config_; - Axis* axis_ = nullptr; // set by Axis constructor + Axis* axis_ = nullptr; // set by Axis constructor // TODO: anticogging overhaul: // - expose selected (all?) variables on protocol @@ -95,18 +95,18 @@ public: float vel_setpoint_ = 0.0f; // float vel_setpoint = 800.0f; float vel_integrator_current_ = 0.0f; // [A] - float current_setpoint_ = 0.0f; // [A] + float current_setpoint_ = 0.0f; // [A] - float input_pos_ = 0.0f; - float input_vel_ = 0.0f; - float input_current_ = 0.0f; + float input_pos_ = 0.0f; + float input_vel_ = 0.0f; + float input_current_ = 0.0f; float input_filter_kp_ = 0.0f; float input_filter_ki_ = 0.0f; bool input_pos_updated_ = false; - + uint32_t traj_start_loop_count_ = 0; - bool trajectory_done_ = true; + bool trajectory_done_ = true; bool anticogging_valid_ = false; @@ -115,7 +115,7 @@ public: return make_protocol_member_list( make_protocol_property("error", &error_), make_protocol_property("input_pos", &input_pos_, - [](void* ctx) { static_cast(ctx)->input_pos_updated(); }, this), + [](void* ctx) { static_cast(ctx)->input_pos_updated(); }, this), make_protocol_property("input_vel", &input_vel_), make_protocol_property("input_current", &input_current_), make_protocol_ro_property("pos_setpoint", &pos_setpoint_), @@ -127,34 +127,31 @@ public: make_protocol_property("gain_scheduling_width", &config_.gain_scheduling_width), make_protocol_property("enable_gain_scheduling", &config_.enable_gain_scheduling), make_protocol_object("config", - make_protocol_property("control_mode", &config_.control_mode), - make_protocol_property("input_mode", &config_.input_mode), - make_protocol_property("pos_gain", &config_.pos_gain), - make_protocol_property("vel_gain", &config_.vel_gain), - make_protocol_property("vel_integrator_gain", &config_.vel_integrator_gain), - make_protocol_property("vel_limit", &config_.vel_limit), - make_protocol_property("vel_limit_tolerance", &config_.vel_limit_tolerance), - make_protocol_property("vel_ramp_rate", &config_.vel_ramp_rate), - make_protocol_property("homing_speed", &config_.homing_speed), - make_protocol_property("inertia", &config_.inertia), - make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth, - [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this), - make_protocol_object("anticogging", - make_protocol_ro_property("index", &config_.anticogging.index), - make_protocol_property("pre_calibrated", &config_.anticogging.pre_calibrated), - make_protocol_ro_property("calib_anticogging", &config_.anticogging.calib_anticogging), - make_protocol_property("calib_pos_threshold", &config_.anticogging.calib_pos_threshold), - make_protocol_property("calib_vel_threshold", &config_.anticogging.calib_vel_threshold), - make_protocol_ro_property("cogging_ratio", &config_.anticogging.cogging_ratio) - ) - ), + make_protocol_property("control_mode", &config_.control_mode), + make_protocol_property("input_mode", &config_.input_mode), + make_protocol_property("pos_gain", &config_.pos_gain), + make_protocol_property("vel_gain", &config_.vel_gain), + make_protocol_property("vel_integrator_gain", &config_.vel_integrator_gain), + make_protocol_property("vel_limit", &config_.vel_limit), + make_protocol_property("vel_limit_tolerance", &config_.vel_limit_tolerance), + make_protocol_property("vel_ramp_rate", &config_.vel_ramp_rate), + make_protocol_property("homing_speed", &config_.homing_speed), + make_protocol_property("inertia", &config_.inertia), + make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth, + [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this), + make_protocol_object("anticogging", + make_protocol_ro_property("index", &config_.anticogging.index), + make_protocol_property("pre_calibrated", &config_.anticogging.pre_calibrated), + make_protocol_ro_property("calib_anticogging", &config_.anticogging.calib_anticogging), + make_protocol_property("calib_pos_threshold", &config_.anticogging.calib_pos_threshold), + make_protocol_property("calib_vel_threshold", &config_.anticogging.calib_vel_threshold), + make_protocol_ro_property("cogging_ratio", &config_.anticogging.cogging_ratio))), make_protocol_function("move_incremental", *this, &Controller::move_incremental, "displacement", "from_goal_point"), make_protocol_function("start_anticogging_calibration", *this, &Controller::start_anticogging_calibration), - make_protocol_function("home_axis", *this, &Controller::home_axis) - ); + make_protocol_function("home_axis", *this, &Controller::home_axis)); } }; DEFINE_ENUM_FLAG_OPERATORS(Controller::Error_t) -#endif // __CONTROLLER_HPP +#endif // __CONTROLLER_HPP diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index b21c8cef..fc2eac2a 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -1,12 +1,9 @@ #include "odrive_main.h" - Encoder::Encoder(const EncoderHardwareConfig_t& hw_config, - Config_t& config) : - hw_config_(hw_config), - config_(config) -{ + Config_t& config) : hw_config_(hw_config), + config_(config) { update_pll_gains(); if (config.pre_calibrated && (config.mode == Encoder::MODE_HALL || config.mode == Encoder::MODE_SINCOS)) { @@ -22,7 +19,7 @@ void Encoder::setup() { HAL_TIM_Encoder_Start(hw_config_.timer, TIM_CHANNEL_ALL); set_idx_subscribe(); - if(config_.mode & MODE_FLAG_ABS){ + if (config_.mode & MODE_FLAG_ABS) { abs_spi_cs_pin_init(); abs_spi_init(); } @@ -33,7 +30,7 @@ void Encoder::set_error(Error_t error) { axis_->error_ |= Axis::ERROR_ENCODER_FAILED; } -bool Encoder::do_checks(){ +bool Encoder::do_checks() { return error_ == ERROR_NONE; } @@ -48,10 +45,10 @@ void Encoder::enc_index_cb() { 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 + set_linear_count(0); // Avoid position control transient after search if (config_.pre_calibrated) { is_ready_ = true; - if(axis_->controller_.config_.anticogging.pre_calibrated){ + if (axis_->controller_.config_.anticogging.pre_calibrated) { axis_->controller_.anticogging_valid_ = true; } } else { @@ -70,15 +67,15 @@ void Encoder::enc_index_cb() { void Encoder::set_idx_subscribe(bool override_enable) { if (config_.use_index && (override_enable || !config_.find_idx_on_lockin_only)) { GPIO_subscribe(hw_config_.index_port, hw_config_.index_pin, GPIO_PULLDOWN, - enc_index_cb_wrapper, this); + enc_index_cb_wrapper, this); } else if (!config_.use_index || config_.find_idx_on_lockin_only) { GPIO_unsubscribe(hw_config_.index_port, hw_config_.index_pin); } } 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 + 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)) { @@ -99,8 +96,8 @@ void Encoder::set_linear_count(int32_t count) { uint32_t prim = cpu_enter_critical(); // Update states - shadow_count_ = count; - pos_estimate_ = (float)count; + shadow_count_ = count; + pos_estimate_ = (float)count; tim_cnt_sample_ = count; //Write hardware last @@ -122,14 +119,14 @@ void Encoder::set_circular_count(int32_t count, bool update_offset) { // Update states count_in_cpr_ = mod(count, config_.cpr); - pos_cpr_ = (float)count_in_cpr_; + pos_cpr_ = (float)count_in_cpr_; cpu_exit_critical(prim); } bool Encoder::run_index_search() { config_.use_index = true; - index_found_ = false; + index_found_ = false; if (!config_.idx_search_unidirectional && axis_->motor_.config_.direction == 0) { axis_->motor_.config_.direction = 1; } @@ -140,11 +137,11 @@ bool Encoder::run_index_search() { } bool Encoder::run_direction_find() { - int32_t init_enc_val = shadow_count_; - bool orig_finish_on_distance = axis_->config_.calibration_lockin.finish_on_distance; + int32_t init_enc_val = shadow_count_; + bool orig_finish_on_distance = axis_->config_.calibration_lockin.finish_on_distance; axis_->config_.calibration_lockin.finish_on_distance = true; - axis_->motor_.config_.direction = 1; // Must test spin forwards for direction detect logic - bool status = axis_->run_lockin_spin(axis_->config_.calibration_lockin); + axis_->motor_.config_.direction = 1; // Must test spin forwards for direction detect logic + bool status = axis_->run_lockin_spin(axis_->config_.calibration_lockin); axis_->config_.calibration_lockin.finish_on_distance = orig_finish_on_distance; if (status) { @@ -169,7 +166,7 @@ bool Encoder::run_direction_find() { // TODO: Do the scan with current, not voltage! bool Encoder::run_offset_calibration() { static const float start_lock_duration = 1.0f; - static const int num_steps = (int)(config_.calib_scan_distance / config_.calib_scan_omega * (float)current_meas_hz); + static const int num_steps = (int)(config_.calib_scan_distance / config_.calib_scan_omega * (float)current_meas_hz); // Require index found if enabled if (config_.use_index && !index_found_) { @@ -191,9 +188,9 @@ bool Encoder::run_offset_calibration() { // go to motor zero phase for start_lock_duration to get ready to scan int i = 0; - axis_->run_control_loop([&](){ + axis_->run_control_loop([&]() { if (!axis_->motor_.enqueue_voltage_timings(voltage_magnitude, 0.0f)) - return false; // error set inside enqueue_voltage_timings + return false; // error set inside enqueue_voltage_timings axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); return ++i < start_lock_duration * current_meas_hz; }); @@ -201,20 +198,20 @@ bool Encoder::run_offset_calibration() { return false; int32_t init_enc_val = shadow_count_; - int64_t encvaluesum = 0; + int64_t encvaluesum = 0; // scan forward i = 0; - axis_->run_control_loop([&](){ - float phase = wrap_pm_pi(config_.calib_scan_distance * (float)i / (float)num_steps - config_.calib_scan_distance / 2.0f); + axis_->run_control_loop([&]() { + float phase = wrap_pm_pi(config_.calib_scan_distance * (float)i / (float)num_steps - config_.calib_scan_distance / 2.0f); float v_alpha = voltage_magnitude * our_arm_cos_f32(phase); - float v_beta = voltage_magnitude * our_arm_sin_f32(phase); + float v_beta = voltage_magnitude * our_arm_sin_f32(phase); if (!axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta)) - return false; // error set inside enqueue_voltage_timings + return false; // error set inside enqueue_voltage_timings axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); encvaluesum += shadow_count_; - + return ++i < num_steps; }); if (axis_->error_ != Axis::ERROR_NONE) @@ -235,35 +232,34 @@ bool Encoder::run_offset_calibration() { //TODO avoid recomputing elec_rad_per_enc every time // Check CPR - float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / (float)(config_.cpr)); + float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / (float)(config_.cpr)); float expected_encoder_delta = config_.calib_scan_distance / elec_rad_per_enc; - calib_scan_response_ = fabsf(shadow_count_-init_enc_val); - if(fabsf(calib_scan_response_ - expected_encoder_delta)/expected_encoder_delta > config_.calib_range) - { + calib_scan_response_ = fabsf(shadow_count_ - init_enc_val); + if (fabsf(calib_scan_response_ - expected_encoder_delta) / expected_encoder_delta > config_.calib_range) { set_error(ERROR_CPR_OUT_OF_RANGE); return false; } // scan backwards i = 0; - axis_->run_control_loop([&](){ - float phase = wrap_pm_pi(-config_.calib_scan_distance * (float)i / (float)num_steps + config_.calib_scan_distance / 2.0f); + axis_->run_control_loop([&]() { + float phase = wrap_pm_pi(-config_.calib_scan_distance * (float)i / (float)num_steps + config_.calib_scan_distance / 2.0f); float v_alpha = voltage_magnitude * our_arm_cos_f32(phase); - float v_beta = voltage_magnitude * our_arm_sin_f32(phase); + float v_beta = voltage_magnitude * our_arm_sin_f32(phase); if (!axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta)) - return false; // error set inside enqueue_voltage_timings + return false; // error set inside enqueue_voltage_timings axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); encvaluesum += shadow_count_; - + return ++i < num_steps; }); if (axis_->error_ != Axis::ERROR_NONE) return false; - config_.offset = encvaluesum / (num_steps * 2); - int32_t residual = encvaluesum - ((int64_t)config_.offset * (int64_t)(num_steps * 2)); - config_.offset_float = (float)residual / (float)(num_steps * 2) + 0.5f; // add 0.5 to center-align state to phase + config_.offset = encvaluesum / (num_steps * 2); + int32_t residual = encvaluesum - ((int64_t)config_.offset * (int64_t)(num_steps * 2)); + config_.offset_float = (float)residual / (float)(num_steps * 2) + 0.5f; // add 0.5 to center-align state to phase is_ready_ = true; return true; @@ -271,13 +267,26 @@ bool Encoder::run_offset_calibration() { static bool decode_hall(uint8_t hall_state, int32_t* hall_cnt) { switch (hall_state) { - case 0b001: *hall_cnt = 0; return true; - case 0b011: *hall_cnt = 1; return true; - case 0b010: *hall_cnt = 2; return true; - case 0b110: *hall_cnt = 3; return true; - case 0b100: *hall_cnt = 4; return true; - case 0b101: *hall_cnt = 5; return true; - default: return false; + case 0b001: + *hall_cnt = 0; + return true; + case 0b011: + *hall_cnt = 1; + return true; + case 0b010: + *hall_cnt = 2; + return true; + case 0b110: + *hall_cnt = 3; + return true; + case 0b100: + *hall_cnt = 4; + return true; + case 0b101: + *hall_cnt = 5; + return true; + default: + return false; } } @@ -297,37 +306,36 @@ void Encoder::sample_now() { } break; case MODE_SPI_ABS_AMS: - case MODE_SPI_ABS_CUI: - { + case MODE_SPI_ABS_CUI: { // Do nothing } break; default: { - set_error(ERROR_UNSUPPORTED_ENCODER_MODE); + set_error(ERROR_UNSUPPORTED_ENCODER_MODE); } break; } } -bool Encoder::abs_spi_init(){ +bool Encoder::abs_spi_init() { if ((config_.mode & MODE_FLAG_ABS) == 0x0) return false; - uint32_t cr1,cr2; + uint32_t cr1, cr2; cr1 = hw_config_.spi->Instance->CR1; cr2 = hw_config_.spi->Instance->CR2; - SPI_HandleTypeDef * spi = hw_config_.spi; - spi->Init.Mode = SPI_MODE_MASTER; - spi->Init.Direction = SPI_DIRECTION_2LINES; - spi->Init.DataSize = SPI_DATASIZE_16BIT; - spi->Init.CLKPolarity = SPI_POLARITY_LOW; - spi->Init.CLKPhase = SPI_PHASE_2EDGE; - spi->Init.NSS = SPI_NSS_SOFT; + SPI_HandleTypeDef* spi = hw_config_.spi; + spi->Init.Mode = SPI_MODE_MASTER; + spi->Init.Direction = SPI_DIRECTION_2LINES; + spi->Init.DataSize = SPI_DATASIZE_16BIT; + spi->Init.CLKPolarity = SPI_POLARITY_LOW; + spi->Init.CLKPhase = SPI_PHASE_2EDGE; + spi->Init.NSS = SPI_NSS_SOFT; spi->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; - spi->Init.FirstBit = SPI_FIRSTBIT_MSB; - spi->Init.TIMode = SPI_TIMODE_DISABLE; - spi->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; - spi->Init.CRCPolynomial = 10; + spi->Init.FirstBit = SPI_FIRSTBIT_MSB; + spi->Init.TIMode = SPI_TIMODE_DISABLE; + spi->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; + spi->Init.CRCPolynomial = 10; HAL_SPI_DeInit(spi); HAL_SPI_Init(spi); @@ -340,9 +348,9 @@ bool Encoder::abs_spi_init(){ return true; } -bool Encoder::abs_spi_start_transaction(){ - if (config_.mode & MODE_FLAG_ABS){ - if(hw_config_.spi->State != HAL_SPI_STATE_READY){ +bool Encoder::abs_spi_start_transaction() { + if (config_.mode & MODE_FLAG_ABS) { + if (hw_config_.spi->State != HAL_SPI_STATE_READY) { set_error(ERROR_ABS_SPI_NOT_READY); return false; } @@ -350,54 +358,54 @@ bool Encoder::abs_spi_start_transaction(){ hw_config_.spi->Instance->CR1 = abs_spi_cr1; hw_config_.spi->Instance->CR2 = abs_spi_cr2; HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_RESET); - HAL_SPI_TransmitReceive_DMA(hw_config_.spi,(uint8_t*)abs_spi_dma_tx_,(uint8_t*)abs_spi_dma_rx_,1); + HAL_SPI_TransmitReceive_DMA(hw_config_.spi, (uint8_t*)abs_spi_dma_tx_, (uint8_t*)abs_spi_dma_rx_, 1); } return true; } -uint8_t parity(uint16_t v){ +uint8_t parity(uint16_t v) { v ^= v >> 8; v ^= v >> 4; v ^= v >> 2; v ^= v >> 1; return v & 1; } -void Encoder::abs_spi_cb(){ +void Encoder::abs_spi_cb() { HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); switch (config_.mode) { case MODE_SPI_ABS_AMS: { - uint8_t parity_calc, parity_bit; - parity_calc = parity(abs_spi_dma_rx_[0]&0x7FFF); - parity_bit = abs_spi_dma_rx_[0] >>15; + uint8_t parity_calc, parity_bit; + parity_calc = parity(abs_spi_dma_rx_[0] & 0x7FFF); + parity_bit = abs_spi_dma_rx_[0] >> 15; - if(parity_calc == parity_bit){ - pos_abs_ = abs_spi_dma_rx_[0] & 0x3FFF; - // We are going to ignore values all high or low - // This might happen in normal operation, but its unlikely - // The filter will handle these cases - if(pos_abs_ != 0 && pos_abs_ != 0x3FFF) - abs_spi_pos_updated_ = true; - } - }break; + if (parity_calc == parity_bit) { + pos_abs_ = abs_spi_dma_rx_[0] & 0x3FFF; + // We are going to ignore values all high or low + // This might happen in normal operation, but its unlikely + // The filter will handle these cases + if (pos_abs_ != 0 && pos_abs_ != 0x3FFF) + abs_spi_pos_updated_ = true; + } + } break; default: { - set_error(ERROR_UNSUPPORTED_ENCODER_MODE); + set_error(ERROR_UNSUPPORTED_ENCODER_MODE); } break; } is_ready_ = true; } -void Encoder::abs_spi_cs_pin_init(){ +void Encoder::abs_spi_cs_pin_init() { // Decode cs pin abs_spi_cs_port_ = get_gpio_port_by_pin(config_.abs_spi_cs_gpio_pin); - abs_spi_cs_pin_ = get_gpio_pin_by_pin(config_.abs_spi_cs_gpio_pin); + abs_spi_cs_pin_ = get_gpio_pin_by_pin(config_.abs_spi_cs_gpio_pin); // Init cs pin HAL_GPIO_DeInit(abs_spi_cs_port_, abs_spi_cs_pin_); GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Pin = abs_spi_cs_pin_; - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; - GPIO_InitStruct.Pull = GPIO_PULLUP; + GPIO_InitStruct.Pin = abs_spi_cs_pin_; + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(abs_spi_cs_port_, &GPIO_InitStruct); @@ -414,7 +422,7 @@ bool Encoder::update() { //TODO: use count_in_cpr_ instead as shadow_count_ can overflow //or use 64 bit int16_t delta_enc_16 = (int16_t)tim_cnt_sample_ - (int16_t)shadow_count_; - delta_enc = (int32_t)delta_enc_16; //sign extend + delta_enc = (int32_t)delta_enc_16; //sign extend } break; case MODE_HALL: { @@ -433,41 +441,40 @@ bool Encoder::update() { } break; case MODE_SINCOS: { - float phase = fast_atan2(sincos_sample_s_, sincos_sample_c_); + float phase = fast_atan2(sincos_sample_s_, sincos_sample_c_); int fake_count = (int)(1000.0f * phase); //CPR = 6283 = 2pi * 1k delta_enc = fake_count - count_in_cpr_; delta_enc = mod(delta_enc, 6283); - if (delta_enc > 6283/2) + if (delta_enc > 6283 / 2) delta_enc -= 6283; } break; - + case MODE_SPI_ABS_AMS: - case MODE_SPI_ABS_CUI:{ - if(abs_spi_pos_updated_ == false && abs_spi_pos_init_once_){ + case MODE_SPI_ABS_CUI: { + if (abs_spi_pos_updated_ == false && abs_spi_pos_init_once_) { // Low pass filter the error spi_error_rate_ += current_meas_period * (1.0f - spi_error_rate_); // if (spi_error_rate_ > 0.005f) // set_error(ERROR_ABS_SPI_COM_FAIL); - } - else + } else // Low pass filter the error spi_error_rate_ += current_meas_period * (0.0f - spi_error_rate_); abs_spi_pos_updated_ = false; - delta_enc = pos_abs_ - count_in_cpr_; - delta_enc = mod(delta_enc, config_.cpr); - if (delta_enc > config_.cpr/2) + delta_enc = pos_abs_ - count_in_cpr_; + delta_enc = mod(delta_enc, config_.cpr); + if (delta_enc > config_.cpr / 2) delta_enc -= config_.cpr; - if(!abs_spi_pos_init_once_ && delta_enc != 0){ + if (!abs_spi_pos_init_once_ && delta_enc != 0) { abs_spi_pos_init_once_ = true; } - }break; + } break; default: { - set_error(ERROR_UNSUPPORTED_ENCODER_MODE); - return false; + set_error(ERROR_UNSUPPORTED_ENCODER_MODE); + return false; } break; } @@ -475,25 +482,25 @@ bool Encoder::update() { count_in_cpr_ += delta_enc; count_in_cpr_ = mod(count_in_cpr_, config_.cpr); - if(config_.mode & MODE_FLAG_ABS) + if (config_.mode & MODE_FLAG_ABS) count_in_cpr_ = pos_abs_; //// run pll (for now pll is in units of encoder counts) // Predict current pos pos_estimate_ += current_meas_period * vel_estimate_; - pos_cpr_ += current_meas_period * vel_estimate_; + pos_cpr_ += current_meas_period * vel_estimate_; // discrete phase detector float delta_pos = (float)(shadow_count_ - (int32_t)floorf(pos_estimate_)); float delta_pos_cpr = (float)(count_in_cpr_ - (int32_t)floorf(pos_cpr_)); - delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * (float)(config_.cpr)); + delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * (float)(config_.cpr)); // pll feedback pos_estimate_ += current_meas_period * pll_kp_ * delta_pos; - pos_cpr_ += current_meas_period * pll_kp_ * delta_pos_cpr; + pos_cpr_ += current_meas_period * pll_kp_ * delta_pos_cpr; pos_cpr_ = fmodf_pos(pos_cpr_, (float)(config_.cpr)); - vel_estimate_ += current_meas_period * pll_ki_ * delta_pos_cpr; + vel_estimate_ += current_meas_period * pll_ki_ * delta_pos_cpr; bool snap_to_zero_vel = false; if (fabsf(vel_estimate_) < 0.5f * current_meas_period * pll_ki_) { - vel_estimate_ = 0.0f; //align delta-sigma on zero to prevent jitter + vel_estimate_ = 0.0f; //align delta-sigma on zero to prevent jitter snap_to_zero_vel = true; } @@ -502,7 +509,7 @@ bool Encoder::update() { // if we are stopped, make sure we don't randomly drift if (snap_to_zero_vel || !config_.enable_phase_interpolation) { interpolation_ = 0.5f; - // reset interpolation if encoder edge comes + // reset interpolation if encoder edge comes } else if (delta_enc > 0) { interpolation_ = 0.0f; } else if (delta_enc < 0) { @@ -519,7 +526,7 @@ bool Encoder::update() { //// compute electrical phase //TODO avoid recomputing elec_rad_per_enc every time float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / (float)(config_.cpr)); - float ph = elec_rad_per_enc * (interpolated_enc - config_.offset_float); + float ph = elec_rad_per_enc * (interpolated_enc - config_.offset_float); // ph = fmodf(ph, 2*M_PI); phase_ = wrap_pm_pi(ph); diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index c93c08d8..9cf5291b 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -6,18 +6,18 @@ #endif class Encoder { -public: + public: enum Error_t { - ERROR_NONE = 0, - ERROR_UNSTABLE_GAIN = 0x01, - ERROR_CPR_OUT_OF_RANGE = 0x02, - ERROR_NO_RESPONSE = 0x04, + ERROR_NONE = 0, + ERROR_UNSTABLE_GAIN = 0x01, + ERROR_CPR_OUT_OF_RANGE = 0x02, + ERROR_NO_RESPONSE = 0x04, ERROR_UNSUPPORTED_ENCODER_MODE = 0x08, - ERROR_ILLEGAL_HALL_STATE = 0x10, - ERROR_INDEX_NOT_FOUND_YET = 0x20, - ERROR_ABS_SPI_TIMEOUT = 0x40, - ERROR_ABS_SPI_COM_FAIL = 0x80, - ERROR_ABS_SPI_NOT_READY = 0x100, + ERROR_ILLEGAL_HALL_STATE = 0x10, + ERROR_INDEX_NOT_FOUND_YET = 0x20, + ERROR_ABS_SPI_TIMEOUT = 0x40, + ERROR_ABS_SPI_COM_FAIL = 0x80, + ERROR_ABS_SPI_NOT_READY = 0x100, }; enum Mode_t { @@ -31,30 +31,30 @@ public: struct Config_t { Encoder::Mode_t mode = Encoder::MODE_INCREMENTAL; - bool use_index = false; - bool pre_calibrated = false; // If true, this means the offset stored in - // configuration is valid and does not need - // be determined by run_offset_calibration. - // In this case the encoder will enter ready - // state as soon as the index is found. - bool zero_count_on_find_idx = true; - int32_t cpr = (2048 * 4); // Default resolution of CUI-AMT102 encoder, - int32_t offset = 0; // Offset between encoder count and rotor electrical phase - float offset_float = 0.0f; // Sub-count phase alignment offset - bool enable_phase_interpolation = true; // Use velocity to interpolate inside the count state - float calib_range = 0.02f; // Accuracy required to pass encoder cpr check - float calib_scan_distance = 16.0f * M_PI; // rad electrical - float calib_scan_omega = 4.0f * M_PI; // rad/s electrical - float bandwidth = 1000.0f; - bool find_idx_on_lockin_only = false; // Only be sensitive during lockin scan constant vel state - bool idx_search_unidirectional = false; // Only allow index search in known direction - bool ignore_illegal_hall_state = false; // dont error on bad states like 000 or 111 - uint16_t abs_spi_cs_gpio_pin = 0; + bool use_index = false; + bool pre_calibrated = false; // If true, this means the offset stored in + // configuration is valid and does not need + // be determined by run_offset_calibration. + // In this case the encoder will enter ready + // state as soon as the index is found. + bool zero_count_on_find_idx = true; + int32_t cpr = (2048 * 4); // Default resolution of CUI-AMT102 encoder, + int32_t offset = 0; // Offset between encoder count and rotor electrical phase + float offset_float = 0.0f; // Sub-count phase alignment offset + bool enable_phase_interpolation = true; // Use velocity to interpolate inside the count state + float calib_range = 0.02f; // Accuracy required to pass encoder cpr check + float calib_scan_distance = 16.0f * M_PI; // rad electrical + float calib_scan_omega = 4.0f * M_PI; // rad/s electrical + float bandwidth = 1000.0f; + bool find_idx_on_lockin_only = false; // Only be sensitive during lockin scan constant vel state + bool idx_search_unidirectional = false; // Only allow index search in known direction + bool ignore_illegal_hall_state = false; // dont error on bad states like 000 or 111 + uint16_t abs_spi_cs_gpio_pin = 0; }; Encoder(const EncoderHardwareConfig_t& hw_config, - Config_t& config); - + Config_t& config); + void setup(); void set_error(Error_t error); bool do_checks(); @@ -76,27 +76,27 @@ public: const EncoderHardwareConfig_t& hw_config_; Config_t& config_; - Axis* axis_ = nullptr; // set by Axis constructor + Axis* axis_ = nullptr; // set by Axis constructor - Error_t error_ = ERROR_NONE; - bool index_found_ = false; - bool is_ready_ = false; - int32_t shadow_count_ = 0; - int32_t count_in_cpr_ = 0; - float interpolation_ = 0.0f; - float phase_ = 0.0f; // [count] - float pos_estimate_ = 0.0f; // [count] - float pos_cpr_ = 0.0f; // [count] - float vel_estimate_ = 0.0f; // [count/s] - float pll_kp_ = 0.0f; // [count/s / count] - float pll_ki_ = 0.0f; // [(count/s^2) / count] - float calib_scan_response_ = 0.0f; // debug report from offset calib - int32_t pos_abs_ = 0; - float spi_error_rate_ = 0.0f; + Error_t error_ = ERROR_NONE; + bool index_found_ = false; + bool is_ready_ = false; + int32_t shadow_count_ = 0; + int32_t count_in_cpr_ = 0; + float interpolation_ = 0.0f; + float phase_ = 0.0f; // [count] + float pos_estimate_ = 0.0f; // [count] + float pos_cpr_ = 0.0f; // [count] + float vel_estimate_ = 0.0f; // [count/s] + float pll_kp_ = 0.0f; // [count/s / count] + float pll_ki_ = 0.0f; // [(count/s^2) / count] + float calib_scan_response_ = 0.0f; // debug report from offset calib + int32_t pos_abs_ = 0; + float spi_error_rate_ = 0.0f; - int16_t tim_cnt_sample_ = 0; // + int16_t tim_cnt_sample_ = 0; // // Updated by low_level pwm_adc_cb - uint8_t hall_state_ = 0x0; // bit[0] = HallA, .., bit[2] = HallC + uint8_t hall_state_ = 0x0; // bit[0] = HallA, .., bit[2] = HallC float sincos_sample_s_ = 0.0f; float sincos_sample_c_ = 0.0f; @@ -106,14 +106,14 @@ public: void abs_spi_cs_pin_init(); uint16_t abs_spi_dma_tx_[2] = {0xFFFF, 0x0000}; uint16_t abs_spi_dma_rx_[2]; - bool abs_spi_pos_updated_ = false; + bool abs_spi_pos_updated_ = false; bool abs_spi_pos_init_once_ = false; GPIO_TypeDef* abs_spi_cs_port_; uint16_t abs_spi_cs_pin_; uint32_t abs_spi_cr1; uint32_t abs_spi_cr2; - constexpr float getCoggingRatio(){ + constexpr float getCoggingRatio() { return config_.cpr / 3600.0f; } @@ -136,34 +136,32 @@ public: make_protocol_ro_property("spi_error_rate", &spi_error_rate_), make_protocol_object("config", - make_protocol_property("mode", &config_.mode, - [](void* ctx) { static_cast(ctx)->abs_spi_init(); }, this), - make_protocol_property("use_index", &config_.use_index, - [](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, - [](void* ctx) { static_cast(ctx)->check_pre_calibrated(); }, this), - make_protocol_property("abs_spi_cs_gpio_pin", &config_.abs_spi_cs_gpio_pin, - [](void* ctx) { static_cast(ctx)->abs_spi_cs_pin_init(); }, 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), - make_protocol_property("offset_float", &config_.offset_float), - make_protocol_property("enable_phase_interpolation", &config_.enable_phase_interpolation), - make_protocol_property("bandwidth", &config_.bandwidth, - [](void* ctx) { static_cast(ctx)->update_pll_gains(); }, this), - make_protocol_property("calib_range", &config_.calib_range), - make_protocol_property("calib_scan_distance", &config_.calib_scan_distance), - make_protocol_property("calib_scan_omega", &config_.calib_scan_omega), - make_protocol_property("idx_search_unidirectional", &config_.idx_search_unidirectional), - make_protocol_property("ignore_illegal_hall_state", &config_.ignore_illegal_hall_state) - ), - make_protocol_function("set_linear_count", *this, &Encoder::set_linear_count, "count") - ); + make_protocol_property("mode", &config_.mode, + [](void* ctx) { static_cast(ctx)->abs_spi_init(); }, this), + make_protocol_property("use_index", &config_.use_index, + [](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, + [](void* ctx) { static_cast(ctx)->check_pre_calibrated(); }, this), + make_protocol_property("abs_spi_cs_gpio_pin", &config_.abs_spi_cs_gpio_pin, + [](void* ctx) { static_cast(ctx)->abs_spi_cs_pin_init(); }, 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), + make_protocol_property("offset_float", &config_.offset_float), + make_protocol_property("enable_phase_interpolation", &config_.enable_phase_interpolation), + make_protocol_property("bandwidth", &config_.bandwidth, + [](void* ctx) { static_cast(ctx)->update_pll_gains(); }, this), + make_protocol_property("calib_range", &config_.calib_range), + make_protocol_property("calib_scan_distance", &config_.calib_scan_distance), + make_protocol_property("calib_scan_omega", &config_.calib_scan_omega), + make_protocol_property("idx_search_unidirectional", &config_.idx_search_unidirectional), + make_protocol_property("ignore_illegal_hall_state", &config_.ignore_illegal_hall_state)), + make_protocol_function("set_linear_count", *this, &Encoder::set_linear_count, "count")); } }; DEFINE_ENUM_FLAG_OPERATORS(Encoder::Error_t) -#endif // __ENCODER_HPP +#endif // __ENCODER_HPP diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index 809e52f1..da8e260a 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -6,18 +6,18 @@ Endstop::Endstop(Endstop::Config_t& config) } void Endstop::update() { - uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); + uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); - auto last_pin_state = pin_state_; - pin_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); + auto last_pin_state = pin_state_; + pin_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); if (pin_state_ != last_pin_state) { debounce_timer_ = axis_->loop_counter_ * current_meas_period; } if (config_.enabled) { float now = axis_->loop_counter_ * current_meas_period; - if ((now - debounce_timer_) >= (config_.debounce_ms * 0.001f)) { // Debounce timer expired, take the new pin state - endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state - debounce_timer_ = now - (config_.debounce_ms * 0.001f); // Ensure timer doesn't have overflow issues + if ((now - debounce_timer_) >= (config_.debounce_ms * 0.001f)) { // Debounce timer expired, take the new pin state + endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state + debounce_timer_ = now - (config_.debounce_ms * 0.001f); // Ensure timer doesn't have overflow issues } else { endstop_state_ = endstop_state_; // Do nothing } @@ -30,18 +30,18 @@ bool Endstop::getEndstopState() { return endstop_state_; } -void Endstop::update_endstop_config(){ +void Endstop::update_endstop_config() { set_endstop_enabled(config_.enabled); } void Endstop::set_endstop_enabled(bool enable) { if (config_.gpio_num != 0) { - uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); + uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); if (enable) { HAL_GPIO_DeInit(gpio_port, gpio_pin); GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Pin = gpio_pin; + GPIO_InitStruct.Pin = gpio_pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = config_.is_active_high ? GPIO_PULLDOWN : GPIO_PULLUP; HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index cdca3fdc..f1262ed9 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -5,10 +5,10 @@ class Endstop { public: struct Config_t { uint16_t gpio_num; - bool enabled = false; - int32_t offset = 0; + bool enabled = false; + int32_t offset = 0; bool is_active_high = false; - float debounce_ms = 100.0f; + float debounce_ms = 100.0f; }; Endstop(Endstop::Config_t& config); @@ -38,7 +38,7 @@ class Endstop { } private: - bool pin_state_ = false; + bool pin_state_ = false; volatile float debounce_timer_ = 0; }; #endif \ No newline at end of file diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 12d70d39..79349a8a 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -28,21 +28,21 @@ /* Private macros ------------------------------------------------------------*/ /* Private typedef -----------------------------------------------------------*/ /* Global constant data ------------------------------------------------------*/ -const float adc_full_scale = (float)(1 << 12); +const float adc_full_scale = (float)(1 << 12); const float adc_ref_voltage = 3.3f; /* Global variables ----------------------------------------------------------*/ // This value is updated by the DC-bus reading ADC. // Arbitrary non-zero inital value to avoid division by zero if ADC reading is late -float vbus_voltage = 12.0f; +float vbus_voltage = 12.0f; bool brake_resistor_armed = false; /* Private constant data -----------------------------------------------------*/ -static const GPIO_TypeDef* GPIOs_to_samp[] = { GPIOA, GPIOB, GPIOC }; -static const int num_GPIO = sizeof(GPIOs_to_samp) / sizeof(GPIOs_to_samp[0]); +static const GPIO_TypeDef* GPIOs_to_samp[] = {GPIOA, GPIOB, GPIOC}; +static const int num_GPIO = sizeof(GPIOs_to_samp) / sizeof(GPIOs_to_samp[0]); /* Private variables ---------------------------------------------------------*/ // Two motors, sampling port A,B,C (coherent with current meas timing) -static uint16_t GPIO_port_samples [2][num_GPIO]; +static uint16_t GPIO_port_samples[2][num_GPIO]; /* CPU critical section helpers ----------------------------------------------*/ /* Safety critical functions -------------------------------------------------*/ @@ -109,8 +109,8 @@ void safety_critical_arm_motor_pwm(Motor& motor) { // safety_critical_arm_motor_phases is called. // @returns true if the motor was in a state other than disarmed before bool safety_critical_disarm_motor_pwm(Motor& motor) { - uint32_t mask = cpu_enter_critical(); - bool was_armed = motor.armed_state_ != Motor::ARMED_STATE_DISARMED; + uint32_t mask = cpu_enter_critical(); + bool was_armed = motor.armed_state_ != Motor::ARMED_STATE_DISARMED; motor.armed_state_ = Motor::ARMED_STATE_DISARMED; __HAL_TIM_MOE_DISABLE_UNCONDITIONALLY(motor.hw_config_.timer); cpu_exit_critical(mask); @@ -154,7 +154,7 @@ void safety_critical_apply_motor_pwm_timings(Motor& motor, uint16_t timings[3]) // @brief Arms the brake resistor void safety_critical_arm_brake_resistor() { - uint32_t mask = cpu_enter_critical(); + uint32_t mask = cpu_enter_critical(); brake_resistor_armed = true; htim2.Instance->CCR3 = 0; htim2.Instance->CCR4 = TIM_APB1_PERIOD_CLOCKS + 1; @@ -166,7 +166,7 @@ void safety_critical_arm_brake_resistor() { // After calling this, the brake resistor can only be armed again // by calling safety_critical_arm_brake_resistor(). void safety_critical_disarm_brake_resistor() { - uint32_t mask = cpu_enter_critical(); + uint32_t mask = cpu_enter_critical(); brake_resistor_armed = false; htim2.Instance->CCR3 = 0; htim2.Instance->CCR4 = TIM_APB1_PERIOD_CLOCKS + 1; @@ -217,7 +217,7 @@ void start_adc_pwm() { start_pwm(&htim8); // TODO: explain why this offset sync_timers(&htim1, &htim8, TIM_CLOCKSOURCE_ITR0, TIM_1_8_PERIOD_CLOCKS / 2 - 1 * 128, - &htim13); + &htim13); // Motor output starts in the disabled state __HAL_TIM_MOE_DISABLE_UNCONDITIONALLY(&htim1); @@ -242,7 +242,7 @@ void start_adc_pwm() { void start_pwm(TIM_HandleTypeDef* htim) { // Init PWM - int half_load = TIM_1_8_PERIOD_CLOCKS / 2; + int half_load = TIM_1_8_PERIOD_CLOCKS / 2; htim->Instance->CCR1 = half_load; htim->Instance->CCR2 = half_load; htim->Instance->CCR3 = half_load; @@ -265,8 +265,8 @@ void sync_timers(TIM_HandleTypeDef* htim_a, TIM_HandleTypeDef* htim_b, // Store intial timer configs uint16_t MOE_store_a = htim_a->Instance->BDTR & (TIM_BDTR_MOE); uint16_t MOE_store_b = htim_b->Instance->BDTR & (TIM_BDTR_MOE); - uint16_t CR2_store = htim_a->Instance->CR2; - uint16_t SMCR_store = htim_b->Instance->SMCR; + uint16_t CR2_store = htim_a->Instance->CR2; + uint16_t SMCR_store = htim_b->Instance->SMCR; // Turn off output htim_a->Instance->BDTR &= ~(TIM_BDTR_MOE); htim_b->Instance->BDTR &= ~(TIM_BDTR_MOE); @@ -299,12 +299,12 @@ void sync_timers(TIM_HandleTypeDef* htim_a, TIM_HandleTypeDef* htim_b, // Set and start reference timebase timer (if used) if (htim_refbase) { htim_refbase->Instance->CNT = count_offset; - htim_refbase->Instance->CR1 |= (TIM_CR1_CEN); // start + htim_refbase->Instance->CR1 |= (TIM_CR1_CEN); // start } // Start Timer a htim_a->Instance->CR1 |= (TIM_CR1_CEN); // Restore timer configs - htim_a->Instance->CR2 = CR2_store; + htim_a->Instance->CR2 = CR2_store; htim_b->Instance->SMCR = SMCR_store; // restore output htim_a->Instance->BDTR |= MOE_store_a; @@ -312,7 +312,7 @@ void sync_timers(TIM_HandleTypeDef* htim_a, TIM_HandleTypeDef* htim_b, } // @brief ADC1 measurements are written to this buffer by DMA -uint16_t adc_measurements_[ADC_CHANNEL_COUNT] = { 0 }; +uint16_t adc_measurements_[ADC_CHANNEL_COUNT] = {0}; // @brief Starts the general purpose ADC on the ADC1 peripheral. // The measured ADC voltages can be read with get_adc_voltage(). @@ -327,20 +327,19 @@ void start_general_purpose_adc() { ADC_ChannelConfTypeDef sConfig; // Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) - hadc1.Instance = ADC1; - hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; - hadc1.Init.Resolution = ADC_RESOLUTION_12B; - hadc1.Init.ScanConvMode = ENABLE; - hadc1.Init.ContinuousConvMode = ENABLE; + hadc1.Instance = ADC1; + hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; + hadc1.Init.Resolution = ADC_RESOLUTION_12B; + hadc1.Init.ScanConvMode = ENABLE; + hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; - hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; - hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; - hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; - hadc1.Init.NbrOfConversion = ADC_CHANNEL_COUNT; + hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; + hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; + hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; + hadc1.Init.NbrOfConversion = ADC_CHANNEL_COUNT; hadc1.Init.DMAContinuousRequests = ENABLE; - hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; - if (HAL_ADC_Init(&hadc1) != HAL_OK) - { + hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; + if (HAL_ADC_Init(&hadc1) != HAL_OK) { _Error_Handler((char*)__FILE__, __LINE__); } @@ -348,7 +347,7 @@ void start_general_purpose_adc() { sConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES; for (uint32_t channel = 0; channel < ADC_CHANNEL_COUNT; ++channel) { sConfig.Channel = channel << ADC_CR1_AWDCH_Pos; - sConfig.Rank = channel + 1; // rank numbering starts at 1 + sConfig.Rank = channel + 1; // rank numbering starts at 1 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) _Error_Handler((char*)__FILE__, __LINE__); } @@ -414,7 +413,7 @@ float get_adc_voltage(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin) { if (channel < ADC_CHANNEL_COUNT) return ((float)adc_measurements_[channel]) * (adc_ref_voltage / adc_full_scale); else - return 0.0f / 0.0f; // NaN + return 0.0f / 0.0f; // NaN } //-------------------------------- @@ -425,7 +424,7 @@ void vbus_sense_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { static const float voltage_scale = adc_ref_voltage * VBUS_S_DIVIDER_RATIO / adc_full_scale; // Only one conversion in sequence, so only rank1 uint32_t ADCValue = HAL_ADCEx_InjectedGetValue(hadc, ADC_INJECTED_RANK_1); - vbus_voltage = ADCValue * voltage_scale; + vbus_voltage = ADCValue * voltage_scale; if (axes[0] && !axes[0]->error_ && axes[1] && !axes[1]->error_) { if (oscilloscope_pos >= OSCILLOSCOPE_SIZE) oscilloscope_pos = 0; @@ -478,10 +477,10 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { // Motor 1 is on Timer 8, which triggers ADC 2 and 3 on a regular conversion // If the corresponding timer is counting up, we just sampled in SVM vector 0, i.e. real current // If we are counting down, we just sampled in SVM vector 7, with zero current - Axis& axis = injected ? *axes[0] : *axes[1]; - int axis_num = injected ? 0 : 1; - Axis& other_axis = injected ? *axes[1] : *axes[0]; - bool counting_down = axis.motor_.hw_config_.timer->Instance->CR1 & TIM_CR1_DIR; + Axis& axis = injected ? *axes[0] : *axes[1]; + int axis_num = injected ? 0 : 1; + Axis& other_axis = injected ? *axes[1] : *axes[0]; + bool counting_down = axis.motor_.hw_config_.timer->Instance->CR1 & TIM_CR1_DIR; bool current_meas_not_DC_CAL = !counting_down; // Check the timing of the sequencing @@ -493,12 +492,12 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { bool update_timings = false; if (hadc == &hadc2) { if (&axis == axes[1] && counting_down) - update_timings = true; // update timings of M0 + update_timings = true; // update timings of M0 else if (&axis == axes[0] && !counting_down) - update_timings = true; // update timings of M1 + update_timings = true; // update timings of M1 - if((current_meas_not_DC_CAL && !axis_num) || - (axis_num && !current_meas_not_DC_CAL)){ + if ((current_meas_not_DC_CAL && !axis_num) || + (axis_num && !current_meas_not_DC_CAL)) { axis.encoder_.abs_spi_start_transaction(); } } @@ -515,8 +514,7 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { } else { other_axis.motor_.next_timings_valid_ = false; safety_critical_apply_motor_pwm_timings( - other_axis.motor_, other_axis.motor_.next_timings_ - ); + other_axis.motor_, other_axis.motor_.next_timings_); } update_brake_current(); } @@ -559,21 +557,20 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { } void tim_update_cb(TIM_HandleTypeDef* htim) { - // If the corresponding timer is counting up, we just sampled in SVM vector 0, i.e. real current // If we are counting down, we just sampled in SVM vector 7, with zero current bool counting_down = htim->Instance->CR1 & TIM_CR1_DIR; if (counting_down) return; - + int sample_ch; Axis* axis; if (htim == &htim1) { sample_ch = 0; - axis = axes[0]; + axis = axes[0]; } else if (htim == &htim8) { sample_ch = 1; - axis = axes[1]; + axis = axes[1]; } else { low_level_fault(Motor::ERROR_UNEXPECTED_TIMER_CALLBACK); return; @@ -595,10 +592,10 @@ void update_brake_current() { Ibus_sum += axes[i]->motor_.current_control_.Ibus; } } - + // Don't start braking until -Ibus > regen_current_allowed - float brake_current = std::max(-Ibus_sum - board_config.max_regen_current, 0.0f); - float brake_duty = std::max(brake_current * std::abs(board_config.brake_resistance) / vbus_voltage, 0.0f); + float brake_current = std::max(-Ibus_sum - board_config.max_regen_current, 0.0f); + float brake_duty = std::max(brake_current * std::abs(board_config.brake_resistance) / vbus_voltage, 0.0f); // Duty limit at 90% to allow bootstrap caps to charge // If brake_duty is NaN, this expression will also evaluate to false @@ -613,7 +610,6 @@ void update_brake_current() { } } - /* RC PWM input --------------------------------------------------------------*/ // @brief Returns the ODrive GPIO number for a given @@ -641,11 +637,16 @@ int tim_2_5_channel_num_to_gpio_num(int channel) { uint32_t gpio_num_to_tim_2_5_channel(int gpio_num) { #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 switch (gpio_num) { - case 1: return TIM_CHANNEL_1; - case 2: return TIM_CHANNEL_2; - case 3: return TIM_CHANNEL_3; - case 4: return TIM_CHANNEL_4; - default: return 0; + case 1: + return TIM_CHANNEL_1; + case 2: + return TIM_CHANNEL_2; + case 3: + return TIM_CHANNEL_3; + case 4: + return TIM_CHANNEL_4; + default: + return 0; } #else // Only ch4 is available on v3.2 @@ -659,21 +660,22 @@ uint32_t gpio_num_to_tim_2_5_channel(int gpio_num) { void pwm_in_init() { GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_PULLDOWN; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_PULLDOWN; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Alternate = GPIO_AF2_TIM5; TIM_IC_InitTypeDef sConfigIC; - sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_BOTHEDGE; + sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_BOTHEDGE; sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; - sConfigIC.ICFilter = 15; + sConfigIC.ICFilter = 15; #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 for (int gpio_num = 1; gpio_num <= 4; ++gpio_num) { #else - int gpio_num = 4; { + int gpio_num = 4; + { #endif if (is_endpoint_ref_valid(board_config.pwm_mappings[gpio_num - 1].endpoint)) { GPIO_InitStruct.Pin = get_gpio_pin_by_pin(gpio_num); @@ -686,12 +688,12 @@ void pwm_in_init() { } //TODO: These expressions have integer division by 1MHz, so it will be incorrect for clock speeds of not-integer MHz -#define TIM_2_5_CLOCK_HZ TIM_APB1_CLOCK_HZ -#define PWM_MIN_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 1000UL) // 1ms high is considered full reverse -#define PWM_MAX_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 2000UL) // 2ms high is considered full forward -#define PWM_MIN_LEGAL_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 500UL) // ignore high periods shorter than 0.5ms -#define PWM_MAX_LEGAL_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 2500UL) // ignore high periods longer than 2.5ms -#define PWM_INVERT_INPUT false +#define TIM_2_5_CLOCK_HZ TIM_APB1_CLOCK_HZ +#define PWM_MIN_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 1000UL) // 1ms high is considered full reverse +#define PWM_MAX_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 2000UL) // 2ms high is considered full forward +#define PWM_MIN_LEGAL_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 500UL) // ignore high periods shorter than 0.5ms +#define PWM_MAX_LEGAL_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 2500UL) // ignore high periods longer than 2.5ms +#define PWM_INVERT_INPUT false void handle_pulse(int gpio_num, uint32_t high_time) { if (high_time < PWM_MIN_LEGAL_HIGH_TIME || high_time > PWM_MAX_LEGAL_HIGH_TIME) @@ -702,7 +704,7 @@ void handle_pulse(int gpio_num, uint32_t high_time) { if (high_time > PWM_MAX_HIGH_TIME) high_time = PWM_MAX_HIGH_TIME; float fraction = (float)(high_time - PWM_MIN_HIGH_TIME) / (float)(PWM_MAX_HIGH_TIME - PWM_MIN_HIGH_TIME); - float value = board_config.pwm_mappings[gpio_num - 1].min + + float value = board_config.pwm_mappings[gpio_num - 1].min + (fraction * (board_config.pwm_mappings[gpio_num - 1].max - board_config.pwm_mappings[gpio_num - 1].min)); Endpoint* endpoint = get_endpoint(board_config.pwm_mappings[gpio_num - 1].endpoint); @@ -713,41 +715,36 @@ void handle_pulse(int gpio_num, uint32_t high_time) { } void pwm_in_cb(int channel, uint32_t timestamp) { - static uint32_t last_timestamp[GPIO_COUNT] = { 0 }; - static bool last_pin_state[GPIO_COUNT] = { false }; - static bool last_sample_valid[GPIO_COUNT] = { false }; + static uint32_t last_timestamp[GPIO_COUNT] = {0}; + static bool last_pin_state[GPIO_COUNT] = {false}; + static bool last_sample_valid[GPIO_COUNT] = {false}; int gpio_num = tim_2_5_channel_num_to_gpio_num(channel); if (gpio_num < 1 || gpio_num > GPIO_COUNT) return; bool current_pin_state = HAL_GPIO_ReadPin(get_gpio_port_by_pin(gpio_num), get_gpio_pin_by_pin(gpio_num)) != GPIO_PIN_RESET; - if (last_sample_valid[gpio_num - 1] - && (last_pin_state[gpio_num - 1] != PWM_INVERT_INPUT) - && (current_pin_state == PWM_INVERT_INPUT)) { + if (last_sample_valid[gpio_num - 1] && (last_pin_state[gpio_num - 1] != PWM_INVERT_INPUT) && (current_pin_state == PWM_INVERT_INPUT)) { handle_pulse(gpio_num, timestamp - last_timestamp[gpio_num - 1]); } - last_timestamp[gpio_num - 1] = timestamp; - last_pin_state[gpio_num - 1] = current_pin_state; + last_timestamp[gpio_num - 1] = timestamp; + last_pin_state[gpio_num - 1] = current_pin_state; last_sample_valid[gpio_num - 1] = true; } - /* Analog speed control input */ -static void update_analog_endpoint(const struct PWMMapping_t *map, int gpio) -{ +static void update_analog_endpoint(const struct PWMMapping_t* map, int gpio) { float fraction = get_adc_voltage(get_gpio_port_by_pin(gpio), get_gpio_pin_by_pin(gpio)) / 3.3f; - float value = map->min + (fraction * (map->max - map->min)); + float value = map->min + (fraction * (map->max - map->min)); get_endpoint(map->endpoint)->set_from_float(value); } -static void analog_polling_thread(void *) -{ +static void analog_polling_thread(void*) { while (true) { for (int i = 0; i < GPIO_COUNT; i++) { - struct PWMMapping_t *map = &board_config.analog_mappings[i]; + struct PWMMapping_t* map = &board_config.analog_mappings[i]; if (is_endpoint_ref_valid(map->endpoint)) update_analog_endpoint(map, i + 1); @@ -756,16 +753,13 @@ static void analog_polling_thread(void *) } } -void start_analog_thread() -{ - osThreadDef(thread_def, analog_polling_thread, osPriorityLow, 0, 4*512); +void start_analog_thread() { + osThreadDef(thread_def, analog_polling_thread, osPriorityLow, 0, 4 * 512); osThreadCreate(osThread(thread_def), NULL); } - -void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) -{ - if(hspi->pRxBuffPtr == (uint8_t*)axes[0]->encoder_.abs_spi_dma_rx_) +void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef* hspi) { + if (hspi->pRxBuffPtr == (uint8_t*)axes[0]->encoder_.abs_spi_dma_rx_) axes[0]->encoder_.abs_spi_cb(); else if (hspi->pRxBuffPtr == (uint8_t*)axes[1]->encoder_.abs_spi_dma_rx_) axes[1]->encoder_.abs_spi_cb(); diff --git a/Firmware/MotorControl/low_level.h b/Firmware/MotorControl/low_level.h index 503b98e1..e56f4adc 100644 --- a/Firmware/MotorControl/low_level.h +++ b/Firmware/MotorControl/low_level.h @@ -11,9 +11,9 @@ extern "C" { #endif /* Includes ------------------------------------------------------------------*/ +#include #include #include -#include /* Exported types ------------------------------------------------------------*/ /* Exported constants --------------------------------------------------------*/ @@ -69,4 +69,4 @@ inline void cpu_exit_critical(uint32_t priority_mask) { } #endif -#endif //__LOW_LEVEL_H +#endif //__LOW_LEVEL_H diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 17707eaa..173d0239 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -1,13 +1,13 @@ #define __MAIN_CPP__ -#include "odrive_main.h" #include "nvm_config.hpp" +#include "odrive_main.h" -#include "freertos_vars.h" -#include -#include #include +#include +#include #include +#include "freertos_vars.h" BoardConfig_t board_config; ODriveCAN::Config_t can_config; @@ -21,7 +21,7 @@ Endstop::Config_t min_endstop_configs[AXIS_COUNT]; Endstop::Config_t max_endstop_configs[AXIS_COUNT]; bool user_config_loaded_; -SystemStats_t system_stats_ = { 0 }; +SystemStats_t system_stats_ = {0}; Axis *axes[AXIS_COUNT]; ODriveCAN *odCAN; @@ -36,7 +36,8 @@ typedef Config< TrapezoidalTrajectory::Config_t[AXIS_COUNT], Endstop::Config_t[AXIS_COUNT], Endstop::Config_t[AXIS_COUNT], - Axis::Config_t[AXIS_COUNT]> ConfigFormat; + Axis::Config_t[AXIS_COUNT]> + ConfigFormat; void save_configuration(void) { if (ConfigFormat::safe_store_config( @@ -50,7 +51,8 @@ void save_configuration(void) { &min_endstop_configs, &max_endstop_configs, &axis_configs)) { - printf("saving configuration failed\r\n"); osDelay(5); + printf("saving configuration failed\r\n"); + osDelay(5); } else { user_config_loaded_ = true; } @@ -60,26 +62,26 @@ extern "C" int load_configuration(void) { // Try to load configs if (NVM_init() || ConfigFormat::safe_load_config( - &board_config, - &can_config, - &encoder_configs, - &sensorless_configs, - &controller_configs, - &motor_configs, - &trap_configs, - &min_endstop_configs, - &max_endstop_configs, - &axis_configs)) { + &board_config, + &can_config, + &encoder_configs, + &sensorless_configs, + &controller_configs, + &motor_configs, + &trap_configs, + &min_endstop_configs, + &max_endstop_configs, + &axis_configs)) { //If loading failed, restore defaults board_config = BoardConfig_t(); - can_config = ODriveCAN::Config_t(); + can_config = ODriveCAN::Config_t(); for (size_t i = 0; i < AXIS_COUNT; ++i) { - encoder_configs[i] = Encoder::Config_t(); + encoder_configs[i] = Encoder::Config_t(); sensorless_configs[i] = SensorlessEstimator::Config_t(); controller_configs[i] = Controller::Config_t(); - motor_configs[i] = Motor::Config_t(); - trap_configs[i] = TrapezoidalTrajectory::Config_t(); - axis_configs[i] = Axis::Config_t(); + motor_configs[i] = Motor::Config_t(); + trap_configs[i] = TrapezoidalTrajectory::Config_t(); + axis_configs[i] = Axis::Config_t(); // Default step/dir pins are different, so we need to explicitly load them Axis::load_default_step_dir_pin_config(hw_configs[i].axis_config, &axis_configs[i]); Axis::load_default_can_id(i, axis_configs[i]); @@ -98,7 +100,8 @@ void erase_configuration(void) { void enter_dfu_mode() { if ((hw_version_major == 3) && (hw_version_minor >= 5)) { - __asm volatile ("CPSID I\n\t":::"memory"); // disable interrupts + __asm volatile("CPSID I\n\t" :: + : "memory"); // disable interrupts _reboot_cookie = 0xDEADBEEF; NVIC_SystemReset(); } else { @@ -118,26 +121,26 @@ extern "C" { int odrive_main(void); void vApplicationStackOverflowHook(xTaskHandle *pxTask, signed portCHAR *pcTaskName) { - for (;;); // TODO: safe action + for (;;) + ; // TODO: safe action } void vApplicationIdleHook(void) { if (system_stats_.fully_booted) { - system_stats_.uptime = xTaskGetTickCount(); - system_stats_.min_heap_space = xPortGetMinimumEverFreeHeapSize(); - system_stats_.min_stack_space_comms = uxTaskGetStackHighWaterMark(comm_thread) * sizeof(StackType_t); - system_stats_.min_stack_space_axis0 = uxTaskGetStackHighWaterMark(axes[0]->thread_id_) * sizeof(StackType_t); - system_stats_.min_stack_space_axis1 = uxTaskGetStackHighWaterMark(axes[1]->thread_id_) * sizeof(StackType_t); - system_stats_.min_stack_space_usb = uxTaskGetStackHighWaterMark(usb_thread) * sizeof(StackType_t); - system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread) * sizeof(StackType_t); + system_stats_.uptime = xTaskGetTickCount(); + system_stats_.min_heap_space = xPortGetMinimumEverFreeHeapSize(); + system_stats_.min_stack_space_comms = uxTaskGetStackHighWaterMark(comm_thread) * sizeof(StackType_t); + system_stats_.min_stack_space_axis0 = uxTaskGetStackHighWaterMark(axes[0]->thread_id_) * sizeof(StackType_t); + system_stats_.min_stack_space_axis1 = uxTaskGetStackHighWaterMark(axes[1]->thread_id_) * sizeof(StackType_t); + system_stats_.min_stack_space_usb = uxTaskGetStackHighWaterMark(usb_thread) * sizeof(StackType_t); + system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread) * sizeof(StackType_t); system_stats_.min_stack_space_usb_irq = uxTaskGetStackHighWaterMark(usb_irq_thread) * sizeof(StackType_t); system_stats_.min_stack_space_startup = uxTaskGetStackHighWaterMark(defaultTaskHandle) * sizeof(StackType_t); - system_stats_.min_stack_space_can = uxTaskGetStackHighWaterMark(odCAN->thread_id_) * sizeof(StackType_t); + system_stats_.min_stack_space_can = uxTaskGetStackHighWaterMark(odCAN->thread_id_) * sizeof(StackType_t); } } } int odrive_main(void) { - #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 if (board_config.enable_i2c_instead_of_can) { // Set up the direction GPIO as input @@ -166,7 +169,7 @@ int odrive_main(void) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Pin = GPIO_1_Pin; + GPIO_InitStruct.Pin = GPIO_1_Pin; HAL_GPIO_Init(GPIO_1_GPIO_Port, &GPIO_InitStruct); GPIO_InitStruct.Pin = GPIO_2_Pin; HAL_GPIO_Init(GPIO_2_GPIO_Port, &GPIO_InitStruct); @@ -182,20 +185,20 @@ int odrive_main(void) { // Construct all objects. odCAN = new ODriveCAN(&hcan1, can_config); for (size_t i = 0; i < AXIS_COUNT; ++i) { - Encoder *encoder = new Encoder(hw_configs[i].encoder_config, + Encoder *encoder = new Encoder(hw_configs[i].encoder_config, encoder_configs[i]); SensorlessEstimator *sensorless_estimator = new SensorlessEstimator(sensorless_configs[i]); - Controller *controller = new Controller(controller_configs[i]); - Motor *motor = new Motor(hw_configs[i].motor_config, + Controller *controller = new Controller(controller_configs[i]); + Motor *motor = new Motor(hw_configs[i].motor_config, hw_configs[i].gate_driver_config, motor_configs[i]); - TrapezoidalTrajectory *trap = new TrapezoidalTrajectory(trap_configs[i]); - Endstop *min_endstop = new Endstop(min_endstop_configs[i]); - Endstop *max_endstop = new Endstop(max_endstop_configs[i]); - axes[i] = new Axis(i, hw_configs[i].axis_config, axis_configs[i], - *encoder, *sensorless_estimator, *controller, *motor, *trap, *min_endstop, *max_endstop); + TrapezoidalTrajectory *trap = new TrapezoidalTrajectory(trap_configs[i]); + Endstop *min_endstop = new Endstop(min_endstop_configs[i]); + Endstop *max_endstop = new Endstop(max_endstop_configs[i]); + axes[i] = new Axis(i, hw_configs[i].axis_config, axis_configs[i], + *encoder, *sensorless_estimator, *controller, *motor, *trap, *min_endstop, *max_endstop); } - + // Start ADC for temperature measurements and user measurements start_general_purpose_adc(); diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 2a9cd703..92ba073f 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -4,20 +4,18 @@ #include "drv8301.h" #include "odrive_main.h" - Motor::Motor(const MotorHardwareConfig_t& hw_config, const GateDriverHardwareConfig_t& gate_driver_config, - Config_t& config) : - hw_config_(hw_config), - gate_driver_config_(gate_driver_config), - config_(config), - gate_driver_({ - .spiHandle = gate_driver_config_.spi, - .EngpioHandle = gate_driver_config_.enable_port, - .EngpioNumber = gate_driver_config_.enable_pin, - .nCSgpioHandle = gate_driver_config_.nCS_port, - .nCSgpioNumber = gate_driver_config_.nCS_pin, - }) { + Config_t& config) : hw_config_(hw_config), + gate_driver_config_(gate_driver_config), + config_(config), + gate_driver_({ + .spiHandle = gate_driver_config_.spi, + .EngpioHandle = gate_driver_config_.enable_port, + .EngpioNumber = gate_driver_config_.enable_pin, + .nCSgpioHandle = gate_driver_config_.nCS_port, + .nCSgpioNumber = gate_driver_config_.nCS_pin, + }) { update_current_controller_gains(); } @@ -33,7 +31,6 @@ Motor::Motor(const MotorHardwareConfig_t& hw_config, // // @returns: True on success, false otherwise bool Motor::arm() { - // Reset controller states, integrators, setpoints, etc. axis_->controller_.reset(); reset_current_control(); @@ -58,7 +55,7 @@ void Motor::reset_current_control() { void Motor::update_current_controller_gains() { // Calculate current control gains current_control_.p_gain = config_.current_control_bandwidth * config_.phase_inductance; - float plant_pole = config_.phase_resistance / config_.phase_inductance; + float plant_pole = config_.phase_resistance / config_.phase_inductance; current_control_.i_gain = plant_pole * current_control_.p_gain; } @@ -72,29 +69,28 @@ void Motor::DRV8301_setup() { // Solve for exact gain, then snap down to have equal or larger range as requested // or largest possible range otherwise - static const float kMargin = 0.90f; - static const float kTripMargin = 1.0f; // Trip level is at edge of linear range of amplifer - static const float max_output_swing = 1.35f; // [V] out of amplifier - float max_unity_gain_current = kMargin * max_output_swing * hw_config_.shunt_conductance; // [A] - float requested_gain = max_unity_gain_current / config_.requested_current_range; // [V/V] + static const float kMargin = 0.90f; + static const float kTripMargin = 1.0f; // Trip level is at edge of linear range of amplifer + static const float max_output_swing = 1.35f; // [V] out of amplifier + float max_unity_gain_current = kMargin * max_output_swing * hw_config_.shunt_conductance; // [A] + float requested_gain = max_unity_gain_current / config_.requested_current_range; // [V/V] // Decoding array for snapping gain - std::array, 4> gain_choices = { + std::array, 4> gain_choices = { std::make_pair(10.0f, DRV8301_ShuntAmpGain_10VpV), std::make_pair(20.0f, DRV8301_ShuntAmpGain_20VpV), std::make_pair(40.0f, DRV8301_ShuntAmpGain_40VpV), - std::make_pair(80.0f, DRV8301_ShuntAmpGain_80VpV) - }; + std::make_pair(80.0f, DRV8301_ShuntAmpGain_80VpV)}; // We use lower_bound in reverse because it snaps up by default, we want to snap down. - auto gain_snap_down = std::lower_bound(gain_choices.crbegin(), gain_choices.crend(), requested_gain, - [](std::pair pair, float val){ - return pair.first > val; - }); + auto gain_snap_down = std::lower_bound(gain_choices.crbegin(), gain_choices.crend(), requested_gain, + [](std::pair pair, float val) { + return pair.first > val; + }); // If we snap to outside the array, clip to smallest val - if(gain_snap_down == gain_choices.crend()) - --gain_snap_down; + if (gain_snap_down == gain_choices.crend()) + --gain_snap_down; // Values for current controller phase_current_rev_gain_ = 1.0f / gain_snap_down->first; @@ -111,7 +107,7 @@ void Motor::DRV8301_setup() { local_regs->Ctrl_Reg_1.OC_MODE = DRV8301_OcMode_LatchShutDown; // Overcurrent set to approximately 150A at 100degC. This may need tweaking. local_regs->Ctrl_Reg_1.OC_ADJ_SET = DRV8301_VdsLevel_0p730_V; - local_regs->Ctrl_Reg_2.GAIN = gain_snap_down->second; + local_regs->Ctrl_Reg_2.GAIN = gain_snap_down->second; local_regs->SndCmd = true; DRV8301_writeData(&gate_driver_, local_regs); @@ -136,7 +132,7 @@ bool Motor::check_DRV_fault() { return true; } -void Motor::set_error(Motor::Error_t error){ +void Motor::set_error(Motor::Error_t error) { error_ |= error; axis_->error_ |= Axis::ERROR_MOTOR_FAILED; safety_critical_disarm_motor_pwm(*this); @@ -144,17 +140,17 @@ void Motor::set_error(Motor::Error_t error){ } float Motor::get_inverter_temp() { - float adc = adc_measurements_[hw_config_.inverter_thermistor_adc_ch]; + float adc = adc_measurements_[hw_config_.inverter_thermistor_adc_ch]; float normalized_voltage = adc / adc_full_scale; return horner_fma(normalized_voltage, thermistor_poly_coeffs, thermistor_num_coeffs); } bool Motor::update_thermal_limits() { - float fet_temp = get_inverter_temp(); - float temp_margin = config_.inverter_temp_limit_upper - fet_temp; + float fet_temp = get_inverter_temp(); + float temp_margin = config_.inverter_temp_limit_upper - fet_temp; float derating_range = config_.inverter_temp_limit_upper - config_.inverter_temp_limit_lower; thermal_current_lim_ = config_.current_lim * (temp_margin / derating_range); - if (!(thermal_current_lim_ >= 0.0f)) { //Funny polarity to also catch NaN + if (!(thermal_current_lim_ >= 0.0f)) { //Funny polarity to also catch NaN thermal_current_lim_ = 0.0f; } if (fet_temp > config_.inverter_temp_limit_upper + 5) { @@ -181,7 +177,7 @@ float Motor::effective_current_lim() { float current_lim = config_.current_lim; // Hardware limit if (axis_->motor_.config_.motor_type == Motor::MOTOR_TYPE_GIMBAL) { - current_lim = std::min(current_lim, 0.98f*one_by_sqrt3*vbus_voltage); + current_lim = std::min(current_lim, 0.98f * one_by_sqrt3 * vbus_voltage); } else { current_lim = std::min(current_lim, axis_->motor_.current_control_.max_allowed_current); } @@ -193,7 +189,7 @@ float Motor::effective_current_lim() { void Motor::log_timing(TimingLog_t log_idx) { static const uint16_t clocks_per_cnt = (uint16_t)((float)TIM_1_8_CLOCK_HZ / (float)TIM_APB1_CLOCK_HZ); - uint16_t timing = clocks_per_cnt * htim13.Instance->CNT; // TODO: Use a hw_config + uint16_t timing = clocks_per_cnt * htim13.Instance->CNT; // TODO: Use a hw_config if (log_idx < TIMING_LOG_NUM_SLOTS) { timing_log_[log_idx] = timing; @@ -201,10 +197,10 @@ void Motor::log_timing(TimingLog_t log_idx) { } float Motor::phase_current_from_adcval(uint32_t ADCValue) { - int adcval_bal = (int)ADCValue - (1 << 11); + int adcval_bal = (int)ADCValue - (1 << 11); float amp_out_volt = (3.3f / (float)(1 << 12)) * (float)adcval_bal; - float shunt_volt = amp_out_volt * phase_current_rev_gain_; - float current = shunt_volt * hw_config_.shunt_conductance; + float shunt_volt = amp_out_volt * phase_current_rev_gain_; + float current = shunt_volt * hw_config_.shunt_conductance; return current; } @@ -214,12 +210,12 @@ float Motor::phase_current_from_adcval(uint32_t ADCValue) { // TODO check Ibeta balance to verify good motor connection bool Motor::measure_phase_resistance(float test_current, float max_voltage) { - static const float kI = 10.0f; // [(V/s)/A] - static const int num_test_cycles = static_cast(3.0f / CURRENT_MEAS_PERIOD); // Test runs for 3s - float test_voltage = 0.0f; - + static const float kI = 10.0f; // [(V/s)/A] + static const int num_test_cycles = static_cast(3.0f / CURRENT_MEAS_PERIOD); // Test runs for 3s + float test_voltage = 0.0f; + size_t i = 0; - axis_->run_control_loop([&](){ + axis_->run_control_loop([&]() { float Ialpha = -(current_meas_.phB + current_meas_.phC); test_voltage += (kI * current_meas_period) * (test_current - Ialpha); if (test_voltage > max_voltage || test_voltage < -max_voltage) @@ -227,7 +223,7 @@ bool Motor::measure_phase_resistance(float test_current, float max_voltage) { // Test voltage along phase A if (!enqueue_voltage_timings(test_voltage, 0.0f)) - return false; // error set inside enqueue_voltage_timings + return false; // error set inside enqueue_voltage_timings log_timing(TIMING_LOG_MEAS_R); return ++i < num_test_cycles; @@ -239,24 +235,24 @@ bool Motor::measure_phase_resistance(float test_current, float max_voltage) { //if (!enqueue_voltage_timings(motor, 0.0f, 0.0f)) // return false; // error set inside enqueue_voltage_timings - float R = test_voltage / test_current; + float R = test_voltage / test_current; config_.phase_resistance = R; - return true; // if we ran to completion that means success + return true; // if we ran to completion that means success } bool Motor::measure_phase_inductance(float voltage_low, float voltage_high) { - float test_voltages[2] = {voltage_low, voltage_high}; - float Ialphas[2] = {0.0f}; + float test_voltages[2] = {voltage_low, voltage_high}; + float Ialphas[2] = {0.0f}; static const int num_cycles = 5000; size_t t = 0; - axis_->run_control_loop([&](){ + axis_->run_control_loop([&]() { int i = t & 1; Ialphas[i] += -current_meas_.phB - current_meas_.phC; // Test voltage along phase A if (!enqueue_voltage_timings(test_voltages[i], 0.0f)) - return false; // error set inside enqueue_voltage_timings + return false; // error set inside enqueue_voltage_timings log_timing(TIMING_LOG_MEAS_L); return ++t < (num_cycles << 1); @@ -272,7 +268,7 @@ bool Motor::measure_phase_inductance(float voltage_low, float voltage_high) { // Note: A more correct formula would also take into account that there is a finite timestep. // However, the discretisation in the current control loop inverts the same discrepancy float dI_by_dt = (Ialphas[1] - Ialphas[0]) / (current_meas_period * (float)num_cycles); - float L = v_L / dI_by_dt; + float L = v_L / dI_by_dt; config_.phase_inductance = L; // TODO arbitrary values set for now @@ -281,7 +277,6 @@ bool Motor::measure_phase_inductance(float voltage_low, float voltage_high) { return true; } - bool Motor::run_calibration() { float R_calib_max_voltage = config_.resistance_calib_max_voltage; if (config_.motor_type == MOTOR_TYPE_HIGH_CURRENT) { @@ -296,7 +291,7 @@ bool Motor::run_calibration() { } update_current_controller_gains(); - + is_calibrated_ = true; return true; } @@ -305,17 +300,17 @@ bool Motor::enqueue_modulation_timings(float mod_alpha, float mod_beta) { float tA, tB, tC; if (SVM(mod_alpha, mod_beta, &tA, &tB, &tC) != 0) return set_error(ERROR_MODULATION_MAGNITUDE), false; - next_timings_[0] = (uint16_t)(tA * (float)TIM_1_8_PERIOD_CLOCKS); - next_timings_[1] = (uint16_t)(tB * (float)TIM_1_8_PERIOD_CLOCKS); - next_timings_[2] = (uint16_t)(tC * (float)TIM_1_8_PERIOD_CLOCKS); + next_timings_[0] = (uint16_t)(tA * (float)TIM_1_8_PERIOD_CLOCKS); + next_timings_[1] = (uint16_t)(tB * (float)TIM_1_8_PERIOD_CLOCKS); + next_timings_[2] = (uint16_t)(tC * (float)TIM_1_8_PERIOD_CLOCKS); next_timings_valid_ = true; return true; } bool Motor::enqueue_voltage_timings(float v_alpha, float v_beta) { - float vfactor = 1.0f / ((2.0f / 3.0f) * vbus_voltage); + float vfactor = 1.0f / ((2.0f / 3.0f) * vbus_voltage); float mod_alpha = vfactor * v_alpha; - float mod_beta = vfactor * v_beta; + float mod_beta = vfactor * v_beta; if (!enqueue_modulation_timings(mod_alpha, mod_beta)) return false; log_timing(TIMING_LOG_FOC_VOLTAGE); @@ -324,10 +319,10 @@ bool Motor::enqueue_voltage_timings(float v_alpha, float v_beta) { // We should probably make FOC Current call FOC Voltage to avoid duplication. bool Motor::FOC_voltage(float v_d, float v_q, float pwm_phase) { - float c = our_arm_cos_f32(pwm_phase); - float s = our_arm_sin_f32(pwm_phase); - float v_alpha = c*v_d - s*v_q; - float v_beta = c*v_q + s*v_d; + float c = our_arm_cos_f32(pwm_phase); + float s = our_arm_sin_f32(pwm_phase); + float v_alpha = c * v_d - s * v_q; + float v_beta = c * v_q + s * v_d; return enqueue_voltage_timings(v_alpha, v_beta); } @@ -339,21 +334,20 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha ictrl.Iq_setpoint = Iq_des; // Check for current sense saturation - if (fabsf(current_meas_.phB) > ictrl.overcurrent_trip_level - || fabsf(current_meas_.phC) > ictrl.overcurrent_trip_level) { + if (fabsf(current_meas_.phB) > ictrl.overcurrent_trip_level || fabsf(current_meas_.phC) > ictrl.overcurrent_trip_level) { set_error(ERROR_CURRENT_SENSE_SATURATION); return false; } // Clarke transform float Ialpha = -current_meas_.phB - current_meas_.phC; - float Ibeta = one_by_sqrt3 * (current_meas_.phB - current_meas_.phC); + float Ibeta = one_by_sqrt3 * (current_meas_.phB - current_meas_.phC); // Park transform float c_I = our_arm_cos_f32(I_phase); float s_I = our_arm_sin_f32(I_phase); - float Id = c_I * Ialpha + s_I * Ibeta; - float Iq = c_I * Ibeta - s_I * Ialpha; + float Id = c_I * Ialpha + s_I * Ibeta; + float Iq = c_I * Ibeta - s_I * Ialpha; ictrl.Iq_measured += ictrl.I_measured_report_filter_k * (Iq - ictrl.Iq_measured); ictrl.Id_measured += ictrl.I_measured_report_filter_k * (Id - ictrl.Id_measured); @@ -375,8 +369,8 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha float mod_to_V = (2.0f / 3.0f) * vbus_voltage; float V_to_mod = 1.0f / mod_to_V; - float mod_d = V_to_mod * Vd; - float mod_q = V_to_mod * Vq; + float mod_d = V_to_mod * Vd; + float mod_q = V_to_mod * Vq; // Vector modulation saturation, lock integrator if saturated // TODO make maximum modulation configurable @@ -396,24 +390,23 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha ictrl.Ibus = mod_d * Id + mod_q * Iq; // Inverse park transform - float c_p = our_arm_cos_f32(pwm_phase); - float s_p = our_arm_sin_f32(pwm_phase); + float c_p = our_arm_cos_f32(pwm_phase); + float s_p = our_arm_sin_f32(pwm_phase); float mod_alpha = c_p * mod_d - s_p * mod_q; float mod_beta = c_p * mod_q + s_p * mod_d; // Report final applied voltage in stationary frame (for sensorles estimator) ictrl.final_v_alpha = mod_to_V * mod_alpha; - ictrl.final_v_beta = mod_to_V * mod_beta; + ictrl.final_v_beta = mod_to_V * mod_beta; // Apply SVM if (!enqueue_modulation_timings(mod_alpha, mod_beta)) - return false; // error set inside enqueue_modulation_timings + return false; // error set inside enqueue_modulation_timings log_timing(TIMING_LOG_FOC_CURRENT); return true; } - bool Motor::update(float current_setpoint, float phase, float phase_vel) { current_setpoint *= config_.direction; phase *= config_.direction; @@ -424,12 +417,12 @@ bool Motor::update(float current_setpoint, float phase, float phase_vel) { // Execute current command // TODO: move this into the mot if (config_.motor_type == MOTOR_TYPE_HIGH_CURRENT) { - if(!FOC_current(0.0f, current_setpoint, phase, pwm_phase)){ + if (!FOC_current(0.0f, current_setpoint, phase, pwm_phase)) { return false; } } else if (config_.motor_type == MOTOR_TYPE_GIMBAL) { //In gimbal motor mode, current is reinterptreted as voltage. - if(!FOC_voltage(0.0f, current_setpoint, pwm_phase)) + if (!FOC_voltage(0.0f, current_setpoint, pwm_phase)) return false; } else { set_error(ERROR_NOT_IMPLEMENTED_MOTOR_TYPE); diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index d31985ee..5edefc5b 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -8,22 +8,22 @@ #include "drv8301.h" class Motor { -public: + public: enum Error_t { - ERROR_NONE = 0, + ERROR_NONE = 0, ERROR_PHASE_RESISTANCE_OUT_OF_RANGE = 0x0001, ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE = 0x0002, - ERROR_ADC_FAILED = 0x0004, - ERROR_DRV_FAULT = 0x0008, - ERROR_CONTROL_DEADLINE_MISSED = 0x0010, - ERROR_NOT_IMPLEMENTED_MOTOR_TYPE = 0x0020, - ERROR_BRAKE_CURRENT_OUT_OF_RANGE = 0x0040, - ERROR_MODULATION_MAGNITUDE = 0x0080, - ERROR_BRAKE_DEADTIME_VIOLATION = 0x0100, - ERROR_UNEXPECTED_TIMER_CALLBACK = 0x0200, - ERROR_CURRENT_SENSE_SATURATION = 0x0400, - ERROR_INVERTER_OVER_TEMP = 0x0800, - ERROR_CURRENT_UNSTABLE = 0x1000 + ERROR_ADC_FAILED = 0x0004, + ERROR_DRV_FAULT = 0x0008, + ERROR_CONTROL_DEADLINE_MISSED = 0x0010, + ERROR_NOT_IMPLEMENTED_MOTOR_TYPE = 0x0020, + ERROR_BRAKE_CURRENT_OUT_OF_RANGE = 0x0040, + ERROR_MODULATION_MAGNITUDE = 0x0080, + ERROR_BRAKE_DEADTIME_VIOLATION = 0x0100, + ERROR_UNEXPECTED_TIMER_CALLBACK = 0x0200, + ERROR_CURRENT_SENSE_SATURATION = 0x0400, + ERROR_INVERTER_OVER_TEMP = 0x0800, + ERROR_CURRENT_UNSTABLE = 0x1000 }; enum MotorType_t { @@ -37,41 +37,41 @@ public: float phC; }; - struct CurrentControl_t{ - float p_gain; // [V/A] - float i_gain; // [V/As] - float v_current_control_integral_d; // [V] - float v_current_control_integral_q; // [V] - float Ibus; // DC bus current [A] + struct CurrentControl_t { + float p_gain; // [V/A] + float i_gain; // [V/As] + float v_current_control_integral_d; // [V] + float v_current_control_integral_q; // [V] + float Ibus; // DC bus current [A] // Voltage applied at end of cycle: - float final_v_alpha; // [V] - float final_v_beta; // [V] - float Iq_setpoint; // [A] - float Iq_measured; // [A] - float Id_measured; // [A] + float final_v_alpha; // [V] + float final_v_beta; // [V] + float Iq_setpoint; // [A] + float Iq_measured; // [A] + float Id_measured; // [A] float I_measured_report_filter_k; - float max_allowed_current; // [A] - float overcurrent_trip_level; // [A] + float max_allowed_current; // [A] + float overcurrent_trip_level; // [A] }; // NOTE: for gimbal motors, all units of A are instead V. // example: vel_gain is [V/(count/s)] instead of [A/(count/s)] // example: current_lim and calibration_current will instead determine the maximum voltage applied to the motor. struct Config_t { - bool pre_calibrated = false; // can be set to true to indicate that all values here are valid - int32_t pole_pairs = 7; - float calibration_current = 10.0f; // [A] - float resistance_calib_max_voltage = 2.0f; // [V] - You may need to increase this if this voltage isn't sufficient to drive calibration_current through the motor. - float phase_inductance = 0.0f; // to be set by measure_phase_inductance - float phase_resistance = 0.0f; // to be set by measure_phase_resistance - int32_t direction = 0; // 1 or -1 (0 = unspecified) - MotorType_t motor_type = MOTOR_TYPE_HIGH_CURRENT; + bool pre_calibrated = false; // can be set to true to indicate that all values here are valid + int32_t pole_pairs = 7; + float calibration_current = 10.0f; // [A] + float resistance_calib_max_voltage = 2.0f; // [V] - You may need to increase this if this voltage isn't sufficient to drive calibration_current through the motor. + float phase_inductance = 0.0f; // to be set by measure_phase_inductance + float phase_resistance = 0.0f; // to be set by measure_phase_resistance + int32_t direction = 0; // 1 or -1 (0 = unspecified) + MotorType_t motor_type = MOTOR_TYPE_HIGH_CURRENT; // Read out max_allowed_current to see max supported value for current_lim. // float current_lim = 70.0f; //[A] - float current_lim = 10.0f; //[A] + float current_lim = 10.0f; //[A] float current_lim_tolerance = 1.25f; // multiple of current_lim // Value used to compute shunt amplifier gains - float requested_current_range = 60.0f; // [A] + float requested_current_range = 60.0f; // [A] float current_control_bandwidth = 1000.0f; // [rad/s] float inverter_temp_limit_lower = 100; float inverter_temp_limit_upper = 120; @@ -98,8 +98,8 @@ public: }; Motor(const MotorHardwareConfig_t& hw_config, - const GateDriverHardwareConfig_t& gate_driver_config, - Config_t& config); + const GateDriverHardwareConfig_t& gate_driver_config, + Config_t& config); bool arm(); void disarm(); @@ -130,48 +130,47 @@ public: const MotorHardwareConfig_t& hw_config_; const GateDriverHardwareConfig_t gate_driver_config_; Config_t& config_; - Axis* axis_ = nullptr; // set by Axis constructor + Axis* axis_ = nullptr; // set by Axis constructor -//private: + //private: - DRV8301_Obj gate_driver_; // initialized in constructor + DRV8301_Obj gate_driver_; // initialized in constructor uint16_t next_timings_[3] = { TIM_1_8_PERIOD_CLOCKS / 2, TIM_1_8_PERIOD_CLOCKS / 2, - TIM_1_8_PERIOD_CLOCKS / 2 - }; - bool next_timings_valid_ = false; - uint16_t last_cpu_time_ = 0; - int timing_log_index_ = 0; - uint16_t timing_log_[TIMING_LOG_NUM_SLOTS] = { 0 }; + TIM_1_8_PERIOD_CLOCKS / 2}; + bool next_timings_valid_ = false; + uint16_t last_cpu_time_ = 0; + int timing_log_index_ = 0; + uint16_t timing_log_[TIMING_LOG_NUM_SLOTS] = {0}; // variables exposed on protocol Error_t error_ = ERROR_NONE; // Do not write to this variable directly! // It is for exclusive use by the safety_critical_... functions. - ArmedState_t armed_state_ = ARMED_STATE_DISARMED; - bool is_calibrated_ = config_.pre_calibrated; - Iph_BC_t current_meas_ = {0.0f, 0.0f}; - Iph_BC_t DC_calib_ = {0.0f, 0.0f}; - float phase_current_rev_gain_ = 0.0f; // Reverse gain for ADC to Amps (to be set by DRV8301_setup) + ArmedState_t armed_state_ = ARMED_STATE_DISARMED; + bool is_calibrated_ = config_.pre_calibrated; + Iph_BC_t current_meas_ = {0.0f, 0.0f}; + Iph_BC_t DC_calib_ = {0.0f, 0.0f}; + float phase_current_rev_gain_ = 0.0f; // Reverse gain for ADC to Amps (to be set by DRV8301_setup) CurrentControl_t current_control_ = { - .p_gain = 0.0f, // [V/A] should be auto set after resistance and inductance measurement - .i_gain = 0.0f, // [V/As] should be auto set after resistance and inductance measurement + .p_gain = 0.0f, // [V/A] should be auto set after resistance and inductance measurement + .i_gain = 0.0f, // [V/As] should be auto set after resistance and inductance measurement .v_current_control_integral_d = 0.0f, .v_current_control_integral_q = 0.0f, - .Ibus = 0.0f, - .final_v_alpha = 0.0f, - .final_v_beta = 0.0f, - .Iq_setpoint = 0.0f, - .Iq_measured = 0.0f, - .Id_measured = 0.0f, - .I_measured_report_filter_k = 1.0f, - .max_allowed_current = 0.0f, - .overcurrent_trip_level = 0.0f, + .Ibus = 0.0f, + .final_v_alpha = 0.0f, + .final_v_beta = 0.0f, + .Iq_setpoint = 0.0f, + .Iq_measured = 0.0f, + .Id_measured = 0.0f, + .I_measured_report_filter_k = 1.0f, + .max_allowed_current = 0.0f, + .overcurrent_trip_level = 0.0f, }; DRV8301_FaultType_e drv_fault_ = DRV8301_FaultType_NoFault; - DRV_SPI_8301_Vars_t gate_driver_regs_; //Local view of DRV registers (initialized by DRV8301_setup) - float thermal_current_lim_ = 10.0f; //[A] + DRV_SPI_8301_Vars_t gate_driver_regs_; //Local view of DRV registers (initialized by DRV8301_setup) + float thermal_current_lim_ = 10.0f; //[A] // Communication protocol definitions auto make_protocol_definitions() { @@ -187,59 +186,55 @@ public: make_protocol_ro_property("thermal_current_lim", &thermal_current_lim_), make_protocol_function("get_inverter_temp", *this, &Motor::get_inverter_temp), make_protocol_object("current_control", - make_protocol_property("p_gain", ¤t_control_.p_gain), - make_protocol_property("i_gain", ¤t_control_.i_gain), - make_protocol_property("v_current_control_integral_d", ¤t_control_.v_current_control_integral_d), - make_protocol_property("v_current_control_integral_q", ¤t_control_.v_current_control_integral_q), - make_protocol_property("Ibus", ¤t_control_.Ibus), - make_protocol_property("final_v_alpha", ¤t_control_.final_v_alpha), - make_protocol_property("final_v_beta", ¤t_control_.final_v_beta), - make_protocol_property("Iq_setpoint", ¤t_control_.Iq_setpoint), - make_protocol_property("Iq_measured", ¤t_control_.Iq_measured), - make_protocol_property("Id_measured", ¤t_control_.Id_measured), - make_protocol_property("I_measured_report_filter_k", ¤t_control_.I_measured_report_filter_k), - make_protocol_ro_property("max_allowed_current", ¤t_control_.max_allowed_current), - make_protocol_ro_property("overcurrent_trip_level", ¤t_control_.overcurrent_trip_level) - ), + make_protocol_property("p_gain", ¤t_control_.p_gain), + make_protocol_property("i_gain", ¤t_control_.i_gain), + make_protocol_property("v_current_control_integral_d", ¤t_control_.v_current_control_integral_d), + make_protocol_property("v_current_control_integral_q", ¤t_control_.v_current_control_integral_q), + make_protocol_property("Ibus", ¤t_control_.Ibus), + make_protocol_property("final_v_alpha", ¤t_control_.final_v_alpha), + make_protocol_property("final_v_beta", ¤t_control_.final_v_beta), + make_protocol_property("Iq_setpoint", ¤t_control_.Iq_setpoint), + make_protocol_property("Iq_measured", ¤t_control_.Iq_measured), + make_protocol_property("Id_measured", ¤t_control_.Id_measured), + make_protocol_property("I_measured_report_filter_k", ¤t_control_.I_measured_report_filter_k), + make_protocol_ro_property("max_allowed_current", ¤t_control_.max_allowed_current), + make_protocol_ro_property("overcurrent_trip_level", ¤t_control_.overcurrent_trip_level)), make_protocol_object("gate_driver", - make_protocol_ro_property("drv_fault", &drv_fault_) - // make_protocol_ro_property("status_reg_1", &gate_driver_regs_.Stat_Reg_1_Value), - // make_protocol_ro_property("status_reg_2", &gate_driver_regs_.Stat_Reg_2_Value), - // make_protocol_ro_property("ctrl_reg_1", &gate_driver_regs_.Ctrl_Reg_1_Value), - // make_protocol_ro_property("ctrl_reg_2", &gate_driver_regs_.Ctrl_Reg_2_Value) - ), + make_protocol_ro_property("drv_fault", &drv_fault_) + // make_protocol_ro_property("status_reg_1", &gate_driver_regs_.Stat_Reg_1_Value), + // make_protocol_ro_property("status_reg_2", &gate_driver_regs_.Stat_Reg_2_Value), + // make_protocol_ro_property("ctrl_reg_1", &gate_driver_regs_.Ctrl_Reg_1_Value), + // make_protocol_ro_property("ctrl_reg_2", &gate_driver_regs_.Ctrl_Reg_2_Value) + ), make_protocol_object("timing_log", - make_protocol_ro_property("TIMING_LOG_GENERAL", &timing_log_[TIMING_LOG_GENERAL]), - make_protocol_ro_property("TIMING_LOG_ADC_CB_I", &timing_log_[TIMING_LOG_ADC_CB_I]), - make_protocol_ro_property("TIMING_LOG_ADC_CB_DC", &timing_log_[TIMING_LOG_ADC_CB_DC]), - make_protocol_ro_property("TIMING_LOG_MEAS_R", &timing_log_[TIMING_LOG_MEAS_R]), - make_protocol_ro_property("TIMING_LOG_MEAS_L", &timing_log_[TIMING_LOG_MEAS_L]), - make_protocol_ro_property("TIMING_LOG_ENC_CALIB", &timing_log_[TIMING_LOG_ENC_CALIB]), - make_protocol_ro_property("TIMING_LOG_IDX_SEARCH", &timing_log_[TIMING_LOG_IDX_SEARCH]), - make_protocol_ro_property("TIMING_LOG_FOC_VOLTAGE", &timing_log_[TIMING_LOG_FOC_VOLTAGE]), - make_protocol_ro_property("TIMING_LOG_FOC_CURRENT", &timing_log_[TIMING_LOG_FOC_CURRENT]) - ), + make_protocol_ro_property("TIMING_LOG_GENERAL", &timing_log_[TIMING_LOG_GENERAL]), + make_protocol_ro_property("TIMING_LOG_ADC_CB_I", &timing_log_[TIMING_LOG_ADC_CB_I]), + make_protocol_ro_property("TIMING_LOG_ADC_CB_DC", &timing_log_[TIMING_LOG_ADC_CB_DC]), + make_protocol_ro_property("TIMING_LOG_MEAS_R", &timing_log_[TIMING_LOG_MEAS_R]), + make_protocol_ro_property("TIMING_LOG_MEAS_L", &timing_log_[TIMING_LOG_MEAS_L]), + make_protocol_ro_property("TIMING_LOG_ENC_CALIB", &timing_log_[TIMING_LOG_ENC_CALIB]), + make_protocol_ro_property("TIMING_LOG_IDX_SEARCH", &timing_log_[TIMING_LOG_IDX_SEARCH]), + make_protocol_ro_property("TIMING_LOG_FOC_VOLTAGE", &timing_log_[TIMING_LOG_FOC_VOLTAGE]), + make_protocol_ro_property("TIMING_LOG_FOC_CURRENT", &timing_log_[TIMING_LOG_FOC_CURRENT])), make_protocol_object("config", - make_protocol_property("pre_calibrated", &config_.pre_calibrated), - make_protocol_property("pole_pairs", &config_.pole_pairs), - make_protocol_property("calibration_current", &config_.calibration_current), - make_protocol_property("resistance_calib_max_voltage", &config_.resistance_calib_max_voltage), - make_protocol_property("phase_inductance", &config_.phase_inductance), - make_protocol_property("phase_resistance", &config_.phase_resistance), - make_protocol_property("direction", &config_.direction), - make_protocol_property("motor_type", &config_.motor_type), - make_protocol_property("current_lim", &config_.current_lim), - make_protocol_property("current_lim_tolerance", &config_.current_lim_tolerance), - make_protocol_property("inverter_temp_limit_lower", &config_.inverter_temp_limit_lower), - make_protocol_property("inverter_temp_limit_upper", &config_.inverter_temp_limit_upper), - make_protocol_property("requested_current_range", &config_.requested_current_range), - make_protocol_property("current_control_bandwidth", &config_.current_control_bandwidth, - [](void* ctx) { static_cast(ctx)->update_current_controller_gains(); }, this) - ) - ); + make_protocol_property("pre_calibrated", &config_.pre_calibrated), + make_protocol_property("pole_pairs", &config_.pole_pairs), + make_protocol_property("calibration_current", &config_.calibration_current), + make_protocol_property("resistance_calib_max_voltage", &config_.resistance_calib_max_voltage), + make_protocol_property("phase_inductance", &config_.phase_inductance), + make_protocol_property("phase_resistance", &config_.phase_resistance), + make_protocol_property("direction", &config_.direction), + make_protocol_property("motor_type", &config_.motor_type), + make_protocol_property("current_lim", &config_.current_lim), + make_protocol_property("current_lim_tolerance", &config_.current_lim_tolerance), + make_protocol_property("inverter_temp_limit_lower", &config_.inverter_temp_limit_lower), + make_protocol_property("inverter_temp_limit_upper", &config_.inverter_temp_limit_upper), + make_protocol_property("requested_current_range", &config_.requested_current_range), + make_protocol_property("current_control_bandwidth", &config_.current_control_bandwidth, + [](void* ctx) { static_cast(ctx)->update_current_controller_gains(); }, this))); } }; DEFINE_ENUM_FLAG_OPERATORS(Motor::Error_t) -#endif // __MOTOR_HPP +#endif // __MOTOR_HPP diff --git a/Firmware/MotorControl/nvm_config.hpp b/Firmware/MotorControl/nvm_config.hpp index 2a96f595..a7195427 100644 --- a/Firmware/MotorControl/nvm_config.hpp +++ b/Firmware/MotorControl/nvm_config.hpp @@ -11,9 +11,8 @@ #include #include -#include "nvm.h" #include - +#include "nvm.h" /* Private defines -----------------------------------------------------------*/ #define CONFIG_CRC16_INIT 0xabcd @@ -33,7 +32,6 @@ static constexpr uint16_t config_version = 0x0001; /* Private function prototypes -----------------------------------------------*/ /* Function implementations --------------------------------------------------*/ - // @brief Manages configuration load and store operations from and to NVM // // The NVM stores consecutive one-to-one copies of arbitrary objects. @@ -43,10 +41,10 @@ static constexpr uint16_t config_version = 0x0001; // - Config handles loading/storing of the first object (type T) and leaves // the rest of the objects to an "inner" class Config. // - Config<> represents the leaf of the recursion. -template +template struct Config; -template<> +template <> struct Config<> { static size_t get_size() { return 0; @@ -59,7 +57,7 @@ struct Config<> { } }; -template +template struct Config { static size_t get_size() { return sizeof(T) + Config::get_size(); @@ -71,13 +69,13 @@ struct Config { // of the last comitted NVM block // @param crc16: the result of the CRC calculation is written to this address // @param val0, vals: the values to be loaded - static int load_config(size_t offset, uint16_t* crc16, T* val0, Ts* ... vals) { + static int load_config(size_t offset, uint16_t* crc16, T* val0, Ts*... vals) { size_t size = sizeof(T); // save current CRC (in case val0 and crc16 point to the same address) size_t previous_crc16 = *crc16; - if (NVM_read(offset, (uint8_t *)val0, size)) + if (NVM_read(offset, (uint8_t*)val0, size)) return -1; - *crc16 = calc_crc16(previous_crc16, (uint8_t *)val0, size); + *crc16 = calc_crc16(previous_crc16, (uint8_t*)val0, size); if (Config::load_config(offset + size, crc16, vals...)) return -1; return 0; @@ -89,13 +87,13 @@ struct Config { // of the currently active NVM write block // @param crc16: the result of the CRC calculation is written to this address // @param val0, vals: the values to be stored - static int store_config(size_t offset, uint16_t* crc16, const T* val0, const Ts* ... vals) { + static int store_config(size_t offset, uint16_t* crc16, const T* val0, const Ts*... vals) { size_t size = sizeof(T); - if (NVM_write(offset, (uint8_t *)val0, size)) + if (NVM_write(offset, (uint8_t*)val0, size)) return -1; // update CRC _after_ writing (in case val0 and crc16 point to the same address) if (crc16) - *crc16 = calc_crc16(*crc16, (uint8_t *)val0, size); + *crc16 = calc_crc16(*crc16, (uint8_t*)val0, size); if (Config::store_config(offset + size, crc16, vals...)) return -1; return 0; @@ -103,7 +101,7 @@ struct Config { // @brief Loads one or more consecutive objects from the NVM. The loaded data // is validated using a CRC value that is stored at the beginning of the data. - static int safe_load_config(T* val0, Ts* ... vals) { + static int safe_load_config(T* val0, Ts*... vals) { //printf("have %d bytes\r\n", NVM_get_max_read_length()); osDelay(5); if (Config::get_size() > NVM_get_max_read_length()) return -1; @@ -122,7 +120,7 @@ struct Config { // changes of the config structs during firmware update. Note that if the total // config data length changes, the CRC validation will fail even if the developer // forgets to update the config version number. - static int safe_store_config(const T* val0, const Ts* ... vals) { + static int safe_store_config(const T* val0, const Ts*... vals) { size_t size = Config::get_size() + 2; //printf("config is %d bytes\r\n", size); osDelay(5); if (size > NVM_get_max_write_length()) @@ -132,7 +130,7 @@ struct Config { uint16_t crc16 = CONFIG_CRC16_INIT ^ config_version; if (Config::store_config(0, &crc16, val0, vals...)) return -1; - if (Config::store_config(size - 2, nullptr, (uint8_t *)&crc16 + 1, (uint8_t *)&crc16)) + if (Config::store_config(size - 2, nullptr, (uint8_t*)&crc16 + 1, (uint8_t*)&crc16)) return -1; if (NVM_commit()) return -1; diff --git a/Firmware/MotorControl/sensorless_estimator.cpp b/Firmware/MotorControl/sensorless_estimator.cpp index 43191ce3..59971d82 100644 --- a/Firmware/MotorControl/sensorless_estimator.cpp +++ b/Firmware/MotorControl/sensorless_estimator.cpp @@ -1,9 +1,7 @@ #include "odrive_main.h" -SensorlessEstimator::SensorlessEstimator(Config_t& config) : - config_(config) - {}; +SensorlessEstimator::SensorlessEstimator(Config_t& config) : config_(config){}; bool SensorlessEstimator::update() { // Algorithm based on paper: Sensorless Control of Surface-Mount Permanent-Magnet Synchronous Motors Based on a Nonlinear Observer @@ -37,10 +35,10 @@ bool SensorlessEstimator::update() { } // Non-linear observer (see paper eqn 8): - float pm_flux_sqr = config_.pm_flux_linkage * config_.pm_flux_linkage; - float est_pm_flux_sqr = eta[0] * eta[0] + eta[1] * eta[1]; + float pm_flux_sqr = config_.pm_flux_linkage * config_.pm_flux_linkage; + float est_pm_flux_sqr = eta[0] * eta[0] + eta[1] * eta[1]; float bandwidth_factor = 1.0f / pm_flux_sqr; - float eta_factor = 0.5f * (config_.observer_gain * bandwidth_factor) * (pm_flux_sqr - est_pm_flux_sqr); + float eta_factor = 0.5f * (config_.observer_gain * bandwidth_factor) * (pm_flux_sqr - est_pm_flux_sqr); // alpha-beta vector operations for (int i = 0; i <= 1; ++i) { @@ -71,9 +69,9 @@ bool SensorlessEstimator::update() { // predict PLL phase with velocity pll_pos_ = wrap_pm_pi(pll_pos_ + current_meas_period * vel_estimate_); // update PLL phase with observer permanent magnet phase - phase_ = fast_atan2(eta[1], eta[0]); + phase_ = fast_atan2(eta[1], eta[0]); float delta_phase = wrap_pm_pi(phase_ - pll_pos_); - pll_pos_ = wrap_pm_pi(pll_pos_ + current_meas_period * pll_kp * delta_phase); + pll_pos_ = wrap_pm_pi(pll_pos_ + current_meas_period * pll_kp * delta_phase); // update PLL velocity vel_estimate_ += current_meas_period * pll_ki * delta_phase; diff --git a/Firmware/MotorControl/sensorless_estimator.hpp b/Firmware/MotorControl/sensorless_estimator.hpp index 719a3227..f97a8ab4 100644 --- a/Firmware/MotorControl/sensorless_estimator.hpp +++ b/Firmware/MotorControl/sensorless_estimator.hpp @@ -2,35 +2,35 @@ #define __SENSORLESS_ESTIMATOR_HPP class SensorlessEstimator { -public: + public: enum Error_t { - ERROR_NONE = 0, + ERROR_NONE = 0, ERROR_UNSTABLE_GAIN = 0x01, }; struct Config_t { - float observer_gain = 1000.0f; // [rad/s] - float pll_bandwidth = 1000.0f; // [rad/s] - float pm_flux_linkage = 1.58e-3f; // [V / (rad/s)] { 5.51328895422 / ( * ) } + float observer_gain = 1000.0f; // [rad/s] + float pll_bandwidth = 1000.0f; // [rad/s] + float pm_flux_linkage = 1.58e-3f; // [V / (rad/s)] { 5.51328895422 / ( * ) } }; explicit SensorlessEstimator(Config_t& config); bool update(); - Axis* axis_ = nullptr; // set by Axis constructor + Axis* axis_ = nullptr; // set by Axis constructor Config_t& config_; // TODO: expose on protocol - Error_t error_ = ERROR_NONE; - float phase_ = 0.0f; // [rad] - float pll_pos_ = 0.0f; // [rad] - float vel_estimate_ = 0.0f; // [rad/s] + Error_t error_ = ERROR_NONE; + float phase_ = 0.0f; // [rad] + float pll_pos_ = 0.0f; // [rad] + float vel_estimate_ = 0.0f; // [rad/s] // float pll_kp_ = 0.0f; // [rad/s / rad] // float pll_ki_ = 0.0f; // [(rad/s^2) / rad] - float flux_state_[2] = {0.0f, 0.0f}; // [Vs] - float V_alpha_beta_memory_[2] = {0.0f, 0.0f}; // [V] - bool estimator_good_ = false; + float flux_state_[2] = {0.0f, 0.0f}; // [Vs] + float V_alpha_beta_memory_[2] = {0.0f, 0.0f}; // [V] + bool estimator_good_ = false; // Communication protocol definitions auto make_protocol_definitions() { @@ -42,11 +42,9 @@ public: // make_protocol_property("pll_kp", &pll_kp_), // make_protocol_property("pll_ki", &pll_ki_), make_protocol_object("config", - make_protocol_property("observer_gain", &config_.observer_gain), - make_protocol_property("pll_bandwidth", &config_.pll_bandwidth), - make_protocol_property("pm_flux_linkage", &config_.pm_flux_linkage) - ) - ); + make_protocol_property("observer_gain", &config_.observer_gain), + make_protocol_property("pll_bandwidth", &config_.pll_bandwidth), + make_protocol_property("pm_flux_linkage", &config_.pm_flux_linkage))); } }; diff --git a/Firmware/MotorControl/trapTraj.cpp b/Firmware/MotorControl/trapTraj.cpp index f1e41aa5..2cd050a4 100644 --- a/Firmware/MotorControl/trapTraj.cpp +++ b/Firmware/MotorControl/trapTraj.cpp @@ -19,13 +19,13 @@ TrapezoidalTrajectory::TrapezoidalTrajectory(Config_t& config) : config_(config) bool TrapezoidalTrajectory::planTrapezoidal(float Xf, float Xi, float Vi, float Vmax, float Amax, float Dmax) { - float dX = Xf - Xi; // Distance to travel - float stop_dist = (Vi * Vi) / (2.0f * Dmax); // Minimum stopping distance - float dXstop = std::copysign(stop_dist, Vi); // Minimum stopping displacement - float s = sign_hard(dX - dXstop); // Sign of coast velocity (if any) - Ar_ = s * Amax; // Maximum Acceleration (signed) - Dr_ = -s * Dmax; // Maximum Deceleration (signed) - Vr_ = s * Vmax; // Maximum Velocity (signed) + float dX = Xf - Xi; // Distance to travel + float stop_dist = (Vi * Vi) / (2.0f * Dmax); // Minimum stopping distance + float dXstop = std::copysign(stop_dist, Vi); // Minimum stopping displacement + float s = sign_hard(dX - dXstop); // Sign of coast velocity (if any) + Ar_ = s * Amax; // Maximum Acceleration (signed) + Dr_ = -s * Dmax; // Maximum Deceleration (signed) + Vr_ = s * Vmax; // Maximum Velocity (signed) // If we start with a speed faster than cruising, then we need to decel instead of accel // aka "double deceleration move" in the paper @@ -39,12 +39,12 @@ bool TrapezoidalTrajectory::planTrapezoidal(float Xf, float Xi, float Vi, // Integral of velocity ramps over the full accel and decel times to get // minimum displacement required to reach cuising speed - float dXmin = 0.5f*Ta_*(Vr_ + Vi) + 0.5f*Td_*Vr_; + float dXmin = 0.5f * Ta_ * (Vr_ + Vi) + 0.5f * Td_ * Vr_; // Are we displacing enough to reach cruising speed? - if (s*dX < s*dXmin) { + if (s * dX < s * dXmin) { // Short move (triangle profile) - Vr_ = s * sqrtf((Dr_*SQ(Vi) + 2*Ar_*Dr_*dX) / (Dr_ - Ar_)); + Vr_ = s * sqrtf((Dr_ * SQ(Vi) + 2 * Ar_ * Dr_ * dX) / (Dr_ - Ar_)); Ta_ = std::max(0.0f, (Vr_ - Vi) / Ar_); Td_ = std::max(0.0f, -Vr_ / Dr_); Tv_ = 0.0f; @@ -54,11 +54,11 @@ bool TrapezoidalTrajectory::planTrapezoidal(float Xf, float Xi, float Vi, } // Fill in the rest of the values used at evaluation-time - Tf_ = Ta_ + Tv_ + Td_; - Xi_ = Xi; - Xf_ = Xf; - Vi_ = Vi; - yAccel_ = Xi + Vi*Ta_ + 0.5f*Ar_*SQ(Ta_); // pos at end of accel phase + Tf_ = Ta_ + Tv_ + Td_; + Xi_ = Xi; + Xf_ = Xf; + Vi_ = Vi; + yAccel_ = Xi + Vi * Ta_ + 0.5f * Ar_ * SQ(Ta_); // pos at end of accel phase return true; } @@ -70,17 +70,17 @@ TrapezoidalTrajectory::Step_t TrapezoidalTrajectory::eval(float t) { trajStep.Yd = Vi_; trajStep.Ydd = 0.0f; } else if (t < Ta_) { // Accelerating - trajStep.Y = Xi_ + Vi_*t + 0.5f*Ar_*SQ(t); - trajStep.Yd = Vi_ + Ar_*t; + trajStep.Y = Xi_ + Vi_ * t + 0.5f * Ar_ * SQ(t); + trajStep.Yd = Vi_ + Ar_ * t; trajStep.Ydd = Ar_; } else if (t < Ta_ + Tv_) { // Coasting - trajStep.Y = yAccel_ + Vr_*(t - Ta_); + trajStep.Y = yAccel_ + Vr_ * (t - Ta_); trajStep.Yd = Vr_; trajStep.Ydd = 0.0f; } else if (t < Tf_) { // Deceleration float td = t - Tf_; - trajStep.Y = Xf_ + 0.5f*Dr_*SQ(td); - trajStep.Yd = Dr_*td; + trajStep.Y = Xf_ + 0.5f * Dr_ * SQ(td); + trajStep.Yd = Dr_ * td; trajStep.Ydd = Dr_; } else if (t >= Tf_) { // Final Condition trajStep.Y = Xf_; diff --git a/Firmware/MotorControl/trapTraj.hpp b/Firmware/MotorControl/trapTraj.hpp index 6c343c42..69cba7ed 100644 --- a/Firmware/MotorControl/trapTraj.hpp +++ b/Firmware/MotorControl/trapTraj.hpp @@ -2,13 +2,13 @@ #define _TRAP_TRAJ_H class TrapezoidalTrajectory { -public: + public: struct Config_t { - float vel_limit = 20000.0f; // [count/s] - float accel_limit = 5000.0f; // [count/s^2] - float decel_limit = 5000.0f; // [count/s^2] + float vel_limit = 20000.0f; // [count/s] + float accel_limit = 5000.0f; // [count/s^2] + float decel_limit = 5000.0f; // [count/s^2] }; - + struct Step_t { float Y; float Yd; @@ -23,11 +23,9 @@ public: auto make_protocol_definitions() { return make_protocol_member_list( make_protocol_object("config", - make_protocol_property("vel_limit", &config_.vel_limit), - make_protocol_property("accel_limit", &config_.accel_limit), - make_protocol_property("decel_limit", &config_.decel_limit) - ) - ); + make_protocol_property("vel_limit", &config_.vel_limit), + make_protocol_property("accel_limit", &config_.accel_limit), + make_protocol_property("decel_limit", &config_.decel_limit))); } Axis* axis_ = nullptr; // set by Axis constructor diff --git a/Firmware/MotorControl/utils.c b/Firmware/MotorControl/utils.c index 3278d614..6bf2e167 100644 --- a/Firmware/MotorControl/utils.c +++ b/Firmware/MotorControl/utils.c @@ -1,10 +1,9 @@ -#include -#include -#include #include +#include +#include #include - +#include int SVM(float alpha, float beta, float* tA, float* tB, float* tC) { int Sextant; @@ -13,30 +12,30 @@ int SVM(float alpha, float beta, float* tA, float* tB, float* tC) { if (alpha >= 0.0f) { //quadrant I if (one_by_sqrt3 * beta > alpha) - Sextant = 2; //sextant v2-v3 + Sextant = 2; //sextant v2-v3 else - Sextant = 1; //sextant v1-v2 + Sextant = 1; //sextant v1-v2 } else { //quadrant II if (-one_by_sqrt3 * beta > alpha) - Sextant = 3; //sextant v3-v4 + Sextant = 3; //sextant v3-v4 else - Sextant = 2; //sextant v2-v3 + Sextant = 2; //sextant v2-v3 } } else { if (alpha >= 0.0f) { //quadrant IV if (-one_by_sqrt3 * beta > alpha) - Sextant = 5; //sextant v5-v6 + Sextant = 5; //sextant v5-v6 else - Sextant = 6; //sextant v6-v1 + Sextant = 6; //sextant v6-v1 } else { //quadrant III if (one_by_sqrt3 * beta > alpha) - Sextant = 4; //sextant v4-v5 + Sextant = 4; //sextant v4-v5 else - Sextant = 5; //sextant v5-v6 + Sextant = 5; //sextant v5-v6 } } @@ -116,9 +115,7 @@ int SVM(float alpha, float beta, float* tA, float* tB, float* tC) { // if any of the results becomes NaN, result_valid will evaluate to false int result_valid = - *tA >= 0.0f && *tA <= 1.0f - && *tB >= 0.0f && *tB <= 1.0f - && *tC >= 0.0f && *tC <= 1.0f; + *tA >= 0.0f && *tA <= 1.0f && *tB >= 0.0f && *tB <= 1.0f && *tC >= 0.0f && *tC <= 1.0f; return result_valid ? 0 : -1; } @@ -149,7 +146,7 @@ float fast_atan2(float y, float x) { // Evaluate polynomials using Fused Multiply Add intrisic instruction. // coeffs[0] is highest order, as per numpy.polyfit // p(x) = coeffs[0] * x^deg + ... + coeffs[deg], for some degree "deg" -float horner_fma(float x, const float *coeffs, size_t count) { +float horner_fma(float x, const float* coeffs, size_t count) { float result = 0.0f; for (int idx = 0; idx < count; ++idx) result = fmaf(result, x, coeffs[idx]); @@ -157,7 +154,7 @@ float horner_fma(float x, const float *coeffs, size_t count) { } // Modulo (as opposed to remainder), per https://stackoverflow.com/a/19288271 -int mod(int dividend, int divisor){ +int mod(int dividend, int divisor) { int r = dividend % divisor; return (r < 0) ? (r + divisor) : r; } @@ -166,7 +163,7 @@ int mod(int dividend, int divisor){ // If the deadline has already passed, the return value is 0 (except if // the deadline is very far in the past) uint32_t deadline_to_timeout(uint32_t deadline_ms) { - uint32_t now_ms = (uint32_t)((1000ull * (uint64_t)osKernelSysTick()) / osKernelSysTickFrequency); + uint32_t now_ms = (uint32_t)((1000ull * (uint64_t)osKernelSysTick()) / osKernelSysTickFrequency); uint32_t timeout_ms = deadline_ms - now_ms; return (timeout_ms & 0x80000000) ? 0 : timeout_ms; } @@ -188,18 +185,17 @@ int is_in_the_future(uint32_t time_ms) { uint32_t micros(void) { register uint32_t ms, cycle_cnt; do { - ms = HAL_GetTick(); + ms = HAL_GetTick(); cycle_cnt = TIM_TIME_BASE->CNT; - } while (ms != HAL_GetTick()); + } while (ms != HAL_GetTick()); return (ms * 1000) + cycle_cnt; } // @brief: Busy wait delay for given amount of microseconds (us) -void delay_us(uint32_t us) -{ +void delay_us(uint32_t us) { uint32_t start = micros(); - while (micros() - start < (uint32_t) us) { + while (micros() - start < (uint32_t)us) { __ASM("nop"); } } diff --git a/Firmware/MotorControl/utils.h b/Firmware/MotorControl/utils.h index 3145c19a..bd847d7a 100644 --- a/Firmware/MotorControl/utils.h +++ b/Firmware/MotorControl/utils.h @@ -6,8 +6,8 @@ extern "C" { #endif -#include #include +#include /** * @brief Flash size register address @@ -67,7 +67,7 @@ extern "C" { static const float one_by_sqrt3 = 0.57735026919f; static const float two_by_sqrt3 = 1.15470053838f; -static const float sqrt3_by_2 = 0.86602540378f; +static const float sqrt3_by_2 = 0.86602540378f; //beware of inserting large values! static inline float wrap_pm(float x, float pm_range) { @@ -93,7 +93,7 @@ static inline float fmodf_pos(float x, float y) { // as per the magnitude invariant clarke transform // The magnitude of the alpha-beta vector may not be larger than sqrt(3)/2 // Returns 0 on success, and -1 if the input was out of range -int SVM(float alpha, float beta, float* tA, float* tB, float* tC); +int SVM(float alpha, float beta, float *tA, float *tB, float *tC); float fast_atan2(float y, float x); float horner_fma(float x, const float *coeffs, size_t count); From 63bebaa8dea9adcf8e2fbcd64eb420a4e4948e3b Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 24 Aug 2019 21:44:23 -0400 Subject: [PATCH 195/549] Add "enable" switches for controller options --- Firmware/MotorControl/controller.cpp | 14 ++++++++------ Firmware/MotorControl/controller.hpp | 16 ++++++++++++---- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index a4d1355b..db7d3642 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -222,12 +222,14 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // Velocity limiting float vel_lim = config_.vel_limit; - if (vel_des > vel_lim) vel_des = vel_lim; - if (vel_des < -vel_lim) vel_des = -vel_lim; + if (config_.enable_vel_limit) { + if (vel_des > vel_lim) vel_des = vel_lim; + if (vel_des < -vel_lim) vel_des = -vel_lim; + } // Check for overspeed fault (done in this module (controller) for cohesion with vel_lim) - if (config_.vel_limit_tolerance > 0.0f) { // 0.0f to disable - if (fabsf(vel_estimate) > config_.vel_limit_tolerance * vel_lim) { + if (config_.enable_overspeed_error) { // 0.0f to disable + if (std::abs(vel_estimate) > config_.vel_limit_tolerance * vel_lim) { set_error(ERROR_OVERSPEED); return false; } @@ -239,7 +241,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // Anti-cogging is enabled after calibration // We get the current position and apply a current feed-forward // ensuring that we handle negative encoder positions properly (-1 == motor->encoder.encoder_cpr - 1) - if (anticogging_valid_) { + if (anticogging_valid_ && config_.anticogging.enable) { Iq += config_.anticogging.cogging_map[std::clamp(mod(static_cast(anticogging_pos), 3600), 0, 3600)]; } @@ -252,7 +254,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s Iq += vel_integrator_current_; // Velocity limiting in current mode - if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL && config_.vel_limit > 0.0f) { + if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL && config_.enable_current_vel_limit) { Iq = limitVel(config_.vel_limit, vel_estimate, config_.vel_gain, Iq); } diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 502b097a..a2370f49 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -40,6 +40,7 @@ class Controller { float calib_pos_threshold = 1.0f; float calib_vel_threshold = 1.0f; float cogging_ratio = 1.0f; + bool enable = true; } Anticogging_t; struct Config_t { @@ -57,8 +58,11 @@ class Controller { float input_filter_bandwidth = 2.0f; // [1/s] float homing_speed = 2000.0f; // [counts/s] Anticogging_t anticogging; - float gain_scheduling_width = 10.0f; - bool enable_gain_scheduling = false; + float gain_scheduling_width = 10.0f; + bool enable_gain_scheduling = false; + bool enable_vel_limit = true; + bool enable_overspeed_error = true; + bool enable_current_vel_limit = true; }; explicit Controller(Config_t& config); @@ -125,8 +129,11 @@ class Controller { make_protocol_property("vel_integrator_current", &vel_integrator_current_), make_protocol_property("anticogging_valid", &anticogging_valid_), make_protocol_property("gain_scheduling_width", &config_.gain_scheduling_width), - make_protocol_property("enable_gain_scheduling", &config_.enable_gain_scheduling), make_protocol_object("config", + make_protocol_property("enable_vel_limit", &config_.enable_vel_limit), + make_protocol_property("enable_current_mode_vel_limit", &config_.enable_current_vel_limit), + make_protocol_property("enable_gain_scheduling", &config_.enable_gain_scheduling), + make_protocol_property("enable_overspeed_error", &config_.enable_overspeed_error), make_protocol_property("control_mode", &config_.control_mode), make_protocol_property("input_mode", &config_.input_mode), make_protocol_property("pos_gain", &config_.pos_gain), @@ -145,7 +152,8 @@ class Controller { make_protocol_ro_property("calib_anticogging", &config_.anticogging.calib_anticogging), make_protocol_property("calib_pos_threshold", &config_.anticogging.calib_pos_threshold), make_protocol_property("calib_vel_threshold", &config_.anticogging.calib_vel_threshold), - make_protocol_ro_property("cogging_ratio", &config_.anticogging.cogging_ratio))), + make_protocol_ro_property("cogging_ratio", &config_.anticogging.cogging_ratio), + make_protocol_property("anticogging_enabled", &config_.anticogging.enable))), make_protocol_function("move_incremental", *this, &Controller::move_incremental, "displacement", "from_goal_point"), make_protocol_function("start_anticogging_calibration", *this, &Controller::start_anticogging_calibration), make_protocol_function("home_axis", *this, &Controller::home_axis)); From 36f4dc0ada013bbd0df37d6a5835920fe997c29b Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 24 Aug 2019 21:50:59 -0400 Subject: [PATCH 196/549] Add watchdog enable option --- Firmware/MotorControl/axis.cpp | 1 + Firmware/MotorControl/axis.hpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index d85489dd..311b4886 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -206,6 +206,7 @@ void Axis::watchdog_feed() { // @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(!config_.enable_watchdog) return true; if (get_watchdog_reset() == 0) return true; // explicit check here to ensure that we don't underflow back to UINT32_MAX diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index af305a43..efadbcea 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -77,6 +77,7 @@ class Axis { float counts_per_step = 2.0f; float watchdog_timeout = 0.0f; // [s] (0 disables watchdog) + bool enable_watchdog = false; // Defaults loaded from hw_config in load_configuration in main.cpp uint16_t step_gpio_pin = 0; @@ -283,6 +284,7 @@ class Axis { 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), + make_protocol_property("enable_watchdog", &config_.enable_watchdog), 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, From 6edb34df7770e539696f580299aa9aac4c9ac542 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 24 Aug 2019 23:47:30 -0400 Subject: [PATCH 197/549] Reduce default endstop debounce to 50ms --- Firmware/MotorControl/endstop.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index f1262ed9..e30ab3d7 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -8,7 +8,7 @@ class Endstop { bool enabled = false; int32_t offset = 0; bool is_active_high = false; - float debounce_ms = 100.0f; + float debounce_ms = 50.0f; }; Endstop(Endstop::Config_t& config); @@ -39,6 +39,7 @@ class Endstop { private: bool pin_state_ = false; + float pos_when_pressed_ = 0.0f; volatile float debounce_timer_ = 0; }; #endif \ No newline at end of file From ec2cd83f1fd4cc16dcc91ebd01d60746dabf0764 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 24 Aug 2019 23:48:16 -0400 Subject: [PATCH 198/549] Add compensation term for axis debounce during homing --- Firmware/MotorControl/axis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 311b4886..51a66c18 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -320,7 +320,7 @@ bool Axis::run_closed_loop_control_loop() { controller_.vel_setpoint_ = 0.0f; // Change directions without decelerating // Set our current position in encoder counts to make control more logical - encoder_.set_linear_count(min_endstop_.config_.offset); + encoder_.set_linear_count(min_endstop_.config_.offset - static_cast(controller_.config_.homing_speed * min_endstop_.config_.debounce_ms / 1000.0f)); controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ; From 2e3768c329606b6c0bbfc70ace0710d509301e32 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 25 Aug 2019 17:13:10 -0400 Subject: [PATCH 199/549] Start moving casts and math functions to std:: --- Firmware/MotorControl/axis.cpp | 4 ++-- Firmware/MotorControl/controller.cpp | 6 +++--- Firmware/MotorControl/encoder.cpp | 30 ++++++++++++++-------------- Firmware/MotorControl/motor.cpp | 2 +- Firmware/Tests/test_runner.cpp | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 51a66c18..fbef6135 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -241,9 +241,9 @@ bool Axis::run_lockin_spin(const LockinConfig_t& lockin_config) { auto spin_done = [&](bool vel_override = false) -> bool { bool done = false; if (lockin_config.finish_on_vel || vel_override) - done = done || fabsf(vel) >= fabsf(lockin_config.vel); + done = done || std::abs(vel) >= std::abs(lockin_config.vel); if (lockin_config.finish_on_distance) - done = done || fabsf(distance) >= fabsf(lockin_config.finish_distance); + done = done || std::abs(distance) >= std::abs(lockin_config.finish_distance); if (lockin_config.finish_on_enc_idx) done = done || encoder_.index_found_; return done; diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index db7d3642..088b6d15 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -88,8 +88,8 @@ bool Controller::home_axis() { bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) { if (config_.anticogging.calib_anticogging) { float pos_err = input_pos_ - pos_estimate; - if (fabsf(pos_err) <= config_.anticogging.calib_pos_threshold && - fabsf(vel_estimate) < config_.anticogging.calib_vel_threshold) { + if (std::abs(pos_err) <= config_.anticogging.calib_pos_threshold && + std::abs(vel_estimate) < config_.anticogging.calib_vel_threshold) { config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_current_; } if (config_.anticogging.index < 3600) { @@ -214,7 +214,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s } vel_des += config_.pos_gain * pos_err; // V-shaped gain shedule based on position error - float abs_pos_err = fabsf(pos_err); + float abs_pos_err = std::abs(pos_err); if (config_.enable_gain_scheduling && abs_pos_err <= config_.gain_scheduling_width) { gain_scheduling_multiplier = abs_pos_err / config_.gain_scheduling_width; } diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index fc2eac2a..c91361fd 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -97,7 +97,7 @@ void Encoder::set_linear_count(int32_t count) { // Update states shadow_count_ = count; - pos_estimate_ = (float)count; + pos_estimate_ = static_cast(count); tim_cnt_sample_ = count; //Write hardware last @@ -119,7 +119,7 @@ void Encoder::set_circular_count(int32_t count, bool update_offset) { // Update states count_in_cpr_ = mod(count, config_.cpr); - pos_cpr_ = (float)count_in_cpr_; + pos_cpr_ = static_cast(count_in_cpr_); cpu_exit_critical(prim); } @@ -166,7 +166,7 @@ bool Encoder::run_direction_find() { // TODO: Do the scan with current, not voltage! bool Encoder::run_offset_calibration() { static const float start_lock_duration = 1.0f; - static const int num_steps = (int)(config_.calib_scan_distance / config_.calib_scan_omega * (float)current_meas_hz); + static const int num_steps = (int)(config_.calib_scan_distance / config_.calib_scan_omega * static_cast(current_meas_hz)); // Require index found if enabled if (config_.use_index && !index_found_) { @@ -203,7 +203,7 @@ bool Encoder::run_offset_calibration() { // scan forward i = 0; axis_->run_control_loop([&]() { - float phase = wrap_pm_pi(config_.calib_scan_distance * (float)i / (float)num_steps - config_.calib_scan_distance / 2.0f); + float phase = wrap_pm_pi(config_.calib_scan_distance * static_cast(i) / static_cast(num_steps) - config_.calib_scan_distance / 2.0f); float v_alpha = voltage_magnitude * our_arm_cos_f32(phase); float v_beta = voltage_magnitude * our_arm_sin_f32(phase); if (!axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta)) @@ -232,10 +232,10 @@ bool Encoder::run_offset_calibration() { //TODO avoid recomputing elec_rad_per_enc every time // Check CPR - float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / (float)(config_.cpr)); + float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / static_cast(config_.cpr)); float expected_encoder_delta = config_.calib_scan_distance / elec_rad_per_enc; - calib_scan_response_ = fabsf(shadow_count_ - init_enc_val); - if (fabsf(calib_scan_response_ - expected_encoder_delta) / expected_encoder_delta > config_.calib_range) { + calib_scan_response_ = std::abs(shadow_count_ - init_enc_val); + if (std::abs(calib_scan_response_ - expected_encoder_delta) / expected_encoder_delta > config_.calib_range) { set_error(ERROR_CPR_OUT_OF_RANGE); return false; } @@ -243,7 +243,7 @@ bool Encoder::run_offset_calibration() { // scan backwards i = 0; axis_->run_control_loop([&]() { - float phase = wrap_pm_pi(-config_.calib_scan_distance * (float)i / (float)num_steps + config_.calib_scan_distance / 2.0f); + float phase = wrap_pm_pi(-config_.calib_scan_distance * static_cast(i) / static_cast(num_steps) + config_.calib_scan_distance / 2.0f); float v_alpha = voltage_magnitude * our_arm_cos_f32(phase); float v_beta = voltage_magnitude * our_arm_sin_f32(phase); if (!axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta)) @@ -259,7 +259,7 @@ bool Encoder::run_offset_calibration() { config_.offset = encvaluesum / (num_steps * 2); int32_t residual = encvaluesum - ((int64_t)config_.offset * (int64_t)(num_steps * 2)); - config_.offset_float = (float)residual / (float)(num_steps * 2) + 0.5f; // add 0.5 to center-align state to phase + config_.offset_float = static_cast(residual) / static_cast(num_steps * 2) + 0.5f; // add 0.5 to center-align state to phase is_ready_ = true; return true; @@ -490,16 +490,16 @@ bool Encoder::update() { pos_estimate_ += current_meas_period * vel_estimate_; pos_cpr_ += current_meas_period * vel_estimate_; // discrete phase detector - float delta_pos = (float)(shadow_count_ - (int32_t)floorf(pos_estimate_)); - float delta_pos_cpr = (float)(count_in_cpr_ - (int32_t)floorf(pos_cpr_)); - delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * (float)(config_.cpr)); + float delta_pos = static_cast(shadow_count_) - static_cast(std::floor(pos_estimate_)); + float delta_pos_cpr = static_cast(count_in_cpr_) - static_cast(std::floor(pos_cpr_)); + delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * static_cast(config_.cpr)); // pll feedback pos_estimate_ += current_meas_period * pll_kp_ * delta_pos; pos_cpr_ += current_meas_period * pll_kp_ * delta_pos_cpr; - pos_cpr_ = fmodf_pos(pos_cpr_, (float)(config_.cpr)); + pos_cpr_ = fmodf_pos(pos_cpr_, static_cast(config_.cpr)); vel_estimate_ += current_meas_period * pll_ki_ * delta_pos_cpr; bool snap_to_zero_vel = false; - if (fabsf(vel_estimate_) < 0.5f * current_meas_period * pll_ki_) { + if (std::abs(vel_estimate_) < 0.5f * current_meas_period * pll_ki_) { vel_estimate_ = 0.0f; //align delta-sigma on zero to prevent jitter snap_to_zero_vel = true; } @@ -525,7 +525,7 @@ bool Encoder::update() { //// compute electrical phase //TODO avoid recomputing elec_rad_per_enc every time - float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / (float)(config_.cpr)); + float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / static_cast(config_.cpr)); float ph = elec_rad_per_enc * (interpolated_enc - config_.offset_float); // ph = fmodf(ph, 2*M_PI); phase_ = wrap_pm_pi(ph); diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 92ba073f..81b0dc40 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -334,7 +334,7 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha ictrl.Iq_setpoint = Iq_des; // Check for current sense saturation - if (fabsf(current_meas_.phB) > ictrl.overcurrent_trip_level || fabsf(current_meas_.phC) > ictrl.overcurrent_trip_level) { + if (std::abs(current_meas_.phB) > ictrl.overcurrent_trip_level || std::abs(current_meas_.phC) > ictrl.overcurrent_trip_level) { set_error(ERROR_CURRENT_SENSE_SATURATION); return false; } diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index 0b116fc6..d5a211f9 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -281,7 +281,7 @@ TEST_SUITE("vel_ramp") { float max_step_size = 0.000125f * vel_ramp_rate; float full_step = input_vel_ - vel_setpoint_; float step; - if (fabsf(full_step) > max_step_size) { + if (std::abs(full_step) > max_step_size) { step = std::copysignf(max_step_size, full_step); } else { step = full_step; From b880d7986eb5127990c7eb985fc4cc27ab4c7e6e Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 26 Aug 2019 22:32:45 -0400 Subject: [PATCH 200/549] Fix homing offset compensation per @riewert --- Firmware/MotorControl/axis.cpp | 4 ++-- Firmware/MotorControl/endstop.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index fbef6135..a2bae293 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -316,11 +316,11 @@ bool Axis::run_closed_loop_control_loop() { if (homing_.homing_state == HOMING_STATE_HOMING) { if (min_endstop_.getEndstopState()) { // pos_setpoint is the starting position for the trap_traj so we need to set it. - controller_.pos_setpoint_ = min_endstop_.config_.offset; + controller_.pos_setpoint_ = min_endstop_.config_.offset - (controller_.config_.homing_speed * (min_endstop_.config_.debounce_ms / 1000.0f)); controller_.vel_setpoint_ = 0.0f; // Change directions without decelerating // Set our current position in encoder counts to make control more logical - encoder_.set_linear_count(min_endstop_.config_.offset - static_cast(controller_.config_.homing_speed * min_endstop_.config_.debounce_ms / 1000.0f)); + encoder_.set_linear_count(static_cast(controller_.pos_setpoint_)); controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ; diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index e30ab3d7..45195a2f 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -4,11 +4,11 @@ class Endstop { public: struct Config_t { + float offset = 0; + float debounce_ms = 50.0f; uint16_t gpio_num; bool enabled = false; - int32_t offset = 0; bool is_active_high = false; - float debounce_ms = 50.0f; }; Endstop(Endstop::Config_t& config); From 2b901830d829d0335b4ad5ccac0b27e474bf44d6 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 27 Aug 2019 20:50:14 -0400 Subject: [PATCH 201/549] Add anticogging_valid check during absolute encoder setup --- Firmware/MotorControl/encoder.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 82a6d0b5..1ede2f47 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -22,6 +22,9 @@ void Encoder::setup() { if (config_.mode & MODE_FLAG_ABS) { abs_spi_cs_pin_init(); abs_spi_init(); + if (axis_->controller_.config_.anticogging.pre_calibrated) { + axis_->controller_.anticogging_valid_ = true; + } } } From 4ad2fc60a8e0f2467ac5f99d5760560ac53808b2 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 29 Aug 2019 20:24:09 -0400 Subject: [PATCH 202/549] Reset vel_integrator_current when going into closed loop mode --- Firmware/MotorControl/axis.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index a2bae293..98d790bd 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -302,6 +302,10 @@ bool Axis::run_sensorless_control_loop() { bool Axis::run_closed_loop_control_loop() { // To avoid any transient on startup, we intialize the setpoint to be the current position controller_.pos_setpoint_ = encoder_.pos_estimate_; + + // Avoid integrator windup issues + controller_.vel_integrator_current_ = 0.0f; + set_step_dir_active(config_.enable_step_dir); run_control_loop([this]() { // Note that all estimators are updated in the loop prefix in run_control_loop From 5df4ceda33ccacf73795958c40cdfbda2b8e484d Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 31 Aug 2019 17:41:32 -0400 Subject: [PATCH 203/549] Add current ramping input mode --- Firmware/MotorControl/controller.cpp | 7 +++++++ Firmware/MotorControl/controller.hpp | 2 ++ tools/odrive/enums.py | 1 + 3 files changed, 10 insertions(+) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 088b6d15..47b27db9 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -150,6 +150,13 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s vel_setpoint_ += step; current_setpoint_ = step / current_meas_period * config_.inertia; } break; + case INPUT_MODE_CURRENT_RAMP: { + float max_step_size = std::abs(current_meas_period * config_.current_ramp_rate); + float full_step = input_current_ - current_setpoint_; + float step = std::clamp(full_step, -max_step_size, max_step_size); + + current_setpoint_ += step; + } break; case INPUT_MODE_POS_FILTER: { // 2nd order pos tracking filter float delta_pos = input_pos_ - pos_setpoint_; // Pos error diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index a2370f49..b6a8a339 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -30,6 +30,7 @@ class Controller { INPUT_MODE_POS_FILTER, INPUT_MODE_MIX_CHANNELS, INPUT_MODE_TRAP_TRAJ, + INPUT_MODE_CURRENT_RAMP, }; typedef struct { @@ -53,6 +54,7 @@ class Controller { float vel_limit = 20000.0f; // [counts/s] float vel_limit_tolerance = 1.2f; // ratio to vel_lim. 0.0f to disable float vel_ramp_rate = 10000.0f; // [(counts/s) / s] + float current_ramp_rate = 1.0f; // A / sec bool setpoints_in_cpr = false; float inertia = 0.0f; // [A/(count/s^2)] float input_filter_bandwidth = 2.0f; // [1/s] diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 1ac3d67c..c1e1232a 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -73,6 +73,7 @@ INPUT_MODE_VEL_RAMP = 2 INPUT_MODE_POS_FILTER = 3 INPUT_MODE_MIX_CHANNELS = 4 INPUT_MODE_TRAP_TRAJ = 5 +INPUT_MODE_CURRENT_RAMP = 6 ENCODER_MODE_INCREMENTAL = 0x00 ENCODER_MODE_HALL = 0x01 From f4e2c3d286d71c488d45118b98df0fd8b7127b3b Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 31 Aug 2019 18:03:00 -0400 Subject: [PATCH 204/549] Add mirror mode --- Firmware/MotorControl/controller.cpp | 12 ++++++++++-- Firmware/MotorControl/controller.hpp | 15 ++++++++++----- tools/odrive/enums.py | 4 ++++ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 47b27db9..720d8bf8 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -152,8 +152,8 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s } break; case INPUT_MODE_CURRENT_RAMP: { float max_step_size = std::abs(current_meas_period * config_.current_ramp_rate); - float full_step = input_current_ - current_setpoint_; - float step = std::clamp(full_step, -max_step_size, max_step_size); + float full_step = input_current_ - current_setpoint_; + float step = std::clamp(full_step, -max_step_size, max_step_size); current_setpoint_ += step; } break; @@ -166,6 +166,14 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s vel_setpoint_ += current_meas_period * accel; // delta vel pos_setpoint_ += current_meas_period * vel_setpoint_; // Delta pos } break; + case INPUT_MODE_MIRROR: { + if (config_.axis_to_mirror < AXIS_COUNT) { + input_pos_ = axes[config_.axis_to_mirror]->encoder_.pos_estimate_; + input_vel_ = axes[config_.axis_to_mirror]->encoder_.vel_estimate_; + } else { + set_error(ERROR_INVALID_MIRROR_AXIS); + } + } break; // case INPUT_MODE_MIX_CHANNELS: { // // NOT YET IMPLEMENTED // } break; diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index b6a8a339..e910ab46 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -8,10 +8,11 @@ class Controller { public: enum Error_t { - ERROR_NONE = 0, - ERROR_OVERSPEED = 0x01, - ERROR_INVALID_INPUT_MODE = 0x02, - ERROR_UNSTABLE_GAIN = 0x04, + ERROR_NONE = 0, + ERROR_OVERSPEED = 0x01, + ERROR_INVALID_INPUT_MODE = 0x02, + ERROR_UNSTABLE_GAIN = 0x04, + ERROR_INVALID_MIRROR_AXIS = 0x08, }; // Note: these should be sorted from lowest level of control to @@ -31,6 +32,7 @@ class Controller { INPUT_MODE_MIX_CHANNELS, INPUT_MODE_TRAP_TRAJ, INPUT_MODE_CURRENT_RAMP, + INPUT_MODE_MIRROR, }; typedef struct { @@ -54,7 +56,7 @@ class Controller { float vel_limit = 20000.0f; // [counts/s] float vel_limit_tolerance = 1.2f; // ratio to vel_lim. 0.0f to disable float vel_ramp_rate = 10000.0f; // [(counts/s) / s] - float current_ramp_rate = 1.0f; // A / sec + float current_ramp_rate = 1.0f; // A / sec bool setpoints_in_cpr = false; float inertia = 0.0f; // [A/(count/s^2)] float input_filter_bandwidth = 2.0f; // [1/s] @@ -65,6 +67,7 @@ class Controller { bool enable_vel_limit = true; bool enable_overspeed_error = true; bool enable_current_vel_limit = true; + uint8_t axis_to_mirror = -1; }; explicit Controller(Config_t& config); @@ -144,8 +147,10 @@ class Controller { make_protocol_property("vel_limit", &config_.vel_limit), make_protocol_property("vel_limit_tolerance", &config_.vel_limit_tolerance), make_protocol_property("vel_ramp_rate", &config_.vel_ramp_rate), + make_protocol_property("current_ramp_rate", &config_.current_ramp_rate), make_protocol_property("homing_speed", &config_.homing_speed), make_protocol_property("inertia", &config_.inertia), + make_protocol_property("axis_to_mirror", &config_.axis_to_mirror), make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth, [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this), make_protocol_object("anticogging", diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index c1e1232a..08ea6ec0 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -57,6 +57,9 @@ class errors: class controller: ERROR_NONE = 0 ERROR_OVERSPEED = 0x01 + ERROR_INVALID_INPUT_MODE = 0x02 + ERROR_UNSTABLE_GAIN = 0x04 + ERROR_INVALID_MIRROR_AXIS = 0x08 MOTOR_TYPE_HIGH_CURRENT = 0 #MOTOR_TYPE_LOW_CURRENT = 1 @@ -74,6 +77,7 @@ INPUT_MODE_POS_FILTER = 3 INPUT_MODE_MIX_CHANNELS = 4 INPUT_MODE_TRAP_TRAJ = 5 INPUT_MODE_CURRENT_RAMP = 6 +INPUT_MODE_MIRROR = 7 ENCODER_MODE_INCREMENTAL = 0x00 ENCODER_MODE_HALL = 0x01 From e9c8eb6c03e5f77154f2657feeec2a1c855e93cc Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 31 Aug 2019 19:20:47 -0400 Subject: [PATCH 205/549] Add mirror_ratio --- Firmware/MotorControl/controller.cpp | 5 +++-- Firmware/MotorControl/controller.hpp | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 720d8bf8..48646068 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -168,10 +168,11 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s } break; case INPUT_MODE_MIRROR: { if (config_.axis_to_mirror < AXIS_COUNT) { - input_pos_ = axes[config_.axis_to_mirror]->encoder_.pos_estimate_; - input_vel_ = axes[config_.axis_to_mirror]->encoder_.vel_estimate_; + pos_setpoint_ = axes[config_.axis_to_mirror]->encoder_.pos_estimate_ * config_.mirror_ratio; + vel_setpoint_ = axes[config_.axis_to_mirror]->encoder_.vel_estimate_ * config_.mirror_ratio; } else { set_error(ERROR_INVALID_MIRROR_AXIS); + return false; } } break; // case INPUT_MODE_MIX_CHANNELS: { diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index e910ab46..54e2acdc 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -68,6 +68,7 @@ class Controller { bool enable_overspeed_error = true; bool enable_current_vel_limit = true; uint8_t axis_to_mirror = -1; + float mirror_ratio = 1.0f; }; explicit Controller(Config_t& config); @@ -151,6 +152,7 @@ class Controller { make_protocol_property("homing_speed", &config_.homing_speed), make_protocol_property("inertia", &config_.inertia), make_protocol_property("axis_to_mirror", &config_.axis_to_mirror), + make_protocol_property("mirror_ratio", &config_.mirror_ratio), make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth, [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this), make_protocol_object("anticogging", From 3f4433f50deb28b68fb96cf0e5a918bfad2a9e99 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 1 Sep 2019 17:02:15 -0400 Subject: [PATCH 206/549] Enable rudimentary dual-encoder support --- Firmware/MotorControl/axis.cpp | 15 ++++++++++++--- Firmware/MotorControl/axis.hpp | 4 ++++ Firmware/MotorControl/controller.cpp | 4 ++-- Firmware/MotorControl/controller.hpp | 17 ++++++++++++----- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 98d790bd..e2400c9a 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -206,7 +206,7 @@ void Axis::watchdog_feed() { // @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(!config_.enable_watchdog) return true; + if (!config_.enable_watchdog) return true; if (get_watchdog_reset() == 0) return true; // explicit check here to ensure that we don't underflow back to UINT32_MAX @@ -305,12 +305,21 @@ bool Axis::run_closed_loop_control_loop() { // Avoid integrator windup issues controller_.vel_integrator_current_ = 0.0f; - + set_step_dir_active(config_.enable_step_dir); run_control_loop([this]() { // Note that all estimators are updated in the loop prefix in run_control_loop float current_setpoint; - if (!controller_.update(encoder_.pos_estimate_, encoder_.vel_estimate_, ¤t_setpoint)) + if (controller_.config_.use_load_encoder) { + if (controller_.config_.load_encoder_axis < AXIS_COUNT) { + Axis* ax = axes[controller_.config_.load_encoder_axis]; + if (!controller_.update(ax->encoder_.pos_estimate_, encoder_.vel_estimate_, ¤t_setpoint)) + return error_ |= ERROR_CONTROLLER_FAILED, false; + } else{ + controller_.set_error(Controller::ERROR_INVALID_LOAD_ENCODER); + return error_ |= ERROR_CONTROLLER_FAILED, false; + } + } else if (!controller_.update(encoder_.pos_estimate_, encoder_.vel_estimate_, ¤t_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; //TODO: Make controller.set_error float phase_vel = 2 * M_PI * encoder_.vel_estimate_ / (float)encoder_.config_.cpr * motor_.config_.pole_pairs; if (!motor_.update(current_setpoint, encoder_.phase_, phase_vel)) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index efadbcea..a52eb858 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -88,6 +88,10 @@ class Axis { LockinConfig_t lockin; uint8_t can_node_id = 0; // Both axes will have the same id to start uint32_t can_heartbeat_rate_ms = 100; + + bool use_load_encoder = false; + uint8_t load_encoder_axis = -1; + float load_encoder_ratio = 1.0f; }; struct Homing_t { diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 48646068..215d3c17 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -129,8 +129,8 @@ float limitVel(const float vel_limit, const float vel_estimate, const float vel_ bool Controller::update(float pos_estimate, float vel_estimate, float* current_setpoint_output) { // Only runs if config_.anticogging.calib_anticogging is true; non-blocking - anticogging_calibration(pos_estimate, vel_estimate); - float anticogging_pos = pos_estimate / axis_->encoder_.getCoggingRatio(); + anticogging_calibration(axis_->encoder_.pos_estimate_, vel_estimate); + float anticogging_pos = axis_->encoder_.pos_estimate_ / axis_->encoder_.getCoggingRatio(); // Update inputs switch (config_.input_mode) { diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 54e2acdc..c31692b0 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -8,11 +8,12 @@ class Controller { public: enum Error_t { - ERROR_NONE = 0, - ERROR_OVERSPEED = 0x01, - ERROR_INVALID_INPUT_MODE = 0x02, - ERROR_UNSTABLE_GAIN = 0x04, - ERROR_INVALID_MIRROR_AXIS = 0x08, + ERROR_NONE = 0, + ERROR_OVERSPEED = 0x01, + ERROR_INVALID_INPUT_MODE = 0x02, + ERROR_UNSTABLE_GAIN = 0x04, + ERROR_INVALID_MIRROR_AXIS = 0x08, + ERROR_INVALID_LOAD_ENCODER = 0x10, }; // Note: these should be sorted from lowest level of control to @@ -69,6 +70,9 @@ class Controller { bool enable_current_vel_limit = true; uint8_t axis_to_mirror = -1; float mirror_ratio = 1.0f; + bool use_load_encoder = false; + uint8_t load_encoder_axis = -1; + float load_encoder_ratio = 1.0f; }; explicit Controller(Config_t& config); @@ -153,6 +157,9 @@ class Controller { make_protocol_property("inertia", &config_.inertia), make_protocol_property("axis_to_mirror", &config_.axis_to_mirror), make_protocol_property("mirror_ratio", &config_.mirror_ratio), + make_protocol_property("use_load_encoder", &config_.use_load_encoder), + make_protocol_property("load_encoder_ratio", &config_.load_encoder_ratio), + make_protocol_property("load_encoder_axis", &config_.load_encoder_axis), make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth, [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this), make_protocol_object("anticogging", From 5a3fc8690ff0566798a9959fad75cbe0ab2a1a10 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 9 Sep 2019 23:18:00 -0400 Subject: [PATCH 207/549] Remove homing offset compensation --- Firmware/MotorControl/axis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index e2400c9a..945de0ef 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -329,7 +329,7 @@ bool Axis::run_closed_loop_control_loop() { if (homing_.homing_state == HOMING_STATE_HOMING) { if (min_endstop_.getEndstopState()) { // pos_setpoint is the starting position for the trap_traj so we need to set it. - controller_.pos_setpoint_ = min_endstop_.config_.offset - (controller_.config_.homing_speed * (min_endstop_.config_.debounce_ms / 1000.0f)); + controller_.pos_setpoint_ = min_endstop_.config_.offset; controller_.vel_setpoint_ = 0.0f; // Change directions without decelerating // Set our current position in encoder counts to make control more logical From 47bcdfa08f60b2ca49c7228722319cd9bf3f558f Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 9 Sep 2019 20:42:10 -0700 Subject: [PATCH 208/549] fix broken analog feature due to out of RAM for stack --- Firmware/MotorControl/low_level.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 6125c99c..3d02ee73 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -753,6 +753,6 @@ static void analog_polling_thread(void *) void start_analog_thread() { - osThreadDef(thread_def, analog_polling_thread, osPriorityLow, 0, 4*512); + osThreadDef(thread_def, analog_polling_thread, osPriorityLow, 0, 128); osThreadCreate(osThread(thread_def), NULL); } From 6a929d4236303cb3aec7dc1a83c30bd1e899c853 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 12 Sep 2019 18:11:32 -0700 Subject: [PATCH 209/549] improve oscilloscope functionality --- Firmware/MotorControl/low_level.cpp | 5 ----- Firmware/MotorControl/motor.cpp | 25 +++++++++++++++++++++++++ Firmware/MotorControl/odrive_main.h | 2 +- tools/odrive/shell.py | 5 +++-- tools/odrive/utils.py | 6 ++++++ 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 3d02ee73..82e54049 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -426,11 +426,6 @@ void vbus_sense_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { // Only one conversion in sequence, so only rank1 uint32_t ADCValue = HAL_ADCEx_InjectedGetValue(hadc, ADC_INJECTED_RANK_1); vbus_voltage = ADCValue * voltage_scale; - if (axes[0] && !axes[0]->error_ && axes[1] && !axes[1]->error_) { - if (oscilloscope_pos >= OSCILLOSCOPE_SIZE) - oscilloscope_pos = 0; - oscilloscope[oscilloscope_pos++] = vbus_voltage; - } } static void decode_hall_samples(Encoder& enc, uint16_t GPIO_samples[num_GPIO]) { diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index fd628c89..17306e59 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -410,6 +410,31 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha return false; // error set inside enqueue_modulation_timings log_timing(TIMING_LOG_FOC_CURRENT); + if (axis_->axis_num_ == 0) { + + // Edit these to suit your capture needs + float trigger_data = ictrl.v_current_control_integral_d; + float trigger_threshold = 0.5f; + float sample_data = Ialpha; + + static bool ready = false; + static bool capturing = false; + if (trigger_data < trigger_threshold) { + ready = true; + } + if (ready && trigger_data >= trigger_threshold) { + capturing = true; + ready = false; + } + if (capturing) { + oscilloscope[oscilloscope_pos] = sample_data; + if (++oscilloscope_pos >= OSCILLOSCOPE_SIZE) { + oscilloscope_pos = 0; + capturing = false; + } + } + } + return true; } diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 677fb996..e9d4978d 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -94,7 +94,7 @@ constexpr size_t AXIS_COUNT = 2; extern Axis *axes[AXIS_COUNT]; // if you use the oscilloscope feature you can bump up this value -#define OSCILLOSCOPE_SIZE 128 +#define OSCILLOSCOPE_SIZE 4096 extern float oscilloscope[OSCILLOSCOPE_SIZE]; extern size_t oscilloscope_pos; diff --git a/tools/odrive/shell.py b/tools/odrive/shell.py index c25278e3..cb9cd25f 100644 --- a/tools/odrive/shell.py +++ b/tools/odrive/shell.py @@ -5,7 +5,7 @@ import threading import fibre import odrive import odrive.enums -from odrive.utils import start_liveplotter, dump_errors +from odrive.utils import start_liveplotter, dump_errors, oscilloscope_dump #from odrive.enums import * # pylint: disable=W0614 def print_banner(): @@ -77,7 +77,8 @@ def launch_shell(args, logger, app_shutdown_token): interactive_variables = { 'start_liveplotter': start_liveplotter, - 'dump_errors': dump_errors + 'dump_errors': dump_errors, + 'oscilloscope_dump': oscilloscope_dump } # Expose all enums from odrive.enums diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index f5ce0c7f..9ffe20e6 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -60,6 +60,12 @@ def dump_errors(odrv, clear=False): else: print(prefix + _VT100Colors['green'] + "no error" + _VT100Colors['default']) +def oscilloscope_dump(odrv, num_vals, filename='oscilloscope.csv'): + with open(filename, 'w') as f: + for x in range(num_vals): + f.write(str(odrv.get_oscilloscope_val(x))) + f.write('\n') + data_rate = 10 plot_rate = 10 num_samples = 1000 From 163035d6ae44c271d448522b00e1997d08391fd1 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 12 Sep 2019 18:12:36 -0700 Subject: [PATCH 210/549] add analysis folder to workspace --- ODrive_Workspace.code-workspace | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ODrive_Workspace.code-workspace b/ODrive_Workspace.code-workspace index 3d990eaf..89da475f 100644 --- a/ODrive_Workspace.code-workspace +++ b/ODrive_Workspace.code-workspace @@ -8,6 +8,9 @@ }, { "path": "docs" + }, + { + "path": "analysis" } ], "settings": { From 50368da97c9e7737e49e1b7b81956861f19be827 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 13 Sep 2019 13:56:44 -0700 Subject: [PATCH 211/549] add new model fitting tools --- analysis/motor_analysis/ac_induction_motor.py | 216 ++++++++++++++++++ tools/plot_oscilloscope.py | 9 + 2 files changed, 225 insertions(+) create mode 100644 analysis/motor_analysis/ac_induction_motor.py create mode 100644 tools/plot_oscilloscope.py diff --git a/analysis/motor_analysis/ac_induction_motor.py b/analysis/motor_analysis/ac_induction_motor.py new file mode 100644 index 00000000..14fe075f --- /dev/null +++ b/analysis/motor_analysis/ac_induction_motor.py @@ -0,0 +1,216 @@ + +import numpy as np +import matplotlib.pyplot as plt +from scipy.integrate import solve_ivp +from scipy.optimize import least_squares +from engineering_notation import EngNumber + +filename = "oscilloscope.csv" + +PLOT_INITAL = True +PLOT_PROGRESS = False + +class ACMotor(): + """ + Models an induction motor based on Eq 10 in [1]. + [1] https://pdfs.semanticscholar.org/4770/15e472da4c2e05e9ff8c1b921c76a938f786.pdf + + + Note: This model refers all rotor quantities to the stator, i.e. the + quantities are as if the motor had a winding ratio of k = 1. + """ + + # parameters: (name, range) + parameter_definitions = [ + ('stator_inductance', (0, np.inf)), # aka l_s, [Henry] + ('stator_resistance', (0, np.inf)), # aka r_s, [Ohm] + ('rotor_inductance', (0, np.inf)), # aka l_r [Henry] + # ('rotor_resistance', (0, np.inf)), # aka r_r [Ohm] + ('mutual_inductance_factor', (0, 1.0)), #[unitless] = l_m**2 / (l_s * l_r) + ] + # parameter index lookup + pl = {r[0]:i for i, r in enumerate(parameter_definitions)} + + # states: (name, initial_value) + state_definitions = [ + ('stator_current', 0.0), # aka i_s, [A] + ('rotor_flux', 0.0), # aka Phi_r, [Wb] + ] # complex numbers + # state index lookup + sl = {r[0]:i for i, r in enumerate(state_definitions)} + + def __init__(self, params): + self.params = params + + # Assigned in run(): + # self.stator_voltage = None + # self.omega_stator = None + # self.omega_rotor = None + + def system_function(self, t, y): + # local shorthand for params + p = self.params + pl = ACMotor.pl + sl = ACMotor.sl + + # rotor_resistance = p[pl['rotor_resistance']] + rotor_resistance = 1.0 + mutual_inductance = np.sqrt(p[pl['mutual_inductance_factor']] * p[pl['stator_inductance']] * p[pl['rotor_inductance']]) + + tau_rotor = p[pl['rotor_inductance']] / rotor_resistance # [s] + coupling_factor = mutual_inductance / p[pl['rotor_inductance']] # aka k_r [unitless] + r_sigma = p[pl['stator_resistance']] + coupling_factor**2 * rotor_resistance # [Ohm] + leakage_factor = 1.0 - mutual_inductance**2 / (p[pl['rotor_inductance']] * p[pl['stator_inductance']]) # aka sigma [unitless] + tau_stator_prime = leakage_factor * p[pl['stator_inductance']] / r_sigma # [s] + + # [1] Eq 10a + dstator_current_dt = ( + -1.0j * self.omega_stator * tau_stator_prime * y[sl['stator_current']] + - coupling_factor / (r_sigma * tau_rotor) * (1.0j*self.omega_rotor * tau_rotor - 1.0) * y[sl['rotor_flux']] + + 1.0 / r_sigma * self.stator_voltage + - y[sl['stator_current']] + ) / tau_stator_prime + + # [1] Eq 10b + drotor_flux_dt = ( + -1.0j * (self.omega_stator - self.omega_rotor) * tau_rotor * y[sl['rotor_flux']] + + mutual_inductance * y[sl['stator_current']] + - y[sl['rotor_flux']] + ) / tau_rotor + + return [dstator_current_dt, drotor_flux_dt] + + def run(self, time_series, voltage, omega_stator, omega_rotor): + self.stator_voltage = voltage + self.omega_stator = omega_stator + self.omega_rotor = omega_rotor + + # y0 = np.nan * np.ones([2, time_series.size], dtype=np.complex) + # y0[:, 0] = [0.0, 0.0] # initial condition + y0 = np.array([x[1] for x in ACMotor.state_definitions], dtype=np.complex) + + result = solve_ivp(self.system_function, (time_series[0], time_series[-1]), y0, t_eval=time_series) + return result + # if not result.success: + # return None + # y_func = interp1d(result.t, result.y) + # return y_func(time_series) + +def plot_data(t, y, ref, title): + fig, ax1 = plt.subplots() + ax2 = ax1.twinx() + ax1.plot(t, ref, label='Measured current') + ax1.plot(t, y[0], label='Stator current') + ax2.plot(t, 1000*y[1], 'g', label='Rotor flux') + ax1.set_xlabel('time [s]') + ax1.set_ylabel('Current [A]') + ax2.set_ylabel('Flux [mWb]') + plt.title(title) + fig.legend() + plt.show() + + +# load test data +t = np.arange(4096)/8000.0 +voltage_step = 1.0 +with open(filename, 'r') as fp: + test_response = np.array([float(x) for x in fp.readlines()]) + + +inital_parameters = np.zeros(len(ACMotor.parameter_definitions)) +inital_parameters[ACMotor.pl['stator_inductance']] = 6.205440877238289e-05 +inital_parameters[ACMotor.pl['stator_resistance']] = 0.031451061367988586 +inital_parameters[ACMotor.pl['rotor_inductance']] = 0.25e-0 +# inital_parameters[ACMotor.pl['rotor_resistance']] = 1.0e-0 +# inital_parameters[ACMotor.pl['mutual_inductance']] = 2.40e-4 +inital_parameters[ACMotor.pl['mutual_inductance_factor']] = 0.8 + +# inital_parameters[ACMotor.pl['stator_resistance']] = 1.298 +# inital_parameters[ACMotor.pl['stator_inductance']] = 0.157228647 +# inital_parameters[ACMotor.pl['rotor_resistance']] = 0.975052932 +# inital_parameters[ACMotor.pl['rotor_inductance']] = 0.16674423623999998 +# inital_parameters[ACMotor.pl['mutual_inductance']] = 0.157221177 + +# Plot initial run +if(PLOT_INITAL): + motor = ACMotor(inital_parameters) + result = motor.run( + time_series = t, + voltage = voltage_step, + omega_stator = 0, + omega_rotor= 0) + + # plt.plot(t, result.y[0], label='Stator current [A] (initial)') + # plt.plot(t, 1000*result.y[1], label='Rotor flux [mWb] (initial)') + plot_data(t, result.y, test_response, 'initial') + + +# Fit to data +def get_residuals(params): + print(params) + motor = ACMotor(params) + result = motor.run( + time_series = t, + voltage = voltage_step, + omega_stator = 0, + omega_rotor= 0) + + residuals = test_response - np.real(result.y[0]) + fitness = sum(residuals**2) + print(fitness) + + if(PLOT_PROGRESS): + plot_data(t, result.y, test_response, 'progress') + + return residuals + +optiresult = least_squares(get_residuals, inital_parameters, + bounds=list(zip(*[x[1] for x in ACMotor.parameter_definitions])), + x_scale='jac', + diff_step = 1e-2 * np.array([ + 7.58192590e-04, + 3.07166671e-02, + 6.85207075e-02, + # 4.66461518e+00, + 8.67540012e-01]) +) +print(optiresult.message) + +print() +print('Fitted parameters:') +for i, r in enumerate(ACMotor.parameter_definitions): + print('{} = {}'.format(r[0], EngNumber(optiresult.x[i]))) + +print() +print('Derived parameters:') +mutual_inductance = np.sqrt( + optiresult.x[ACMotor.pl['mutual_inductance_factor']] + * optiresult.x[ACMotor.pl['stator_inductance']] + * optiresult.x[ACMotor.pl['rotor_inductance']] +) +coupling_factor = mutual_inductance / optiresult.x[ACMotor.pl['rotor_inductance']] +torque_constant = coupling_factor * mutual_inductance +print('mutual_inductance = {}H'.format(EngNumber(mutual_inductance))) +print('coupling_factor = {}'.format(EngNumber(coupling_factor))) +print('torque_constant = {}Nm/A^2'.format(EngNumber(torque_constant))) + +motor = ACMotor(optiresult.x) +result = motor.run( + time_series = t, + voltage = voltage_step, + omega_stator = 0, + omega_rotor= 0) + +final_stator_current = np.real(result.y[0,-1]) +final_rotor_flux = np.real(result.y[1,-1]) + +final_torque_per_amp = coupling_factor * final_rotor_flux + +print() +print('Final values:') +print('final_rotor_flux = {}Wb'.format(EngNumber(final_rotor_flux))) +print('final_stator_current = {}A'.format(EngNumber(final_stator_current))) +print('final_torque_per_amp = {}Nm/A'.format(EngNumber(final_torque_per_amp))) + +plot_data(t, result.y, test_response, 'final') + diff --git a/tools/plot_oscilloscope.py b/tools/plot_oscilloscope.py new file mode 100644 index 00000000..2a1809b8 --- /dev/null +++ b/tools/plot_oscilloscope.py @@ -0,0 +1,9 @@ + +from matplotlib import pyplot as plt +import sys + +with open(sys.argv[1]) as f: + data = list(map(float, f)) + +plt.plot(data) +plt.show() \ No newline at end of file From 6f575f46af107b1caf3c84d3d7a77579674f5e7b Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 13 Sep 2019 13:59:00 -0700 Subject: [PATCH 212/549] clean up some commented out stuff --- analysis/motor_analysis/ac_induction_motor.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/analysis/motor_analysis/ac_induction_motor.py b/analysis/motor_analysis/ac_induction_motor.py index 14fe075f..2f5c1902 100644 --- a/analysis/motor_analysis/ac_induction_motor.py +++ b/analysis/motor_analysis/ac_induction_motor.py @@ -85,16 +85,10 @@ class ACMotor(): self.omega_stator = omega_stator self.omega_rotor = omega_rotor - # y0 = np.nan * np.ones([2, time_series.size], dtype=np.complex) - # y0[:, 0] = [0.0, 0.0] # initial condition y0 = np.array([x[1] for x in ACMotor.state_definitions], dtype=np.complex) result = solve_ivp(self.system_function, (time_series[0], time_series[-1]), y0, t_eval=time_series) return result - # if not result.success: - # return None - # y_func = interp1d(result.t, result.y) - # return y_func(time_series) def plot_data(t, y, ref, title): fig, ax1 = plt.subplots() @@ -140,8 +134,6 @@ if(PLOT_INITAL): omega_stator = 0, omega_rotor= 0) - # plt.plot(t, result.y[0], label='Stator current [A] (initial)') - # plt.plot(t, 1000*result.y[1], label='Rotor flux [mWb] (initial)') plot_data(t, result.y, test_response, 'initial') From f9e373b29c392d4e06764ab46264bc679a70a441 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 13 Sep 2019 15:00:53 -0700 Subject: [PATCH 213/549] add rotor current plot --- analysis/motor_analysis/ac_induction_motor.py | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/analysis/motor_analysis/ac_induction_motor.py b/analysis/motor_analysis/ac_induction_motor.py index 2f5c1902..0a3f20a9 100644 --- a/analysis/motor_analysis/ac_induction_motor.py +++ b/analysis/motor_analysis/ac_induction_motor.py @@ -47,6 +47,13 @@ class ACMotor(): # self.omega_stator = None # self.omega_rotor = None + def get_mutual_inductance(self): + return np.sqrt( + self.params[ACMotor.pl['mutual_inductance_factor']] + * self.params[ACMotor.pl['stator_inductance']] + * self.params[ACMotor.pl['rotor_inductance']] + ) + def system_function(self, t, y): # local shorthand for params p = self.params @@ -55,7 +62,7 @@ class ACMotor(): # rotor_resistance = p[pl['rotor_resistance']] rotor_resistance = 1.0 - mutual_inductance = np.sqrt(p[pl['mutual_inductance_factor']] * p[pl['stator_inductance']] * p[pl['rotor_inductance']]) + mutual_inductance = self.get_mutual_inductance() tau_rotor = p[pl['rotor_inductance']] / rotor_resistance # [s] coupling_factor = mutual_inductance / p[pl['rotor_inductance']] # aka k_r [unitless] @@ -88,17 +95,24 @@ class ACMotor(): y0 = np.array([x[1] for x in ACMotor.state_definitions], dtype=np.complex) result = solve_ivp(self.system_function, (time_series[0], time_series[-1]), y0, t_eval=time_series) - return result + y = result.y + + # compute derived state + rotor_inductance = self.params[ACMotor.pl['rotor_inductance']] + rotor_current = (1/rotor_inductance) * (y[1] - self.get_mutual_inductance() * y[0]) + return np.vstack((y, rotor_current)) def plot_data(t, y, ref, title): - fig, ax1 = plt.subplots() - ax2 = ax1.twinx() + fig, (ax1, ax2) = plt.subplots(2, sharex=True) + ax1b = ax1.twinx() ax1.plot(t, ref, label='Measured current') ax1.plot(t, y[0], label='Stator current') - ax2.plot(t, 1000*y[1], 'g', label='Rotor flux') + ax2.plot(t, y[2], label='Rotor current') + ax1b.plot(t, 1000*y[1], 'g', label='Rotor flux') ax1.set_xlabel('time [s]') ax1.set_ylabel('Current [A]') - ax2.set_ylabel('Flux [mWb]') + ax1b.set_ylabel('Flux [mWb]') + ax2.set_ylabel('Current [A]') plt.title(title) fig.legend() plt.show() @@ -128,31 +142,31 @@ inital_parameters[ACMotor.pl['mutual_inductance_factor']] = 0.8 # Plot initial run if(PLOT_INITAL): motor = ACMotor(inital_parameters) - result = motor.run( + y = motor.run( time_series = t, voltage = voltage_step, omega_stator = 0, omega_rotor= 0) - plot_data(t, result.y, test_response, 'initial') + plot_data(t, y, test_response, 'initial') # Fit to data def get_residuals(params): print(params) motor = ACMotor(params) - result = motor.run( + y = motor.run( time_series = t, voltage = voltage_step, omega_stator = 0, omega_rotor= 0) - residuals = test_response - np.real(result.y[0]) + residuals = test_response - np.real(y[0]) fitness = sum(residuals**2) print(fitness) if(PLOT_PROGRESS): - plot_data(t, result.y, test_response, 'progress') + plot_data(t, y, test_response, 'progress') return residuals @@ -175,11 +189,7 @@ for i, r in enumerate(ACMotor.parameter_definitions): print() print('Derived parameters:') -mutual_inductance = np.sqrt( - optiresult.x[ACMotor.pl['mutual_inductance_factor']] - * optiresult.x[ACMotor.pl['stator_inductance']] - * optiresult.x[ACMotor.pl['rotor_inductance']] -) +mutual_inductance = motor.get_mutual_inductance() coupling_factor = mutual_inductance / optiresult.x[ACMotor.pl['rotor_inductance']] torque_constant = coupling_factor * mutual_inductance print('mutual_inductance = {}H'.format(EngNumber(mutual_inductance))) @@ -187,14 +197,14 @@ print('coupling_factor = {}'.format(EngNumber(coupling_factor))) print('torque_constant = {}Nm/A^2'.format(EngNumber(torque_constant))) motor = ACMotor(optiresult.x) -result = motor.run( +y = motor.run( time_series = t, voltage = voltage_step, omega_stator = 0, omega_rotor= 0) -final_stator_current = np.real(result.y[0,-1]) -final_rotor_flux = np.real(result.y[1,-1]) +final_stator_current = np.real(y[0,-1]) +final_rotor_flux = np.real(y[1,-1]) final_torque_per_amp = coupling_factor * final_rotor_flux @@ -204,5 +214,5 @@ print('final_rotor_flux = {}Wb'.format(EngNumber(final_rotor_flux))) print('final_stator_current = {}A'.format(EngNumber(final_stator_current))) print('final_torque_per_amp = {}Nm/A'.format(EngNumber(final_torque_per_amp))) -plot_data(t, result.y, test_response, 'final') +plot_data(t, y, test_response, 'final') From 36710551110b2844bc85deec54e2da463c3cefe5 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 13 Sep 2019 17:20:06 -0700 Subject: [PATCH 214/549] report assumed parameters --- analysis/motor_analysis/ac_induction_motor.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/analysis/motor_analysis/ac_induction_motor.py b/analysis/motor_analysis/ac_induction_motor.py index 0a3f20a9..9b48c874 100644 --- a/analysis/motor_analysis/ac_induction_motor.py +++ b/analysis/motor_analysis/ac_induction_motor.py @@ -9,6 +9,8 @@ filename = "oscilloscope.csv" PLOT_INITAL = True PLOT_PROGRESS = False +REPORT_PROGRESS = True +assumed_rotor_resistance = 1.0 # (TODO: set to None to estimate) class ACMotor(): """ @@ -22,11 +24,11 @@ class ACMotor(): # parameters: (name, range) parameter_definitions = [ - ('stator_inductance', (0, np.inf)), # aka l_s, [Henry] - ('stator_resistance', (0, np.inf)), # aka r_s, [Ohm] - ('rotor_inductance', (0, np.inf)), # aka l_r [Henry] - # ('rotor_resistance', (0, np.inf)), # aka r_r [Ohm] - ('mutual_inductance_factor', (0, 1.0)), #[unitless] = l_m**2 / (l_s * l_r) + ('stator_inductance', (0, np.inf), 'H'), # aka l_s, [Henry] + ('stator_resistance', (0, np.inf), 'ohm'), # aka r_s, [Ohm] + ('rotor_inductance', (0, np.inf), 'H'), # aka l_r [Henry] + # ('rotor_resistance', (0, np.inf), 'ohm'), # aka r_r [Ohm] + ('mutual_inductance_factor', (0, 1.0), ''), #[unitless] = l_m**2 / (l_s * l_r) ] # parameter index lookup pl = {r[0]:i for i, r in enumerate(parameter_definitions)} @@ -61,7 +63,7 @@ class ACMotor(): sl = ACMotor.sl # rotor_resistance = p[pl['rotor_resistance']] - rotor_resistance = 1.0 + rotor_resistance = assumed_rotor_resistance mutual_inductance = self.get_mutual_inductance() tau_rotor = p[pl['rotor_inductance']] / rotor_resistance # [s] @@ -153,7 +155,7 @@ if(PLOT_INITAL): # Fit to data def get_residuals(params): - print(params) + if REPORT_PROGRESS: print(params) motor = ACMotor(params) y = motor.run( time_series = t, @@ -163,7 +165,7 @@ def get_residuals(params): residuals = test_response - np.real(y[0]) fitness = sum(residuals**2) - print(fitness) + if REPORT_PROGRESS: print(fitness) if(PLOT_PROGRESS): plot_data(t, y, test_response, 'progress') @@ -182,10 +184,14 @@ optiresult = least_squares(get_residuals, inital_parameters, ) print(optiresult.message) +print() +print('Given parameters:') +print('rotor_resistance = {}ohm'.format(EngNumber(assumed_rotor_resistance))) + print() print('Fitted parameters:') for i, r in enumerate(ACMotor.parameter_definitions): - print('{} = {}'.format(r[0], EngNumber(optiresult.x[i]))) + print('{} = {}{}'.format(r[0], EngNumber(optiresult.x[i]), r[2])) print() print('Derived parameters:') From 66cb273540996b39a21962370114de0cb803b5fc Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 14 Sep 2019 17:00:36 -0700 Subject: [PATCH 215/549] fix use of old motor object when calculating derived parameters --- analysis/motor_analysis/ac_induction_motor.py | 136 ++++++++++-------- 1 file changed, 77 insertions(+), 59 deletions(-) diff --git a/analysis/motor_analysis/ac_induction_motor.py b/analysis/motor_analysis/ac_induction_motor.py index 9b48c874..e467d365 100644 --- a/analysis/motor_analysis/ac_induction_motor.py +++ b/analysis/motor_analysis/ac_induction_motor.py @@ -8,9 +8,10 @@ from engineering_notation import EngNumber filename = "oscilloscope.csv" PLOT_INITAL = True +DO_FITTING = True PLOT_PROGRESS = False REPORT_PROGRESS = True -assumed_rotor_resistance = 1.0 # (TODO: set to None to estimate) +assumed_rotor_resistance = 1 class ACMotor(): """ @@ -104,13 +105,51 @@ class ACMotor(): rotor_current = (1/rotor_inductance) * (y[1] - self.get_mutual_inductance() * y[0]) return np.vstack((y, rotor_current)) + def print_parameter_info(self): + print() + print('Given parameters:') + print('rotor_resistance = {}ohm'.format(EngNumber(assumed_rotor_resistance))) + + print() + print('Fitted parameters:') + for i, r in enumerate(ACMotor.parameter_definitions): + print('{} = {}{}'.format(r[0], EngNumber(self.params[i]), r[2])) + + print() + print('Derived parameters:') + mutual_inductance = motor.get_mutual_inductance() + coupling_factor = mutual_inductance / self.params[ACMotor.pl['rotor_inductance']] + torque_constant = coupling_factor * mutual_inductance + print('mutual_inductance = {}H'.format(EngNumber(mutual_inductance))) + print('coupling_factor = {}'.format(EngNumber(coupling_factor))) + print('torque_constant = {}Nm/A^2'.format(EngNumber(torque_constant))) + + def print_run_info(self, y): + final_stator_current_d = np.real(y[0,-1]) + final_stator_current_q = np.imag(y[0,-1]) + final_rotor_flux_d = np.real(y[1,-1]) + + mutual_inductance = self.get_mutual_inductance() + coupling_factor = mutual_inductance / self.params[ACMotor.pl['rotor_inductance']] + final_torque_per_q_amp = coupling_factor * final_rotor_flux_d + + print() + print('Final values:') + print('final_rotor_flux_d = {}Wb'.format(EngNumber(final_rotor_flux_d))) + print('final_stator_current_d = {}A'.format(EngNumber(final_stator_current_d))) + print('final_stator_current_q = {}A'.format(EngNumber(final_stator_current_q))) + print('final_torque_per_q_amp = {}Nm/A'.format(EngNumber(final_torque_per_q_amp))) + def plot_data(t, y, ref, title): fig, (ax1, ax2) = plt.subplots(2, sharex=True) ax1b = ax1.twinx() ax1.plot(t, ref, label='Measured current') - ax1.plot(t, y[0], label='Stator current') - ax2.plot(t, y[2], label='Rotor current') - ax1b.plot(t, 1000*y[1], 'g', label='Rotor flux') + ax1.plot(t, np.real(y[0]), label='Stator current (d)') + ax1.plot(t, np.imag(y[0]), label='Stator current (q)') + ax2.plot(t, np.real(y[2]), label='Rotor current (d)') + ax2.plot(t, np.imag(y[2]), label='Rotor current (q)') + ax1b.plot(t, 1000*np.real(y[1]), 'C2', label='Rotor flux (d)') + ax1b.plot(t, 1000*np.imag(y[1]), 'C3', label='Rotor flux (q)') ax1.set_xlabel('time [s]') ax1.set_ylabel('Current [A]') ax1b.set_ylabel('Flux [mWb]') @@ -119,7 +158,6 @@ def plot_data(t, y, ref, title): fig.legend() plt.show() - # load test data t = np.arange(4096)/8000.0 voltage_step = 1.0 @@ -128,12 +166,12 @@ with open(filename, 'r') as fp: inital_parameters = np.zeros(len(ACMotor.parameter_definitions)) -inital_parameters[ACMotor.pl['stator_inductance']] = 6.205440877238289e-05 -inital_parameters[ACMotor.pl['stator_resistance']] = 0.031451061367988586 -inital_parameters[ACMotor.pl['rotor_inductance']] = 0.25e-0 +inital_parameters[ACMotor.pl['stator_inductance']] = 7.72181086e-04 +inital_parameters[ACMotor.pl['stator_resistance']] = 3.06884624e-02 +inital_parameters[ACMotor.pl['rotor_inductance']] = assumed_rotor_resistance*6.82013522e-02 # inital_parameters[ACMotor.pl['rotor_resistance']] = 1.0e-0 # inital_parameters[ACMotor.pl['mutual_inductance']] = 2.40e-4 -inital_parameters[ACMotor.pl['mutual_inductance_factor']] = 0.8 +inital_parameters[ACMotor.pl['mutual_inductance_factor']] = 8.68671978e-01 # inital_parameters[ACMotor.pl['stator_resistance']] = 1.298 # inital_parameters[ACMotor.pl['stator_inductance']] = 0.157228647 @@ -142,14 +180,18 @@ inital_parameters[ACMotor.pl['mutual_inductance_factor']] = 0.8 # inital_parameters[ACMotor.pl['mutual_inductance']] = 0.157221177 # Plot initial run -if(PLOT_INITAL): +if PLOT_INITAL: + print() + print('Initial run:') motor = ACMotor(inital_parameters) + motor.print_parameter_info() + y = motor.run( time_series = t, voltage = voltage_step, omega_stator = 0, omega_rotor= 0) - + motor.print_run_info(y) plot_data(t, y, test_response, 'initial') @@ -167,58 +209,34 @@ def get_residuals(params): fitness = sum(residuals**2) if REPORT_PROGRESS: print(fitness) - if(PLOT_PROGRESS): + if PLOT_PROGRESS: plot_data(t, y, test_response, 'progress') return residuals -optiresult = least_squares(get_residuals, inital_parameters, - bounds=list(zip(*[x[1] for x in ACMotor.parameter_definitions])), - x_scale='jac', - diff_step = 1e-2 * np.array([ - 7.58192590e-04, - 3.07166671e-02, - 6.85207075e-02, - # 4.66461518e+00, - 8.67540012e-01]) -) -print(optiresult.message) +if DO_FITTING: + print() + print('Fitting parameters:') + optiresult = least_squares(get_residuals, inital_parameters, + bounds=list(zip(*[x[1] for x in ACMotor.parameter_definitions])), + x_scale='jac', + diff_step = 1e-2 * np.array([ + 7.58192590e-04, + 3.07166671e-02, + 6.85207075e-02, + # 4.66461518e+00, + 8.67540012e-01]) + ) + print(optiresult.message) -print() -print('Given parameters:') -print('rotor_resistance = {}ohm'.format(EngNumber(assumed_rotor_resistance))) + motor = ACMotor(optiresult.x) + motor.print_parameter_info() -print() -print('Fitted parameters:') -for i, r in enumerate(ACMotor.parameter_definitions): - print('{} = {}{}'.format(r[0], EngNumber(optiresult.x[i]), r[2])) - -print() -print('Derived parameters:') -mutual_inductance = motor.get_mutual_inductance() -coupling_factor = mutual_inductance / optiresult.x[ACMotor.pl['rotor_inductance']] -torque_constant = coupling_factor * mutual_inductance -print('mutual_inductance = {}H'.format(EngNumber(mutual_inductance))) -print('coupling_factor = {}'.format(EngNumber(coupling_factor))) -print('torque_constant = {}Nm/A^2'.format(EngNumber(torque_constant))) - -motor = ACMotor(optiresult.x) -y = motor.run( - time_series = t, - voltage = voltage_step, - omega_stator = 0, - omega_rotor= 0) - -final_stator_current = np.real(y[0,-1]) -final_rotor_flux = np.real(y[1,-1]) - -final_torque_per_amp = coupling_factor * final_rotor_flux - -print() -print('Final values:') -print('final_rotor_flux = {}Wb'.format(EngNumber(final_rotor_flux))) -print('final_stator_current = {}A'.format(EngNumber(final_stator_current))) -print('final_torque_per_amp = {}Nm/A'.format(EngNumber(final_torque_per_amp))) - -plot_data(t, y, test_response, 'final') + y = motor.run( + time_series = t, + voltage = voltage_step, + omega_stator = 0, + omega_rotor= 0) + motor.print_run_info(y) + plot_data(t, y, test_response, 'final') From 9e782cce7b9240fe5e66d95c0b86933fd79a2e74 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 15 Sep 2019 01:15:51 -0400 Subject: [PATCH 216/549] Revert "Formatting pass" This reverts commit 2b58d15d8d42db762d568bff8b58f0a2af0c1cbf. --- Firmware/MotorControl/axis.cpp | 129 ++++++------ Firmware/MotorControl/axis.hpp | 149 ++++++------- Firmware/MotorControl/controller.cpp | 93 ++++---- Firmware/MotorControl/controller.hpp | 43 ++-- Firmware/MotorControl/encoder.cpp | 190 ++++++++--------- Firmware/MotorControl/encoder.hpp | 144 ++++++------- Firmware/MotorControl/endstop.cpp | 18 +- Firmware/MotorControl/low_level.cpp | 167 ++++++++------- Firmware/MotorControl/low_level.h | 4 +- Firmware/MotorControl/main.cpp | 91 ++++---- Firmware/MotorControl/motor.cpp | 144 +++++++------ Firmware/MotorControl/motor.hpp | 199 +++++++++--------- Firmware/MotorControl/nvm_config.hpp | 28 +-- .../MotorControl/sensorless_estimator.cpp | 14 +- .../MotorControl/sensorless_estimator.hpp | 34 +-- Firmware/MotorControl/trapTraj.cpp | 40 ++-- Firmware/MotorControl/trapTraj.hpp | 18 +- Firmware/MotorControl/utils.c | 44 ++-- Firmware/MotorControl/utils.h | 6 +- 19 files changed, 793 insertions(+), 762 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 945de0ef..00411b0b 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -3,9 +3,9 @@ #include #include "gpio.h" -#include "communication/interface_can.hpp" #include "odrive_main.h" #include "utils.h" +#include "communication/interface_can.hpp" Axis::Axis(int axis_num, const AxisHardwareConfig_t& hw_config, @@ -26,12 +26,13 @@ Axis::Axis(int axis_num, motor_(motor), trap_(trap), min_endstop_(min_endstop), - max_endstop_(max_endstop) { - encoder_.axis_ = this; + max_endstop_(max_endstop) +{ + encoder_.axis_ = this; sensorless_estimator_.axis_ = this; - controller_.axis_ = this; - motor_.axis_ = this; - trap_.axis_ = this; + controller_.axis_ = this; + motor_.axis_ = this; + trap_.axis_ = this; decode_step_dir_pins(); watchdog_feed(); min_endstop_.axis_ = this; @@ -40,29 +41,29 @@ Axis::Axis(int axis_num, Axis::LockinConfig_t Axis::default_calibration() { Axis::LockinConfig_t config; - config.current = 10.0f; // [A] - config.ramp_time = 0.4f; // [s] - config.ramp_distance = 1 * M_PI; // [rad] - config.accel = 20.0f; // [rad/s^2] - config.vel = 40.0f; // [rad/s] - config.finish_distance = 100.0f * 2.0f * M_PI; // [rad] - config.finish_on_vel = false; + config.current = 10.0f; // [A] + config.ramp_time = 0.4f; // [s] + config.ramp_distance = 1 * M_PI; // [rad] + config.accel = 20.0f; // [rad/s^2] + config.vel = 40.0f; // [rad/s] + config.finish_distance = 100.0f * 2.0f * M_PI; // [rad] + config.finish_on_vel = false; config.finish_on_distance = true; - config.finish_on_enc_idx = true; + config.finish_on_enc_idx = true; return config; } Axis::LockinConfig_t Axis::default_sensorless() { Axis::LockinConfig_t config; - config.current = 10.0f; // [A] - config.ramp_time = 0.4f; // [s] - config.ramp_distance = 1 * M_PI; // [rad] - config.accel = 200.0f; // [rad/s^2] - config.vel = 400.0f; // [rad/s] - config.finish_distance = 100.0f; // [rad] - config.finish_on_vel = true; + config.current = 10.0f; // [A] + config.ramp_time = 0.4f; // [s] + config.ramp_distance = 1 * M_PI; // [rad] + config.accel = 200.0f; // [rad/s^2] + config.vel = 400.0f; // [rad/s] + config.finish_distance = 100.0f; // [rad] + config.finish_on_vel = true; config.finish_on_distance = false; - config.finish_on_enc_idx = false; + config.finish_on_enc_idx = false; return config; } @@ -70,6 +71,7 @@ static void step_cb_wrapper(void* ctx) { reinterpret_cast(ctx)->step_cb(); } + // @brief Sets up all components of the axis, // such as gate driver and encoder hardware. void Axis::setup() { @@ -85,7 +87,7 @@ static void run_state_machine_loop_wrapper(void* ctx) { // @brief Starts run_state_machine_loop in a new thread void Axis::start_thread() { osThreadDef(thread_def, run_state_machine_loop_wrapper, hw_config_.thread_priority, 0, 4 * 512); - thread_id_ = osThreadCreate(osThread(thread_def), this); + thread_id_ = osThreadCreate(osThread(thread_def), this); thread_id_valid_ = true; } @@ -106,27 +108,27 @@ bool Axis::wait_for_current_meas() { void Axis::step_cb() { if (step_dir_active_) { GPIO_PinState dir_pin = HAL_GPIO_ReadPin(dir_port_, dir_pin_); - float dir = (dir_pin == GPIO_PIN_SET) ? 1.0f : -1.0f; + float dir = (dir_pin == GPIO_PIN_SET) ? 1.0f : -1.0f; controller_.input_pos_ += dir * config_.counts_per_step; controller_.input_pos_updated(); } }; void Axis::load_default_step_dir_pin_config( - const AxisHardwareConfig_t& hw_config, Config_t* config) { + const AxisHardwareConfig_t& hw_config, Config_t* config) { config->step_gpio_pin = hw_config.step_gpio_pin; - config->dir_gpio_pin = hw_config.dir_gpio_pin; + config->dir_gpio_pin = hw_config.dir_gpio_pin; } -void Axis::load_default_can_id(const int& id, Config_t& config) { +void Axis::load_default_can_id(const int& id, Config_t& config){ config.can_node_id = id; } void Axis::decode_step_dir_pins() { step_port_ = get_gpio_port_by_pin(config_.step_gpio_pin); - step_pin_ = get_gpio_pin_by_pin(config_.step_gpio_pin); - dir_port_ = get_gpio_port_by_pin(config_.dir_gpio_pin); - dir_pin_ = get_gpio_pin_by_pin(config_.dir_gpio_pin); + step_pin_ = get_gpio_pin_by_pin(config_.step_gpio_pin); + dir_port_ = get_gpio_port_by_pin(config_.dir_gpio_pin); + dir_pin_ = get_gpio_pin_by_pin(config_.dir_gpio_pin); } // @brief (de)activates step/dir input @@ -134,7 +136,7 @@ void Axis::set_step_dir_active(bool active) { if (active) { // Set up the direction GPIO as input GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Pin = dir_pin_; + GPIO_InitStruct.Pin = dir_pin_; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(dir_port_, &GPIO_InitStruct); @@ -172,8 +174,9 @@ bool Axis::do_checks() { } } - if (board_config.power_supply_wattage > 0.0f && - (Ibus_sum * vbus_voltage) > board_config.power_supply_wattage) { + if(board_config.power_supply_wattage > 0.0f && + (Ibus_sum * vbus_voltage) > board_config.power_supply_wattage) + { error_ |= ERROR_DC_BUS_OVER_POWER; } @@ -219,10 +222,10 @@ bool Axis::watchdog_check() { } } -bool Axis::run_lockin_spin(const LockinConfig_t& lockin_config) { +bool Axis::run_lockin_spin(const LockinConfig_t &lockin_config) { // Spiral up current for softer rotor lock-in lockin_state_ = LOCKIN_STATE_RAMP; - float x = 0.0f; + float x = 0.0f; run_control_loop([&]() { float phase = wrap_pm_pi(lockin_config.ramp_distance * x); float I_mag = lockin_config.current * x; @@ -231,11 +234,11 @@ bool Axis::run_lockin_spin(const LockinConfig_t& lockin_config) { return false; return x < 1.0f; }); - + // Spin states float distance = lockin_config.ramp_distance; - float phase = wrap_pm_pi(distance); - float vel = distance / lockin_config.ramp_time; + float phase = wrap_pm_pi(distance); + float vel = distance / lockin_config.ramp_time; // Function of states to check if we are done auto spin_done = [&](bool vel_override = false) -> bool { @@ -258,7 +261,7 @@ bool Axis::run_lockin_spin(const LockinConfig_t& lockin_config) { if (!motor_.update(lockin_config.current, phase, vel)) return false; - return !spin_done(true); //vel_override to go to next phase + return !spin_done(true); //vel_override to go to next phase }); if (!encoder_.index_found_) @@ -267,7 +270,7 @@ bool Axis::run_lockin_spin(const LockinConfig_t& lockin_config) { // Constant speed if (!spin_done()) { lockin_state_ = LOCKIN_STATE_CONST_VEL; - vel = lockin_config.vel; // reset to actual specified vel to avoid small integration error + vel = lockin_config.vel; // reset to actual specified vel to avoid small integration error run_control_loop([&]() { distance += vel * current_meas_period; phase = wrap_pm_pi(phase + vel * current_meas_period); @@ -284,7 +287,7 @@ bool Axis::run_lockin_spin(const LockinConfig_t& lockin_config) { // Note run_sensorless_control_loop and run_closed_loop_control_loop are very similar and differ only in where we get the estimate from. bool Axis::run_sensorless_control_loop() { - run_control_loop([this]() { + run_control_loop([this](){ if (controller_.config_.control_mode >= Controller::CTRL_MODE_POSITION_CONTROL) return error_ |= ERROR_POS_CTRL_DURING_SENSORLESS, false; @@ -293,7 +296,7 @@ bool Axis::run_sensorless_control_loop() { if (!controller_.update(sensorless_estimator_.pll_pos_, sensorless_estimator_.vel_estimate_, ¤t_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; if (!motor_.update(current_setpoint, sensorless_estimator_.phase_, sensorless_estimator_.vel_estimate_)) - return false; // set_error should update axis.error_ + return false; // set_error should update axis.error_ return true; }); return check_for_errors(); @@ -307,7 +310,7 @@ bool Axis::run_closed_loop_control_loop() { controller_.vel_integrator_current_ = 0.0f; set_step_dir_active(config_.enable_step_dir); - run_control_loop([this]() { + run_control_loop([this](){ // Note that all estimators are updated in the loop prefix in run_control_loop float current_setpoint; if (controller_.config_.use_load_encoder) { @@ -323,7 +326,7 @@ bool Axis::run_closed_loop_control_loop() { return error_ |= ERROR_CONTROLLER_FAILED, false; //TODO: Make controller.set_error float phase_vel = 2 * M_PI * encoder_.vel_estimate_ / (float)encoder_.config_.cpr * motor_.config_.pole_pairs; if (!motor_.update(current_setpoint, encoder_.phase_, phase_vel)) - return false; // set_error should update axis.error_ + return false; // set_error should update axis.error_ // Handle the homing case if (homing_.homing_state == HOMING_STATE_HOMING) { @@ -336,21 +339,21 @@ bool Axis::run_closed_loop_control_loop() { encoder_.set_linear_count(static_cast(controller_.pos_setpoint_)); controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; - controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ; + controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ; controller_.input_pos_ = 0.0f; controller_.input_pos_updated(); - controller_.input_vel_ = 0.0f; + controller_.input_vel_ = 0.0f; controller_.input_current_ = 0.0f; homing_.homing_state = HOMING_STATE_MOVE_TO_ZERO; } } else if (homing_.homing_state == HOMING_STATE_MOVE_TO_ZERO) { - if (!min_endstop_.getEndstopState() && controller_.trajectory_done_) { + if(!min_endstop_.getEndstopState() && controller_.trajectory_done_){ controller_.config_.control_mode = homing_.storedControlMode; - controller_.config_.input_mode = homing_.storedInputMode; - homing_.homing_state = HOMING_STATE_IDLE; - homing_.isHomed = true; + controller_.config_.input_mode = homing_.storedInputMode; + homing_.homing_state = HOMING_STATE_IDLE; + homing_.isHomed = true; } } else { // Check for endstop presses @@ -378,6 +381,7 @@ bool Axis::run_idle_loop() { // Infinite loop that does calibration and enters main control loop as appropriate void Axis::run_state_machine_loop() { + // arm! motor_.arm(); @@ -392,11 +396,12 @@ void Axis::run_state_machine_loop() { task_chain_[pos++] = AXIS_STATE_ENCODER_INDEX_SEARCH; if (config_.startup_encoder_offset_calibration) task_chain_[pos++] = AXIS_STATE_ENCODER_OFFSET_CALIBRATION; - if (config_.startup_closed_loop_control) { - if (config_.startup_homing) + if (config_.startup_closed_loop_control){ + if(config_.startup_homing) task_chain_[pos++] = AXIS_STATE_HOMING; task_chain_[pos++] = AXIS_STATE_CLOSED_LOOP_CONTROL; - } else if (config_.startup_sensorless_control) + } + else if (config_.startup_sensorless_control) task_chain_[pos++] = AXIS_STATE_SENSORLESS_CONTROL; task_chain_[pos++] = AXIS_STATE_IDLE; } else if (requested_state_ == AXIS_STATE_FULL_CALIBRATION_SEQUENCE) { @@ -410,7 +415,7 @@ void Axis::run_state_machine_loop() { task_chain_[pos++] = AXIS_STATE_IDLE; } task_chain_[pos++] = AXIS_STATE_UNDEFINED; // TODO: bounds checking - requested_state_ = AXIS_STATE_UNDEFINED; + requested_state_ = AXIS_STATE_UNDEFINED; // Auto-clear any invalid state error error_ &= ~ERROR_INVALID_STATE; } @@ -428,7 +433,7 @@ void Axis::run_state_machine_loop() { case AXIS_STATE_ENCODER_INDEX_SEARCH: { if (!motor_.is_calibrated_) goto invalid_state_label; - if (encoder_.config_.idx_search_unidirectional && motor_.config_.direction == 0) + if (encoder_.config_.idx_search_unidirectional && motor_.config_.direction==0) goto invalid_state_label; status = encoder_.run_index_search(); @@ -452,25 +457,25 @@ void Axis::run_state_machine_loop() { } break; case AXIS_STATE_LOCKIN_SPIN: { - if (!motor_.is_calibrated_ || motor_.config_.direction == 0) + if (!motor_.is_calibrated_ || motor_.config_.direction==0) goto invalid_state_label; status = run_lockin_spin(config_.lockin); } break; case AXIS_STATE_SENSORLESS_CONTROL: { - if (!motor_.is_calibrated_ || motor_.config_.direction == 0) - goto invalid_state_label; - status = run_lockin_spin(config_.sensorless_ramp); // TODO: restart if desired + if (!motor_.is_calibrated_ || motor_.config_.direction==0) + goto invalid_state_label; + status = run_lockin_spin(config_.sensorless_ramp); // TODO: restart if desired if (status) { // call to controller.reset() that happend when arming means that vel_setpoint // is zeroed. So we make the setpoint the spinup target for smooth transition. controller_.vel_setpoint_ = config_.sensorless_ramp.vel; - status = run_sensorless_control_loop(); + status = run_sensorless_control_loop(); } } break; case AXIS_STATE_CLOSED_LOOP_CONTROL: { - if (!motor_.is_calibrated_ || motor_.config_.direction == 0) + if (!motor_.is_calibrated_ || motor_.config_.direction==0) goto invalid_state_label; if (!encoder_.is_ready_) goto invalid_state_label; @@ -479,7 +484,7 @@ void Axis::run_state_machine_loop() { case AXIS_STATE_IDLE: { run_idle_loop(); - status = motor_.arm(); // done with idling - try to arm the motor + status = motor_.arm(); // done with idling - try to arm the motor } break; default: diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index a52eb858..4264b23b 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -5,6 +5,7 @@ #error "This file should not be included directly. Include odrive_main.h instead." #endif + enum HomingState_t { HOMING_STATE_IDLE, HOMING_STATE_HOMING, @@ -12,52 +13,52 @@ enum HomingState_t { }; class Axis { - public: +public: enum Error_t { - ERROR_NONE = 0x00, - ERROR_INVALID_STATE = 0x01, // + template void run_control_loop(const T& update_handler) { while (requested_state_ == AXIS_STATE_UNDEFINED) { // look for errors at axis level and also all subcomponents bool checks_ok = do_checks(); // Update all estimators // Note: updates run even if checks fail - bool updates_ok = do_updates(); + bool updates_ok = do_updates(); - // make sure the watchdog is being fed. + // make sure the watchdog is being fed. bool watchdog_ok = watchdog_check(); - + 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 @@ -220,7 +221,7 @@ class Axis { } } - bool run_lockin_spin(const LockinConfig_t& lockin_config); + bool run_lockin_spin(const LockinConfig_t &lockin_config); bool run_sensorless_control_loop(); bool run_closed_loop_control_loop(); bool run_idle_loop(); @@ -247,8 +248,8 @@ class Axis { volatile bool thread_id_valid_ = false; // variables exposed on protocol - Error_t error_ = ERROR_NONE; - bool step_dir_active_ = false; // auto enabled after calibration, based on config.enable_step_dir + Error_t error_ = ERROR_NONE; + bool step_dir_active_ = false; // auto enabled after calibration, based on config.enable_step_dir // updated from config in constructor, and on protocol hook GPIO_TypeDef* step_port_; @@ -256,16 +257,16 @@ class Axis { GPIO_TypeDef* dir_port_; uint16_t dir_pin_; - State_t requested_state_ = AXIS_STATE_STARTUP_SEQUENCE; - State_t task_chain_[10] = {AXIS_STATE_UNDEFINED}; - State_t& current_state_ = task_chain_[0]; - uint32_t loop_counter_ = 0; + State_t requested_state_ = AXIS_STATE_STARTUP_SEQUENCE; + State_t task_chain_[10] = { AXIS_STATE_UNDEFINED }; + State_t& current_state_ = task_chain_[0]; + uint32_t loop_counter_ = 0; LockinState_t lockin_state_ = LOCKIN_STATE_INACTIVE; Homing_t homing_; uint32_t last_heartbeat_ = 0; // watchdog - uint32_t watchdog_current_value_ = 0; + uint32_t watchdog_current_value_= 0; // Communication protocol definitions auto make_protocol_definitions() { @@ -329,10 +330,12 @@ class Axis { make_protocol_object("min_endstop", min_endstop_.make_protocol_definitions()), make_protocol_object("max_endstop", max_endstop_.make_protocol_definitions()), make_protocol_function("watchdog_feed", *this, &Axis::watchdog_feed), - make_protocol_function("clear_errors", *this, &Axis::clear_errors)); + make_protocol_function("clear_errors", *this, &Axis::clear_errors) + ); } }; + DEFINE_ENUM_FLAG_OPERATORS(Axis::Error_t) #endif /* __AXIS_HPP */ diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 215d3c17..19833c15 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -3,15 +3,17 @@ #include -Controller::Controller(Config_t& config) : config_(config) { +Controller::Controller(Config_t& config) : + config_(config) +{ update_filter_gains(); } void Controller::reset() { - pos_setpoint_ = 0.0f; - vel_setpoint_ = 0.0f; + pos_setpoint_ = 0.0f; + vel_setpoint_ = 0.0f; vel_integrator_current_ = 0.0f; - current_setpoint_ = 0.0f; + current_setpoint_ = 0.0f; } void Controller::set_error(Error_t error) { @@ -33,13 +35,13 @@ void Controller::move_to_pos(float goal_point) { axis_->trap_.config_.accel_limit, axis_->trap_.config_.decel_limit); traj_start_loop_count_ = axis_->loop_counter_; - trajectory_done_ = false; + trajectory_done_ = false; } -void Controller::move_incremental(float displacement, bool from_input_pos = true) { - if (from_input_pos) { +void Controller::move_incremental(float displacement, bool from_input_pos = true){ + if(from_input_pos){ input_pos_ += displacement; - } else { + } else{ input_pos_ = pos_setpoint_ + displacement; } @@ -60,17 +62,17 @@ void Controller::start_anticogging_calibration() { bool Controller::home_axis() { if (axis_->min_endstop_.config_.enabled) { axis_->homing_.storedControlMode = config_.control_mode; - axis_->homing_.storedInputMode = config_.input_mode; + axis_->homing_.storedInputMode = config_.input_mode; config_.control_mode = CTRL_MODE_VELOCITY_CONTROL; - config_.input_mode = INPUT_MODE_VEL_RAMP; + config_.input_mode = INPUT_MODE_VEL_RAMP; input_pos_ = 0.0f; input_pos_updated(); - input_vel_ = -config_.homing_speed; + input_vel_ = -config_.homing_speed; input_current_ = 0.0f; - axis_->homing_.isHomed = false; + axis_->homing_.isHomed = false; axis_->homing_.homing_state = HOMING_STATE_HOMING; } else { return false; @@ -94,19 +96,19 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) } if (config_.anticogging.index < 3600) { config_.control_mode = CTRL_MODE_POSITION_CONTROL; - input_pos_ = config_.anticogging.index * axis_->encoder_.getCoggingRatio(); - input_vel_ = 0.0f; - input_current_ = 0.0f; + input_pos_ = config_.anticogging.index * axis_->encoder_.getCoggingRatio(); + input_vel_ = 0.0f; + input_current_ = 0.0f; input_pos_updated(); return false; } else { config_.anticogging.index = 0; - config_.control_mode = CTRL_MODE_POSITION_CONTROL; - input_pos_ = 0.0f; // Send the motor home - input_vel_ = 0.0f; - input_current_ = 0.0f; + config_.control_mode = CTRL_MODE_POSITION_CONTROL; + input_pos_ = 0.0f; // Send the motor home + input_vel_ = 0.0f; + input_current_ = 0.0f; input_pos_updated(); - anticogging_valid_ = true; + anticogging_valid_ = true; config_.anticogging.calib_anticogging = false; return true; } @@ -115,8 +117,8 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) } void Controller::update_filter_gains() { - input_filter_ki_ = 2.0f * config_.input_filter_bandwidth; // basic conversion to discrete time - input_filter_kp_ = 0.25f * (input_filter_ki_ * input_filter_ki_); // Critically damped + input_filter_ki_ = 2.0f * config_.input_filter_bandwidth; // basic conversion to discrete time + input_filter_kp_ = 0.25f * (input_filter_ki_ * input_filter_ki_); // Critically damped } namespace { @@ -138,14 +140,14 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // do nothing } break; case INPUT_MODE_PASSTHROUGH: { - pos_setpoint_ = input_pos_; - vel_setpoint_ = input_vel_; + pos_setpoint_ = input_pos_; + vel_setpoint_ = input_vel_; current_setpoint_ = input_current_; } break; case INPUT_MODE_VEL_RAMP: { float max_step_size = std::abs(current_meas_period * config_.vel_ramp_rate); - float full_step = input_vel_ - vel_setpoint_; - float step = std::clamp(full_step, -max_step_size, max_step_size); + float full_step = input_vel_ - vel_setpoint_; + float step = std::clamp(full_step, -max_step_size, max_step_size); vel_setpoint_ += step; current_setpoint_ = step / current_meas_period * config_.inertia; @@ -159,12 +161,12 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s } break; case INPUT_MODE_POS_FILTER: { // 2nd order pos tracking filter - float delta_pos = input_pos_ - pos_setpoint_; // Pos error - float delta_vel = input_vel_ - vel_setpoint_; // Vel error - float accel = input_filter_kp_ * delta_pos + input_filter_ki_ * delta_vel; // Feedback - current_setpoint_ = accel * config_.inertia; // Accel - vel_setpoint_ += current_meas_period * accel; // delta vel - pos_setpoint_ += current_meas_period * vel_setpoint_; // Delta pos + float delta_pos = input_pos_ - pos_setpoint_; // Pos error + float delta_vel = input_vel_ - vel_setpoint_; // Vel error + float accel = input_filter_kp_*delta_pos + input_filter_ki_*delta_vel; // Feedback + current_setpoint_ = accel * config_.inertia; // Accel + vel_setpoint_ += current_meas_period * accel; // delta vel + pos_setpoint_ += current_meas_period * vel_setpoint_; // Delta pos } break; case INPUT_MODE_MIRROR: { if (config_.axis_to_mirror < AXIS_COUNT) { @@ -179,7 +181,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // // NOT YET IMPLEMENTED // } break; case INPUT_MODE_TRAP_TRAJ: { - if (input_pos_updated_) { + if(input_pos_updated_){ move_to_pos(input_pos_); input_pos_updated_ = false; } @@ -192,28 +194,29 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s if (t > axis_->trap_.Tf_) { // Drop into position control mode when done to avoid problems on loop counter delta overflow config_.control_mode = CTRL_MODE_POSITION_CONTROL; - pos_setpoint_ = input_pos_; - vel_setpoint_ = 0.0f; - current_setpoint_ = 0.0f; - trajectory_done_ = true; + pos_setpoint_ = input_pos_; + vel_setpoint_ = 0.0f; + current_setpoint_ = 0.0f; + trajectory_done_ = true; } else { TrapezoidalTrajectory::Step_t traj_step = axis_->trap_.eval(t); - pos_setpoint_ = traj_step.Y; - vel_setpoint_ = traj_step.Yd; - current_setpoint_ = traj_step.Ydd * config_.inertia; + pos_setpoint_ = traj_step.Y; + vel_setpoint_ = traj_step.Yd; + current_setpoint_ = traj_step.Ydd * config_.inertia; } - anticogging_pos = pos_setpoint_; // FF the position setpoint instead of the pos_estimate + anticogging_pos = pos_setpoint_; // FF the position setpoint instead of the pos_estimate } break; default: { set_error(ERROR_INVALID_INPUT_MODE); return false; } + } // Position control // TODO Decide if we want to use encoder or pll position here float gain_scheduling_multiplier = 1.0f; - float vel_des = vel_setpoint_; + float vel_des = vel_setpoint_; if (config_.control_mode >= CTRL_MODE_POSITION_CONTROL) { float pos_err; if (config_.setpoints_in_cpr) { @@ -276,14 +279,14 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // Current limiting bool limited = false; - float Ilim = axis_->motor_.effective_current_lim(); + float Ilim = axis_->motor_.effective_current_lim(); if (Iq > Ilim) { limited = true; - Iq = Ilim; + Iq = Ilim; } if (Iq < -Ilim) { limited = true; - Iq = -Ilim; + Iq = -Ilim; } // Velocity integrator (behaviour dependent on limiting) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index c31692b0..6e6565bb 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -6,7 +6,7 @@ #endif class Controller { - public: +public: enum Error_t { ERROR_NONE = 0, ERROR_OVERSPEED = 0x01, @@ -18,14 +18,14 @@ class Controller { // Note: these should be sorted from lowest level of control to // highest level of control, to allow "<" style comparisons. - enum ControlMode_t { - CTRL_MODE_VOLTAGE_CONTROL = 0, - CTRL_MODE_CURRENT_CONTROL = 1, + enum ControlMode_t{ + CTRL_MODE_VOLTAGE_CONTROL = 0, + CTRL_MODE_CURRENT_CONTROL = 1, CTRL_MODE_VELOCITY_CONTROL = 2, CTRL_MODE_POSITION_CONTROL = 3 }; - enum InputMode_t { + enum InputMode_t{ INPUT_MODE_INACTIVE, INPUT_MODE_PASSTHROUGH, INPUT_MODE_VEL_RAMP, @@ -39,8 +39,8 @@ class Controller { typedef struct { uint32_t index = 0; float cogging_map[3600]; - bool pre_calibrated = false; - bool calib_anticogging = false; + bool pre_calibrated = false; + bool calib_anticogging = false; float calib_pos_threshold = 1.0f; float calib_vel_threshold = 1.0f; float cogging_ratio = 1.0f; @@ -49,9 +49,9 @@ class Controller { struct Config_t { ControlMode_t control_mode = CTRL_MODE_POSITION_CONTROL; //see: ControlMode_t - InputMode_t input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t - float pos_gain = 20.0f; // [(counts/s) / counts] - float vel_gain = 5.0f / 10000.0f; // [A/(counts/s)] + InputMode_t input_mode = INPUT_MODE_PASSTHROUGH; //see: InputMode_t + float pos_gain = 20.0f; // [(counts/s) / counts] + float vel_gain = 5.0f / 10000.0f; // [A/(counts/s)] // float vel_gain = 5.0f / 200.0f, // [A/(rad/s)] float vel_integrator_gain = 10.0f / 10000.0f; // [A/(counts/s * s)] float vel_limit = 20000.0f; // [counts/s] @@ -86,7 +86,7 @@ class Controller { void move_incremental(float displacement, bool from_goal_point); bool home_axis(); - + // TODO: make this more similar to other calibration loops void start_anticogging_calibration(); bool anticogging_calibration(float pos_estimate, float vel_estimate); @@ -95,7 +95,7 @@ class Controller { bool update(float pos_estimate, float vel_estimate, float* current_setpoint); Config_t& config_; - Axis* axis_ = nullptr; // set by Axis constructor + Axis* axis_ = nullptr; // set by Axis constructor // TODO: anticogging overhaul: // - expose selected (all?) variables on protocol @@ -109,18 +109,18 @@ class Controller { float vel_setpoint_ = 0.0f; // float vel_setpoint = 800.0f; float vel_integrator_current_ = 0.0f; // [A] - float current_setpoint_ = 0.0f; // [A] + float current_setpoint_ = 0.0f; // [A] - float input_pos_ = 0.0f; - float input_vel_ = 0.0f; - float input_current_ = 0.0f; + float input_pos_ = 0.0f; + float input_vel_ = 0.0f; + float input_current_ = 0.0f; float input_filter_kp_ = 0.0f; float input_filter_ki_ = 0.0f; bool input_pos_updated_ = false; - + uint32_t traj_start_loop_count_ = 0; - bool trajectory_done_ = true; + bool trajectory_done_ = true; bool anticogging_valid_ = false; @@ -129,7 +129,7 @@ class Controller { return make_protocol_member_list( make_protocol_property("error", &error_), make_protocol_property("input_pos", &input_pos_, - [](void* ctx) { static_cast(ctx)->input_pos_updated(); }, this), + [](void* ctx) { static_cast(ctx)->input_pos_updated(); }, this), make_protocol_property("input_vel", &input_vel_), make_protocol_property("input_current", &input_current_), make_protocol_ro_property("pos_setpoint", &pos_setpoint_), @@ -172,10 +172,11 @@ class Controller { make_protocol_property("anticogging_enabled", &config_.anticogging.enable))), make_protocol_function("move_incremental", *this, &Controller::move_incremental, "displacement", "from_goal_point"), make_protocol_function("start_anticogging_calibration", *this, &Controller::start_anticogging_calibration), - make_protocol_function("home_axis", *this, &Controller::home_axis)); + make_protocol_function("home_axis", *this, &Controller::home_axis) + ); } }; DEFINE_ENUM_FLAG_OPERATORS(Controller::Error_t) -#endif // __CONTROLLER_HPP +#endif // __CONTROLLER_HPP diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 1ede2f47..ac2d8e0c 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -1,9 +1,12 @@ #include "odrive_main.h" + Encoder::Encoder(const EncoderHardwareConfig_t& hw_config, - Config_t& config) : hw_config_(hw_config), - config_(config) { + Config_t& config) : + hw_config_(hw_config), + config_(config) +{ update_pll_gains(); if (config.pre_calibrated && (config.mode == Encoder::MODE_HALL || config.mode == Encoder::MODE_SINCOS)) { @@ -19,7 +22,7 @@ void Encoder::setup() { HAL_TIM_Encoder_Start(hw_config_.timer, TIM_CHANNEL_ALL); set_idx_subscribe(); - if (config_.mode & MODE_FLAG_ABS) { + if(config_.mode & MODE_FLAG_ABS){ abs_spi_cs_pin_init(); abs_spi_init(); if (axis_->controller_.config_.anticogging.pre_calibrated) { @@ -33,7 +36,7 @@ void Encoder::set_error(Error_t error) { axis_->error_ |= Axis::ERROR_ENCODER_FAILED; } -bool Encoder::do_checks() { +bool Encoder::do_checks(){ return error_ == ERROR_NONE; } @@ -48,10 +51,10 @@ void Encoder::enc_index_cb() { 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 + set_linear_count(0); // Avoid position control transient after search if (config_.pre_calibrated) { is_ready_ = true; - if (axis_->controller_.config_.anticogging.pre_calibrated) { + if(axis_->controller_.config_.anticogging.pre_calibrated){ axis_->controller_.anticogging_valid_ = true; } } else { @@ -70,15 +73,15 @@ void Encoder::enc_index_cb() { void Encoder::set_idx_subscribe(bool override_enable) { if (config_.use_index && (override_enable || !config_.find_idx_on_lockin_only)) { GPIO_subscribe(hw_config_.index_port, hw_config_.index_pin, GPIO_PULLDOWN, - enc_index_cb_wrapper, this); + enc_index_cb_wrapper, this); } else if (!config_.use_index || config_.find_idx_on_lockin_only) { GPIO_unsubscribe(hw_config_.index_port, hw_config_.index_pin); } } 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 + 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)) { @@ -129,7 +132,7 @@ void Encoder::set_circular_count(int32_t count, bool update_offset) { bool Encoder::run_index_search() { config_.use_index = true; - index_found_ = false; + index_found_ = false; if (!config_.idx_search_unidirectional && axis_->motor_.config_.direction == 0) { axis_->motor_.config_.direction = 1; } @@ -140,11 +143,11 @@ bool Encoder::run_index_search() { } bool Encoder::run_direction_find() { - int32_t init_enc_val = shadow_count_; - bool orig_finish_on_distance = axis_->config_.calibration_lockin.finish_on_distance; + int32_t init_enc_val = shadow_count_; + bool orig_finish_on_distance = axis_->config_.calibration_lockin.finish_on_distance; axis_->config_.calibration_lockin.finish_on_distance = true; - axis_->motor_.config_.direction = 1; // Must test spin forwards for direction detect logic - bool status = axis_->run_lockin_spin(axis_->config_.calibration_lockin); + axis_->motor_.config_.direction = 1; // Must test spin forwards for direction detect logic + bool status = axis_->run_lockin_spin(axis_->config_.calibration_lockin); axis_->config_.calibration_lockin.finish_on_distance = orig_finish_on_distance; if (status) { @@ -191,9 +194,9 @@ bool Encoder::run_offset_calibration() { // go to motor zero phase for start_lock_duration to get ready to scan int i = 0; - axis_->run_control_loop([&]() { + axis_->run_control_loop([&](){ if (!axis_->motor_.enqueue_voltage_timings(voltage_magnitude, 0.0f)) - return false; // error set inside enqueue_voltage_timings + return false; // error set inside enqueue_voltage_timings axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); return ++i < start_lock_duration * current_meas_hz; }); @@ -201,20 +204,20 @@ bool Encoder::run_offset_calibration() { return false; int32_t init_enc_val = shadow_count_; - int64_t encvaluesum = 0; + int64_t encvaluesum = 0; // scan forward i = 0; axis_->run_control_loop([&]() { float phase = wrap_pm_pi(config_.calib_scan_distance * static_cast(i) / static_cast(num_steps) - config_.calib_scan_distance / 2.0f); float v_alpha = voltage_magnitude * our_arm_cos_f32(phase); - float v_beta = voltage_magnitude * our_arm_sin_f32(phase); + float v_beta = voltage_magnitude * our_arm_sin_f32(phase); if (!axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta)) - return false; // error set inside enqueue_voltage_timings + return false; // error set inside enqueue_voltage_timings axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); encvaluesum += shadow_count_; - + return ++i < num_steps; }); if (axis_->error_ != Axis::ERROR_NONE) @@ -248,13 +251,13 @@ bool Encoder::run_offset_calibration() { axis_->run_control_loop([&]() { float phase = wrap_pm_pi(-config_.calib_scan_distance * static_cast(i) / static_cast(num_steps) + config_.calib_scan_distance / 2.0f); float v_alpha = voltage_magnitude * our_arm_cos_f32(phase); - float v_beta = voltage_magnitude * our_arm_sin_f32(phase); + float v_beta = voltage_magnitude * our_arm_sin_f32(phase); if (!axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta)) - return false; // error set inside enqueue_voltage_timings + return false; // error set inside enqueue_voltage_timings axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); encvaluesum += shadow_count_; - + return ++i < num_steps; }); if (axis_->error_ != Axis::ERROR_NONE) @@ -270,26 +273,13 @@ bool Encoder::run_offset_calibration() { static bool decode_hall(uint8_t hall_state, int32_t* hall_cnt) { switch (hall_state) { - case 0b001: - *hall_cnt = 0; - return true; - case 0b011: - *hall_cnt = 1; - return true; - case 0b010: - *hall_cnt = 2; - return true; - case 0b110: - *hall_cnt = 3; - return true; - case 0b100: - *hall_cnt = 4; - return true; - case 0b101: - *hall_cnt = 5; - return true; - default: - return false; + case 0b001: *hall_cnt = 0; return true; + case 0b011: *hall_cnt = 1; return true; + case 0b010: *hall_cnt = 2; return true; + case 0b110: *hall_cnt = 3; return true; + case 0b100: *hall_cnt = 4; return true; + case 0b101: *hall_cnt = 5; return true; + default: return false; } } @@ -309,36 +299,37 @@ void Encoder::sample_now() { } break; case MODE_SPI_ABS_AMS: - case MODE_SPI_ABS_CUI: { + case MODE_SPI_ABS_CUI: + { // Do nothing } break; default: { - set_error(ERROR_UNSUPPORTED_ENCODER_MODE); + set_error(ERROR_UNSUPPORTED_ENCODER_MODE); } break; } } -bool Encoder::abs_spi_init() { +bool Encoder::abs_spi_init(){ if ((config_.mode & MODE_FLAG_ABS) == 0x0) return false; - uint32_t cr1, cr2; + uint32_t cr1,cr2; cr1 = hw_config_.spi->Instance->CR1; cr2 = hw_config_.spi->Instance->CR2; - SPI_HandleTypeDef* spi = hw_config_.spi; - spi->Init.Mode = SPI_MODE_MASTER; - spi->Init.Direction = SPI_DIRECTION_2LINES; - spi->Init.DataSize = SPI_DATASIZE_16BIT; - spi->Init.CLKPolarity = SPI_POLARITY_LOW; - spi->Init.CLKPhase = SPI_PHASE_2EDGE; - spi->Init.NSS = SPI_NSS_SOFT; + SPI_HandleTypeDef * spi = hw_config_.spi; + spi->Init.Mode = SPI_MODE_MASTER; + spi->Init.Direction = SPI_DIRECTION_2LINES; + spi->Init.DataSize = SPI_DATASIZE_16BIT; + spi->Init.CLKPolarity = SPI_POLARITY_LOW; + spi->Init.CLKPhase = SPI_PHASE_2EDGE; + spi->Init.NSS = SPI_NSS_SOFT; spi->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; - spi->Init.FirstBit = SPI_FIRSTBIT_MSB; - spi->Init.TIMode = SPI_TIMODE_DISABLE; - spi->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; - spi->Init.CRCPolynomial = 10; + spi->Init.FirstBit = SPI_FIRSTBIT_MSB; + spi->Init.TIMode = SPI_TIMODE_DISABLE; + spi->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; + spi->Init.CRCPolynomial = 10; HAL_SPI_DeInit(spi); HAL_SPI_Init(spi); @@ -351,9 +342,9 @@ bool Encoder::abs_spi_init() { return true; } -bool Encoder::abs_spi_start_transaction() { - if (config_.mode & MODE_FLAG_ABS) { - if (hw_config_.spi->State != HAL_SPI_STATE_READY) { +bool Encoder::abs_spi_start_transaction(){ + if (config_.mode & MODE_FLAG_ABS){ + if(hw_config_.spi->State != HAL_SPI_STATE_READY){ set_error(ERROR_ABS_SPI_NOT_READY); return false; } @@ -361,54 +352,54 @@ bool Encoder::abs_spi_start_transaction() { hw_config_.spi->Instance->CR1 = abs_spi_cr1; hw_config_.spi->Instance->CR2 = abs_spi_cr2; HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_RESET); - HAL_SPI_TransmitReceive_DMA(hw_config_.spi, (uint8_t*)abs_spi_dma_tx_, (uint8_t*)abs_spi_dma_rx_, 1); + HAL_SPI_TransmitReceive_DMA(hw_config_.spi,(uint8_t*)abs_spi_dma_tx_,(uint8_t*)abs_spi_dma_rx_,1); } return true; } -uint8_t parity(uint16_t v) { +uint8_t parity(uint16_t v){ v ^= v >> 8; v ^= v >> 4; v ^= v >> 2; v ^= v >> 1; return v & 1; } -void Encoder::abs_spi_cb() { +void Encoder::abs_spi_cb(){ HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); switch (config_.mode) { case MODE_SPI_ABS_AMS: { - uint8_t parity_calc, parity_bit; - parity_calc = parity(abs_spi_dma_rx_[0] & 0x7FFF); - parity_bit = abs_spi_dma_rx_[0] >> 15; + uint8_t parity_calc, parity_bit; + parity_calc = parity(abs_spi_dma_rx_[0]&0x7FFF); + parity_bit = abs_spi_dma_rx_[0] >>15; - if (parity_calc == parity_bit) { - pos_abs_ = abs_spi_dma_rx_[0] & 0x3FFF; - // We are going to ignore values all high or low - // This might happen in normal operation, but its unlikely - // The filter will handle these cases - if (pos_abs_ != 0 && pos_abs_ != 0x3FFF) - abs_spi_pos_updated_ = true; - } - } break; + if(parity_calc == parity_bit){ + pos_abs_ = abs_spi_dma_rx_[0] & 0x3FFF; + // We are going to ignore values all high or low + // This might happen in normal operation, but its unlikely + // The filter will handle these cases + if(pos_abs_ != 0 && pos_abs_ != 0x3FFF) + abs_spi_pos_updated_ = true; + } + }break; default: { - set_error(ERROR_UNSUPPORTED_ENCODER_MODE); + set_error(ERROR_UNSUPPORTED_ENCODER_MODE); } break; } is_ready_ = true; } -void Encoder::abs_spi_cs_pin_init() { +void Encoder::abs_spi_cs_pin_init(){ // Decode cs pin abs_spi_cs_port_ = get_gpio_port_by_pin(config_.abs_spi_cs_gpio_pin); - abs_spi_cs_pin_ = get_gpio_pin_by_pin(config_.abs_spi_cs_gpio_pin); + abs_spi_cs_pin_ = get_gpio_pin_by_pin(config_.abs_spi_cs_gpio_pin); // Init cs pin HAL_GPIO_DeInit(abs_spi_cs_port_, abs_spi_cs_pin_); GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Pin = abs_spi_cs_pin_; - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; - GPIO_InitStruct.Pull = GPIO_PULLUP; + GPIO_InitStruct.Pin = abs_spi_cs_pin_; + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(abs_spi_cs_port_, &GPIO_InitStruct); @@ -425,7 +416,7 @@ bool Encoder::update() { //TODO: use count_in_cpr_ instead as shadow_count_ can overflow //or use 64 bit int16_t delta_enc_16 = (int16_t)tim_cnt_sample_ - (int16_t)shadow_count_; - delta_enc = (int32_t)delta_enc_16; //sign extend + delta_enc = (int32_t)delta_enc_16; //sign extend } break; case MODE_HALL: { @@ -444,40 +435,41 @@ bool Encoder::update() { } break; case MODE_SINCOS: { - float phase = fast_atan2(sincos_sample_s_, sincos_sample_c_); + float phase = fast_atan2(sincos_sample_s_, sincos_sample_c_); int fake_count = (int)(1000.0f * phase); //CPR = 6283 = 2pi * 1k delta_enc = fake_count - count_in_cpr_; delta_enc = mod(delta_enc, 6283); - if (delta_enc > 6283 / 2) + if (delta_enc > 6283/2) delta_enc -= 6283; } break; - + case MODE_SPI_ABS_AMS: - case MODE_SPI_ABS_CUI: { - if (abs_spi_pos_updated_ == false && abs_spi_pos_init_once_) { + case MODE_SPI_ABS_CUI:{ + if(abs_spi_pos_updated_ == false && abs_spi_pos_init_once_){ // Low pass filter the error spi_error_rate_ += current_meas_period * (1.0f - spi_error_rate_); // if (spi_error_rate_ > 0.005f) // set_error(ERROR_ABS_SPI_COM_FAIL); - } else + } + else // Low pass filter the error spi_error_rate_ += current_meas_period * (0.0f - spi_error_rate_); abs_spi_pos_updated_ = false; - delta_enc = pos_abs_ - count_in_cpr_; - delta_enc = mod(delta_enc, config_.cpr); - if (delta_enc > config_.cpr / 2) + delta_enc = pos_abs_ - count_in_cpr_; + delta_enc = mod(delta_enc, config_.cpr); + if (delta_enc > config_.cpr/2) delta_enc -= config_.cpr; - if (!abs_spi_pos_init_once_ && delta_enc != 0) { + if(!abs_spi_pos_init_once_ && delta_enc != 0){ abs_spi_pos_init_once_ = true; } - } break; + }break; default: { - set_error(ERROR_UNSUPPORTED_ENCODER_MODE); - return false; + set_error(ERROR_UNSUPPORTED_ENCODER_MODE); + return false; } break; } @@ -485,13 +477,13 @@ bool Encoder::update() { count_in_cpr_ += delta_enc; count_in_cpr_ = mod(count_in_cpr_, config_.cpr); - if (config_.mode & MODE_FLAG_ABS) + if(config_.mode & MODE_FLAG_ABS) count_in_cpr_ = pos_abs_; //// run pll (for now pll is in units of encoder counts) // Predict current pos pos_estimate_ += current_meas_period * vel_estimate_; - pos_cpr_ += current_meas_period * vel_estimate_; + pos_cpr_ += current_meas_period * vel_estimate_; // discrete phase detector float delta_pos = static_cast(shadow_count_) - static_cast(std::floor(pos_estimate_)); float delta_pos_cpr = static_cast(count_in_cpr_) - static_cast(std::floor(pos_cpr_)); @@ -512,7 +504,7 @@ bool Encoder::update() { // if we are stopped, make sure we don't randomly drift if (snap_to_zero_vel || !config_.enable_phase_interpolation) { interpolation_ = 0.5f; - // reset interpolation if encoder edge comes + // reset interpolation if encoder edge comes } else if (delta_enc > 0) { interpolation_ = 0.0f; } else if (delta_enc < 0) { diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 3e140ca0..d85bcb44 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -6,18 +6,18 @@ #endif class Encoder { - public: +public: enum Error_t { ERROR_NONE = 0, ERROR_UNSTABLE_GAIN = 0x01, ERROR_CPR_POLEPAIRS_MISMATCH = 0x02, ERROR_NO_RESPONSE = 0x04, ERROR_UNSUPPORTED_ENCODER_MODE = 0x08, - ERROR_ILLEGAL_HALL_STATE = 0x10, - ERROR_INDEX_NOT_FOUND_YET = 0x20, - ERROR_ABS_SPI_TIMEOUT = 0x40, - ERROR_ABS_SPI_COM_FAIL = 0x80, - ERROR_ABS_SPI_NOT_READY = 0x100, + ERROR_ILLEGAL_HALL_STATE = 0x10, + ERROR_INDEX_NOT_FOUND_YET = 0x20, + ERROR_ABS_SPI_TIMEOUT = 0x40, + ERROR_ABS_SPI_COM_FAIL = 0x80, + ERROR_ABS_SPI_NOT_READY = 0x100, }; enum Mode_t { @@ -31,30 +31,30 @@ class Encoder { struct Config_t { Encoder::Mode_t mode = Encoder::MODE_INCREMENTAL; - bool use_index = false; - bool pre_calibrated = false; // If true, this means the offset stored in - // configuration is valid and does not need - // be determined by run_offset_calibration. - // In this case the encoder will enter ready - // state as soon as the index is found. - bool zero_count_on_find_idx = true; - int32_t cpr = (2048 * 4); // Default resolution of CUI-AMT102 encoder, - int32_t offset = 0; // Offset between encoder count and rotor electrical phase - float offset_float = 0.0f; // Sub-count phase alignment offset - bool enable_phase_interpolation = true; // Use velocity to interpolate inside the count state - float calib_range = 0.02f; // Accuracy required to pass encoder cpr check - float calib_scan_distance = 16.0f * M_PI; // rad electrical - float calib_scan_omega = 4.0f * M_PI; // rad/s electrical - float bandwidth = 1000.0f; - bool find_idx_on_lockin_only = false; // Only be sensitive during lockin scan constant vel state - bool idx_search_unidirectional = false; // Only allow index search in known direction - bool ignore_illegal_hall_state = false; // dont error on bad states like 000 or 111 - uint16_t abs_spi_cs_gpio_pin = 0; + bool use_index = false; + bool pre_calibrated = false; // If true, this means the offset stored in + // configuration is valid and does not need + // be determined by run_offset_calibration. + // In this case the encoder will enter ready + // state as soon as the index is found. + bool zero_count_on_find_idx = true; + int32_t cpr = (2048 * 4); // Default resolution of CUI-AMT102 encoder, + int32_t offset = 0; // Offset between encoder count and rotor electrical phase + float offset_float = 0.0f; // Sub-count phase alignment offset + bool enable_phase_interpolation = true; // Use velocity to interpolate inside the count state + float calib_range = 0.02f; // Accuracy required to pass encoder cpr check + float calib_scan_distance = 16.0f * M_PI; // rad electrical + float calib_scan_omega = 4.0f * M_PI; // rad/s electrical + float bandwidth = 1000.0f; + bool find_idx_on_lockin_only = false; // Only be sensitive during lockin scan constant vel state + bool idx_search_unidirectional = false; // Only allow index search in known direction + bool ignore_illegal_hall_state = false; // dont error on bad states like 000 or 111 + uint16_t abs_spi_cs_gpio_pin = 0; }; Encoder(const EncoderHardwareConfig_t& hw_config, - Config_t& config); - + Config_t& config); + void setup(); void set_error(Error_t error); bool do_checks(); @@ -76,27 +76,27 @@ class Encoder { const EncoderHardwareConfig_t& hw_config_; Config_t& config_; - Axis* axis_ = nullptr; // set by Axis constructor + Axis* axis_ = nullptr; // set by Axis constructor - Error_t error_ = ERROR_NONE; - bool index_found_ = false; - bool is_ready_ = false; - int32_t shadow_count_ = 0; - int32_t count_in_cpr_ = 0; - float interpolation_ = 0.0f; - float phase_ = 0.0f; // [count] - float pos_estimate_ = 0.0f; // [count] - float pos_cpr_ = 0.0f; // [count] - float vel_estimate_ = 0.0f; // [count/s] - float pll_kp_ = 0.0f; // [count/s / count] - float pll_ki_ = 0.0f; // [(count/s^2) / count] - float calib_scan_response_ = 0.0f; // debug report from offset calib - int32_t pos_abs_ = 0; - float spi_error_rate_ = 0.0f; + Error_t error_ = ERROR_NONE; + bool index_found_ = false; + bool is_ready_ = false; + int32_t shadow_count_ = 0; + int32_t count_in_cpr_ = 0; + float interpolation_ = 0.0f; + float phase_ = 0.0f; // [count] + float pos_estimate_ = 0.0f; // [count] + float pos_cpr_ = 0.0f; // [count] + float vel_estimate_ = 0.0f; // [count/s] + float pll_kp_ = 0.0f; // [count/s / count] + float pll_ki_ = 0.0f; // [(count/s^2) / count] + float calib_scan_response_ = 0.0f; // debug report from offset calib + int32_t pos_abs_ = 0; + float spi_error_rate_ = 0.0f; - int16_t tim_cnt_sample_ = 0; // + int16_t tim_cnt_sample_ = 0; // // Updated by low_level pwm_adc_cb - uint8_t hall_state_ = 0x0; // bit[0] = HallA, .., bit[2] = HallC + uint8_t hall_state_ = 0x0; // bit[0] = HallA, .., bit[2] = HallC float sincos_sample_s_ = 0.0f; float sincos_sample_c_ = 0.0f; @@ -106,14 +106,14 @@ class Encoder { void abs_spi_cs_pin_init(); uint16_t abs_spi_dma_tx_[2] = {0xFFFF, 0x0000}; uint16_t abs_spi_dma_rx_[2]; - bool abs_spi_pos_updated_ = false; + bool abs_spi_pos_updated_ = false; bool abs_spi_pos_init_once_ = false; GPIO_TypeDef* abs_spi_cs_port_; uint16_t abs_spi_cs_pin_; uint32_t abs_spi_cr1; uint32_t abs_spi_cr2; - constexpr float getCoggingRatio() { + constexpr float getCoggingRatio(){ return config_.cpr / 3600.0f; } @@ -136,32 +136,34 @@ class Encoder { make_protocol_ro_property("spi_error_rate", &spi_error_rate_), make_protocol_object("config", - make_protocol_property("mode", &config_.mode, - [](void* ctx) { static_cast(ctx)->abs_spi_init(); }, this), - make_protocol_property("use_index", &config_.use_index, - [](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, - [](void* ctx) { static_cast(ctx)->check_pre_calibrated(); }, this), - make_protocol_property("abs_spi_cs_gpio_pin", &config_.abs_spi_cs_gpio_pin, - [](void* ctx) { static_cast(ctx)->abs_spi_cs_pin_init(); }, 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), - make_protocol_property("offset_float", &config_.offset_float), - make_protocol_property("enable_phase_interpolation", &config_.enable_phase_interpolation), - make_protocol_property("bandwidth", &config_.bandwidth, - [](void* ctx) { static_cast(ctx)->update_pll_gains(); }, this), - make_protocol_property("calib_range", &config_.calib_range), - make_protocol_property("calib_scan_distance", &config_.calib_scan_distance), - make_protocol_property("calib_scan_omega", &config_.calib_scan_omega), - make_protocol_property("idx_search_unidirectional", &config_.idx_search_unidirectional), - make_protocol_property("ignore_illegal_hall_state", &config_.ignore_illegal_hall_state)), - make_protocol_function("set_linear_count", *this, &Encoder::set_linear_count, "count")); + make_protocol_property("mode", &config_.mode, + [](void* ctx) { static_cast(ctx)->abs_spi_init(); }, this), + make_protocol_property("use_index", &config_.use_index, + [](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, + [](void* ctx) { static_cast(ctx)->check_pre_calibrated(); }, this), + make_protocol_property("abs_spi_cs_gpio_pin", &config_.abs_spi_cs_gpio_pin, + [](void* ctx) { static_cast(ctx)->abs_spi_cs_pin_init(); }, 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), + make_protocol_property("offset_float", &config_.offset_float), + make_protocol_property("enable_phase_interpolation", &config_.enable_phase_interpolation), + make_protocol_property("bandwidth", &config_.bandwidth, + [](void* ctx) { static_cast(ctx)->update_pll_gains(); }, this), + make_protocol_property("calib_range", &config_.calib_range), + make_protocol_property("calib_scan_distance", &config_.calib_scan_distance), + make_protocol_property("calib_scan_omega", &config_.calib_scan_omega), + make_protocol_property("idx_search_unidirectional", &config_.idx_search_unidirectional), + make_protocol_property("ignore_illegal_hall_state", &config_.ignore_illegal_hall_state) + ), + make_protocol_function("set_linear_count", *this, &Encoder::set_linear_count, "count") + ); } }; DEFINE_ENUM_FLAG_OPERATORS(Encoder::Error_t) -#endif // __ENCODER_HPP +#endif // __ENCODER_HPP diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index da8e260a..809e52f1 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -6,18 +6,18 @@ Endstop::Endstop(Endstop::Config_t& config) } void Endstop::update() { - uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); + uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); - auto last_pin_state = pin_state_; - pin_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); + auto last_pin_state = pin_state_; + pin_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); if (pin_state_ != last_pin_state) { debounce_timer_ = axis_->loop_counter_ * current_meas_period; } if (config_.enabled) { float now = axis_->loop_counter_ * current_meas_period; - if ((now - debounce_timer_) >= (config_.debounce_ms * 0.001f)) { // Debounce timer expired, take the new pin state - endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state - debounce_timer_ = now - (config_.debounce_ms * 0.001f); // Ensure timer doesn't have overflow issues + if ((now - debounce_timer_) >= (config_.debounce_ms * 0.001f)) { // Debounce timer expired, take the new pin state + endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state + debounce_timer_ = now - (config_.debounce_ms * 0.001f); // Ensure timer doesn't have overflow issues } else { endstop_state_ = endstop_state_; // Do nothing } @@ -30,18 +30,18 @@ bool Endstop::getEndstopState() { return endstop_state_; } -void Endstop::update_endstop_config() { +void Endstop::update_endstop_config(){ set_endstop_enabled(config_.enabled); } void Endstop::set_endstop_enabled(bool enable) { if (config_.gpio_num != 0) { - uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); + uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); if (enable) { HAL_GPIO_DeInit(gpio_port, gpio_pin); GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Pin = gpio_pin; + GPIO_InitStruct.Pin = gpio_pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = config_.is_active_high ? GPIO_PULLDOWN : GPIO_PULLUP; HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 23b7c002..1cd17134 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -28,21 +28,21 @@ /* Private macros ------------------------------------------------------------*/ /* Private typedef -----------------------------------------------------------*/ /* Global constant data ------------------------------------------------------*/ -const float adc_full_scale = (float)(1 << 12); +const float adc_full_scale = (float)(1 << 12); const float adc_ref_voltage = 3.3f; /* Global variables ----------------------------------------------------------*/ // This value is updated by the DC-bus reading ADC. // Arbitrary non-zero inital value to avoid division by zero if ADC reading is late -float vbus_voltage = 12.0f; +float vbus_voltage = 12.0f; bool brake_resistor_armed = false; /* Private constant data -----------------------------------------------------*/ -static const GPIO_TypeDef* GPIOs_to_samp[] = {GPIOA, GPIOB, GPIOC}; -static const int num_GPIO = sizeof(GPIOs_to_samp) / sizeof(GPIOs_to_samp[0]); +static const GPIO_TypeDef* GPIOs_to_samp[] = { GPIOA, GPIOB, GPIOC }; +static const int num_GPIO = sizeof(GPIOs_to_samp) / sizeof(GPIOs_to_samp[0]); /* Private variables ---------------------------------------------------------*/ // Two motors, sampling port A,B,C (coherent with current meas timing) -static uint16_t GPIO_port_samples[2][num_GPIO]; +static uint16_t GPIO_port_samples [2][num_GPIO]; /* CPU critical section helpers ----------------------------------------------*/ /* Safety critical functions -------------------------------------------------*/ @@ -109,8 +109,8 @@ void safety_critical_arm_motor_pwm(Motor& motor) { // safety_critical_arm_motor_phases is called. // @returns true if the motor was in a state other than disarmed before bool safety_critical_disarm_motor_pwm(Motor& motor) { - uint32_t mask = cpu_enter_critical(); - bool was_armed = motor.armed_state_ != Motor::ARMED_STATE_DISARMED; + uint32_t mask = cpu_enter_critical(); + bool was_armed = motor.armed_state_ != Motor::ARMED_STATE_DISARMED; motor.armed_state_ = Motor::ARMED_STATE_DISARMED; __HAL_TIM_MOE_DISABLE_UNCONDITIONALLY(motor.hw_config_.timer); cpu_exit_critical(mask); @@ -154,7 +154,7 @@ void safety_critical_apply_motor_pwm_timings(Motor& motor, uint16_t timings[3]) // @brief Arms the brake resistor void safety_critical_arm_brake_resistor() { - uint32_t mask = cpu_enter_critical(); + uint32_t mask = cpu_enter_critical(); brake_resistor_armed = true; htim2.Instance->CCR3 = 0; htim2.Instance->CCR4 = TIM_APB1_PERIOD_CLOCKS + 1; @@ -166,7 +166,7 @@ void safety_critical_arm_brake_resistor() { // After calling this, the brake resistor can only be armed again // by calling safety_critical_arm_brake_resistor(). void safety_critical_disarm_brake_resistor() { - uint32_t mask = cpu_enter_critical(); + uint32_t mask = cpu_enter_critical(); brake_resistor_armed = false; htim2.Instance->CCR3 = 0; htim2.Instance->CCR4 = TIM_APB1_PERIOD_CLOCKS + 1; @@ -217,7 +217,7 @@ void start_adc_pwm() { start_pwm(&htim8); // TODO: explain why this offset sync_timers(&htim1, &htim8, TIM_CLOCKSOURCE_ITR0, TIM_1_8_PERIOD_CLOCKS / 2 - 1 * 128, - &htim13); + &htim13); // Motor output starts in the disabled state __HAL_TIM_MOE_DISABLE_UNCONDITIONALLY(&htim1); @@ -242,7 +242,7 @@ void start_adc_pwm() { void start_pwm(TIM_HandleTypeDef* htim) { // Init PWM - int half_load = TIM_1_8_PERIOD_CLOCKS / 2; + int half_load = TIM_1_8_PERIOD_CLOCKS / 2; htim->Instance->CCR1 = half_load; htim->Instance->CCR2 = half_load; htim->Instance->CCR3 = half_load; @@ -265,8 +265,8 @@ void sync_timers(TIM_HandleTypeDef* htim_a, TIM_HandleTypeDef* htim_b, // Store intial timer configs uint16_t MOE_store_a = htim_a->Instance->BDTR & (TIM_BDTR_MOE); uint16_t MOE_store_b = htim_b->Instance->BDTR & (TIM_BDTR_MOE); - uint16_t CR2_store = htim_a->Instance->CR2; - uint16_t SMCR_store = htim_b->Instance->SMCR; + uint16_t CR2_store = htim_a->Instance->CR2; + uint16_t SMCR_store = htim_b->Instance->SMCR; // Turn off output htim_a->Instance->BDTR &= ~(TIM_BDTR_MOE); htim_b->Instance->BDTR &= ~(TIM_BDTR_MOE); @@ -299,12 +299,12 @@ void sync_timers(TIM_HandleTypeDef* htim_a, TIM_HandleTypeDef* htim_b, // Set and start reference timebase timer (if used) if (htim_refbase) { htim_refbase->Instance->CNT = count_offset; - htim_refbase->Instance->CR1 |= (TIM_CR1_CEN); // start + htim_refbase->Instance->CR1 |= (TIM_CR1_CEN); // start } // Start Timer a htim_a->Instance->CR1 |= (TIM_CR1_CEN); // Restore timer configs - htim_a->Instance->CR2 = CR2_store; + htim_a->Instance->CR2 = CR2_store; htim_b->Instance->SMCR = SMCR_store; // restore output htim_a->Instance->BDTR |= MOE_store_a; @@ -312,7 +312,7 @@ void sync_timers(TIM_HandleTypeDef* htim_a, TIM_HandleTypeDef* htim_b, } // @brief ADC1 measurements are written to this buffer by DMA -uint16_t adc_measurements_[ADC_CHANNEL_COUNT] = {0}; +uint16_t adc_measurements_[ADC_CHANNEL_COUNT] = { 0 }; // @brief Starts the general purpose ADC on the ADC1 peripheral. // The measured ADC voltages can be read with get_adc_voltage(). @@ -327,19 +327,20 @@ void start_general_purpose_adc() { ADC_ChannelConfTypeDef sConfig; // Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) - hadc1.Instance = ADC1; - hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; - hadc1.Init.Resolution = ADC_RESOLUTION_12B; - hadc1.Init.ScanConvMode = ENABLE; - hadc1.Init.ContinuousConvMode = ENABLE; + hadc1.Instance = ADC1; + hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; + hadc1.Init.Resolution = ADC_RESOLUTION_12B; + hadc1.Init.ScanConvMode = ENABLE; + hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; - hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; - hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; - hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; - hadc1.Init.NbrOfConversion = ADC_CHANNEL_COUNT; + hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; + hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; + hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; + hadc1.Init.NbrOfConversion = ADC_CHANNEL_COUNT; hadc1.Init.DMAContinuousRequests = ENABLE; - hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; - if (HAL_ADC_Init(&hadc1) != HAL_OK) { + hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; + if (HAL_ADC_Init(&hadc1) != HAL_OK) + { _Error_Handler((char*)__FILE__, __LINE__); } @@ -347,7 +348,7 @@ void start_general_purpose_adc() { sConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES; for (uint32_t channel = 0; channel < ADC_CHANNEL_COUNT; ++channel) { sConfig.Channel = channel << ADC_CR1_AWDCH_Pos; - sConfig.Rank = channel + 1; // rank numbering starts at 1 + sConfig.Rank = channel + 1; // rank numbering starts at 1 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) _Error_Handler((char*)__FILE__, __LINE__); } @@ -413,7 +414,7 @@ float get_adc_voltage(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin) { if (channel < ADC_CHANNEL_COUNT) return ((float)adc_measurements_[channel]) * (adc_ref_voltage / adc_full_scale); else - return 0.0f / 0.0f; // NaN + return 0.0f / 0.0f; // NaN } //-------------------------------- @@ -424,7 +425,7 @@ void vbus_sense_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { static const float voltage_scale = adc_ref_voltage * VBUS_S_DIVIDER_RATIO / adc_full_scale; // Only one conversion in sequence, so only rank1 uint32_t ADCValue = HAL_ADCEx_InjectedGetValue(hadc, ADC_INJECTED_RANK_1); - vbus_voltage = ADCValue * voltage_scale; + vbus_voltage = ADCValue * voltage_scale; if (axes[0] && !axes[0]->error_ && axes[1] && !axes[1]->error_) { if (oscilloscope_pos >= OSCILLOSCOPE_SIZE) oscilloscope_pos = 0; @@ -477,10 +478,10 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { // Motor 1 is on Timer 8, which triggers ADC 2 and 3 on a regular conversion // If the corresponding timer is counting up, we just sampled in SVM vector 0, i.e. real current // If we are counting down, we just sampled in SVM vector 7, with zero current - Axis& axis = injected ? *axes[0] : *axes[1]; - int axis_num = injected ? 0 : 1; - Axis& other_axis = injected ? *axes[1] : *axes[0]; - bool counting_down = axis.motor_.hw_config_.timer->Instance->CR1 & TIM_CR1_DIR; + Axis& axis = injected ? *axes[0] : *axes[1]; + int axis_num = injected ? 0 : 1; + Axis& other_axis = injected ? *axes[1] : *axes[0]; + bool counting_down = axis.motor_.hw_config_.timer->Instance->CR1 & TIM_CR1_DIR; bool current_meas_not_DC_CAL = !counting_down; // Check the timing of the sequencing @@ -492,12 +493,12 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { bool update_timings = false; if (hadc == &hadc2) { if (&axis == axes[1] && counting_down) - update_timings = true; // update timings of M0 + update_timings = true; // update timings of M0 else if (&axis == axes[0] && !counting_down) - update_timings = true; // update timings of M1 + update_timings = true; // update timings of M1 - if ((current_meas_not_DC_CAL && !axis_num) || - (axis_num && !current_meas_not_DC_CAL)) { + if((current_meas_not_DC_CAL && !axis_num) || + (axis_num && !current_meas_not_DC_CAL)){ axis.encoder_.abs_spi_start_transaction(); } } @@ -514,7 +515,8 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { } else { other_axis.motor_.next_timings_valid_ = false; safety_critical_apply_motor_pwm_timings( - other_axis.motor_, other_axis.motor_.next_timings_); + other_axis.motor_, other_axis.motor_.next_timings_ + ); } update_brake_current(); } @@ -557,20 +559,21 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { } void tim_update_cb(TIM_HandleTypeDef* htim) { + // If the corresponding timer is counting up, we just sampled in SVM vector 0, i.e. real current // If we are counting down, we just sampled in SVM vector 7, with zero current bool counting_down = htim->Instance->CR1 & TIM_CR1_DIR; if (counting_down) return; - + int sample_ch; Axis* axis; if (htim == &htim1) { sample_ch = 0; - axis = axes[0]; + axis = axes[0]; } else if (htim == &htim8) { sample_ch = 1; - axis = axes[1]; + axis = axes[1]; } else { low_level_fault(Motor::ERROR_UNEXPECTED_TIMER_CALLBACK); return; @@ -592,10 +595,10 @@ void update_brake_current() { Ibus_sum += axes[i]->motor_.current_control_.Ibus; } } - + // Don't start braking until -Ibus > regen_current_allowed - float brake_current = std::max(-Ibus_sum - board_config.max_regen_current, 0.0f); - float brake_duty = std::max(brake_current * std::abs(board_config.brake_resistance) / vbus_voltage, 0.0f); + float brake_current = std::max(-Ibus_sum - board_config.max_regen_current, 0.0f); + float brake_duty = std::max(brake_current * std::abs(board_config.brake_resistance) / vbus_voltage, 0.0f); // Duty limit at 90% to allow bootstrap caps to charge // If brake_duty is NaN, this expression will also evaluate to false @@ -610,6 +613,7 @@ void update_brake_current() { } } + /* RC PWM input --------------------------------------------------------------*/ // @brief Returns the ODrive GPIO number for a given @@ -637,16 +641,11 @@ int tim_2_5_channel_num_to_gpio_num(int channel) { uint32_t gpio_num_to_tim_2_5_channel(int gpio_num) { #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 switch (gpio_num) { - case 1: - return TIM_CHANNEL_1; - case 2: - return TIM_CHANNEL_2; - case 3: - return TIM_CHANNEL_3; - case 4: - return TIM_CHANNEL_4; - default: - return 0; + case 1: return TIM_CHANNEL_1; + case 2: return TIM_CHANNEL_2; + case 3: return TIM_CHANNEL_3; + case 4: return TIM_CHANNEL_4; + default: return 0; } #else // Only ch4 is available on v3.2 @@ -660,22 +659,21 @@ uint32_t gpio_num_to_tim_2_5_channel(int gpio_num) { void pwm_in_init() { GPIO_InitTypeDef GPIO_InitStruct; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_PULLDOWN; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_PULLDOWN; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Alternate = GPIO_AF2_TIM5; TIM_IC_InitTypeDef sConfigIC; - sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_BOTHEDGE; + sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_BOTHEDGE; sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; - sConfigIC.ICFilter = 15; + sConfigIC.ICFilter = 15; #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 for (int gpio_num = 1; gpio_num <= 4; ++gpio_num) { #else - int gpio_num = 4; - { + int gpio_num = 4; { #endif if (is_endpoint_ref_valid(board_config.pwm_mappings[gpio_num - 1].endpoint)) { GPIO_InitStruct.Pin = get_gpio_pin_by_pin(gpio_num); @@ -688,12 +686,12 @@ void pwm_in_init() { } //TODO: These expressions have integer division by 1MHz, so it will be incorrect for clock speeds of not-integer MHz -#define TIM_2_5_CLOCK_HZ TIM_APB1_CLOCK_HZ -#define PWM_MIN_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 1000UL) // 1ms high is considered full reverse -#define PWM_MAX_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 2000UL) // 2ms high is considered full forward -#define PWM_MIN_LEGAL_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 500UL) // ignore high periods shorter than 0.5ms -#define PWM_MAX_LEGAL_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 2500UL) // ignore high periods longer than 2.5ms -#define PWM_INVERT_INPUT false +#define TIM_2_5_CLOCK_HZ TIM_APB1_CLOCK_HZ +#define PWM_MIN_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 1000UL) // 1ms high is considered full reverse +#define PWM_MAX_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 2000UL) // 2ms high is considered full forward +#define PWM_MIN_LEGAL_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 500UL) // ignore high periods shorter than 0.5ms +#define PWM_MAX_LEGAL_HIGH_TIME ((TIM_2_5_CLOCK_HZ / 1000000UL) * 2500UL) // ignore high periods longer than 2.5ms +#define PWM_INVERT_INPUT false void handle_pulse(int gpio_num, uint32_t high_time) { if (high_time < PWM_MIN_LEGAL_HIGH_TIME || high_time > PWM_MAX_LEGAL_HIGH_TIME) @@ -704,7 +702,7 @@ void handle_pulse(int gpio_num, uint32_t high_time) { if (high_time > PWM_MAX_HIGH_TIME) high_time = PWM_MAX_HIGH_TIME; float fraction = (float)(high_time - PWM_MIN_HIGH_TIME) / (float)(PWM_MAX_HIGH_TIME - PWM_MIN_HIGH_TIME); - float value = board_config.pwm_mappings[gpio_num - 1].min + + float value = board_config.pwm_mappings[gpio_num - 1].min + (fraction * (board_config.pwm_mappings[gpio_num - 1].max - board_config.pwm_mappings[gpio_num - 1].min)); Endpoint* endpoint = get_endpoint(board_config.pwm_mappings[gpio_num - 1].endpoint); @@ -715,36 +713,41 @@ void handle_pulse(int gpio_num, uint32_t high_time) { } void pwm_in_cb(int channel, uint32_t timestamp) { - static uint32_t last_timestamp[GPIO_COUNT] = {0}; - static bool last_pin_state[GPIO_COUNT] = {false}; - static bool last_sample_valid[GPIO_COUNT] = {false}; + static uint32_t last_timestamp[GPIO_COUNT] = { 0 }; + static bool last_pin_state[GPIO_COUNT] = { false }; + static bool last_sample_valid[GPIO_COUNT] = { false }; int gpio_num = tim_2_5_channel_num_to_gpio_num(channel); if (gpio_num < 1 || gpio_num > GPIO_COUNT) return; bool current_pin_state = HAL_GPIO_ReadPin(get_gpio_port_by_pin(gpio_num), get_gpio_pin_by_pin(gpio_num)) != GPIO_PIN_RESET; - if (last_sample_valid[gpio_num - 1] && (last_pin_state[gpio_num - 1] != PWM_INVERT_INPUT) && (current_pin_state == PWM_INVERT_INPUT)) { + if (last_sample_valid[gpio_num - 1] + && (last_pin_state[gpio_num - 1] != PWM_INVERT_INPUT) + && (current_pin_state == PWM_INVERT_INPUT)) { handle_pulse(gpio_num, timestamp - last_timestamp[gpio_num - 1]); } - last_timestamp[gpio_num - 1] = timestamp; - last_pin_state[gpio_num - 1] = current_pin_state; + last_timestamp[gpio_num - 1] = timestamp; + last_pin_state[gpio_num - 1] = current_pin_state; last_sample_valid[gpio_num - 1] = true; } + /* Analog speed control input */ -static void update_analog_endpoint(const struct PWMMapping_t* map, int gpio) { +static void update_analog_endpoint(const struct PWMMapping_t *map, int gpio) +{ float fraction = get_adc_voltage(get_gpio_port_by_pin(gpio), get_gpio_pin_by_pin(gpio)) / 3.3f; - float value = map->min + (fraction * (map->max - map->min)); + float value = map->min + (fraction * (map->max - map->min)); get_endpoint(map->endpoint)->set_from_float(value); } -static void analog_polling_thread(void*) { +static void analog_polling_thread(void *) +{ while (true) { for (int i = 0; i < GPIO_COUNT; i++) { - struct PWMMapping_t* map = &board_config.analog_mappings[i]; + struct PWMMapping_t *map = &board_config.analog_mappings[i]; if (is_endpoint_ref_valid(map->endpoint)) update_analog_endpoint(map, i + 1); @@ -758,8 +761,10 @@ void start_analog_thread() { osThreadCreate(osThread(thread_def), NULL); } -void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef* hspi) { - if (hspi->pRxBuffPtr == (uint8_t*)axes[0]->encoder_.abs_spi_dma_rx_) + +void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) +{ + if(hspi->pRxBuffPtr == (uint8_t*)axes[0]->encoder_.abs_spi_dma_rx_) axes[0]->encoder_.abs_spi_cb(); else if (hspi->pRxBuffPtr == (uint8_t*)axes[1]->encoder_.abs_spi_dma_rx_) axes[1]->encoder_.abs_spi_cb(); diff --git a/Firmware/MotorControl/low_level.h b/Firmware/MotorControl/low_level.h index e56f4adc..503b98e1 100644 --- a/Firmware/MotorControl/low_level.h +++ b/Firmware/MotorControl/low_level.h @@ -11,9 +11,9 @@ extern "C" { #endif /* Includes ------------------------------------------------------------------*/ -#include #include #include +#include /* Exported types ------------------------------------------------------------*/ /* Exported constants --------------------------------------------------------*/ @@ -69,4 +69,4 @@ inline void cpu_exit_critical(uint32_t priority_mask) { } #endif -#endif //__LOW_LEVEL_H +#endif //__LOW_LEVEL_H diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 173d0239..17707eaa 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -1,13 +1,13 @@ #define __MAIN_CPP__ -#include "nvm_config.hpp" #include "odrive_main.h" +#include "nvm_config.hpp" -#include -#include -#include -#include #include "freertos_vars.h" +#include +#include +#include +#include BoardConfig_t board_config; ODriveCAN::Config_t can_config; @@ -21,7 +21,7 @@ Endstop::Config_t min_endstop_configs[AXIS_COUNT]; Endstop::Config_t max_endstop_configs[AXIS_COUNT]; bool user_config_loaded_; -SystemStats_t system_stats_ = {0}; +SystemStats_t system_stats_ = { 0 }; Axis *axes[AXIS_COUNT]; ODriveCAN *odCAN; @@ -36,8 +36,7 @@ typedef Config< TrapezoidalTrajectory::Config_t[AXIS_COUNT], Endstop::Config_t[AXIS_COUNT], Endstop::Config_t[AXIS_COUNT], - Axis::Config_t[AXIS_COUNT]> - ConfigFormat; + Axis::Config_t[AXIS_COUNT]> ConfigFormat; void save_configuration(void) { if (ConfigFormat::safe_store_config( @@ -51,8 +50,7 @@ void save_configuration(void) { &min_endstop_configs, &max_endstop_configs, &axis_configs)) { - printf("saving configuration failed\r\n"); - osDelay(5); + printf("saving configuration failed\r\n"); osDelay(5); } else { user_config_loaded_ = true; } @@ -62,26 +60,26 @@ extern "C" int load_configuration(void) { // Try to load configs if (NVM_init() || ConfigFormat::safe_load_config( - &board_config, - &can_config, - &encoder_configs, - &sensorless_configs, - &controller_configs, - &motor_configs, - &trap_configs, - &min_endstop_configs, - &max_endstop_configs, - &axis_configs)) { + &board_config, + &can_config, + &encoder_configs, + &sensorless_configs, + &controller_configs, + &motor_configs, + &trap_configs, + &min_endstop_configs, + &max_endstop_configs, + &axis_configs)) { //If loading failed, restore defaults board_config = BoardConfig_t(); - can_config = ODriveCAN::Config_t(); + can_config = ODriveCAN::Config_t(); for (size_t i = 0; i < AXIS_COUNT; ++i) { - encoder_configs[i] = Encoder::Config_t(); + encoder_configs[i] = Encoder::Config_t(); sensorless_configs[i] = SensorlessEstimator::Config_t(); controller_configs[i] = Controller::Config_t(); - motor_configs[i] = Motor::Config_t(); - trap_configs[i] = TrapezoidalTrajectory::Config_t(); - axis_configs[i] = Axis::Config_t(); + motor_configs[i] = Motor::Config_t(); + trap_configs[i] = TrapezoidalTrajectory::Config_t(); + axis_configs[i] = Axis::Config_t(); // Default step/dir pins are different, so we need to explicitly load them Axis::load_default_step_dir_pin_config(hw_configs[i].axis_config, &axis_configs[i]); Axis::load_default_can_id(i, axis_configs[i]); @@ -100,8 +98,7 @@ void erase_configuration(void) { void enter_dfu_mode() { if ((hw_version_major == 3) && (hw_version_minor >= 5)) { - __asm volatile("CPSID I\n\t" :: - : "memory"); // disable interrupts + __asm volatile ("CPSID I\n\t":::"memory"); // disable interrupts _reboot_cookie = 0xDEADBEEF; NVIC_SystemReset(); } else { @@ -121,26 +118,26 @@ extern "C" { int odrive_main(void); void vApplicationStackOverflowHook(xTaskHandle *pxTask, signed portCHAR *pcTaskName) { - for (;;) - ; // TODO: safe action + for (;;); // TODO: safe action } void vApplicationIdleHook(void) { if (system_stats_.fully_booted) { - system_stats_.uptime = xTaskGetTickCount(); - system_stats_.min_heap_space = xPortGetMinimumEverFreeHeapSize(); - system_stats_.min_stack_space_comms = uxTaskGetStackHighWaterMark(comm_thread) * sizeof(StackType_t); - system_stats_.min_stack_space_axis0 = uxTaskGetStackHighWaterMark(axes[0]->thread_id_) * sizeof(StackType_t); - system_stats_.min_stack_space_axis1 = uxTaskGetStackHighWaterMark(axes[1]->thread_id_) * sizeof(StackType_t); - system_stats_.min_stack_space_usb = uxTaskGetStackHighWaterMark(usb_thread) * sizeof(StackType_t); - system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread) * sizeof(StackType_t); + system_stats_.uptime = xTaskGetTickCount(); + system_stats_.min_heap_space = xPortGetMinimumEverFreeHeapSize(); + system_stats_.min_stack_space_comms = uxTaskGetStackHighWaterMark(comm_thread) * sizeof(StackType_t); + system_stats_.min_stack_space_axis0 = uxTaskGetStackHighWaterMark(axes[0]->thread_id_) * sizeof(StackType_t); + system_stats_.min_stack_space_axis1 = uxTaskGetStackHighWaterMark(axes[1]->thread_id_) * sizeof(StackType_t); + system_stats_.min_stack_space_usb = uxTaskGetStackHighWaterMark(usb_thread) * sizeof(StackType_t); + system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread) * sizeof(StackType_t); system_stats_.min_stack_space_usb_irq = uxTaskGetStackHighWaterMark(usb_irq_thread) * sizeof(StackType_t); system_stats_.min_stack_space_startup = uxTaskGetStackHighWaterMark(defaultTaskHandle) * sizeof(StackType_t); - system_stats_.min_stack_space_can = uxTaskGetStackHighWaterMark(odCAN->thread_id_) * sizeof(StackType_t); + system_stats_.min_stack_space_can = uxTaskGetStackHighWaterMark(odCAN->thread_id_) * sizeof(StackType_t); } } } int odrive_main(void) { + #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 if (board_config.enable_i2c_instead_of_can) { // Set up the direction GPIO as input @@ -169,7 +166,7 @@ int odrive_main(void) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Pin = GPIO_1_Pin; + GPIO_InitStruct.Pin = GPIO_1_Pin; HAL_GPIO_Init(GPIO_1_GPIO_Port, &GPIO_InitStruct); GPIO_InitStruct.Pin = GPIO_2_Pin; HAL_GPIO_Init(GPIO_2_GPIO_Port, &GPIO_InitStruct); @@ -185,20 +182,20 @@ int odrive_main(void) { // Construct all objects. odCAN = new ODriveCAN(&hcan1, can_config); for (size_t i = 0; i < AXIS_COUNT; ++i) { - Encoder *encoder = new Encoder(hw_configs[i].encoder_config, + Encoder *encoder = new Encoder(hw_configs[i].encoder_config, encoder_configs[i]); SensorlessEstimator *sensorless_estimator = new SensorlessEstimator(sensorless_configs[i]); - Controller *controller = new Controller(controller_configs[i]); - Motor *motor = new Motor(hw_configs[i].motor_config, + Controller *controller = new Controller(controller_configs[i]); + Motor *motor = new Motor(hw_configs[i].motor_config, hw_configs[i].gate_driver_config, motor_configs[i]); - TrapezoidalTrajectory *trap = new TrapezoidalTrajectory(trap_configs[i]); - Endstop *min_endstop = new Endstop(min_endstop_configs[i]); - Endstop *max_endstop = new Endstop(max_endstop_configs[i]); - axes[i] = new Axis(i, hw_configs[i].axis_config, axis_configs[i], - *encoder, *sensorless_estimator, *controller, *motor, *trap, *min_endstop, *max_endstop); + TrapezoidalTrajectory *trap = new TrapezoidalTrajectory(trap_configs[i]); + Endstop *min_endstop = new Endstop(min_endstop_configs[i]); + Endstop *max_endstop = new Endstop(max_endstop_configs[i]); + axes[i] = new Axis(i, hw_configs[i].axis_config, axis_configs[i], + *encoder, *sensorless_estimator, *controller, *motor, *trap, *min_endstop, *max_endstop); } - + // Start ADC for temperature measurements and user measurements start_general_purpose_adc(); diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 40de8df3..dd9c47ae 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -4,18 +4,20 @@ #include "drv8301.h" #include "odrive_main.h" + Motor::Motor(const MotorHardwareConfig_t& hw_config, const GateDriverHardwareConfig_t& gate_driver_config, - Config_t& config) : hw_config_(hw_config), - gate_driver_config_(gate_driver_config), - config_(config), - gate_driver_({ - .spiHandle = gate_driver_config_.spi, - .EngpioHandle = gate_driver_config_.enable_port, - .EngpioNumber = gate_driver_config_.enable_pin, - .nCSgpioHandle = gate_driver_config_.nCS_port, - .nCSgpioNumber = gate_driver_config_.nCS_pin, - }) { + Config_t& config) : + hw_config_(hw_config), + gate_driver_config_(gate_driver_config), + config_(config), + gate_driver_({ + .spiHandle = gate_driver_config_.spi, + .EngpioHandle = gate_driver_config_.enable_port, + .EngpioNumber = gate_driver_config_.enable_pin, + .nCSgpioHandle = gate_driver_config_.nCS_port, + .nCSgpioNumber = gate_driver_config_.nCS_pin, + }) { update_current_controller_gains(); } @@ -31,6 +33,7 @@ Motor::Motor(const MotorHardwareConfig_t& hw_config, // // @returns: True on success, false otherwise bool Motor::arm() { + // Reset controller states, integrators, setpoints, etc. axis_->controller_.reset(); reset_current_control(); @@ -55,7 +58,7 @@ void Motor::reset_current_control() { void Motor::update_current_controller_gains() { // Calculate current control gains current_control_.p_gain = config_.current_control_bandwidth * config_.phase_inductance; - float plant_pole = config_.phase_resistance / config_.phase_inductance; + float plant_pole = config_.phase_resistance / config_.phase_inductance; current_control_.i_gain = plant_pole * current_control_.p_gain; } @@ -69,28 +72,29 @@ void Motor::DRV8301_setup() { // Solve for exact gain, then snap down to have equal or larger range as requested // or largest possible range otherwise - static const float kMargin = 0.90f; - static const float kTripMargin = 1.0f; // Trip level is at edge of linear range of amplifer - static const float max_output_swing = 1.35f; // [V] out of amplifier - float max_unity_gain_current = kMargin * max_output_swing * hw_config_.shunt_conductance; // [A] - float requested_gain = max_unity_gain_current / config_.requested_current_range; // [V/V] + static const float kMargin = 0.90f; + static const float kTripMargin = 1.0f; // Trip level is at edge of linear range of amplifer + static const float max_output_swing = 1.35f; // [V] out of amplifier + float max_unity_gain_current = kMargin * max_output_swing * hw_config_.shunt_conductance; // [A] + float requested_gain = max_unity_gain_current / config_.requested_current_range; // [V/V] // Decoding array for snapping gain - std::array, 4> gain_choices = { + std::array, 4> gain_choices = { std::make_pair(10.0f, DRV8301_ShuntAmpGain_10VpV), std::make_pair(20.0f, DRV8301_ShuntAmpGain_20VpV), std::make_pair(40.0f, DRV8301_ShuntAmpGain_40VpV), - std::make_pair(80.0f, DRV8301_ShuntAmpGain_80VpV)}; + std::make_pair(80.0f, DRV8301_ShuntAmpGain_80VpV) + }; // We use lower_bound in reverse because it snaps up by default, we want to snap down. - auto gain_snap_down = std::lower_bound(gain_choices.crbegin(), gain_choices.crend(), requested_gain, - [](std::pair pair, float val) { - return pair.first > val; - }); + auto gain_snap_down = std::lower_bound(gain_choices.crbegin(), gain_choices.crend(), requested_gain, + [](std::pair pair, float val){ + return pair.first > val; + }); // If we snap to outside the array, clip to smallest val - if (gain_snap_down == gain_choices.crend()) - --gain_snap_down; + if(gain_snap_down == gain_choices.crend()) + --gain_snap_down; // Values for current controller phase_current_rev_gain_ = 1.0f / gain_snap_down->first; @@ -107,7 +111,7 @@ void Motor::DRV8301_setup() { local_regs->Ctrl_Reg_1.OC_MODE = DRV8301_OcMode_LatchShutDown; // Overcurrent set to approximately 150A at 100degC. This may need tweaking. local_regs->Ctrl_Reg_1.OC_ADJ_SET = DRV8301_VdsLevel_0p730_V; - local_regs->Ctrl_Reg_2.GAIN = gain_snap_down->second; + local_regs->Ctrl_Reg_2.GAIN = gain_snap_down->second; local_regs->SndCmd = true; DRV8301_writeData(&gate_driver_, local_regs); @@ -132,7 +136,7 @@ bool Motor::check_DRV_fault() { return true; } -void Motor::set_error(Motor::Error_t error) { +void Motor::set_error(Motor::Error_t error){ error_ |= error; axis_->error_ |= Axis::ERROR_MOTOR_FAILED; safety_critical_disarm_motor_pwm(*this); @@ -140,17 +144,17 @@ void Motor::set_error(Motor::Error_t error) { } float Motor::get_inverter_temp() { - float adc = adc_measurements_[hw_config_.inverter_thermistor_adc_ch]; + float adc = adc_measurements_[hw_config_.inverter_thermistor_adc_ch]; float normalized_voltage = adc / adc_full_scale; return horner_fma(normalized_voltage, thermistor_poly_coeffs, thermistor_num_coeffs); } bool Motor::update_thermal_limits() { - float fet_temp = get_inverter_temp(); - float temp_margin = config_.inverter_temp_limit_upper - fet_temp; + float fet_temp = get_inverter_temp(); + float temp_margin = config_.inverter_temp_limit_upper - fet_temp; float derating_range = config_.inverter_temp_limit_upper - config_.inverter_temp_limit_lower; thermal_current_lim_ = config_.current_lim * (temp_margin / derating_range); - if (!(thermal_current_lim_ >= 0.0f)) { //Funny polarity to also catch NaN + if (!(thermal_current_lim_ >= 0.0f)) { //Funny polarity to also catch NaN thermal_current_lim_ = 0.0f; } if (fet_temp > config_.inverter_temp_limit_upper + 5) { @@ -177,7 +181,7 @@ float Motor::effective_current_lim() { float current_lim = config_.current_lim; // Hardware limit if (axis_->motor_.config_.motor_type == Motor::MOTOR_TYPE_GIMBAL) { - current_lim = std::min(current_lim, 0.98f * one_by_sqrt3 * vbus_voltage); + current_lim = std::min(current_lim, 0.98f*one_by_sqrt3*vbus_voltage); } else { current_lim = std::min(current_lim, axis_->motor_.current_control_.max_allowed_current); } @@ -189,7 +193,7 @@ float Motor::effective_current_lim() { void Motor::log_timing(TimingLog_t log_idx) { static const uint16_t clocks_per_cnt = (uint16_t)((float)TIM_1_8_CLOCK_HZ / (float)TIM_APB1_CLOCK_HZ); - uint16_t timing = clocks_per_cnt * htim13.Instance->CNT; // TODO: Use a hw_config + uint16_t timing = clocks_per_cnt * htim13.Instance->CNT; // TODO: Use a hw_config if (log_idx < TIMING_LOG_NUM_SLOTS) { timing_log_[log_idx] = timing; @@ -197,10 +201,10 @@ void Motor::log_timing(TimingLog_t log_idx) { } float Motor::phase_current_from_adcval(uint32_t ADCValue) { - int adcval_bal = (int)ADCValue - (1 << 11); + int adcval_bal = (int)ADCValue - (1 << 11); float amp_out_volt = (3.3f / (float)(1 << 12)) * (float)adcval_bal; - float shunt_volt = amp_out_volt * phase_current_rev_gain_; - float current = shunt_volt * hw_config_.shunt_conductance; + float shunt_volt = amp_out_volt * phase_current_rev_gain_; + float current = shunt_volt * hw_config_.shunt_conductance; return current; } @@ -210,12 +214,12 @@ float Motor::phase_current_from_adcval(uint32_t ADCValue) { // TODO check Ibeta balance to verify good motor connection bool Motor::measure_phase_resistance(float test_current, float max_voltage) { - static const float kI = 10.0f; // [(V/s)/A] - static const int num_test_cycles = static_cast(3.0f / CURRENT_MEAS_PERIOD); // Test runs for 3s - float test_voltage = 0.0f; - + static const float kI = 10.0f; // [(V/s)/A] + static const int num_test_cycles = static_cast(3.0f / CURRENT_MEAS_PERIOD); // Test runs for 3s + float test_voltage = 0.0f; + size_t i = 0; - axis_->run_control_loop([&]() { + axis_->run_control_loop([&](){ float Ialpha = -(current_meas_.phB + current_meas_.phC); test_voltage += (kI * current_meas_period) * (test_current - Ialpha); if (test_voltage > max_voltage || test_voltage < -max_voltage) @@ -223,7 +227,7 @@ bool Motor::measure_phase_resistance(float test_current, float max_voltage) { // Test voltage along phase A if (!enqueue_voltage_timings(test_voltage, 0.0f)) - return false; // error set inside enqueue_voltage_timings + return false; // error set inside enqueue_voltage_timings log_timing(TIMING_LOG_MEAS_R); return ++i < num_test_cycles; @@ -235,24 +239,24 @@ bool Motor::measure_phase_resistance(float test_current, float max_voltage) { //if (!enqueue_voltage_timings(motor, 0.0f, 0.0f)) // return false; // error set inside enqueue_voltage_timings - float R = test_voltage / test_current; + float R = test_voltage / test_current; config_.phase_resistance = R; - return true; // if we ran to completion that means success + return true; // if we ran to completion that means success } bool Motor::measure_phase_inductance(float voltage_low, float voltage_high) { - float test_voltages[2] = {voltage_low, voltage_high}; - float Ialphas[2] = {0.0f}; + float test_voltages[2] = {voltage_low, voltage_high}; + float Ialphas[2] = {0.0f}; static const int num_cycles = 5000; size_t t = 0; - axis_->run_control_loop([&]() { + axis_->run_control_loop([&](){ int i = t & 1; Ialphas[i] += -current_meas_.phB - current_meas_.phC; // Test voltage along phase A if (!enqueue_voltage_timings(test_voltages[i], 0.0f)) - return false; // error set inside enqueue_voltage_timings + return false; // error set inside enqueue_voltage_timings log_timing(TIMING_LOG_MEAS_L); return ++t < (num_cycles << 1); @@ -268,7 +272,7 @@ bool Motor::measure_phase_inductance(float voltage_low, float voltage_high) { // Note: A more correct formula would also take into account that there is a finite timestep. // However, the discretisation in the current control loop inverts the same discrepancy float dI_by_dt = (Ialphas[1] - Ialphas[0]) / (current_meas_period * (float)num_cycles); - float L = v_L / dI_by_dt; + float L = v_L / dI_by_dt; config_.phase_inductance = L; // TODO arbitrary values set for now @@ -277,6 +281,7 @@ bool Motor::measure_phase_inductance(float voltage_low, float voltage_high) { return true; } + bool Motor::run_calibration() { float R_calib_max_voltage = config_.resistance_calib_max_voltage; if (config_.motor_type == MOTOR_TYPE_HIGH_CURRENT) { @@ -291,7 +296,7 @@ bool Motor::run_calibration() { } update_current_controller_gains(); - + is_calibrated_ = true; return true; } @@ -300,17 +305,17 @@ bool Motor::enqueue_modulation_timings(float mod_alpha, float mod_beta) { float tA, tB, tC; if (SVM(mod_alpha, mod_beta, &tA, &tB, &tC) != 0) return set_error(ERROR_MODULATION_MAGNITUDE), false; - next_timings_[0] = (uint16_t)(tA * (float)TIM_1_8_PERIOD_CLOCKS); - next_timings_[1] = (uint16_t)(tB * (float)TIM_1_8_PERIOD_CLOCKS); - next_timings_[2] = (uint16_t)(tC * (float)TIM_1_8_PERIOD_CLOCKS); + next_timings_[0] = (uint16_t)(tA * (float)TIM_1_8_PERIOD_CLOCKS); + next_timings_[1] = (uint16_t)(tB * (float)TIM_1_8_PERIOD_CLOCKS); + next_timings_[2] = (uint16_t)(tC * (float)TIM_1_8_PERIOD_CLOCKS); next_timings_valid_ = true; return true; } bool Motor::enqueue_voltage_timings(float v_alpha, float v_beta) { - float vfactor = 1.0f / ((2.0f / 3.0f) * vbus_voltage); + float vfactor = 1.0f / ((2.0f / 3.0f) * vbus_voltage); float mod_alpha = vfactor * v_alpha; - float mod_beta = vfactor * v_beta; + float mod_beta = vfactor * v_beta; if (!enqueue_modulation_timings(mod_alpha, mod_beta)) return false; log_timing(TIMING_LOG_FOC_VOLTAGE); @@ -319,10 +324,10 @@ bool Motor::enqueue_voltage_timings(float v_alpha, float v_beta) { // We should probably make FOC Current call FOC Voltage to avoid duplication. bool Motor::FOC_voltage(float v_d, float v_q, float pwm_phase) { - float c = our_arm_cos_f32(pwm_phase); - float s = our_arm_sin_f32(pwm_phase); - float v_alpha = c * v_d - s * v_q; - float v_beta = c * v_q + s * v_d; + float c = our_arm_cos_f32(pwm_phase); + float s = our_arm_sin_f32(pwm_phase); + float v_alpha = c*v_d - s*v_q; + float v_beta = c*v_q + s*v_d; return enqueue_voltage_timings(v_alpha, v_beta); } @@ -341,13 +346,13 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha // Clarke transform float Ialpha = -current_meas_.phB - current_meas_.phC; - float Ibeta = one_by_sqrt3 * (current_meas_.phB - current_meas_.phC); + float Ibeta = one_by_sqrt3 * (current_meas_.phB - current_meas_.phC); // Park transform float c_I = our_arm_cos_f32(I_phase); float s_I = our_arm_sin_f32(I_phase); - float Id = c_I * Ialpha + s_I * Ibeta; - float Iq = c_I * Ibeta - s_I * Ialpha; + float Id = c_I * Ialpha + s_I * Ibeta; + float Iq = c_I * Ibeta - s_I * Ialpha; ictrl.Iq_measured += ictrl.I_measured_report_filter_k * (Iq - ictrl.Iq_measured); ictrl.Id_measured += ictrl.I_measured_report_filter_k * (Id - ictrl.Id_measured); @@ -369,8 +374,8 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha float mod_to_V = (2.0f / 3.0f) * vbus_voltage; float V_to_mod = 1.0f / mod_to_V; - float mod_d = V_to_mod * Vd; - float mod_q = V_to_mod * Vq; + float mod_d = V_to_mod * Vd; + float mod_q = V_to_mod * Vq; // Vector modulation saturation, lock integrator if saturated // TODO make maximum modulation configurable @@ -390,23 +395,24 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha ictrl.Ibus = mod_d * Id + mod_q * Iq; // Inverse park transform - float c_p = our_arm_cos_f32(pwm_phase); - float s_p = our_arm_sin_f32(pwm_phase); + float c_p = our_arm_cos_f32(pwm_phase); + float s_p = our_arm_sin_f32(pwm_phase); float mod_alpha = c_p * mod_d - s_p * mod_q; float mod_beta = c_p * mod_q + s_p * mod_d; // Report final applied voltage in stationary frame (for sensorles estimator) ictrl.final_v_alpha = mod_to_V * mod_alpha; - ictrl.final_v_beta = mod_to_V * mod_beta; + ictrl.final_v_beta = mod_to_V * mod_beta; // Apply SVM if (!enqueue_modulation_timings(mod_alpha, mod_beta)) - return false; // error set inside enqueue_modulation_timings + return false; // error set inside enqueue_modulation_timings log_timing(TIMING_LOG_FOC_CURRENT); return true; } + bool Motor::update(float current_setpoint, float phase, float phase_vel) { current_setpoint *= config_.direction; phase *= config_.direction; @@ -417,12 +423,12 @@ bool Motor::update(float current_setpoint, float phase, float phase_vel) { // Execute current command // TODO: move this into the mot if (config_.motor_type == MOTOR_TYPE_HIGH_CURRENT) { - if (!FOC_current(0.0f, current_setpoint, phase, pwm_phase)) { + if(!FOC_current(0.0f, current_setpoint, phase, pwm_phase)){ return false; } } else if (config_.motor_type == MOTOR_TYPE_GIMBAL) { //In gimbal motor mode, current is reinterptreted as voltage. - if (!FOC_voltage(0.0f, current_setpoint, pwm_phase)) + if(!FOC_voltage(0.0f, current_setpoint, pwm_phase)) return false; } else { set_error(ERROR_NOT_IMPLEMENTED_MOTOR_TYPE); diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 8e2d34d0..6e24d895 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -8,9 +8,9 @@ #include "drv8301.h" class Motor { - public: +public: enum Error_t { - ERROR_NONE = 0, + ERROR_NONE = 0, ERROR_PHASE_RESISTANCE_OUT_OF_RANGE = 0x0001, ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE = 0x0002, ERROR_ADC_FAILED = 0x0004, @@ -37,41 +37,41 @@ class Motor { float phC; }; - struct CurrentControl_t { - float p_gain; // [V/A] - float i_gain; // [V/As] - float v_current_control_integral_d; // [V] - float v_current_control_integral_q; // [V] - float Ibus; // DC bus current [A] + struct CurrentControl_t{ + float p_gain; // [V/A] + float i_gain; // [V/As] + float v_current_control_integral_d; // [V] + float v_current_control_integral_q; // [V] + float Ibus; // DC bus current [A] // Voltage applied at end of cycle: - float final_v_alpha; // [V] - float final_v_beta; // [V] - float Iq_setpoint; // [A] - float Iq_measured; // [A] - float Id_measured; // [A] + float final_v_alpha; // [V] + float final_v_beta; // [V] + float Iq_setpoint; // [A] + float Iq_measured; // [A] + float Id_measured; // [A] float I_measured_report_filter_k; - float max_allowed_current; // [A] - float overcurrent_trip_level; // [A] + float max_allowed_current; // [A] + float overcurrent_trip_level; // [A] }; // NOTE: for gimbal motors, all units of A are instead V. // example: vel_gain is [V/(count/s)] instead of [A/(count/s)] // example: current_lim and calibration_current will instead determine the maximum voltage applied to the motor. struct Config_t { - bool pre_calibrated = false; // can be set to true to indicate that all values here are valid - int32_t pole_pairs = 7; - float calibration_current = 10.0f; // [A] - float resistance_calib_max_voltage = 2.0f; // [V] - You may need to increase this if this voltage isn't sufficient to drive calibration_current through the motor. - float phase_inductance = 0.0f; // to be set by measure_phase_inductance - float phase_resistance = 0.0f; // to be set by measure_phase_resistance - int32_t direction = 0; // 1 or -1 (0 = unspecified) - MotorType_t motor_type = MOTOR_TYPE_HIGH_CURRENT; + bool pre_calibrated = false; // can be set to true to indicate that all values here are valid + int32_t pole_pairs = 7; + float calibration_current = 10.0f; // [A] + float resistance_calib_max_voltage = 2.0f; // [V] - You may need to increase this if this voltage isn't sufficient to drive calibration_current through the motor. + float phase_inductance = 0.0f; // to be set by measure_phase_inductance + float phase_resistance = 0.0f; // to be set by measure_phase_resistance + int32_t direction = 0; // 1 or -1 (0 = unspecified) + MotorType_t motor_type = MOTOR_TYPE_HIGH_CURRENT; // Read out max_allowed_current to see max supported value for current_lim. // float current_lim = 70.0f; //[A] - float current_lim = 10.0f; //[A] + float current_lim = 10.0f; //[A] float current_lim_tolerance = 1.25f; // multiple of current_lim // Value used to compute shunt amplifier gains - float requested_current_range = 60.0f; // [A] + float requested_current_range = 60.0f; // [A] float current_control_bandwidth = 1000.0f; // [rad/s] float inverter_temp_limit_lower = 100; float inverter_temp_limit_upper = 120; @@ -98,8 +98,8 @@ class Motor { }; Motor(const MotorHardwareConfig_t& hw_config, - const GateDriverHardwareConfig_t& gate_driver_config, - Config_t& config); + const GateDriverHardwareConfig_t& gate_driver_config, + Config_t& config); bool arm(); void disarm(); @@ -130,47 +130,48 @@ class Motor { const MotorHardwareConfig_t& hw_config_; const GateDriverHardwareConfig_t gate_driver_config_; Config_t& config_; - Axis* axis_ = nullptr; // set by Axis constructor + Axis* axis_ = nullptr; // set by Axis constructor - //private: +//private: - DRV8301_Obj gate_driver_; // initialized in constructor + DRV8301_Obj gate_driver_; // initialized in constructor uint16_t next_timings_[3] = { TIM_1_8_PERIOD_CLOCKS / 2, TIM_1_8_PERIOD_CLOCKS / 2, - TIM_1_8_PERIOD_CLOCKS / 2}; - bool next_timings_valid_ = false; - uint16_t last_cpu_time_ = 0; - int timing_log_index_ = 0; - uint16_t timing_log_[TIMING_LOG_NUM_SLOTS] = {0}; + TIM_1_8_PERIOD_CLOCKS / 2 + }; + bool next_timings_valid_ = false; + uint16_t last_cpu_time_ = 0; + int timing_log_index_ = 0; + uint16_t timing_log_[TIMING_LOG_NUM_SLOTS] = { 0 }; // variables exposed on protocol Error_t error_ = ERROR_NONE; // Do not write to this variable directly! // It is for exclusive use by the safety_critical_... functions. - ArmedState_t armed_state_ = ARMED_STATE_DISARMED; - bool is_calibrated_ = config_.pre_calibrated; - Iph_BC_t current_meas_ = {0.0f, 0.0f}; - Iph_BC_t DC_calib_ = {0.0f, 0.0f}; - float phase_current_rev_gain_ = 0.0f; // Reverse gain for ADC to Amps (to be set by DRV8301_setup) + ArmedState_t armed_state_ = ARMED_STATE_DISARMED; + bool is_calibrated_ = config_.pre_calibrated; + Iph_BC_t current_meas_ = {0.0f, 0.0f}; + Iph_BC_t DC_calib_ = {0.0f, 0.0f}; + float phase_current_rev_gain_ = 0.0f; // Reverse gain for ADC to Amps (to be set by DRV8301_setup) CurrentControl_t current_control_ = { - .p_gain = 0.0f, // [V/A] should be auto set after resistance and inductance measurement - .i_gain = 0.0f, // [V/As] should be auto set after resistance and inductance measurement + .p_gain = 0.0f, // [V/A] should be auto set after resistance and inductance measurement + .i_gain = 0.0f, // [V/As] should be auto set after resistance and inductance measurement .v_current_control_integral_d = 0.0f, .v_current_control_integral_q = 0.0f, - .Ibus = 0.0f, - .final_v_alpha = 0.0f, - .final_v_beta = 0.0f, - .Iq_setpoint = 0.0f, - .Iq_measured = 0.0f, - .Id_measured = 0.0f, - .I_measured_report_filter_k = 1.0f, - .max_allowed_current = 0.0f, - .overcurrent_trip_level = 0.0f, + .Ibus = 0.0f, + .final_v_alpha = 0.0f, + .final_v_beta = 0.0f, + .Iq_setpoint = 0.0f, + .Iq_measured = 0.0f, + .Id_measured = 0.0f, + .I_measured_report_filter_k = 1.0f, + .max_allowed_current = 0.0f, + .overcurrent_trip_level = 0.0f, }; DRV8301_FaultType_e drv_fault_ = DRV8301_FaultType_NoFault; - DRV_SPI_8301_Vars_t gate_driver_regs_; //Local view of DRV registers (initialized by DRV8301_setup) - float thermal_current_lim_ = 10.0f; //[A] + DRV_SPI_8301_Vars_t gate_driver_regs_; //Local view of DRV registers (initialized by DRV8301_setup) + float thermal_current_lim_ = 10.0f; //[A] // Communication protocol definitions auto make_protocol_definitions() { @@ -186,55 +187,59 @@ class Motor { make_protocol_ro_property("thermal_current_lim", &thermal_current_lim_), make_protocol_function("get_inverter_temp", *this, &Motor::get_inverter_temp), make_protocol_object("current_control", - make_protocol_property("p_gain", ¤t_control_.p_gain), - make_protocol_property("i_gain", ¤t_control_.i_gain), - make_protocol_property("v_current_control_integral_d", ¤t_control_.v_current_control_integral_d), - make_protocol_property("v_current_control_integral_q", ¤t_control_.v_current_control_integral_q), - make_protocol_property("Ibus", ¤t_control_.Ibus), - make_protocol_property("final_v_alpha", ¤t_control_.final_v_alpha), - make_protocol_property("final_v_beta", ¤t_control_.final_v_beta), - make_protocol_property("Iq_setpoint", ¤t_control_.Iq_setpoint), - make_protocol_property("Iq_measured", ¤t_control_.Iq_measured), - make_protocol_property("Id_measured", ¤t_control_.Id_measured), - make_protocol_property("I_measured_report_filter_k", ¤t_control_.I_measured_report_filter_k), - make_protocol_ro_property("max_allowed_current", ¤t_control_.max_allowed_current), - make_protocol_ro_property("overcurrent_trip_level", ¤t_control_.overcurrent_trip_level)), + make_protocol_property("p_gain", ¤t_control_.p_gain), + make_protocol_property("i_gain", ¤t_control_.i_gain), + make_protocol_property("v_current_control_integral_d", ¤t_control_.v_current_control_integral_d), + make_protocol_property("v_current_control_integral_q", ¤t_control_.v_current_control_integral_q), + make_protocol_property("Ibus", ¤t_control_.Ibus), + make_protocol_property("final_v_alpha", ¤t_control_.final_v_alpha), + make_protocol_property("final_v_beta", ¤t_control_.final_v_beta), + make_protocol_property("Iq_setpoint", ¤t_control_.Iq_setpoint), + make_protocol_property("Iq_measured", ¤t_control_.Iq_measured), + make_protocol_property("Id_measured", ¤t_control_.Id_measured), + make_protocol_property("I_measured_report_filter_k", ¤t_control_.I_measured_report_filter_k), + make_protocol_ro_property("max_allowed_current", ¤t_control_.max_allowed_current), + make_protocol_ro_property("overcurrent_trip_level", ¤t_control_.overcurrent_trip_level) + ), make_protocol_object("gate_driver", - make_protocol_ro_property("drv_fault", &drv_fault_) - // make_protocol_ro_property("status_reg_1", &gate_driver_regs_.Stat_Reg_1_Value), - // make_protocol_ro_property("status_reg_2", &gate_driver_regs_.Stat_Reg_2_Value), - // make_protocol_ro_property("ctrl_reg_1", &gate_driver_regs_.Ctrl_Reg_1_Value), - // make_protocol_ro_property("ctrl_reg_2", &gate_driver_regs_.Ctrl_Reg_2_Value) - ), + make_protocol_ro_property("drv_fault", &drv_fault_) + // make_protocol_ro_property("status_reg_1", &gate_driver_regs_.Stat_Reg_1_Value), + // make_protocol_ro_property("status_reg_2", &gate_driver_regs_.Stat_Reg_2_Value), + // make_protocol_ro_property("ctrl_reg_1", &gate_driver_regs_.Ctrl_Reg_1_Value), + // make_protocol_ro_property("ctrl_reg_2", &gate_driver_regs_.Ctrl_Reg_2_Value) + ), make_protocol_object("timing_log", - make_protocol_ro_property("TIMING_LOG_GENERAL", &timing_log_[TIMING_LOG_GENERAL]), - make_protocol_ro_property("TIMING_LOG_ADC_CB_I", &timing_log_[TIMING_LOG_ADC_CB_I]), - make_protocol_ro_property("TIMING_LOG_ADC_CB_DC", &timing_log_[TIMING_LOG_ADC_CB_DC]), - make_protocol_ro_property("TIMING_LOG_MEAS_R", &timing_log_[TIMING_LOG_MEAS_R]), - make_protocol_ro_property("TIMING_LOG_MEAS_L", &timing_log_[TIMING_LOG_MEAS_L]), - make_protocol_ro_property("TIMING_LOG_ENC_CALIB", &timing_log_[TIMING_LOG_ENC_CALIB]), - make_protocol_ro_property("TIMING_LOG_IDX_SEARCH", &timing_log_[TIMING_LOG_IDX_SEARCH]), - make_protocol_ro_property("TIMING_LOG_FOC_VOLTAGE", &timing_log_[TIMING_LOG_FOC_VOLTAGE]), - make_protocol_ro_property("TIMING_LOG_FOC_CURRENT", &timing_log_[TIMING_LOG_FOC_CURRENT])), + make_protocol_ro_property("TIMING_LOG_GENERAL", &timing_log_[TIMING_LOG_GENERAL]), + make_protocol_ro_property("TIMING_LOG_ADC_CB_I", &timing_log_[TIMING_LOG_ADC_CB_I]), + make_protocol_ro_property("TIMING_LOG_ADC_CB_DC", &timing_log_[TIMING_LOG_ADC_CB_DC]), + make_protocol_ro_property("TIMING_LOG_MEAS_R", &timing_log_[TIMING_LOG_MEAS_R]), + make_protocol_ro_property("TIMING_LOG_MEAS_L", &timing_log_[TIMING_LOG_MEAS_L]), + make_protocol_ro_property("TIMING_LOG_ENC_CALIB", &timing_log_[TIMING_LOG_ENC_CALIB]), + make_protocol_ro_property("TIMING_LOG_IDX_SEARCH", &timing_log_[TIMING_LOG_IDX_SEARCH]), + make_protocol_ro_property("TIMING_LOG_FOC_VOLTAGE", &timing_log_[TIMING_LOG_FOC_VOLTAGE]), + make_protocol_ro_property("TIMING_LOG_FOC_CURRENT", &timing_log_[TIMING_LOG_FOC_CURRENT]) + ), make_protocol_object("config", - make_protocol_property("pre_calibrated", &config_.pre_calibrated), - make_protocol_property("pole_pairs", &config_.pole_pairs), - make_protocol_property("calibration_current", &config_.calibration_current), - make_protocol_property("resistance_calib_max_voltage", &config_.resistance_calib_max_voltage), - make_protocol_property("phase_inductance", &config_.phase_inductance), - make_protocol_property("phase_resistance", &config_.phase_resistance), - make_protocol_property("direction", &config_.direction), - make_protocol_property("motor_type", &config_.motor_type), - make_protocol_property("current_lim", &config_.current_lim), - make_protocol_property("current_lim_tolerance", &config_.current_lim_tolerance), - make_protocol_property("inverter_temp_limit_lower", &config_.inverter_temp_limit_lower), - make_protocol_property("inverter_temp_limit_upper", &config_.inverter_temp_limit_upper), - make_protocol_property("requested_current_range", &config_.requested_current_range), - make_protocol_property("current_control_bandwidth", &config_.current_control_bandwidth, - [](void* ctx) { static_cast(ctx)->update_current_controller_gains(); }, this))); + make_protocol_property("pre_calibrated", &config_.pre_calibrated), + make_protocol_property("pole_pairs", &config_.pole_pairs), + make_protocol_property("calibration_current", &config_.calibration_current), + make_protocol_property("resistance_calib_max_voltage", &config_.resistance_calib_max_voltage), + make_protocol_property("phase_inductance", &config_.phase_inductance), + make_protocol_property("phase_resistance", &config_.phase_resistance), + make_protocol_property("direction", &config_.direction), + make_protocol_property("motor_type", &config_.motor_type), + make_protocol_property("current_lim", &config_.current_lim), + make_protocol_property("current_lim_tolerance", &config_.current_lim_tolerance), + make_protocol_property("inverter_temp_limit_lower", &config_.inverter_temp_limit_lower), + make_protocol_property("inverter_temp_limit_upper", &config_.inverter_temp_limit_upper), + make_protocol_property("requested_current_range", &config_.requested_current_range), + make_protocol_property("current_control_bandwidth", &config_.current_control_bandwidth, + [](void* ctx) { static_cast(ctx)->update_current_controller_gains(); }, this) + ) + ); } }; DEFINE_ENUM_FLAG_OPERATORS(Motor::Error_t) -#endif // __MOTOR_HPP +#endif // __MOTOR_HPP diff --git a/Firmware/MotorControl/nvm_config.hpp b/Firmware/MotorControl/nvm_config.hpp index a7195427..2a96f595 100644 --- a/Firmware/MotorControl/nvm_config.hpp +++ b/Firmware/MotorControl/nvm_config.hpp @@ -11,8 +11,9 @@ #include #include -#include #include "nvm.h" +#include + /* Private defines -----------------------------------------------------------*/ #define CONFIG_CRC16_INIT 0xabcd @@ -32,6 +33,7 @@ static constexpr uint16_t config_version = 0x0001; /* Private function prototypes -----------------------------------------------*/ /* Function implementations --------------------------------------------------*/ + // @brief Manages configuration load and store operations from and to NVM // // The NVM stores consecutive one-to-one copies of arbitrary objects. @@ -41,10 +43,10 @@ static constexpr uint16_t config_version = 0x0001; // - Config handles loading/storing of the first object (type T) and leaves // the rest of the objects to an "inner" class Config. // - Config<> represents the leaf of the recursion. -template +template struct Config; -template <> +template<> struct Config<> { static size_t get_size() { return 0; @@ -57,7 +59,7 @@ struct Config<> { } }; -template +template struct Config { static size_t get_size() { return sizeof(T) + Config::get_size(); @@ -69,13 +71,13 @@ struct Config { // of the last comitted NVM block // @param crc16: the result of the CRC calculation is written to this address // @param val0, vals: the values to be loaded - static int load_config(size_t offset, uint16_t* crc16, T* val0, Ts*... vals) { + static int load_config(size_t offset, uint16_t* crc16, T* val0, Ts* ... vals) { size_t size = sizeof(T); // save current CRC (in case val0 and crc16 point to the same address) size_t previous_crc16 = *crc16; - if (NVM_read(offset, (uint8_t*)val0, size)) + if (NVM_read(offset, (uint8_t *)val0, size)) return -1; - *crc16 = calc_crc16(previous_crc16, (uint8_t*)val0, size); + *crc16 = calc_crc16(previous_crc16, (uint8_t *)val0, size); if (Config::load_config(offset + size, crc16, vals...)) return -1; return 0; @@ -87,13 +89,13 @@ struct Config { // of the currently active NVM write block // @param crc16: the result of the CRC calculation is written to this address // @param val0, vals: the values to be stored - static int store_config(size_t offset, uint16_t* crc16, const T* val0, const Ts*... vals) { + static int store_config(size_t offset, uint16_t* crc16, const T* val0, const Ts* ... vals) { size_t size = sizeof(T); - if (NVM_write(offset, (uint8_t*)val0, size)) + if (NVM_write(offset, (uint8_t *)val0, size)) return -1; // update CRC _after_ writing (in case val0 and crc16 point to the same address) if (crc16) - *crc16 = calc_crc16(*crc16, (uint8_t*)val0, size); + *crc16 = calc_crc16(*crc16, (uint8_t *)val0, size); if (Config::store_config(offset + size, crc16, vals...)) return -1; return 0; @@ -101,7 +103,7 @@ struct Config { // @brief Loads one or more consecutive objects from the NVM. The loaded data // is validated using a CRC value that is stored at the beginning of the data. - static int safe_load_config(T* val0, Ts*... vals) { + static int safe_load_config(T* val0, Ts* ... vals) { //printf("have %d bytes\r\n", NVM_get_max_read_length()); osDelay(5); if (Config::get_size() > NVM_get_max_read_length()) return -1; @@ -120,7 +122,7 @@ struct Config { // changes of the config structs during firmware update. Note that if the total // config data length changes, the CRC validation will fail even if the developer // forgets to update the config version number. - static int safe_store_config(const T* val0, const Ts*... vals) { + static int safe_store_config(const T* val0, const Ts* ... vals) { size_t size = Config::get_size() + 2; //printf("config is %d bytes\r\n", size); osDelay(5); if (size > NVM_get_max_write_length()) @@ -130,7 +132,7 @@ struct Config { uint16_t crc16 = CONFIG_CRC16_INIT ^ config_version; if (Config::store_config(0, &crc16, val0, vals...)) return -1; - if (Config::store_config(size - 2, nullptr, (uint8_t*)&crc16 + 1, (uint8_t*)&crc16)) + if (Config::store_config(size - 2, nullptr, (uint8_t *)&crc16 + 1, (uint8_t *)&crc16)) return -1; if (NVM_commit()) return -1; diff --git a/Firmware/MotorControl/sensorless_estimator.cpp b/Firmware/MotorControl/sensorless_estimator.cpp index 59971d82..43191ce3 100644 --- a/Firmware/MotorControl/sensorless_estimator.cpp +++ b/Firmware/MotorControl/sensorless_estimator.cpp @@ -1,7 +1,9 @@ #include "odrive_main.h" -SensorlessEstimator::SensorlessEstimator(Config_t& config) : config_(config){}; +SensorlessEstimator::SensorlessEstimator(Config_t& config) : + config_(config) + {}; bool SensorlessEstimator::update() { // Algorithm based on paper: Sensorless Control of Surface-Mount Permanent-Magnet Synchronous Motors Based on a Nonlinear Observer @@ -35,10 +37,10 @@ bool SensorlessEstimator::update() { } // Non-linear observer (see paper eqn 8): - float pm_flux_sqr = config_.pm_flux_linkage * config_.pm_flux_linkage; - float est_pm_flux_sqr = eta[0] * eta[0] + eta[1] * eta[1]; + float pm_flux_sqr = config_.pm_flux_linkage * config_.pm_flux_linkage; + float est_pm_flux_sqr = eta[0] * eta[0] + eta[1] * eta[1]; float bandwidth_factor = 1.0f / pm_flux_sqr; - float eta_factor = 0.5f * (config_.observer_gain * bandwidth_factor) * (pm_flux_sqr - est_pm_flux_sqr); + float eta_factor = 0.5f * (config_.observer_gain * bandwidth_factor) * (pm_flux_sqr - est_pm_flux_sqr); // alpha-beta vector operations for (int i = 0; i <= 1; ++i) { @@ -69,9 +71,9 @@ bool SensorlessEstimator::update() { // predict PLL phase with velocity pll_pos_ = wrap_pm_pi(pll_pos_ + current_meas_period * vel_estimate_); // update PLL phase with observer permanent magnet phase - phase_ = fast_atan2(eta[1], eta[0]); + phase_ = fast_atan2(eta[1], eta[0]); float delta_phase = wrap_pm_pi(phase_ - pll_pos_); - pll_pos_ = wrap_pm_pi(pll_pos_ + current_meas_period * pll_kp * delta_phase); + pll_pos_ = wrap_pm_pi(pll_pos_ + current_meas_period * pll_kp * delta_phase); // update PLL velocity vel_estimate_ += current_meas_period * pll_ki * delta_phase; diff --git a/Firmware/MotorControl/sensorless_estimator.hpp b/Firmware/MotorControl/sensorless_estimator.hpp index f97a8ab4..719a3227 100644 --- a/Firmware/MotorControl/sensorless_estimator.hpp +++ b/Firmware/MotorControl/sensorless_estimator.hpp @@ -2,35 +2,35 @@ #define __SENSORLESS_ESTIMATOR_HPP class SensorlessEstimator { - public: +public: enum Error_t { - ERROR_NONE = 0, + ERROR_NONE = 0, ERROR_UNSTABLE_GAIN = 0x01, }; struct Config_t { - float observer_gain = 1000.0f; // [rad/s] - float pll_bandwidth = 1000.0f; // [rad/s] - float pm_flux_linkage = 1.58e-3f; // [V / (rad/s)] { 5.51328895422 / ( * ) } + float observer_gain = 1000.0f; // [rad/s] + float pll_bandwidth = 1000.0f; // [rad/s] + float pm_flux_linkage = 1.58e-3f; // [V / (rad/s)] { 5.51328895422 / ( * ) } }; explicit SensorlessEstimator(Config_t& config); bool update(); - Axis* axis_ = nullptr; // set by Axis constructor + Axis* axis_ = nullptr; // set by Axis constructor Config_t& config_; // TODO: expose on protocol - Error_t error_ = ERROR_NONE; - float phase_ = 0.0f; // [rad] - float pll_pos_ = 0.0f; // [rad] - float vel_estimate_ = 0.0f; // [rad/s] + Error_t error_ = ERROR_NONE; + float phase_ = 0.0f; // [rad] + float pll_pos_ = 0.0f; // [rad] + float vel_estimate_ = 0.0f; // [rad/s] // float pll_kp_ = 0.0f; // [rad/s / rad] // float pll_ki_ = 0.0f; // [(rad/s^2) / rad] - float flux_state_[2] = {0.0f, 0.0f}; // [Vs] - float V_alpha_beta_memory_[2] = {0.0f, 0.0f}; // [V] - bool estimator_good_ = false; + float flux_state_[2] = {0.0f, 0.0f}; // [Vs] + float V_alpha_beta_memory_[2] = {0.0f, 0.0f}; // [V] + bool estimator_good_ = false; // Communication protocol definitions auto make_protocol_definitions() { @@ -42,9 +42,11 @@ class SensorlessEstimator { // make_protocol_property("pll_kp", &pll_kp_), // make_protocol_property("pll_ki", &pll_ki_), make_protocol_object("config", - make_protocol_property("observer_gain", &config_.observer_gain), - make_protocol_property("pll_bandwidth", &config_.pll_bandwidth), - make_protocol_property("pm_flux_linkage", &config_.pm_flux_linkage))); + make_protocol_property("observer_gain", &config_.observer_gain), + make_protocol_property("pll_bandwidth", &config_.pll_bandwidth), + make_protocol_property("pm_flux_linkage", &config_.pm_flux_linkage) + ) + ); } }; diff --git a/Firmware/MotorControl/trapTraj.cpp b/Firmware/MotorControl/trapTraj.cpp index 2cd050a4..f1e41aa5 100644 --- a/Firmware/MotorControl/trapTraj.cpp +++ b/Firmware/MotorControl/trapTraj.cpp @@ -19,13 +19,13 @@ TrapezoidalTrajectory::TrapezoidalTrajectory(Config_t& config) : config_(config) bool TrapezoidalTrajectory::planTrapezoidal(float Xf, float Xi, float Vi, float Vmax, float Amax, float Dmax) { - float dX = Xf - Xi; // Distance to travel - float stop_dist = (Vi * Vi) / (2.0f * Dmax); // Minimum stopping distance - float dXstop = std::copysign(stop_dist, Vi); // Minimum stopping displacement - float s = sign_hard(dX - dXstop); // Sign of coast velocity (if any) - Ar_ = s * Amax; // Maximum Acceleration (signed) - Dr_ = -s * Dmax; // Maximum Deceleration (signed) - Vr_ = s * Vmax; // Maximum Velocity (signed) + float dX = Xf - Xi; // Distance to travel + float stop_dist = (Vi * Vi) / (2.0f * Dmax); // Minimum stopping distance + float dXstop = std::copysign(stop_dist, Vi); // Minimum stopping displacement + float s = sign_hard(dX - dXstop); // Sign of coast velocity (if any) + Ar_ = s * Amax; // Maximum Acceleration (signed) + Dr_ = -s * Dmax; // Maximum Deceleration (signed) + Vr_ = s * Vmax; // Maximum Velocity (signed) // If we start with a speed faster than cruising, then we need to decel instead of accel // aka "double deceleration move" in the paper @@ -39,12 +39,12 @@ bool TrapezoidalTrajectory::planTrapezoidal(float Xf, float Xi, float Vi, // Integral of velocity ramps over the full accel and decel times to get // minimum displacement required to reach cuising speed - float dXmin = 0.5f * Ta_ * (Vr_ + Vi) + 0.5f * Td_ * Vr_; + float dXmin = 0.5f*Ta_*(Vr_ + Vi) + 0.5f*Td_*Vr_; // Are we displacing enough to reach cruising speed? - if (s * dX < s * dXmin) { + if (s*dX < s*dXmin) { // Short move (triangle profile) - Vr_ = s * sqrtf((Dr_ * SQ(Vi) + 2 * Ar_ * Dr_ * dX) / (Dr_ - Ar_)); + Vr_ = s * sqrtf((Dr_*SQ(Vi) + 2*Ar_*Dr_*dX) / (Dr_ - Ar_)); Ta_ = std::max(0.0f, (Vr_ - Vi) / Ar_); Td_ = std::max(0.0f, -Vr_ / Dr_); Tv_ = 0.0f; @@ -54,11 +54,11 @@ bool TrapezoidalTrajectory::planTrapezoidal(float Xf, float Xi, float Vi, } // Fill in the rest of the values used at evaluation-time - Tf_ = Ta_ + Tv_ + Td_; - Xi_ = Xi; - Xf_ = Xf; - Vi_ = Vi; - yAccel_ = Xi + Vi * Ta_ + 0.5f * Ar_ * SQ(Ta_); // pos at end of accel phase + Tf_ = Ta_ + Tv_ + Td_; + Xi_ = Xi; + Xf_ = Xf; + Vi_ = Vi; + yAccel_ = Xi + Vi*Ta_ + 0.5f*Ar_*SQ(Ta_); // pos at end of accel phase return true; } @@ -70,17 +70,17 @@ TrapezoidalTrajectory::Step_t TrapezoidalTrajectory::eval(float t) { trajStep.Yd = Vi_; trajStep.Ydd = 0.0f; } else if (t < Ta_) { // Accelerating - trajStep.Y = Xi_ + Vi_ * t + 0.5f * Ar_ * SQ(t); - trajStep.Yd = Vi_ + Ar_ * t; + trajStep.Y = Xi_ + Vi_*t + 0.5f*Ar_*SQ(t); + trajStep.Yd = Vi_ + Ar_*t; trajStep.Ydd = Ar_; } else if (t < Ta_ + Tv_) { // Coasting - trajStep.Y = yAccel_ + Vr_ * (t - Ta_); + trajStep.Y = yAccel_ + Vr_*(t - Ta_); trajStep.Yd = Vr_; trajStep.Ydd = 0.0f; } else if (t < Tf_) { // Deceleration float td = t - Tf_; - trajStep.Y = Xf_ + 0.5f * Dr_ * SQ(td); - trajStep.Yd = Dr_ * td; + trajStep.Y = Xf_ + 0.5f*Dr_*SQ(td); + trajStep.Yd = Dr_*td; trajStep.Ydd = Dr_; } else if (t >= Tf_) { // Final Condition trajStep.Y = Xf_; diff --git a/Firmware/MotorControl/trapTraj.hpp b/Firmware/MotorControl/trapTraj.hpp index 69cba7ed..6c343c42 100644 --- a/Firmware/MotorControl/trapTraj.hpp +++ b/Firmware/MotorControl/trapTraj.hpp @@ -2,13 +2,13 @@ #define _TRAP_TRAJ_H class TrapezoidalTrajectory { - public: +public: struct Config_t { - float vel_limit = 20000.0f; // [count/s] - float accel_limit = 5000.0f; // [count/s^2] - float decel_limit = 5000.0f; // [count/s^2] + float vel_limit = 20000.0f; // [count/s] + float accel_limit = 5000.0f; // [count/s^2] + float decel_limit = 5000.0f; // [count/s^2] }; - + struct Step_t { float Y; float Yd; @@ -23,9 +23,11 @@ class TrapezoidalTrajectory { auto make_protocol_definitions() { return make_protocol_member_list( make_protocol_object("config", - make_protocol_property("vel_limit", &config_.vel_limit), - make_protocol_property("accel_limit", &config_.accel_limit), - make_protocol_property("decel_limit", &config_.decel_limit))); + make_protocol_property("vel_limit", &config_.vel_limit), + make_protocol_property("accel_limit", &config_.accel_limit), + make_protocol_property("decel_limit", &config_.decel_limit) + ) + ); } Axis* axis_ = nullptr; // set by Axis constructor diff --git a/Firmware/MotorControl/utils.c b/Firmware/MotorControl/utils.c index 6bf2e167..3278d614 100644 --- a/Firmware/MotorControl/utils.c +++ b/Firmware/MotorControl/utils.c @@ -1,9 +1,10 @@ -#include -#include -#include -#include #include +#include +#include +#include +#include + int SVM(float alpha, float beta, float* tA, float* tB, float* tC) { int Sextant; @@ -12,30 +13,30 @@ int SVM(float alpha, float beta, float* tA, float* tB, float* tC) { if (alpha >= 0.0f) { //quadrant I if (one_by_sqrt3 * beta > alpha) - Sextant = 2; //sextant v2-v3 + Sextant = 2; //sextant v2-v3 else - Sextant = 1; //sextant v1-v2 + Sextant = 1; //sextant v1-v2 } else { //quadrant II if (-one_by_sqrt3 * beta > alpha) - Sextant = 3; //sextant v3-v4 + Sextant = 3; //sextant v3-v4 else - Sextant = 2; //sextant v2-v3 + Sextant = 2; //sextant v2-v3 } } else { if (alpha >= 0.0f) { //quadrant IV if (-one_by_sqrt3 * beta > alpha) - Sextant = 5; //sextant v5-v6 + Sextant = 5; //sextant v5-v6 else - Sextant = 6; //sextant v6-v1 + Sextant = 6; //sextant v6-v1 } else { //quadrant III if (one_by_sqrt3 * beta > alpha) - Sextant = 4; //sextant v4-v5 + Sextant = 4; //sextant v4-v5 else - Sextant = 5; //sextant v5-v6 + Sextant = 5; //sextant v5-v6 } } @@ -115,7 +116,9 @@ int SVM(float alpha, float beta, float* tA, float* tB, float* tC) { // if any of the results becomes NaN, result_valid will evaluate to false int result_valid = - *tA >= 0.0f && *tA <= 1.0f && *tB >= 0.0f && *tB <= 1.0f && *tC >= 0.0f && *tC <= 1.0f; + *tA >= 0.0f && *tA <= 1.0f + && *tB >= 0.0f && *tB <= 1.0f + && *tC >= 0.0f && *tC <= 1.0f; return result_valid ? 0 : -1; } @@ -146,7 +149,7 @@ float fast_atan2(float y, float x) { // Evaluate polynomials using Fused Multiply Add intrisic instruction. // coeffs[0] is highest order, as per numpy.polyfit // p(x) = coeffs[0] * x^deg + ... + coeffs[deg], for some degree "deg" -float horner_fma(float x, const float* coeffs, size_t count) { +float horner_fma(float x, const float *coeffs, size_t count) { float result = 0.0f; for (int idx = 0; idx < count; ++idx) result = fmaf(result, x, coeffs[idx]); @@ -154,7 +157,7 @@ float horner_fma(float x, const float* coeffs, size_t count) { } // Modulo (as opposed to remainder), per https://stackoverflow.com/a/19288271 -int mod(int dividend, int divisor) { +int mod(int dividend, int divisor){ int r = dividend % divisor; return (r < 0) ? (r + divisor) : r; } @@ -163,7 +166,7 @@ int mod(int dividend, int divisor) { // If the deadline has already passed, the return value is 0 (except if // the deadline is very far in the past) uint32_t deadline_to_timeout(uint32_t deadline_ms) { - uint32_t now_ms = (uint32_t)((1000ull * (uint64_t)osKernelSysTick()) / osKernelSysTickFrequency); + uint32_t now_ms = (uint32_t)((1000ull * (uint64_t)osKernelSysTick()) / osKernelSysTickFrequency); uint32_t timeout_ms = deadline_ms - now_ms; return (timeout_ms & 0x80000000) ? 0 : timeout_ms; } @@ -185,17 +188,18 @@ int is_in_the_future(uint32_t time_ms) { uint32_t micros(void) { register uint32_t ms, cycle_cnt; do { - ms = HAL_GetTick(); + ms = HAL_GetTick(); cycle_cnt = TIM_TIME_BASE->CNT; - } while (ms != HAL_GetTick()); + } while (ms != HAL_GetTick()); return (ms * 1000) + cycle_cnt; } // @brief: Busy wait delay for given amount of microseconds (us) -void delay_us(uint32_t us) { +void delay_us(uint32_t us) +{ uint32_t start = micros(); - while (micros() - start < (uint32_t)us) { + while (micros() - start < (uint32_t) us) { __ASM("nop"); } } diff --git a/Firmware/MotorControl/utils.h b/Firmware/MotorControl/utils.h index bd847d7a..3145c19a 100644 --- a/Firmware/MotorControl/utils.h +++ b/Firmware/MotorControl/utils.h @@ -6,8 +6,8 @@ extern "C" { #endif -#include #include +#include /** * @brief Flash size register address @@ -67,7 +67,7 @@ extern "C" { static const float one_by_sqrt3 = 0.57735026919f; static const float two_by_sqrt3 = 1.15470053838f; -static const float sqrt3_by_2 = 0.86602540378f; +static const float sqrt3_by_2 = 0.86602540378f; //beware of inserting large values! static inline float wrap_pm(float x, float pm_range) { @@ -93,7 +93,7 @@ static inline float fmodf_pos(float x, float y) { // as per the magnitude invariant clarke transform // The magnitude of the alpha-beta vector may not be larger than sqrt(3)/2 // Returns 0 on success, and -1 if the input was out of range -int SVM(float alpha, float beta, float *tA, float *tB, float *tC); +int SVM(float alpha, float beta, float* tA, float* tB, float* tC); float fast_atan2(float y, float x); float horner_fma(float x, const float *coeffs, size_t count); From c49e679bd91eaaecbd2b6c97bbeff6c904ca790f Mon Sep 17 00:00:00 2001 From: samuelsadok Date: Thu, 19 Sep 2019 10:10:51 +0200 Subject: [PATCH 217/549] fix bad use of memcpy memcpy is not guaranteed to work with overlapped memory areas, however memmove is. Apart from the semantic difference this fixes a compiler warning and possibly previously unexplained occurrences of `ERROR_INVALID_STATE`. --- Firmware/MotorControl/axis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index e5f25b16..8ed9a4a3 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -438,6 +438,6 @@ void Axis::run_state_machine_loop() { if (!status) current_state_ = AXIS_STATE_IDLE; else - memcpy(task_chain_, task_chain_ + 1, sizeof(task_chain_) - sizeof(task_chain_[0])); + memmove(task_chain_, task_chain_ + 1, sizeof(task_chain_) - sizeof(task_chain_[0])); } } From f5a1235038bb38d0af83601ec77c1bebc7f90739 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 23 Sep 2019 01:42:22 -0400 Subject: [PATCH 218/549] Construct the object tree outside of the comms task --- Firmware/Board/v3/Src/freertos.c | 2 + Firmware/MotorControl/main.cpp | 51 +++++++++++++----------- Firmware/communication/communication.cpp | 12 +++--- Firmware/communication/communication.h | 1 + 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/Firmware/Board/v3/Src/freertos.c b/Firmware/Board/v3/Src/freertos.c index ac6c6de9..73a56574 100644 --- a/Firmware/Board/v3/Src/freertos.c +++ b/Firmware/Board/v3/Src/freertos.c @@ -61,6 +61,7 @@ extern PCD_HandleTypeDef hpcd_USB_OTG_FS; int odrive_main(void); int load_configuration(void); +int construct_objects(void); /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ @@ -191,6 +192,7 @@ void MX_FREERTOS_Init(void) { // Load persistent configuration (or defaults) load_configuration(); + construct_objects(); /* USER CODE END RTOS_SEMAPHORES */ /* USER CODE BEGIN RTOS_TIMERS */ diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index d5acf252..9faee474 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -96,29 +96,8 @@ void enter_dfu_mode() { } } -extern "C" { -int odrive_main(void); -void vApplicationStackOverflowHook(xTaskHandle *pxTask, signed portCHAR *pcTaskName) { - for (;;); // TODO: safe action -} -void vApplicationIdleHook(void) { - if (system_stats_.fully_booted) { - system_stats_.uptime = xTaskGetTickCount(); - system_stats_.min_heap_space = xPortGetMinimumEverFreeHeapSize(); - system_stats_.min_stack_space_comms = uxTaskGetStackHighWaterMark(comm_thread) * sizeof(StackType_t); - system_stats_.min_stack_space_axis0 = uxTaskGetStackHighWaterMark(axes[0]->thread_id_) * sizeof(StackType_t); - system_stats_.min_stack_space_axis1 = uxTaskGetStackHighWaterMark(axes[1]->thread_id_) * sizeof(StackType_t); - system_stats_.min_stack_space_usb = uxTaskGetStackHighWaterMark(usb_thread) * sizeof(StackType_t); - system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread) * sizeof(StackType_t); - system_stats_.min_stack_space_usb_irq = uxTaskGetStackHighWaterMark(usb_irq_thread) * sizeof(StackType_t); - system_stats_.min_stack_space_startup = uxTaskGetStackHighWaterMark(defaultTaskHandle) * sizeof(StackType_t); - } -} -} - -int odrive_main(void) { - -#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 +extern "C" int construct_objects(){ + #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 if (board_config.enable_i2c_instead_of_can) { // Set up the direction GPIO as input GPIO_InitTypeDef GPIO_InitStruct; @@ -172,7 +151,31 @@ int odrive_main(void) { axes[i] = new Axis(i, hw_configs[i].axis_config, axis_configs[i], *encoder, *sensorless_estimator, *controller, *motor, *trap); } - + initTree(); + return 0; +} + +extern "C" { +int odrive_main(void); +void vApplicationStackOverflowHook(xTaskHandle *pxTask, signed portCHAR *pcTaskName) { + for (;;); // TODO: safe action +} +void vApplicationIdleHook(void) { + if (system_stats_.fully_booted) { + system_stats_.uptime = xTaskGetTickCount(); + system_stats_.min_heap_space = xPortGetMinimumEverFreeHeapSize(); + system_stats_.min_stack_space_comms = uxTaskGetStackHighWaterMark(comm_thread) * sizeof(StackType_t); + system_stats_.min_stack_space_axis0 = uxTaskGetStackHighWaterMark(axes[0]->thread_id_) * sizeof(StackType_t); + system_stats_.min_stack_space_axis1 = uxTaskGetStackHighWaterMark(axes[1]->thread_id_) * sizeof(StackType_t); + system_stats_.min_stack_space_usb = uxTaskGetStackHighWaterMark(usb_thread) * sizeof(StackType_t); + system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread) * sizeof(StackType_t); + system_stats_.min_stack_space_usb_irq = uxTaskGetStackHighWaterMark(usb_irq_thread) * sizeof(StackType_t); + system_stats_.min_stack_space_startup = uxTaskGetStackHighWaterMark(defaultTaskHandle) * sizeof(StackType_t); + } +} +} + +int odrive_main(void) { // Start ADC for temperature measurements and user measurements start_general_purpose_adc(); diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 79982e64..b22fae34 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -186,16 +186,18 @@ using tree_type = decltype(make_obj_tree()); uint8_t tree_buffer[sizeof(tree_type)]; -// Thread to handle deffered processing of USB interrupt, and -// read commands out of the UART DMA circular buffer -void communication_task(void * ctx) { - (void) ctx; // unused parameter - +void initTree(){ // TODO: this is supposed to use the move constructor, but currently // the compiler uses the copy-constructor instead. Thus the make_obj_tree // ends up with a stupid stack size of around 8000 bytes. Fix this. auto tree_ptr = new (tree_buffer) tree_type(make_obj_tree()); fibre_publish(*tree_ptr); +} + +// Thread to handle deffered processing of USB interrupt, and +// read commands out of the UART DMA circular buffer +void communication_task(void * ctx) { + (void) ctx; // unused parameter // Allow main init to continue endpoint_list_valid = true; diff --git a/Firmware/communication/communication.h b/Firmware/communication/communication.h index 8b38e6b4..b4bab74b 100644 --- a/Firmware/communication/communication.h +++ b/Firmware/communication/communication.h @@ -21,6 +21,7 @@ extern const uint8_t hw_version_minor; extern const uint8_t hw_version_variant; void init_communication(void); +void initTree(); void communication_task(void * ctx); #ifdef __cplusplus From c8c6f6237cdf36c132a5cbbfe9694aeba8fbac5e Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 22 Sep 2019 23:27:40 -0700 Subject: [PATCH 219/549] finish implementing basic ACIM control --- Firmware/MotorControl/controller.cpp | 21 ++++++++-- Firmware/MotorControl/motor.cpp | 41 +++++++++++++++++-- Firmware/MotorControl/motor.hpp | 25 +++++++++-- .../fibre/python/fibre/usbbulk_transport.py | 2 +- ODrive_Workspace.code-workspace | 4 +- analysis/motor_analysis/ac_induction_motor.py | 4 +- 6 files changed, 83 insertions(+), 14 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index d295246c..1e20c1f4 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -1,6 +1,6 @@ #include "odrive_main.h" - +#include Controller::Controller(Config_t& config) : config_(config) @@ -173,6 +173,19 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s } } + // TODO: Change to controller working in torque units + // Torque per amp gain scheduling (ACIM) + float vel_gain = config_.vel_gain; + float vel_integrator_gain = config_.vel_integrator_gain; + if (axis_->motor_.config_.motor_type == Motor::MOTOR_TYPE_ACIM) { + float effective_flux = axis_->motor_.current_control_.acim_rotor_flux; + float minflux = axis_->motor_.config_.acim_gain_min_flux; + if (fabsf(effective_flux) < minflux) + effective_flux = std::copysignf(minflux, effective_flux); + vel_gain /= effective_flux; + vel_integrator_gain /= effective_flux; + } + // Velocity control float Iq = current_setpoint_; @@ -185,13 +198,15 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s float v_err = vel_des - vel_estimate; if (config_.control_mode >= CTRL_MODE_VELOCITY_CONTROL) { - Iq += config_.vel_gain * v_err; + Iq += vel_gain * v_err; } // Velocity integral action before limiting Iq += vel_integrator_current_; // Current limiting + // TODO: Change to controller working in torque units + // and get the torque limits from a function of the motor bool limited = false; float Ilim = axis_->motor_.effective_current_lim(); if (Iq > Ilim) { @@ -212,7 +227,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // TODO make decayfactor configurable vel_integrator_current_ *= 0.99f; } else { - vel_integrator_current_ += (config_.vel_integrator_gain * current_meas_period) * v_err; + vel_integrator_current_ += (vel_integrator_gain * current_meas_period) * v_err; } } diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 17306e59..da98359e 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -50,6 +50,7 @@ bool Motor::arm() { void Motor::reset_current_control() { current_control_.v_current_control_integral_d = 0.0f; current_control_.v_current_control_integral_q = 0.0f; + current_control_.acim_rotor_flux = 0.0f; } // @brief Tune the current controller based on phase resistance and inductance @@ -284,7 +285,8 @@ bool Motor::measure_phase_inductance(float voltage_low, float voltage_high) { bool Motor::run_calibration() { float R_calib_max_voltage = config_.resistance_calib_max_voltage; - if (config_.motor_type == MOTOR_TYPE_HIGH_CURRENT) { + if (config_.motor_type == MOTOR_TYPE_HIGH_CURRENT + || config_.motor_type == MOTOR_TYPE_ACIM) { if (!measure_phase_resistance(config_.calibration_current, R_calib_max_voltage)) return false; if (!measure_phase_inductance(-R_calib_max_voltage, R_calib_max_voltage)) @@ -444,17 +446,50 @@ bool Motor::update(float current_setpoint, float phase, float phase_vel) { phase *= config_.direction; phase_vel *= config_.direction; + // TODO: 2-norm vs independent clamping (current could be sqrt(2) bigger) + float ilim = effective_current_lim(); + // TODO: use std::clamp (C++17) + float id = MACRO_MIN(MACRO_MAX(current_control_.Id_setpoint, -ilim), ilim); + float iq = MACRO_MIN(MACRO_MAX(current_setpoint, -ilim), ilim); + + if (config_.motor_type == MOTOR_TYPE_ACIM) { + // Note that the effect of the current commands on the real currents is actually 1.5 PWM cycles later + // However the rotor time constant is (usually) so slow that it doesn't matter + // So we elect to write it as if the effect is immediate, to have cleaner code + + // acim_rotor_flux is normalized to units of [A] tracking Id; rotor inductance is unspecified + float dflux_by_dt = config_.acim_slip_velocity * (id - current_control_.acim_rotor_flux); + current_control_.acim_rotor_flux += dflux_by_dt * current_meas_period; + float slip_velocity = config_.acim_slip_velocity * (iq / current_control_.acim_rotor_flux); + // Check for issues with small denominator. Polarity of check to catch NaN too + bool acceptable_vel = fabsf(slip_velocity) <= 0.1f * (float)current_meas_hz; + if (!acceptable_vel) + slip_velocity = 0.0f; + phase_vel += slip_velocity; + // reporting only: + current_control_.async_phase_vel = slip_velocity; + + current_control_.async_phase_offset += slip_velocity * current_meas_period; + current_control_.async_phase_offset = wrap_pm_pi(current_control_.async_phase_offset); + phase += current_control_.async_phase_offset; + phase = wrap_pm_pi(phase); + } + float pwm_phase = phase + 1.5f * current_meas_period * phase_vel; // Execute current command // TODO: move this into the mot if (config_.motor_type == MOTOR_TYPE_HIGH_CURRENT) { - if(!FOC_current(0.0f, current_setpoint, phase, pwm_phase)){ + if(!FOC_current(id, iq, phase, pwm_phase)){ + return false; + } + } else if (config_.motor_type == MOTOR_TYPE_ACIM) { + if(!FOC_current(id, iq, phase, pwm_phase)){ return false; } } else if (config_.motor_type == MOTOR_TYPE_GIMBAL) { //In gimbal motor mode, current is reinterptreted as voltage. - if(!FOC_voltage(0.0f, current_setpoint, pwm_phase)) + if(!FOC_voltage(id, iq, pwm_phase)) return false; } else { set_error(ERROR_NOT_IMPLEMENTED_MOTOR_TYPE); diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index f0783584..168b29ba 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -29,7 +29,8 @@ public: enum MotorType_t { MOTOR_TYPE_HIGH_CURRENT = 0, // MOTOR_TYPE_LOW_CURRENT = 1, //Not yet implemented - MOTOR_TYPE_GIMBAL = 2 + MOTOR_TYPE_GIMBAL = 2, + MOTOR_TYPE_ACIM = 3, }; struct Iph_BC_t { @@ -46,12 +47,16 @@ public: // Voltage applied at end of cycle: float final_v_alpha; // [V] float final_v_beta; // [V] + float Id_setpoint; // [A] float Iq_setpoint; // [A] float Iq_measured; // [A] float Id_measured; // [A] float I_measured_report_filter_k; float max_allowed_current; // [A] float overcurrent_trip_level; // [A] + float acim_rotor_flux; // [A] + float async_phase_vel; // [rad/s electrical] + float async_phase_offset; // [rad electrical] }; // NOTE: for gimbal motors, all units of A are instead V. @@ -75,6 +80,8 @@ public: float current_control_bandwidth = 1000.0f; // [rad/s] float inverter_temp_limit_lower = 100; float inverter_temp_limit_upper = 120; + float acim_slip_velocity = 14.706f; // [rad/s electrical] = 1/rotor_tau + float acim_gain_min_flux = 10; // [A] }; enum TimingLog_t { @@ -162,12 +169,16 @@ public: .Ibus = 0.0f, .final_v_alpha = 0.0f, .final_v_beta = 0.0f, + .Id_setpoint = 0.0f, .Iq_setpoint = 0.0f, .Iq_measured = 0.0f, .Id_measured = 0.0f, .I_measured_report_filter_k = 1.0f, .max_allowed_current = 0.0f, .overcurrent_trip_level = 0.0f, + .acim_rotor_flux = 0.0f, + .async_phase_vel = 0.0f, + .async_phase_offset = 0.0f, }; DRV8301_FaultType_e drv_fault_ = DRV8301_FaultType_NoFault; DRV_SPI_8301_Vars_t gate_driver_regs_; //Local view of DRV registers (initialized by DRV8301_setup) @@ -194,12 +205,16 @@ public: make_protocol_property("Ibus", ¤t_control_.Ibus), make_protocol_property("final_v_alpha", ¤t_control_.final_v_alpha), make_protocol_property("final_v_beta", ¤t_control_.final_v_beta), - make_protocol_property("Iq_setpoint", ¤t_control_.Iq_setpoint), + make_protocol_property("Id_setpoint", ¤t_control_.Id_setpoint), + make_protocol_ro_property("Iq_setpoint", ¤t_control_.Iq_setpoint), make_protocol_property("Iq_measured", ¤t_control_.Iq_measured), make_protocol_property("Id_measured", ¤t_control_.Id_measured), make_protocol_property("I_measured_report_filter_k", ¤t_control_.I_measured_report_filter_k), make_protocol_ro_property("max_allowed_current", ¤t_control_.max_allowed_current), - make_protocol_ro_property("overcurrent_trip_level", ¤t_control_.overcurrent_trip_level) + make_protocol_ro_property("overcurrent_trip_level", ¤t_control_.overcurrent_trip_level), + make_protocol_property("acim_rotor_flux", ¤t_control_.acim_rotor_flux), + make_protocol_ro_property("async_phase_vel", ¤t_control_.async_phase_vel), + make_protocol_property("async_phase_offset", ¤t_control_.async_phase_offset) ), make_protocol_object("gate_driver", make_protocol_ro_property("drv_fault", &drv_fault_) @@ -234,7 +249,9 @@ public: make_protocol_property("inverter_temp_limit_upper", &config_.inverter_temp_limit_upper), make_protocol_property("requested_current_range", &config_.requested_current_range), make_protocol_property("current_control_bandwidth", &config_.current_control_bandwidth, - [](void* ctx) { static_cast(ctx)->update_current_controller_gains(); }, this) + [](void* ctx) { static_cast(ctx)->update_current_controller_gains(); }, this), + make_protocol_property("acim_slip_velocity", &config_.acim_slip_velocity), + make_protocol_property("acim_gain_min_flux", &config_.acim_gain_min_flux) ) ); } diff --git a/Firmware/fibre/python/fibre/usbbulk_transport.py b/Firmware/fibre/python/fibre/usbbulk_transport.py index dd32b106..f8a8905a 100644 --- a/Firmware/fibre/python/fibre/usbbulk_transport.py +++ b/Firmware/fibre/python/fibre/usbbulk_transport.py @@ -187,7 +187,7 @@ def discover_channels(path, serial_number, callback, cancellation_token, channel return True while not cancellation_token.is_set(): - logger.debug("USB discover loop") + # logger.debug("USB discover loop") devices = usb.core.find(find_all=True, custom_match=device_matcher) for usb_device in devices: try: diff --git a/ODrive_Workspace.code-workspace b/ODrive_Workspace.code-workspace index 89da475f..2928216c 100644 --- a/ODrive_Workspace.code-workspace +++ b/ODrive_Workspace.code-workspace @@ -25,6 +25,7 @@ "c-cpp-flylint.cppcheck.standard": ["c99","c++14"], "files.associations": { + "*.config": "yaml", "memory": "cpp", "utility": "cpp", "deque": "cpp", @@ -58,7 +59,8 @@ "chrono": "cpp", "condition_variable": "cpp", "future": "cpp", - "arm_math.h": "c" + "arm_math.h": "c", + "cmath": "cpp" } } } diff --git a/analysis/motor_analysis/ac_induction_motor.py b/analysis/motor_analysis/ac_induction_motor.py index e467d365..9cf15228 100644 --- a/analysis/motor_analysis/ac_induction_motor.py +++ b/analysis/motor_analysis/ac_induction_motor.py @@ -148,8 +148,8 @@ def plot_data(t, y, ref, title): ax1.plot(t, np.imag(y[0]), label='Stator current (q)') ax2.plot(t, np.real(y[2]), label='Rotor current (d)') ax2.plot(t, np.imag(y[2]), label='Rotor current (q)') - ax1b.plot(t, 1000*np.real(y[1]), 'C2', label='Rotor flux (d)') - ax1b.plot(t, 1000*np.imag(y[1]), 'C3', label='Rotor flux (q)') + ax1b.plot(t, 1000*np.real(y[1]), 'C3', label='Rotor flux (d)') + ax1b.plot(t, 1000*np.imag(y[1]), 'C4', label='Rotor flux (q)') ax1.set_xlabel('time [s]') ax1.set_ylabel('Current [A]') ax1b.set_ylabel('Flux [mWb]') From 4015ed09dd48107b8020c7d52a3062af968ddafb Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 23 Sep 2019 00:07:45 -0700 Subject: [PATCH 220/549] add ACIM autoflux --- Firmware/MotorControl/controller.cpp | 2 ++ Firmware/MotorControl/motor.cpp | 8 ++++++++ Firmware/MotorControl/motor.hpp | 10 +++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 1e20c1f4..70bfb1a0 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -184,6 +184,8 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s effective_flux = std::copysignf(minflux, effective_flux); vel_gain /= effective_flux; vel_integrator_gain /= effective_flux; + // TODO: also scale the integral value which is also changing units. + // (or again just do control in torque units) } // Velocity control diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index da98359e..78dd9d03 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -457,6 +457,14 @@ bool Motor::update(float current_setpoint, float phase, float phase_vel) { // However the rotor time constant is (usually) so slow that it doesn't matter // So we elect to write it as if the effect is immediate, to have cleaner code + if (config_.acim_autoflux_enable) { + float abs_iq = fabsf(iq); + float gain = abs_iq > id ? config_.acim_autoflux_attack_gain : config_.acim_autoflux_decay_gain; + id += gain * (abs_iq - id) * current_meas_period; + id = MACRO_MIN(MACRO_MAX(id, config_.acim_autoflux_min_Id), ilim); + current_control_.Id_setpoint = id; + } + // acim_rotor_flux is normalized to units of [A] tracking Id; rotor inductance is unspecified float dflux_by_dt = config_.acim_slip_velocity * (id - current_control_.acim_rotor_flux); current_control_.acim_rotor_flux += dflux_by_dt * current_meas_period; diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 168b29ba..cf2c7c63 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -82,6 +82,10 @@ public: float inverter_temp_limit_upper = 120; float acim_slip_velocity = 14.706f; // [rad/s electrical] = 1/rotor_tau float acim_gain_min_flux = 10; // [A] + float acim_autoflux_min_Id = 10; // [A] + bool acim_autoflux_enable = false; + float acim_autoflux_attack_gain = 10.0f; + float acim_autoflux_decay_gain = 1.0f; }; enum TimingLog_t { @@ -251,7 +255,11 @@ public: make_protocol_property("current_control_bandwidth", &config_.current_control_bandwidth, [](void* ctx) { static_cast(ctx)->update_current_controller_gains(); }, this), make_protocol_property("acim_slip_velocity", &config_.acim_slip_velocity), - make_protocol_property("acim_gain_min_flux", &config_.acim_gain_min_flux) + make_protocol_property("acim_gain_min_flux", &config_.acim_gain_min_flux), + make_protocol_property("acim_autoflux_min_Id", &config_.acim_autoflux_min_Id), + make_protocol_property("acim_autoflux_enable", &config_.acim_autoflux_enable), + make_protocol_property("acim_autoflux_attack_gain", &config_.acim_autoflux_attack_gain), + make_protocol_property("acim_autoflux_decay_gain", &config_.acim_autoflux_decay_gain) ) ); } From f8ea7d56842fc6a419440aa864466b63793cd037 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 23 Sep 2019 11:15:14 +0200 Subject: [PATCH 221/549] [homing] make separate control mode [endstops] enable in all control modes [load encoder] use load encoder more consequently --- Firmware/MotorControl/axis.cpp | 172 +++++++++++------- Firmware/MotorControl/axis.hpp | 18 +- Firmware/MotorControl/controller.cpp | 154 +++++++++------- Firmware/MotorControl/controller.hpp | 25 +-- Firmware/MotorControl/encoder.cpp | 4 + Firmware/MotorControl/encoder.hpp | 3 + Firmware/MotorControl/endstop.cpp | 16 +- Firmware/MotorControl/endstop.hpp | 12 +- Firmware/MotorControl/main.cpp | 1 + .../MotorControl/sensorless_estimator.cpp | 2 + .../MotorControl/sensorless_estimator.hpp | 1 + 11 files changed, 242 insertions(+), 166 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 00411b0b..231b2309 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -33,10 +33,10 @@ Axis::Axis(int axis_num, controller_.axis_ = this; motor_.axis_ = this; trap_.axis_ = this; - decode_step_dir_pins(); - watchdog_feed(); min_endstop_.axis_ = this; max_endstop_.axis_ = this; + decode_step_dir_pins(); + watchdog_feed(); } Axis::LockinConfig_t Axis::default_calibration() { @@ -182,10 +182,18 @@ bool Axis::do_checks() { // Sub-components should use set_error which will propegate to this error_ motor_.do_checks(); - encoder_.do_checks(); + // encoder_.do_checks(); // sensorless_estimator_.do_checks(); // controller_.do_checks(); + // Check for endstop presses + bool vel_dependent_stopping = (current_state_ == AXIS_STATE_HOMING) && (controller_.config_.control_mode >= Controller::CTRL_MODE_VELOCITY_CONTROL); + if (min_endstop_.config_.enabled && min_endstop_.get_state() && (!vel_dependent_stopping || controller_.vel_setpoint_ < 0.0f)) { + error_ |= ERROR_MIN_ENDSTOP_PRESSED; + } else if (max_endstop_.config_.enabled && max_endstop_.get_state() && (!vel_dependent_stopping || controller_.vel_setpoint_ > 0.0f)) { + error_ |= ERROR_MAX_ENDSTOP_PRESSED; + } + return check_for_errors(); } @@ -287,13 +295,15 @@ bool Axis::run_lockin_spin(const LockinConfig_t &lockin_config) { // Note run_sensorless_control_loop and run_closed_loop_control_loop are very similar and differ only in where we get the estimate from. bool Axis::run_sensorless_control_loop() { - run_control_loop([this](){ - if (controller_.config_.control_mode >= Controller::CTRL_MODE_POSITION_CONTROL) - return error_ |= ERROR_POS_CTRL_DURING_SENSORLESS, false; + controller_.pos_estimate_src_ = nullptr; + controller_.pos_estimate_valid_src_ = nullptr; + controller_.vel_estimate_src_ = &sensorless_estimator_.vel_estimate_; + controller_.vel_estimate_valid_src_ = &sensorless_estimator_.vel_estimate_valid_; + run_control_loop([this](){ // Note that all estimators are updated in the loop prefix in run_control_loop float current_setpoint; - if (!controller_.update(sensorless_estimator_.pll_pos_, sensorless_estimator_.vel_estimate_, ¤t_setpoint)) + if (!controller_.update(¤t_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; if (!motor_.update(current_setpoint, sensorless_estimator_.phase_, sensorless_estimator_.vel_estimate_)) return false; // set_error should update axis.error_ @@ -303,8 +313,12 @@ bool Axis::run_sensorless_control_loop() { } bool Axis::run_closed_loop_control_loop() { + if (!controller_.select_encoder(controller_.config_.load_encoder_axis)) { + return error_ |= ERROR_CONTROLLER_FAILED, false; + } + // To avoid any transient on startup, we intialize the setpoint to be the current position - controller_.pos_setpoint_ = encoder_.pos_estimate_; + controller_.pos_setpoint_ = *controller_.pos_estimate_src_; // Avoid integrator windup issues controller_.vel_integrator_current_ = 0.0f; @@ -313,62 +327,99 @@ bool Axis::run_closed_loop_control_loop() { run_control_loop([this](){ // Note that all estimators are updated in the loop prefix in run_control_loop float current_setpoint; - if (controller_.config_.use_load_encoder) { - if (controller_.config_.load_encoder_axis < AXIS_COUNT) { - Axis* ax = axes[controller_.config_.load_encoder_axis]; - if (!controller_.update(ax->encoder_.pos_estimate_, encoder_.vel_estimate_, ¤t_setpoint)) - return error_ |= ERROR_CONTROLLER_FAILED, false; - } else{ - controller_.set_error(Controller::ERROR_INVALID_LOAD_ENCODER); - return error_ |= ERROR_CONTROLLER_FAILED, false; - } - } else if (!controller_.update(encoder_.pos_estimate_, encoder_.vel_estimate_, ¤t_setpoint)) - return error_ |= ERROR_CONTROLLER_FAILED, false; //TODO: Make controller.set_error + if (!controller_.update(¤t_setpoint)) + return error_ |= ERROR_CONTROLLER_FAILED, false; + float phase_vel = 2 * M_PI * encoder_.vel_estimate_ / (float)encoder_.config_.cpr * motor_.config_.pole_pairs; if (!motor_.update(current_setpoint, encoder_.phase_, phase_vel)) return false; // set_error should update axis.error_ - // Handle the homing case - if (homing_.homing_state == HOMING_STATE_HOMING) { - if (min_endstop_.getEndstopState()) { - // pos_setpoint is the starting position for the trap_traj so we need to set it. - controller_.pos_setpoint_ = min_endstop_.config_.offset; - controller_.vel_setpoint_ = 0.0f; // Change directions without decelerating - - // Set our current position in encoder counts to make control more logical - encoder_.set_linear_count(static_cast(controller_.pos_setpoint_)); - - controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; - controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ; - - controller_.input_pos_ = 0.0f; - controller_.input_pos_updated(); - controller_.input_vel_ = 0.0f; - controller_.input_current_ = 0.0f; - - homing_.homing_state = HOMING_STATE_MOVE_TO_ZERO; - } - } else if (homing_.homing_state == HOMING_STATE_MOVE_TO_ZERO) { - if(!min_endstop_.getEndstopState() && controller_.trajectory_done_){ - controller_.config_.control_mode = homing_.storedControlMode; - controller_.config_.input_mode = homing_.storedInputMode; - homing_.homing_state = HOMING_STATE_IDLE; - homing_.isHomed = true; - } - } else { - // Check for endstop presses - if (min_endstop_.config_.enabled && min_endstop_.getEndstopState()) { - return error_ |= ERROR_MIN_ENDSTOP_PRESSED, false; - } else if (max_endstop_.config_.enabled && max_endstop_.getEndstopState()) { - return error_ |= ERROR_MAX_ENDSTOP_PRESSED, false; - } - } return true; }); set_step_dir_active(false); return check_for_errors(); } + +// Slowly drive in the negative direction at homing_speed until the min endstop is pressed +// When pressed, set the linear count to the offset (default 0), and then go to position 0 +bool Axis::run_homing() { + Controller::ControlMode_t stored_control_mode = controller_.config_.control_mode; + Controller::InputMode_t stored_input_mode = controller_.config_.input_mode; + + if (!min_endstop_.config_.enabled) { + return error_ |= ERROR_MIN_ENDSTOP_PRESSED, false; // TODO: define new error code + } + + controller_.config_.control_mode = Controller::CTRL_MODE_VELOCITY_CONTROL; + controller_.config_.input_mode = Controller::INPUT_MODE_VEL_RAMP; + + controller_.input_pos_ = 0.0f; + controller_.input_pos_updated(); + controller_.input_vel_ = -controller_.config_.homing_speed; + controller_.input_current_ = 0.0f; + + homing_.is_homed = false; + + if (!controller_.select_encoder(controller_.config_.load_encoder_axis)) { + return error_ |= ERROR_CONTROLLER_FAILED, false; + } + + // To avoid any transient on startup, we intialize the setpoint to be the current position + controller_.pos_setpoint_ = *controller_.pos_estimate_src_; + + // Avoid integrator windup issues + controller_.vel_integrator_current_ = 0.0f; + + run_control_loop([this](){ + // Note that all estimators are updated in the loop prefix in run_control_loop + float current_setpoint; + if (!controller_.update(¤t_setpoint)) + return error_ |= ERROR_CONTROLLER_FAILED, false; + + float phase_vel = 2 * M_PI * encoder_.vel_estimate_ / (float)encoder_.config_.cpr * motor_.config_.pole_pairs; + if (!motor_.update(current_setpoint, encoder_.phase_, phase_vel)) + return false; // set_error should update axis.error_ + + return !min_endstop_.get_state(); + }); + error_ &= ~ERROR_MIN_ENDSTOP_PRESSED; // clear this error since we deliberately drove into the endstop + + // pos_setpoint is the starting position for the trap_traj so we need to set it. + controller_.pos_setpoint_ = min_endstop_.config_.offset; + controller_.vel_setpoint_ = 0.0f; // Change directions without decelerating + + // Set our current position in encoder counts to make control more logical + encoder_.set_linear_count(static_cast(controller_.pos_setpoint_)); + + controller_.config_.control_mode = Controller::CTRL_MODE_POSITION_CONTROL; + controller_.config_.input_mode = Controller::INPUT_MODE_TRAP_TRAJ; + + controller_.input_pos_ = 0.0f; + controller_.input_pos_updated(); + controller_.input_vel_ = 0.0f; + controller_.input_current_ = 0.0f; + + run_control_loop([this](){ + // Note that all estimators are updated in the loop prefix in run_control_loop + float current_setpoint; + if (!controller_.update(¤t_setpoint)) + return error_ |= ERROR_CONTROLLER_FAILED, false; + + float phase_vel = 2 * M_PI * encoder_.vel_estimate_ / (float)encoder_.config_.cpr * motor_.config_.pole_pairs; + if (!motor_.update(current_setpoint, encoder_.phase_, phase_vel)) + return false; // set_error should update axis.error_ + + return !controller_.trajectory_done_; + }); + + controller_.config_.control_mode = stored_control_mode; + controller_.config_.input_mode = stored_input_mode; + homing_.is_homed = true; + + return check_for_errors(); +} + bool Axis::run_idle_loop() { // run_control_loop ignores missed modulation timing updates // if and only if we're in AXIS_STATE_IDLE @@ -396,11 +447,10 @@ void Axis::run_state_machine_loop() { task_chain_[pos++] = AXIS_STATE_ENCODER_INDEX_SEARCH; if (config_.startup_encoder_offset_calibration) task_chain_[pos++] = AXIS_STATE_ENCODER_OFFSET_CALIBRATION; - if (config_.startup_closed_loop_control){ - if(config_.startup_homing) - task_chain_[pos++] = AXIS_STATE_HOMING; + if (config_.startup_homing) + task_chain_[pos++] = AXIS_STATE_HOMING; + if (config_.startup_closed_loop_control) task_chain_[pos++] = AXIS_STATE_CLOSED_LOOP_CONTROL; - } else if (config_.startup_sensorless_control) task_chain_[pos++] = AXIS_STATE_SENSORLESS_CONTROL; task_chain_[pos++] = AXIS_STATE_IDLE; @@ -446,9 +496,9 @@ void Axis::run_state_machine_loop() { status = encoder_.run_direction_find(); } break; - case AXIS_STATE_HOMING: - status = controller_.home_axis(); - break; + case AXIS_STATE_HOMING: { + status = run_homing(); + } break; case AXIS_STATE_ENCODER_OFFSET_CALIBRATION: { if (!motor_.is_calibrated_) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 4264b23b..858df43b 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -5,13 +5,6 @@ #error "This file should not be included directly. Include odrive_main.h instead." #endif - -enum HomingState_t { - HOMING_STATE_IDLE, - HOMING_STATE_HOMING, - HOMING_STATE_MOVE_TO_ZERO -}; - class Axis { public: enum Error_t { @@ -26,7 +19,7 @@ public: ERROR_SENSORLESS_ESTIMATOR_FAILED = 0x80, 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_POS_CTRL_DURING_SENSORLESS = 0x400, // DEPRECATED ERROR_WATCHDOG_TIMER_EXPIRED = 0x800, ERROR_MIN_ENDSTOP_PRESSED = 0x1000, ERROR_MAX_ENDSTOP_PRESSED = 0x2000, @@ -96,10 +89,7 @@ public: }; struct Homing_t { - HomingState_t homing_state = HOMING_STATE_IDLE; - Controller::ControlMode_t storedControlMode = Controller::CTRL_MODE_POSITION_CONTROL; - Controller::InputMode_t storedInputMode = Controller::INPUT_MODE_PASSTHROUGH; - bool isHomed = false; + bool is_homed = false; }; enum thread_signals { @@ -224,6 +214,7 @@ public: bool run_lockin_spin(const LockinConfig_t &lockin_config); bool run_sensorless_control_loop(); bool run_closed_loop_control_loop(); + bool run_homing(); bool run_idle_loop(); constexpr uint32_t get_watchdog_reset() { @@ -277,8 +268,7 @@ public: make_protocol_property("requested_state", &requested_state_), make_protocol_ro_property("loop_counter", &loop_counter_), make_protocol_ro_property("lockin_state", &lockin_state_), - make_protocol_ro_property("homing_state", &homing_.homing_state), - make_protocol_property("is_homed", &homing_.isHomed), + make_protocol_property("is_homed", &homing_.is_homed), make_protocol_object("config", make_protocol_property("startup_motor_calibration", &config_.startup_motor_calibration), make_protocol_property("startup_encoder_index_search", &config_.startup_encoder_index_search), diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 19833c15..aeb09fd5 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -29,6 +29,25 @@ void Controller::input_pos_updated() { input_pos_updated_ = true; } +bool Controller::select_encoder(size_t encoder_num) { + if (encoder_num < AXIS_COUNT) { + Axis* ax = axes[encoder_num]; + if (config_.setpoints_in_cpr) { + pos_estimate_src_ = &ax->encoder_.pos_cpr_; + pos_wrap_src_ = &ax->encoder_.config_.cpr; + } else { + pos_estimate_src_ = &ax->encoder_.pos_estimate_; + pos_wrap_src_ = nullptr; + } + pos_estimate_valid_src_ = &ax->encoder_.pos_estimate_valid_; + vel_estimate_src_ = &ax->encoder_.vel_estimate_; + vel_estimate_valid_src_ = &ax->encoder_.vel_estimate_valid_; + return true; + } else { + return set_error(Controller::ERROR_INVALID_LOAD_ENCODER), false; + } +} + void Controller::move_to_pos(float goal_point) { axis_->trap_.planTrapezoidal(goal_point, pos_setpoint_, vel_setpoint_, axis_->trap_.config_.vel_limit, @@ -55,30 +74,6 @@ void Controller::start_anticogging_calibration() { } } -// Slowly drive in the negative direction at homing_speed until the min endstop is pressed -// When pressed, set the linear count to the offset (default 0), and then - -//TODO: This needs to be upgraded to use its own run_control_loop! -bool Controller::home_axis() { - if (axis_->min_endstop_.config_.enabled) { - axis_->homing_.storedControlMode = config_.control_mode; - axis_->homing_.storedInputMode = config_.input_mode; - - config_.control_mode = CTRL_MODE_VELOCITY_CONTROL; - config_.input_mode = INPUT_MODE_VEL_RAMP; - - input_pos_ = 0.0f; - input_pos_updated(); - input_vel_ = -config_.homing_speed; - input_current_ = 0.0f; - - axis_->homing_.isHomed = false; - axis_->homing_.homing_state = HOMING_STATE_HOMING; - } else { - return false; - } - return true; -} /* * This anti-cogging implementation iterates through each encoder position, @@ -88,32 +83,29 @@ bool Controller::home_axis() { * This holding current is added as a feedforward term in the control loop. */ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate) { - if (config_.anticogging.calib_anticogging) { - float pos_err = input_pos_ - pos_estimate; - if (std::abs(pos_err) <= config_.anticogging.calib_pos_threshold && - std::abs(vel_estimate) < config_.anticogging.calib_vel_threshold) { - config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_current_; - } - if (config_.anticogging.index < 3600) { - config_.control_mode = CTRL_MODE_POSITION_CONTROL; - input_pos_ = config_.anticogging.index * axis_->encoder_.getCoggingRatio(); - input_vel_ = 0.0f; - input_current_ = 0.0f; - input_pos_updated(); - return false; - } else { - config_.anticogging.index = 0; - config_.control_mode = CTRL_MODE_POSITION_CONTROL; - input_pos_ = 0.0f; // Send the motor home - input_vel_ = 0.0f; - input_current_ = 0.0f; - input_pos_updated(); - anticogging_valid_ = true; - config_.anticogging.calib_anticogging = false; - return true; - } + float pos_err = input_pos_ - pos_estimate; + if (std::abs(pos_err) <= config_.anticogging.calib_pos_threshold && + std::abs(vel_estimate) < config_.anticogging.calib_vel_threshold) { + config_.anticogging.cogging_map[std::clamp(config_.anticogging.index++, 0, 3600)] = vel_integrator_current_; + } + if (config_.anticogging.index < 3600) { + config_.control_mode = CTRL_MODE_POSITION_CONTROL; + input_pos_ = config_.anticogging.index * axis_->encoder_.getCoggingRatio(); + input_vel_ = 0.0f; + input_current_ = 0.0f; + input_pos_updated(); + return false; + } else { + config_.anticogging.index = 0; + config_.control_mode = CTRL_MODE_POSITION_CONTROL; + input_pos_ = 0.0f; // Send the motor home + input_vel_ = 0.0f; + input_current_ = 0.0f; + input_pos_updated(); + anticogging_valid_ = true; + config_.anticogging.calib_anticogging = false; + return true; } - return false; } void Controller::update_filter_gains() { @@ -129,10 +121,22 @@ float limitVel(const float vel_limit, const float vel_estimate, const float vel_ } } // namespace -bool Controller::update(float pos_estimate, float vel_estimate, float* current_setpoint_output) { - // Only runs if config_.anticogging.calib_anticogging is true; non-blocking - anticogging_calibration(axis_->encoder_.pos_estimate_, vel_estimate); - float anticogging_pos = axis_->encoder_.pos_estimate_ / axis_->encoder_.getCoggingRatio(); +bool Controller::update(float* current_setpoint_output) { + float* pos_estimate_src = (pos_estimate_valid_src_ && *pos_estimate_valid_src_) + ? pos_estimate_src_ : nullptr; + float* vel_estimate_src = (vel_estimate_valid_src_ && *vel_estimate_valid_src_) + ? vel_estimate_src_ : nullptr; + + float anticogging_pos = 0.0f; + if (config_.anticogging.calib_anticogging) { + if (!axis_->encoder_.pos_estimate_valid_ || !axis_->encoder_.vel_estimate_valid_) { + set_error(ERROR_INVALID_ESTIMATE); + return false; + } + // non-blocking + anticogging_calibration(axis_->encoder_.pos_estimate_, axis_->encoder_.vel_estimate_); + anticogging_pos = axis_->encoder_.pos_estimate_ / axis_->encoder_.getCoggingRatio(); + } // Update inputs switch (config_.input_mode) { @@ -219,18 +223,22 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s float vel_des = vel_setpoint_; if (config_.control_mode >= CTRL_MODE_POSITION_CONTROL) { float pos_err; - if (config_.setpoints_in_cpr) { - // TODO this breaks the semantics that estimates come in on the arguments. - // It's probably better to call a get_estimate that will arbitrate (enc vs sensorless) instead. - float cpr = (float)(axis_->encoder_.config_.cpr); + if (!pos_estimate_src) { + set_error(ERROR_INVALID_ESTIMATE); + return false; + } + + if (pos_wrap_src_) { + float cpr = *pos_wrap_src_; // Keep pos setpoint from drifting pos_setpoint_ = fmodf_pos(pos_setpoint_, cpr); // Circular delta - pos_err = pos_setpoint_ - axis_->encoder_.pos_cpr_; + pos_err = pos_setpoint_ - *pos_estimate_src; pos_err = wrap_pm(pos_err, 0.5f * cpr); } else { - pos_err = pos_setpoint_ - pos_estimate; + pos_err = pos_setpoint_ - *pos_estimate_src; } + vel_des += config_.pos_gain * pos_err; // V-shaped gain shedule based on position error float abs_pos_err = std::abs(pos_err); @@ -248,7 +256,11 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s // Check for overspeed fault (done in this module (controller) for cohesion with vel_lim) if (config_.enable_overspeed_error) { // 0.0f to disable - if (std::abs(vel_estimate) > config_.vel_limit_tolerance * vel_lim) { + if (!vel_estimate_src) { + set_error(ERROR_INVALID_ESTIMATE); + return false; + } + if (std::abs(*vel_estimate_src) > config_.vel_limit_tolerance * vel_lim) { set_error(ERROR_OVERSPEED); return false; } @@ -264,17 +276,27 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s Iq += config_.anticogging.cogging_map[std::clamp(mod(static_cast(anticogging_pos), 3600), 0, 3600)]; } - float v_err = vel_des - vel_estimate; + float v_err = 0.0f; if (config_.control_mode >= CTRL_MODE_VELOCITY_CONTROL) { - Iq += (config_.vel_gain * gain_scheduling_multiplier) * v_err; - } + if (!vel_estimate_src) { + set_error(ERROR_INVALID_ESTIMATE); + return false; + } - // Velocity integral action before limiting - Iq += vel_integrator_current_; + v_err = vel_des - *vel_estimate_src; + Iq += (config_.vel_gain * gain_scheduling_multiplier) * v_err; + + // Velocity integral action before limiting + Iq += vel_integrator_current_; + } // Velocity limiting in current mode if (config_.control_mode < CTRL_MODE_VELOCITY_CONTROL && config_.enable_current_vel_limit) { - Iq = limitVel(config_.vel_limit, vel_estimate, config_.vel_gain, Iq); + if (!vel_estimate_src) { + set_error(ERROR_INVALID_ESTIMATE); + return false; + } + Iq = limitVel(config_.vel_limit, *vel_estimate_src, config_.vel_gain, Iq); } // Current limiting diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 6e6565bb..28f3b026 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -14,6 +14,7 @@ public: ERROR_UNSTABLE_GAIN = 0x04, ERROR_INVALID_MIRROR_AXIS = 0x08, ERROR_INVALID_LOAD_ENCODER = 0x10, + ERROR_INVALID_ESTIMATE = 0x20, }; // Note: these should be sorted from lowest level of control to @@ -54,8 +55,8 @@ public: float vel_gain = 5.0f / 10000.0f; // [A/(counts/s)] // float vel_gain = 5.0f / 200.0f, // [A/(rad/s)] float vel_integrator_gain = 10.0f / 10000.0f; // [A/(counts/s * s)] - float vel_limit = 20000.0f; // [counts/s] - float vel_limit_tolerance = 1.2f; // ratio to vel_lim. 0.0f to disable + float vel_limit = 20000.0f; // [counts/s] Infinity to disable. + float vel_limit_tolerance = 1.2f; // ratio to vel_lim. Infinity to disable. float vel_ramp_rate = 10000.0f; // [(counts/s) / s] float current_ramp_rate = 1.0f; // A / sec bool setpoints_in_cpr = false; @@ -67,11 +68,10 @@ public: bool enable_gain_scheduling = false; bool enable_vel_limit = true; bool enable_overspeed_error = true; - bool enable_current_vel_limit = true; + bool enable_current_vel_limit = true; // enable velocity limit in current control mode (requires a valid velocity estimator) uint8_t axis_to_mirror = -1; float mirror_ratio = 1.0f; - bool use_load_encoder = false; - uint8_t load_encoder_axis = -1; + uint8_t load_encoder_axis = -1; // default depends on Axis number and is set in load_configuration() float load_encoder_ratio = 1.0f; }; @@ -80,19 +80,18 @@ public: void set_error(Error_t error); void input_pos_updated(); + bool select_encoder(size_t encoder_num); // Trajectory-Planned control void move_to_pos(float goal_point); void move_incremental(float displacement, bool from_goal_point); - - bool home_axis(); // TODO: make this more similar to other calibration loops void start_anticogging_calibration(); bool anticogging_calibration(float pos_estimate, float vel_estimate); void update_filter_gains(); - bool update(float pos_estimate, float vel_estimate, float* current_setpoint); + bool update(float* current_setpoint); Config_t& config_; Axis* axis_ = nullptr; // set by Axis constructor @@ -105,6 +104,12 @@ public: Error_t error_ = ERROR_NONE; + float* pos_estimate_src_ = nullptr; + bool* pos_estimate_valid_src_ = nullptr; + float* vel_estimate_src_ = nullptr; + bool* vel_estimate_valid_src_ = nullptr; + int32_t* pos_wrap_src_ = nullptr; // enables circular position setpoints if not null. The value pointed to is the maximum position value. + float pos_setpoint_ = 0.0f; float vel_setpoint_ = 0.0f; // float vel_setpoint = 800.0f; @@ -157,7 +162,6 @@ public: make_protocol_property("inertia", &config_.inertia), make_protocol_property("axis_to_mirror", &config_.axis_to_mirror), make_protocol_property("mirror_ratio", &config_.mirror_ratio), - make_protocol_property("use_load_encoder", &config_.use_load_encoder), make_protocol_property("load_encoder_ratio", &config_.load_encoder_ratio), make_protocol_property("load_encoder_axis", &config_.load_encoder_axis), make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth, @@ -171,8 +175,7 @@ public: make_protocol_ro_property("cogging_ratio", &config_.anticogging.cogging_ratio), make_protocol_property("anticogging_enabled", &config_.anticogging.enable))), make_protocol_function("move_incremental", *this, &Controller::move_incremental, "displacement", "from_goal_point"), - make_protocol_function("start_anticogging_calibration", *this, &Controller::start_anticogging_calibration), - make_protocol_function("home_axis", *this, &Controller::home_axis) + make_protocol_function("start_anticogging_calibration", *this, &Controller::start_anticogging_calibration) ); } }; diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index ac2d8e0c..5fc32687 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -32,6 +32,8 @@ void Encoder::setup() { } void Encoder::set_error(Error_t error) { + vel_estimate_valid_ = false; + pos_estimate_valid_ = false; error_ |= error; axis_->error_ |= Axis::ERROR_ENCODER_FAILED; } @@ -525,5 +527,7 @@ bool Encoder::update() { // ph = fmodf(ph, 2*M_PI); phase_ = wrap_pm_pi(ph); + vel_estimate_valid_ = true; + pos_estimate_valid_ = true; return true; } diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index d85bcb44..58eea584 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -94,6 +94,9 @@ public: int32_t pos_abs_ = 0; float spi_error_rate_ = 0.0f; + bool pos_estimate_valid_ = false; + bool vel_estimate_valid_ = false; + int16_t tim_cnt_sample_ = 0; // // Updated by low_level pwm_adc_cb uint8_t hall_state_ = 0x0; // bit[0] = HallA, .., bit[2] = HallC diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index 809e52f1..72bcae3a 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -2,19 +2,19 @@ Endstop::Endstop(Endstop::Config_t& config) : config_(config) { - set_endstop_enabled(config_.enabled); + update_config(); } void Endstop::update() { uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); - auto last_pin_state = pin_state_; + bool last_pin_state = pin_state_; pin_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); + float now = axis_->loop_counter_ * current_meas_period; if (pin_state_ != last_pin_state) { - debounce_timer_ = axis_->loop_counter_ * current_meas_period; + debounce_timer_ = now; } if (config_.enabled) { - float now = axis_->loop_counter_ * current_meas_period; if ((now - debounce_timer_) >= (config_.debounce_ms * 0.001f)) { // Debounce timer expired, take the new pin state endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state debounce_timer_ = now - (config_.debounce_ms * 0.001f); // Ensure timer doesn't have overflow issues @@ -26,15 +26,15 @@ void Endstop::update() { } } -bool Endstop::getEndstopState() { +bool Endstop::get_state() { return endstop_state_; } -void Endstop::update_endstop_config(){ - set_endstop_enabled(config_.enabled); +void Endstop::update_config(){ + set_enabled(config_.enabled); } -void Endstop::set_endstop_enabled(bool enable) { +void Endstop::set_enabled(bool enable) { if (config_.gpio_num != 0) { uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index 45195a2f..9b1fffee 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -6,7 +6,7 @@ class Endstop { struct Config_t { float offset = 0; float debounce_ms = 50.0f; - uint16_t gpio_num; + uint16_t gpio_num = 0; bool enabled = false; bool is_active_high = false; }; @@ -16,11 +16,11 @@ class Endstop { Endstop::Config_t& config_; Axis* axis_ = nullptr; - void update_endstop_config(); - void set_endstop_enabled(bool enable); + void update_config(); + void set_enabled(bool enabled); void update(); - bool getEndstopState(); + bool get_state(); bool endstop_state_ = false; @@ -29,9 +29,9 @@ class Endstop { make_protocol_ro_property("endstop_state", &endstop_state_), make_protocol_object("config", make_protocol_property("gpio_num", &config_.gpio_num, - [](void* ctx) { static_cast(ctx)->update_endstop_config(); }, this), + [](void* ctx) { static_cast(ctx)->update_config(); }, this), make_protocol_property("enabled", &config_.enabled, - [](void* ctx) { static_cast(ctx)->update_endstop_config(); }, this), + [](void* ctx) { static_cast(ctx)->update_config(); }, this), make_protocol_property("offset", &config_.offset), make_protocol_property("is_active_high", &config_.is_active_high), make_protocol_property("debounce_ms", &config_.debounce_ms))); diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 17707eaa..6c4f6283 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -85,6 +85,7 @@ extern "C" int load_configuration(void) { Axis::load_default_can_id(i, axis_configs[i]); min_endstop_configs[i] = Endstop::Config_t(); max_endstop_configs[i] = Endstop::Config_t(); + controller_configs[i].load_encoder_axis = i; } } else { user_config_loaded_ = true; diff --git a/Firmware/MotorControl/sensorless_estimator.cpp b/Firmware/MotorControl/sensorless_estimator.cpp index 43191ce3..aebbc09b 100644 --- a/Firmware/MotorControl/sensorless_estimator.cpp +++ b/Firmware/MotorControl/sensorless_estimator.cpp @@ -65,6 +65,7 @@ bool SensorlessEstimator::update() { // Check that we don't get problems with discrete time approximation if (!(current_meas_period * pll_kp < 1.0f)) { error_ |= ERROR_UNSTABLE_GAIN; + vel_estimate_valid_ = false; return false; } @@ -77,5 +78,6 @@ bool SensorlessEstimator::update() { // update PLL velocity vel_estimate_ += current_meas_period * pll_ki * delta_phase; + vel_estimate_valid_ = true; return true; }; diff --git a/Firmware/MotorControl/sensorless_estimator.hpp b/Firmware/MotorControl/sensorless_estimator.hpp index 719a3227..e47db893 100644 --- a/Firmware/MotorControl/sensorless_estimator.hpp +++ b/Firmware/MotorControl/sensorless_estimator.hpp @@ -26,6 +26,7 @@ public: float phase_ = 0.0f; // [rad] float pll_pos_ = 0.0f; // [rad] float vel_estimate_ = 0.0f; // [rad/s] + bool vel_estimate_valid_ = false; // float pll_kp_ = 0.0f; // [rad/s / rad] // float pll_ki_ = 0.0f; // [(rad/s^2) / rad] float flux_state_[2] = {0.0f, 0.0f}; // [Vs] From 0153e3509a2ac678c6fb3219e8f23f538aca7133 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 24 Sep 2019 21:32:50 -0400 Subject: [PATCH 222/549] Fix anticogging --- Firmware/MotorControl/controller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index aeb09fd5..738b0b9c 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -127,7 +127,8 @@ bool Controller::update(float* current_setpoint_output) { float* vel_estimate_src = (vel_estimate_valid_src_ && *vel_estimate_valid_src_) ? vel_estimate_src_ : nullptr; - float anticogging_pos = 0.0f; + // Calib_anticogging is only true when calibration is occurring, so we can't block anticogging_pos + float anticogging_pos = axis_->encoder_.pos_estimate_ / axis_->encoder_.getCoggingRatio(); if (config_.anticogging.calib_anticogging) { if (!axis_->encoder_.pos_estimate_valid_ || !axis_->encoder_.vel_estimate_valid_) { set_error(ERROR_INVALID_ESTIMATE); @@ -135,7 +136,6 @@ bool Controller::update(float* current_setpoint_output) { } // non-blocking anticogging_calibration(axis_->encoder_.pos_estimate_, axis_->encoder_.vel_estimate_); - anticogging_pos = axis_->encoder_.pos_estimate_ / axis_->encoder_.getCoggingRatio(); } // Update inputs From ecf1ccd25e4641c40a86af7ae70f609a002fba71 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 25 Sep 2019 22:47:40 -0400 Subject: [PATCH 223/549] Add stack_size vars, reduce stack usage --- Firmware/Board/v3/Inc/freertos_vars.h | 2 ++ Firmware/Board/v3/Src/freertos.c | 6 ++++-- Firmware/MotorControl/axis.cpp | 2 +- Firmware/MotorControl/axis.hpp | 1 + Firmware/MotorControl/low_level.cpp | 2 +- Firmware/MotorControl/main.cpp | 11 +++++++++++ Firmware/MotorControl/odrive_main.h | 9 +++++++++ Firmware/communication/communication.cpp | 11 ++++++++++- Firmware/communication/communication.h | 1 + Firmware/communication/interface_can.cpp | 2 +- Firmware/communication/interface_can.hpp | 1 + Firmware/communication/interface_uart.cpp | 3 ++- Firmware/communication/interface_uart.h | 1 + Firmware/communication/interface_usb.cpp | 3 ++- Firmware/communication/interface_usb.h | 1 + 15 files changed, 48 insertions(+), 8 deletions(-) diff --git a/Firmware/Board/v3/Inc/freertos_vars.h b/Firmware/Board/v3/Inc/freertos_vars.h index 6e4c0696..7e86c971 100644 --- a/Firmware/Board/v3/Inc/freertos_vars.h +++ b/Firmware/Board/v3/Inc/freertos_vars.h @@ -11,5 +11,7 @@ extern osSemaphoreId sem_can; extern osThreadId defaultTaskHandle; extern osThreadId usb_irq_thread; +extern const uint32_t stack_size_usb_irq_thread; +extern const uint32_t stack_size_default_task; #endif /* __FREERTOS_H */ \ No newline at end of file diff --git a/Firmware/Board/v3/Src/freertos.c b/Firmware/Board/v3/Src/freertos.c index 5edfd079..42ff9751 100644 --- a/Firmware/Board/v3/Src/freertos.c +++ b/Firmware/Board/v3/Src/freertos.c @@ -89,12 +89,14 @@ osSemaphoreId sem_usb_tx; osSemaphoreId sem_can; osThreadId usb_irq_thread; +const uint32_t stack_size_usb_irq_thread = 1024; // Bytes // Place FreeRTOS heap in core coupled memory for better performance __attribute__((section(".ccmram"))) uint8_t ucHeap[configTOTAL_HEAP_SIZE]; /* USER CODE END Variables */ osThreadId defaultTaskHandle; +const uint32_t stack_size_default_task = 1024; // Bytes /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN FunctionPrototypes */ @@ -150,7 +152,7 @@ void usb_deferred_interrupt_thread(void * ctx) { void init_deferred_interrupts(void) { // Start USB interrupt handler thread - osThreadDef(task_usb_pump, usb_deferred_interrupt_thread, osPriorityAboveNormal, 0, 512); + osThreadDef(task_usb_pump, usb_deferred_interrupt_thread, osPriorityAboveNormal, 0, stack_size_usb_irq_thread / sizeof(StackType_t)); usb_irq_thread = osThreadCreate(osThread(task_usb_pump), NULL); } @@ -206,7 +208,7 @@ void MX_FREERTOS_Init(void) { /* Create the thread(s) */ /* definition and creation of defaultTask */ - osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 256); + osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, stack_size_default_task / sizeof(StackType_t)); defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL); /* USER CODE BEGIN RTOS_THREADS */ diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 5f31f0b7..b97cfb98 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -84,7 +84,7 @@ static void run_state_machine_loop_wrapper(void* ctx) { // @brief Starts run_state_machine_loop in a new thread void Axis::start_thread() { - osThreadDef(thread_def, run_state_machine_loop_wrapper, hw_config_.thread_priority, 0, 4 * 512); + osThreadDef(thread_def, run_state_machine_loop_wrapper, hw_config_.thread_priority, 0, stack_size_ / sizeof(StackType_t)); thread_id_ = osThreadCreate(osThread(thread_def), this); thread_id_valid_ = true; } diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index a52eb858..f9199d8b 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -244,6 +244,7 @@ class Axis { Endstop& max_endstop_; osThreadId thread_id_; + const uint32_t stack_size_ = 1024; // Bytes volatile bool thread_id_valid_ = false; // variables exposed on protocol diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 23b7c002..866bedc0 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -754,7 +754,7 @@ static void analog_polling_thread(void*) { } void start_analog_thread() { - osThreadDef(thread_def, analog_polling_thread, osPriorityLow, 0, 128); + osThreadDef(thread_def, analog_polling_thread, osPriorityLow, 0, 512 / sizeof(StackType_t)); osThreadCreate(osThread(thread_def), NULL); } diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 62d6c47d..43c3e476 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -195,6 +195,17 @@ void vApplicationIdleHook(void) { system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread) * sizeof(StackType_t); system_stats_.min_stack_space_usb_irq = uxTaskGetStackHighWaterMark(usb_irq_thread) * sizeof(StackType_t); system_stats_.min_stack_space_startup = uxTaskGetStackHighWaterMark(defaultTaskHandle) * sizeof(StackType_t); + system_stats_.min_stack_space_can = uxTaskGetStackHighWaterMark(odCAN->thread_id_) * sizeof(StackType_t); + + // Actual usage, in bytes, so we don't have to math + system_stats_.stack_usage_axis0 = axes[0]->stack_size_ - system_stats_.min_stack_space_axis0; + system_stats_.stack_usage_axis1 = axes[1]->stack_size_ - system_stats_.min_stack_space_axis1; + system_stats_.stack_usage_comms = stack_size_comm_thread - system_stats_.min_stack_space_comms; + system_stats_.stack_usage_usb = stack_size_usb_thread - system_stats_.min_stack_space_usb; + system_stats_.stack_usage_uart = stack_size_uart_thread - system_stats_.min_stack_space_uart; + system_stats_.stack_usage_usb_irq = stack_size_usb_irq_thread - system_stats_.min_stack_space_usb_irq; + system_stats_.stack_usage_startup = stack_size_default_task - system_stats_.min_stack_space_startup; + system_stats_.stack_usage_can = odCAN->stack_size_ - system_stats_.min_stack_space_can; } } } diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 88f4de0c..5b89f8ff 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -55,6 +55,15 @@ typedef struct { uint32_t min_stack_space_usb_irq; uint32_t min_stack_space_startup; uint32_t min_stack_space_can; + + uint32_t stack_usage_axis0; + uint32_t stack_usage_axis1; + uint32_t stack_usage_comms; + uint32_t stack_usage_usb; + uint32_t stack_usage_uart; + uint32_t stack_usage_usb_irq; + uint32_t stack_usage_startup; + uint32_t stack_usage_can; } SystemStats_t; extern SystemStats_t system_stats_; diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index ba494340..8a22508e 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -64,6 +64,7 @@ const uint8_t fw_version_revision = FW_VERSION_REVISION; const uint8_t fw_version_unreleased = FW_VERSION_UNRELEASED; // 0 for official releases, 1 otherwise osThreadId comm_thread; +const uint32_t stack_size_comm_thread = 2048; // Bytes volatile bool endpoint_list_valid = false; static uint32_t test_property = 0; @@ -84,7 +85,7 @@ void init_communication(void) { printf("hi!\r\n"); // Start command handling thread - osThreadDef(task_cmd_parse, communication_task, osPriorityNormal, 0, 8000 /* in 32-bit words */); // TODO: fix stack issues + osThreadDef(task_cmd_parse, communication_task, osPriorityNormal, 0, stack_size_comm_thread / sizeof(StackType_t)); comm_thread = osThreadCreate(osThread(task_cmd_parse), NULL); while (!endpoint_list_valid) @@ -135,6 +136,14 @@ static inline auto make_obj_tree() { make_protocol_ro_property("min_stack_space_can", &system_stats_.min_stack_space_can), make_protocol_ro_property("min_stack_space_usb_irq", &system_stats_.min_stack_space_usb_irq), make_protocol_ro_property("min_stack_space_startup", &system_stats_.min_stack_space_startup), + make_protocol_ro_property("stack_usage_axis0", &system_stats_.stack_usage_axis0), + make_protocol_ro_property("stack_usage_axis1", &system_stats_.stack_usage_axis1), + make_protocol_ro_property("stack_usage_comms", &system_stats_.stack_usage_comms), + make_protocol_ro_property("stack_usage_usb", &system_stats_.stack_usage_usb), + make_protocol_ro_property("stack_usage_uart", &system_stats_.stack_usage_uart), + make_protocol_ro_property("stack_usage_usb_irq", &system_stats_.stack_usage_usb_irq), + make_protocol_ro_property("stack_usage_startup", &system_stats_.stack_usage_startup), + make_protocol_ro_property("stack_usage_can", &system_stats_.stack_usage_can), make_protocol_object("usb", make_protocol_ro_property("rx_cnt", &usb_stats_.rx_cnt), make_protocol_ro_property("tx_cnt", &usb_stats_.tx_cnt), diff --git a/Firmware/communication/communication.h b/Firmware/communication/communication.h index b4bab74b..85519b39 100644 --- a/Firmware/communication/communication.h +++ b/Firmware/communication/communication.h @@ -15,6 +15,7 @@ extern "C" { #include extern osThreadId comm_thread; +extern const uint32_t stack_size_comm_thread; extern const uint8_t hw_version_major; extern const uint8_t hw_version_minor; diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 251dec43..00637597 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -78,7 +78,7 @@ bool ODriveCAN::start_can_server() { if (status == HAL_OK) status = HAL_CAN_ActivateNotification(handle_, CAN_IT_RX_FIFO0_MSG_PENDING); - osThreadDef(can_server_thread_def, can_server_thread_wrapper, osPriorityNormal, 0, 512); + osThreadDef(can_server_thread_def, can_server_thread_wrapper, osPriorityNormal, 0, stack_size_ / sizeof(StackType_t)); thread_id_ = osThreadCreate(osThread(can_server_thread_def), this); thread_id_valid_ = true; diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index dcd06232..7a1e9c9a 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -54,6 +54,7 @@ class ODriveCAN { // Thread Relevant Data osThreadId thread_id_; + const uint32_t stack_size_ = 1024; // Bytes Error_t error_ = ERROR_NONE; volatile bool thread_id_valid_ = false; diff --git a/Firmware/communication/interface_uart.cpp b/Firmware/communication/interface_uart.cpp index b3141138..dc8a4ce6 100644 --- a/Firmware/communication/interface_uart.cpp +++ b/Firmware/communication/interface_uart.cpp @@ -22,6 +22,7 @@ static uint32_t dma_last_rcv_idx; // static thread_local uint32_t deadline_ms = 0; osThreadId uart_thread; +const uint32_t stack_size_uart_thread = 2048; // Bytes class UART4Sender : public StreamSink { @@ -98,7 +99,7 @@ void start_uart_server() { dma_last_rcv_idx = UART_RX_BUFFER_SIZE - huart4.hdmarx->Instance->NDTR; // Start UART communication thread - osThreadDef(uart_server_thread_def, uart_server_thread, osPriorityNormal, 0, 1024 /* the ascii protocol needs considerable stack space */); + osThreadDef(uart_server_thread_def, uart_server_thread, osPriorityNormal, 0, stack_size_uart_thread / sizeof(StackType_t) /* the ascii protocol needs considerable stack space */); uart_thread = osThreadCreate(osThread(uart_server_thread_def), NULL); } diff --git a/Firmware/communication/interface_uart.h b/Firmware/communication/interface_uart.h index 8ef39ec4..65033a6f 100644 --- a/Firmware/communication/interface_uart.h +++ b/Firmware/communication/interface_uart.h @@ -11,6 +11,7 @@ extern "C" { #include extern osThreadId uart_thread; +extern const uint32_t stack_size_uart_thread; void start_uart_server(void); diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index 036a8203..7f49c0b5 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -14,6 +14,7 @@ #include osThreadId usb_thread; +const uint32_t stack_size_usb_thread = 2048; // Bytes USBStats_t usb_stats_ = {0}; class USBSender : public PacketSink { @@ -177,6 +178,6 @@ void usb_rx_process_packet(uint8_t *buf, uint32_t len, uint8_t endpoint_pair) { void start_usb_server() { // Start USB communication thread - osThreadDef(usb_server_thread_def, usb_server_thread, osPriorityNormal, 0, 1024); + osThreadDef(usb_server_thread_def, usb_server_thread, osPriorityNormal, 0, stack_size_usb_thread / sizeof(StackType_t)); usb_thread = osThreadCreate(osThread(usb_server_thread_def), NULL); } diff --git a/Firmware/communication/interface_usb.h b/Firmware/communication/interface_usb.h index 0a5b94ff..c78ecc5d 100644 --- a/Firmware/communication/interface_usb.h +++ b/Firmware/communication/interface_usb.h @@ -12,6 +12,7 @@ extern "C" { #include extern osThreadId usb_thread; +extern const uint32_t stack_size_usb_thread; typedef struct { uint32_t rx_cnt; From 7faa9df2f46b527e4d1a29803fa318e52b3469f5 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 27 Sep 2019 13:00:34 +0200 Subject: [PATCH 224/549] support "unsigned int" type in native protocol This fixes a compile error when using enums with large numerical values. The int type of such enums is unsigned int, which was previously not supported. --- Firmware/fibre/cpp/include/fibre/protocol.hpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index c1f3cc68..366f46c2 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -398,7 +398,7 @@ bool default_readwrite_endpoint_handler(endpoint_ref_t* value, const uint8_t* in } template -static inline const char* get_default_json_modifier(); +static constexpr inline const char* get_default_json_modifier(); template<> inline constexpr const char* get_default_json_modifier() { @@ -441,6 +441,14 @@ inline constexpr const char* get_default_json_modifier() { return "\"type\":\"uint32\",\"access\":\"rw\""; } template<> +inline constexpr const char* get_default_json_modifier() { + return "\"type\":\"uint32\",\"access\":\"r\""; // TODO: automatically detect size +} +template<> +inline constexpr const char* get_default_json_modifier() { + return "\"type\":\"uint32\",\"access\":\"rw\""; // TODO: automatically detect size +} +template<> inline constexpr const char* get_default_json_modifier() { return "\"type\":\"uint16\",\"access\":\"r\""; } @@ -537,6 +545,11 @@ template<> struct format_traits_t { using type = void; static constexpr const char * fmt = "%lu"; static constexpr const char * fmtp = "%lu"; }; +// TODO: change all overloads to fundamental int type space +template<> struct format_traits_t { using type = void; + static constexpr const char * fmt = "%ud"; + static constexpr const char * fmtp = "%ud"; +}; template<> struct format_traits_t { using type = void; static constexpr const char * fmt = "%hd"; static constexpr const char * fmtp = "%hd"; From 8ce66c6ec79e24fd69b9530213025831d0309646 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 27 Sep 2019 17:57:42 +0200 Subject: [PATCH 225/549] change power supply limit from watts to amps --- CHANGELOG.md | 2 +- Firmware/MotorControl/axis.cpp | 9 +++++---- Firmware/MotorControl/axis.hpp | 3 ++- Firmware/MotorControl/odrive_main.h | 3 ++- Firmware/communication/communication.cpp | 3 ++- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 685845fc..eb0d96f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * Gain scheduling for anti-hunt when close to 0 position error * Velocity Limiting in Current Control mode according to `vel_limit` and `vel_gain` * Regen current limiting according to `max_regen_limit`, in Amps -* DC Bus Power limiting according to `power_supply_wattage` +* DC Bus hard current limiting according to `power_supply_min_current` and `power_supply_max_current` * Unit Testing with Doctest has been started for select algorithms, see [Firmware/Tests/test_runner.cpp](Firmware/Tests/test_runner.cpp) * Added support for Flylint VSCode Extension for static code analysis * Using an STM32F405 .svd file allows CortexDebug to view registers during debugging diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 4319bb3c..21dd73ea 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -174,10 +174,11 @@ bool Axis::do_checks() { } } - if(board_config.power_supply_wattage > 0.0f && - (Ibus_sum * vbus_voltage) > board_config.power_supply_wattage) - { - error_ |= ERROR_DC_BUS_OVER_POWER; + if (Ibus_sum > board_config.power_supply_max_current) { + error_ |= ERROR_DC_BUS_OVER_CURRENT; + } + if (Ibus_sum < board_config.power_supply_min_current) { + error_ |= ERROR_DC_BUS_UNDER_CURRENT; } // Sub-components should use set_error which will propegate to this error_ diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 858df43b..46e3b15b 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -24,7 +24,8 @@ public: ERROR_MIN_ENDSTOP_PRESSED = 0x1000, ERROR_MAX_ENDSTOP_PRESSED = 0x2000, ERROR_ESTOP_REQUESTED = 0x4000, - ERROR_DC_BUS_OVER_POWER = 0x8000, + ERROR_DC_BUS_UNDER_CURRENT = 0x8000, // too much current pushed into the power supply + ERROR_DC_BUS_OVER_CURRENT = 0x10000, // too much current pulled out of the power supply }; enum State_t { diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 88f4de0c..88d7b902 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -83,7 +83,8 @@ struct BoardConfig_t { //= 3 make_protocol_object("gpio1_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[0])), make_protocol_object("gpio2_pwm_mapping", make_protocol_definitions(board_config.pwm_mappings[1])), From 82e8cd64fb3062ce00a640d6303a1a33626f1c80 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 27 Sep 2019 18:00:59 +0200 Subject: [PATCH 226/549] cosmetic changes --- Firmware/MotorControl/axis.cpp | 4 +++- Firmware/MotorControl/axis.hpp | 5 +---- Firmware/MotorControl/controller.cpp | 6 ++---- Firmware/MotorControl/controller.hpp | 2 -- Firmware/MotorControl/encoder.cpp | 15 ++++++++------- Firmware/MotorControl/main.cpp | 4 ++-- 6 files changed, 16 insertions(+), 20 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 21dd73ea..a72b05e2 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -348,8 +348,10 @@ bool Axis::run_homing() { Controller::ControlMode_t stored_control_mode = controller_.config_.control_mode; Controller::InputMode_t stored_input_mode = controller_.config_.input_mode; + // TODO: theoretically this check should be inside the update loop, + // otherwise someone could disable the endstop while homing is in progress. if (!min_endstop_.config_.enabled) { - return error_ |= ERROR_MIN_ENDSTOP_PRESSED, false; // TODO: define new error code + return error_ |= ERROR_HOMING_WITHOUT_ENDSTOP, false; } controller_.config_.control_mode = Controller::CTRL_MODE_VELOCITY_CONTROL; diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 46e3b15b..f2770ee3 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -26,6 +26,7 @@ public: ERROR_ESTOP_REQUESTED = 0x4000, ERROR_DC_BUS_UNDER_CURRENT = 0x8000, // too much current pushed into the power supply ERROR_DC_BUS_OVER_CURRENT = 0x10000, // too much current pulled out of the power supply + ERROR_HOMING_WITHOUT_ENDSTOP = 0x20000, // the min endstop was not enabled during homing }; enum State_t { @@ -83,10 +84,6 @@ public: LockinConfig_t lockin; uint8_t can_node_id = 0; // Both axes will have the same id to start uint32_t can_heartbeat_rate_ms = 100; - - bool use_load_encoder = false; - uint8_t load_encoder_axis = -1; - float load_encoder_ratio = 1.0f; }; struct Homing_t { diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 738b0b9c..f1687494 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -113,13 +113,11 @@ void Controller::update_filter_gains() { input_filter_kp_ = 0.25f * (input_filter_ki_ * input_filter_ki_); // Critically damped } -namespace { -float limitVel(const float vel_limit, const float vel_estimate, const float vel_gain, const float Iq) { +static float limitVel(const float vel_limit, const float vel_estimate, const float vel_gain, const float Iq) { float Imax = (vel_limit - vel_estimate) * vel_gain; float Imin = (-vel_limit - vel_estimate) * vel_gain; return std::clamp(Iq, Imin, Imax); } -} // namespace bool Controller::update(float* current_setpoint_output) { float* pos_estimate_src = (pos_estimate_valid_src_ && *pos_estimate_valid_src_) @@ -169,7 +167,7 @@ bool Controller::update(float* current_setpoint_output) { float delta_vel = input_vel_ - vel_setpoint_; // Vel error float accel = input_filter_kp_*delta_pos + input_filter_ki_*delta_vel; // Feedback current_setpoint_ = accel * config_.inertia; // Accel - vel_setpoint_ += current_meas_period * accel; // delta vel + vel_setpoint_ += std::clamp(current_meas_period * accel, 2.0f * std::abs(delta_vel), -2.0f * std::abs(delta_vel)); // delta vel pos_setpoint_ += current_meas_period * vel_setpoint_; // Delta pos } break; case INPUT_MODE_MIRROR: { diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 28f3b026..25261568 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -72,7 +72,6 @@ public: uint8_t axis_to_mirror = -1; float mirror_ratio = 1.0f; uint8_t load_encoder_axis = -1; // default depends on Axis number and is set in load_configuration() - float load_encoder_ratio = 1.0f; }; explicit Controller(Config_t& config); @@ -162,7 +161,6 @@ public: make_protocol_property("inertia", &config_.inertia), make_protocol_property("axis_to_mirror", &config_.axis_to_mirror), make_protocol_property("mirror_ratio", &config_.mirror_ratio), - make_protocol_property("load_encoder_ratio", &config_.load_encoder_ratio), make_protocol_property("load_encoder_axis", &config_.load_encoder_axis), make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth, [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this), diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 5fc32687..dbcbab4f 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -449,22 +449,23 @@ bool Encoder::update() { case MODE_SPI_ABS_AMS: case MODE_SPI_ABS_CUI:{ - if(abs_spi_pos_updated_ == false && abs_spi_pos_init_once_){ + if (!abs_spi_pos_updated_ && abs_spi_pos_init_once_) { // Low pass filter the error spi_error_rate_ += current_meas_period * (1.0f - spi_error_rate_); - // if (spi_error_rate_ > 0.005f) - // set_error(ERROR_ABS_SPI_COM_FAIL); - } - else + if (spi_error_rate_ > 0.005f) + set_error(ERROR_ABS_SPI_COM_FAIL); + } else { // Low pass filter the error spi_error_rate_ += current_meas_period * (0.0f - spi_error_rate_); + } abs_spi_pos_updated_ = false; delta_enc = pos_abs_ - count_in_cpr_; delta_enc = mod(delta_enc, config_.cpr); - if (delta_enc > config_.cpr/2) + if (delta_enc > config_.cpr/2) { delta_enc -= config_.cpr; - if(!abs_spi_pos_init_once_ && delta_enc != 0){ + } + if (!abs_spi_pos_init_once_ && delta_enc != 0) { abs_spi_pos_init_once_ = true; } diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 6c4f6283..23d6e84a 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -24,7 +24,7 @@ bool user_config_loaded_; SystemStats_t system_stats_ = { 0 }; Axis *axes[AXIS_COUNT]; -ODriveCAN *odCAN; +ODriveCAN *odCAN = nullptr; typedef Config< BoardConfig_t, @@ -132,7 +132,7 @@ void vApplicationIdleHook(void) { system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread) * sizeof(StackType_t); system_stats_.min_stack_space_usb_irq = uxTaskGetStackHighWaterMark(usb_irq_thread) * sizeof(StackType_t); system_stats_.min_stack_space_startup = uxTaskGetStackHighWaterMark(defaultTaskHandle) * sizeof(StackType_t); - system_stats_.min_stack_space_can = uxTaskGetStackHighWaterMark(odCAN->thread_id_) * sizeof(StackType_t); + system_stats_.min_stack_space_can = odCAN ? uxTaskGetStackHighWaterMark(odCAN->thread_id_) * sizeof(StackType_t) : 0; } } } From 20d7bdf8c007086cda4a3473fa71db1bc4aee995 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 27 Sep 2019 18:10:24 +0200 Subject: [PATCH 227/549] update documentation --- docs/_data/index.yaml | 2 ++ docs/commands.md | 6 +++--- docs/endstops.md | 8 ++++---- docs/getting-started.md | 3 +-- docs/interfaces.md | 4 ++++ 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/docs/_data/index.yaml b/docs/_data/index.yaml index bd258a11..6d34a8d2 100644 --- a/docs/_data/index.yaml +++ b/docs/_data/index.yaml @@ -15,6 +15,8 @@ sections: url: interfaces - title: Encoders url: encoders + - title: Homing & Endstops + url: endstops - title: Control & Tuning url: control - title: Hoverboard Guide diff --git a/docs/commands.md b/docs/commands.md index 1dce7336..2d26586a 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -61,9 +61,9 @@ Possible values are: * `CTRL_MODE_VOLTAGE_CONTROL` - this one is not normally used. # Control Commands -* `.controller.pos_setpoint = ` -* `.controller.vel_setpoint = ` -* `.controller.current_setpoint = ` +* `.controller.input_pos = ` +* `.controller.input_vel = ` +* `.controller.input_current = ` ## System monitoring commands diff --git a/docs/endstops.md b/docs/endstops.md index ca63bd19..3eae059d 100644 --- a/docs/endstops.md +++ b/docs/endstops.md @@ -35,10 +35,11 @@ Enables/disables detection of the endstop. If disabled, homing and e-stop canno This is the position of the endstops on the relevant axis, in counts. For example, if you want a position command of `0` to represent a position 100 counts away from the endstop, the offset would be `-100.0` (because the endstop is located at axis position `-100.0`). ``` -..max_endstop.config.offset = ..min_endstop.config.offset = ``` +This setting is only used for homing. Only the offset of the `min_endstop` is used. + ### debounce_ms The debouncing time for this endstop. Most switches exhibit some sort of bounce, and this setting will help prevent the switch from triggering repeatedly. It works for both HIGH and LOW transitions, regardless of the setting of `is_active_high`. Debouncing is a good practice for digital inputs, read up on it [here](https://en.wikipedia.org/wiki/Switch). `debounce_ms` has units of miliseconds. @@ -94,17 +95,16 @@ homing_speed | float | 2000.0f ### Performing the Homing Sequence -Homing is possible once the ODrive has closed-loop control over the axis. To trigger homing, we must first be in `AXIS_STATE_CLOSED_LOOP_CONTROL`, then call `..controller.home_axis()` This starts the homing sequence, which works as follows: +Homing is possible once the ODrive has closed-loop control over the axis. To trigger homing, we must enter `AXIS_STATE_HOMING`. This starts the homing sequence, which works as follows: 1. The axis moves towards the `min_endstop` at `homing_speed` 2. The axis presses the `min_endstop` 3. The axis moves away from the `min_endstop` to the home position ### Homing at Startup -It is possible to configure the odrive to enter homing immediately after startup. For safety reasons, we require the user to specifically enable closed loop control at startup, even if homing is requested. Thus, to enable homing at startup, the following must be configured: +It is possible to configure the odrive to enter homing immediately after startup. To enable homing at startup, the following must be configured: ``` -..config.startup_closed_loop_control = True ..config.startup_homing = True ``` diff --git a/docs/getting-started.md b/docs/getting-started.md index 444bdaf4..1ea259c2 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -343,8 +343,7 @@ You can now control the velocity with `axis.controller.input_vel = 5000` [count/ Set `axis.controller.config.control_mode = CTRL_MODE_CURRENT_CONTROL`.
    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.* - +Note: If you exceed `vel_limit` in current control mode, the current is reduced. To disable this, set `axis.controller.enable_current_vel_limit = False`. ## Watchdog Timer Each axis has a configurable watchdog timer that can stop the motors if the diff --git a/docs/interfaces.md b/docs/interfaces.md index e9a8781d..ca9db588 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -169,3 +169,7 @@ Pinout: * GPIO 1: Tx (connect to Rx of other device) * GPIO 2: Rx (connect to Tx of other device) * GND: you must connect the grounds of the devices together. Use any GND pin on J3 of the ODrive. + +## CAN Simple Protocol + +See [CAN Protocol](can-protocol). From ea315a72f601b3292d5d8f5ab56b025e98fb9274 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 27 Sep 2019 18:10:42 +0200 Subject: [PATCH 228/549] update python enums --- tools/odrive/enums.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 08ea6ec0..f015cb60 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -12,7 +12,7 @@ AXIS_STATE_ENCODER_OFFSET_CALIBRATION = 7 AXIS_STATE_CLOSED_LOOP_CONTROL = 8 AXIS_STATE_LOCKIN_SPIN = 9 AXIS_STATE_ENCODER_DIR_FIND = 10 -AXIS_STATE_HOMING = 9 +AXIS_STATE_HOMING = 11 class errors: class axis: @@ -29,6 +29,12 @@ class errors: ERROR_CONTROLLER_FAILED = 0x200 ERROR_POS_CTRL_DURING_SENSORLESS = 0x400 ERROR_WATCHDOG_TIMER_EXPIRED = 0x800 + ERROR_MIN_ENDSTOP_PRESSED = 0x1000 + ERROR_MAX_ENDSTOP_PRESSED = 0x2000 + ERROR_ESTOP_REQUESTED = 0x4000 + ERROR_DC_BUS_UNDER_CURRENT = 0x8000 + ERROR_DC_BUS_OVER_CURRENT = 0x10000 + ERROR_HOMING_WITHOUT_ENDSTOP = 0x20000 class motor: ERROR_NONE = 0 @@ -53,6 +59,9 @@ class errors: ERROR_UNSUPPORTED_ENCODER_MODE = 0x08 ERROR_ILLEGAL_HALL_STATE = 0x10 ERROR_INDEX_NOT_FOUND_YET = 0x20 + ERROR_ABS_SPI_TIMEOUT = 0x40, + ERROR_ABS_SPI_COM_FAIL = 0x80, + ERROR_ABS_SPI_NOT_READY = 0x100, class controller: ERROR_NONE = 0 @@ -82,5 +91,5 @@ INPUT_MODE_MIRROR = 7 ENCODER_MODE_INCREMENTAL = 0x00 ENCODER_MODE_HALL = 0x01 ENCODER_MODE_SINCOS = 0x02 -ENCODER_MODE_SPI_ABS_CUI = 0x100 +#ENCODER_MODE_SPI_ABS_CUI = 0x100 # currently not functional ENCODER_MODE_SPI_ABS_AMS = 0x101 From e4c558fb1cb13e7a42054f7a1bb455f2a044cce0 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 27 Sep 2019 18:42:29 -0700 Subject: [PATCH 229/549] add pole pairs to torque calculation --- analysis/motor_analysis/ac_induction_motor.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/analysis/motor_analysis/ac_induction_motor.py b/analysis/motor_analysis/ac_induction_motor.py index 9cf15228..ccc06ec8 100644 --- a/analysis/motor_analysis/ac_induction_motor.py +++ b/analysis/motor_analysis/ac_induction_motor.py @@ -8,10 +8,11 @@ from engineering_notation import EngNumber filename = "oscilloscope.csv" PLOT_INITAL = True -DO_FITTING = True +DO_FITTING = False PLOT_PROGRESS = False REPORT_PROGRESS = True assumed_rotor_resistance = 1 +pole_pairs = 2 class ACMotor(): """ @@ -119,10 +120,12 @@ class ACMotor(): print('Derived parameters:') mutual_inductance = motor.get_mutual_inductance() coupling_factor = mutual_inductance / self.params[ACMotor.pl['rotor_inductance']] - torque_constant = coupling_factor * mutual_inductance + torque_constant = pole_pairs * coupling_factor * mutual_inductance + motor_constant = torque_constant / (3.0 * self.params[ACMotor.pl['stator_resistance']]) print('mutual_inductance = {}H'.format(EngNumber(mutual_inductance))) print('coupling_factor = {}'.format(EngNumber(coupling_factor))) print('torque_constant = {}Nm/A^2'.format(EngNumber(torque_constant))) + print('motor_constant = {}Nm/W'.format(EngNumber(motor_constant))) def print_run_info(self, y): final_stator_current_d = np.real(y[0,-1]) @@ -131,7 +134,7 @@ class ACMotor(): mutual_inductance = self.get_mutual_inductance() coupling_factor = mutual_inductance / self.params[ACMotor.pl['rotor_inductance']] - final_torque_per_q_amp = coupling_factor * final_rotor_flux_d + final_torque_per_q_amp = pole_pairs * coupling_factor * final_rotor_flux_d print() print('Final values:') From 970f740963743a0fd09b7aec09d1b0cdfbcb5bc3 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 27 Sep 2019 22:13:38 -0400 Subject: [PATCH 230/549] Fix fibre definitions formatting & remove AlignConsecutiveAssignments --- Firmware/.clang-format | 1 - Firmware/MotorControl/axis.hpp | 84 ++++++++++++++-------------- Firmware/MotorControl/controller.hpp | 56 +++++++++---------- Firmware/MotorControl/endstop.hpp | 14 ++--- 4 files changed, 77 insertions(+), 78 deletions(-) diff --git a/Firmware/.clang-format b/Firmware/.clang-format index eac50873..d2eb1915 100644 --- a/Firmware/.clang-format +++ b/Firmware/.clang-format @@ -1,6 +1,5 @@ --- BasedOnStyle: Google -AlignConsecutiveAssignments: 'true' AllowShortCaseLabelsOnASingleLine: 'true' IndentWidth: '4' diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index f2770ee3..25d53d5d 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -268,48 +268,48 @@ public: make_protocol_ro_property("lockin_state", &lockin_state_), make_protocol_property("is_homed", &homing_.is_homed), make_protocol_object("config", - make_protocol_property("startup_motor_calibration", &config_.startup_motor_calibration), - make_protocol_property("startup_encoder_index_search", &config_.startup_encoder_index_search), - make_protocol_property("startup_encoder_offset_calibration", &config_.startup_encoder_offset_calibration), - make_protocol_property("startup_closed_loop_control", &config_.startup_closed_loop_control), - make_protocol_property("startup_sensorless_control", &config_.startup_sensorless_control), - make_protocol_property("startup_homing", &config_.startup_homing), - 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), - make_protocol_property("enable_watchdog", &config_.enable_watchdog), - 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, - [](void* ctx) { static_cast(ctx)->decode_step_dir_pins(); }, this), - make_protocol_object("calibration_lockin", - make_protocol_property("current", &config_.calibration_lockin.current), - make_protocol_property("ramp_time", &config_.calibration_lockin.ramp_time), - make_protocol_property("ramp_distance", &config_.calibration_lockin.ramp_distance), - make_protocol_property("accel", &config_.calibration_lockin.accel), - make_protocol_property("vel", &config_.calibration_lockin.vel)), - make_protocol_object("sensorless_ramp", - make_protocol_property("current", &config_.sensorless_ramp.current), - make_protocol_property("ramp_time", &config_.sensorless_ramp.ramp_time), - make_protocol_property("ramp_distance", &config_.sensorless_ramp.ramp_distance), - make_protocol_property("accel", &config_.sensorless_ramp.accel), - make_protocol_property("vel", &config_.sensorless_ramp.vel), - make_protocol_property("finish_distance", &config_.sensorless_ramp.finish_distance), - make_protocol_property("finish_on_vel", &config_.sensorless_ramp.finish_on_vel), - make_protocol_property("finish_on_distance", &config_.sensorless_ramp.finish_on_distance), - make_protocol_property("finish_on_enc_idx", &config_.sensorless_ramp.finish_on_enc_idx)), - make_protocol_object("general_lockin", - make_protocol_property("current", &config_.lockin.current), - make_protocol_property("ramp_time", &config_.lockin.ramp_time), - make_protocol_property("ramp_distance", &config_.lockin.ramp_distance), - make_protocol_property("accel", &config_.lockin.accel), - make_protocol_property("vel", &config_.lockin.vel), - make_protocol_property("finish_distance", &config_.lockin.finish_distance), - make_protocol_property("finish_on_vel", &config_.lockin.finish_on_vel), - make_protocol_property("finish_on_distance", &config_.lockin.finish_on_distance), - make_protocol_property("finish_on_enc_idx", &config_.lockin.finish_on_enc_idx)), - make_protocol_property("can_node_id", &config_.can_node_id), - make_protocol_property("can_heartbeat_rate_ms", &config_.can_heartbeat_rate_ms)), + make_protocol_property("startup_motor_calibration", &config_.startup_motor_calibration), + make_protocol_property("startup_encoder_index_search", &config_.startup_encoder_index_search), + make_protocol_property("startup_encoder_offset_calibration", &config_.startup_encoder_offset_calibration), + make_protocol_property("startup_closed_loop_control", &config_.startup_closed_loop_control), + make_protocol_property("startup_sensorless_control", &config_.startup_sensorless_control), + make_protocol_property("startup_homing", &config_.startup_homing), + 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), + make_protocol_property("enable_watchdog", &config_.enable_watchdog), + 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, + [](void* ctx) { static_cast(ctx)->decode_step_dir_pins(); }, this), + make_protocol_object("calibration_lockin", + make_protocol_property("current", &config_.calibration_lockin.current), + make_protocol_property("ramp_time", &config_.calibration_lockin.ramp_time), + make_protocol_property("ramp_distance", &config_.calibration_lockin.ramp_distance), + make_protocol_property("accel", &config_.calibration_lockin.accel), + make_protocol_property("vel", &config_.calibration_lockin.vel)), + make_protocol_object("sensorless_ramp", + make_protocol_property("current", &config_.sensorless_ramp.current), + make_protocol_property("ramp_time", &config_.sensorless_ramp.ramp_time), + make_protocol_property("ramp_distance", &config_.sensorless_ramp.ramp_distance), + make_protocol_property("accel", &config_.sensorless_ramp.accel), + make_protocol_property("vel", &config_.sensorless_ramp.vel), + make_protocol_property("finish_distance", &config_.sensorless_ramp.finish_distance), + make_protocol_property("finish_on_vel", &config_.sensorless_ramp.finish_on_vel), + make_protocol_property("finish_on_distance", &config_.sensorless_ramp.finish_on_distance), + make_protocol_property("finish_on_enc_idx", &config_.sensorless_ramp.finish_on_enc_idx)), + make_protocol_object("general_lockin", + make_protocol_property("current", &config_.lockin.current), + make_protocol_property("ramp_time", &config_.lockin.ramp_time), + make_protocol_property("ramp_distance", &config_.lockin.ramp_distance), + make_protocol_property("accel", &config_.lockin.accel), + make_protocol_property("vel", &config_.lockin.vel), + make_protocol_property("finish_distance", &config_.lockin.finish_distance), + make_protocol_property("finish_on_vel", &config_.lockin.finish_on_vel), + make_protocol_property("finish_on_distance", &config_.lockin.finish_on_distance), + make_protocol_property("finish_on_enc_idx", &config_.lockin.finish_on_enc_idx)), + make_protocol_property("can_node_id", &config_.can_node_id), + make_protocol_property("can_heartbeat_rate_ms", &config_.can_heartbeat_rate_ms)), make_protocol_object("motor", motor_.make_protocol_definitions()), make_protocol_object("controller", controller_.make_protocol_definitions()), make_protocol_object("encoder", encoder_.make_protocol_definitions()), diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 25261568..07508f60 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -144,34 +144,34 @@ public: make_protocol_property("anticogging_valid", &anticogging_valid_), make_protocol_property("gain_scheduling_width", &config_.gain_scheduling_width), make_protocol_object("config", - make_protocol_property("enable_vel_limit", &config_.enable_vel_limit), - make_protocol_property("enable_current_mode_vel_limit", &config_.enable_current_vel_limit), - make_protocol_property("enable_gain_scheduling", &config_.enable_gain_scheduling), - make_protocol_property("enable_overspeed_error", &config_.enable_overspeed_error), - make_protocol_property("control_mode", &config_.control_mode), - make_protocol_property("input_mode", &config_.input_mode), - make_protocol_property("pos_gain", &config_.pos_gain), - make_protocol_property("vel_gain", &config_.vel_gain), - make_protocol_property("vel_integrator_gain", &config_.vel_integrator_gain), - make_protocol_property("vel_limit", &config_.vel_limit), - make_protocol_property("vel_limit_tolerance", &config_.vel_limit_tolerance), - make_protocol_property("vel_ramp_rate", &config_.vel_ramp_rate), - make_protocol_property("current_ramp_rate", &config_.current_ramp_rate), - make_protocol_property("homing_speed", &config_.homing_speed), - make_protocol_property("inertia", &config_.inertia), - make_protocol_property("axis_to_mirror", &config_.axis_to_mirror), - make_protocol_property("mirror_ratio", &config_.mirror_ratio), - make_protocol_property("load_encoder_axis", &config_.load_encoder_axis), - make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth, - [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this), - make_protocol_object("anticogging", - make_protocol_ro_property("index", &config_.anticogging.index), - make_protocol_property("pre_calibrated", &config_.anticogging.pre_calibrated), - make_protocol_ro_property("calib_anticogging", &config_.anticogging.calib_anticogging), - make_protocol_property("calib_pos_threshold", &config_.anticogging.calib_pos_threshold), - make_protocol_property("calib_vel_threshold", &config_.anticogging.calib_vel_threshold), - make_protocol_ro_property("cogging_ratio", &config_.anticogging.cogging_ratio), - make_protocol_property("anticogging_enabled", &config_.anticogging.enable))), + make_protocol_property("enable_vel_limit", &config_.enable_vel_limit), + make_protocol_property("enable_current_mode_vel_limit", &config_.enable_current_vel_limit), + make_protocol_property("enable_gain_scheduling", &config_.enable_gain_scheduling), + make_protocol_property("enable_overspeed_error", &config_.enable_overspeed_error), + make_protocol_property("control_mode", &config_.control_mode), + make_protocol_property("input_mode", &config_.input_mode), + make_protocol_property("pos_gain", &config_.pos_gain), + make_protocol_property("vel_gain", &config_.vel_gain), + make_protocol_property("vel_integrator_gain", &config_.vel_integrator_gain), + make_protocol_property("vel_limit", &config_.vel_limit), + make_protocol_property("vel_limit_tolerance", &config_.vel_limit_tolerance), + make_protocol_property("vel_ramp_rate", &config_.vel_ramp_rate), + make_protocol_property("current_ramp_rate", &config_.current_ramp_rate), + make_protocol_property("homing_speed", &config_.homing_speed), + make_protocol_property("inertia", &config_.inertia), + make_protocol_property("axis_to_mirror", &config_.axis_to_mirror), + make_protocol_property("mirror_ratio", &config_.mirror_ratio), + make_protocol_property("load_encoder_axis", &config_.load_encoder_axis), + make_protocol_property("input_filter_bandwidth", &config_.input_filter_bandwidth, + [](void* ctx) { static_cast(ctx)->update_filter_gains(); }, this), + make_protocol_object("anticogging", + make_protocol_ro_property("index", &config_.anticogging.index), + make_protocol_property("pre_calibrated", &config_.anticogging.pre_calibrated), + make_protocol_ro_property("calib_anticogging", &config_.anticogging.calib_anticogging), + make_protocol_property("calib_pos_threshold", &config_.anticogging.calib_pos_threshold), + make_protocol_property("calib_vel_threshold", &config_.anticogging.calib_vel_threshold), + make_protocol_ro_property("cogging_ratio", &config_.anticogging.cogging_ratio), + make_protocol_property("anticogging_enabled", &config_.anticogging.enable))), make_protocol_function("move_incremental", *this, &Controller::move_incremental, "displacement", "from_goal_point"), make_protocol_function("start_anticogging_calibration", *this, &Controller::start_anticogging_calibration) ); diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index 9b1fffee..ad9ef5ef 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -28,13 +28,13 @@ class Endstop { return make_protocol_member_list( make_protocol_ro_property("endstop_state", &endstop_state_), make_protocol_object("config", - make_protocol_property("gpio_num", &config_.gpio_num, - [](void* ctx) { static_cast(ctx)->update_config(); }, this), - make_protocol_property("enabled", &config_.enabled, - [](void* ctx) { static_cast(ctx)->update_config(); }, this), - make_protocol_property("offset", &config_.offset), - make_protocol_property("is_active_high", &config_.is_active_high), - make_protocol_property("debounce_ms", &config_.debounce_ms))); + make_protocol_property("gpio_num", &config_.gpio_num, + [](void* ctx) { static_cast(ctx)->update_config(); }, this), + make_protocol_property("enabled", &config_.enabled, + [](void* ctx) { static_cast(ctx)->update_config(); }, this), + make_protocol_property("offset", &config_.offset), + make_protocol_property("is_active_high", &config_.is_active_high), + make_protocol_property("debounce_ms", &config_.debounce_ms))); } private: From e2922c34ecaf6debf6a66fab1026b5ab72da0d3a Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 27 Sep 2019 22:15:37 -0400 Subject: [PATCH 231/549] Fix folder settings for clang-format --- Firmware/.vscode/settings.json | 1 - 1 file changed, 1 deletion(-) diff --git a/Firmware/.vscode/settings.json b/Firmware/.vscode/settings.json index ffb1469b..39c28f82 100644 --- a/Firmware/.vscode/settings.json +++ b/Firmware/.vscode/settings.json @@ -1,5 +1,4 @@ { - "C_Cpp.clang_format_style": "{ BasedOnStyle: Google, IndentWidth: 4, ColumnLimit: 0, AlignConsecutiveAssignments: true }", "C_Cpp.intelliSenseEngine": "Default", "C_Cpp.intelliSenseEngineFallback": "Disabled", "files.exclude": { From a37fa95e977690a5141e2370478724c06afe836a Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Tue, 1 Oct 2019 17:02:37 -0700 Subject: [PATCH 232/549] udpate changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8102a27e..2efd1a34 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 +* AC Induction Motor support. + * Tracking of rotor flux through rotor time constant + * Automatic d axis current for Maximum Torque Per Amp (MTPA) + # Releases ## [0.4.11] - 2019-07-25 ### Added From 27add54b3f5a6c2a7f5b0b8c7982e2d33fa5fe68 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 7 Oct 2019 16:19:30 -0700 Subject: [PATCH 233/549] move vel_ramp_enable into controller.config --- CHANGELOG.md | 3 +++ Firmware/MotorControl/controller.cpp | 2 +- Firmware/MotorControl/controller.hpp | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2efd1a34..ac23bac9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ Please add a note of your changes below this heading if you make a Pull Request. * Tracking of rotor flux through rotor time constant * Automatic d axis current for Maximum Torque Per Amp (MTPA) +### Changed +* Moved `controller.vel_ramp_enable` into `controller.config`. + # Releases ## [0.4.11] - 2019-07-25 ### Added diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 70bfb1a0..ea44754a 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -128,7 +128,7 @@ bool Controller::update(float pos_estimate, float vel_estimate, float* current_s } // Ramp rate limited velocity setpoint - if (config_.control_mode == CTRL_MODE_VELOCITY_CONTROL && vel_ramp_enable_) { + if (config_.control_mode == CTRL_MODE_VELOCITY_CONTROL && config_.vel_ramp_enable) { float max_step_size = current_meas_period * config_.vel_ramp_rate; float full_step = vel_ramp_target_ - vel_setpoint_; float step; diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index 020f34d0..24842130 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -30,6 +30,7 @@ public: float vel_integrator_gain = 10.0f / 10000.0f; // [A/(counts/s * s)] float vel_limit = 20000.0f; // [counts/s] float vel_limit_tolerance = 1.2f; // ratio to vel_lim. 0.0f to disable + bool vel_ramp_enable = false; float vel_ramp_rate = 10000.0f; // [(counts/s) / s] bool setpoints_in_cpr = false; }; @@ -86,7 +87,6 @@ public: float vel_integrator_current_ = 0.0f; // [A] float current_setpoint_ = 0.0f; // [A] float vel_ramp_target_ = 0.0f; - bool vel_ramp_enable_ = false; uint32_t traj_start_loop_count_ = 0; @@ -101,7 +101,6 @@ public: make_protocol_property("vel_integrator_current", &vel_integrator_current_), make_protocol_property("current_setpoint", ¤t_setpoint_), make_protocol_property("vel_ramp_target", &vel_ramp_target_), - make_protocol_property("vel_ramp_enable", &vel_ramp_enable_), make_protocol_object("config", make_protocol_property("control_mode", &config_.control_mode), make_protocol_property("pos_gain", &config_.pos_gain), @@ -109,6 +108,7 @@ public: make_protocol_property("vel_integrator_gain", &config_.vel_integrator_gain), make_protocol_property("vel_limit", &config_.vel_limit), make_protocol_property("vel_limit_tolerance", &config_.vel_limit_tolerance), + make_protocol_property("vel_ramp_enable", &config_.vel_ramp_enable), make_protocol_property("vel_ramp_rate", &config_.vel_ramp_rate), make_protocol_property("setpoints_in_cpr", &config_.setpoints_in_cpr) ), From 1a3b6bf0109a16851c949dd9f69ff7e65547b565 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 11 Oct 2019 16:25:18 -0700 Subject: [PATCH 234/549] encoder can now be precalibrated without offset when using ACIM --- Firmware/MotorControl/encoder.cpp | 14 ++++++++------ Firmware/MotorControl/encoder.hpp | 6 +++--- Firmware/MotorControl/main.cpp | 2 +- Firmware/MotorControl/odrive_main.h | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index b2d72646..ca5893a6 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -3,14 +3,17 @@ Encoder::Encoder(const EncoderHardwareConfig_t& hw_config, - Config_t& config) : + Config_t& config, Motor::Config_t motor_config) : hw_config_(hw_config), config_(config) { update_pll_gains(); - if (config.pre_calibrated && (config.mode == Encoder::MODE_HALL || config.mode == Encoder::MODE_SINCOS)) { - is_ready_ = true; + if (config.pre_calibrated) { + if (config.mode == Encoder::MODE_HALL || config.mode == Encoder::MODE_SINCOS) + is_ready_ = true; + if (motor_config.motor_type == Motor::MOTOR_TYPE_ACIM) + is_ready_ = true; } } @@ -79,9 +82,8 @@ void Encoder::update_pll_gains() { } void Encoder::check_pre_calibrated() { - if (!is_ready_) - config_.pre_calibrated = false; - if (config_.mode == MODE_INCREMENTAL && !index_found_) + // TODO: restoring config from python backup is fragile here (ACIM motor type must be set first) + if (!is_ready_ && axis_->motor_.config_.motor_type != Motor::MOTOR_TYPE_ACIM) config_.pre_calibrated = false; } diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 02e991bb..1eec5c2f 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -46,7 +46,7 @@ public: }; Encoder(const EncoderHardwareConfig_t& hw_config, - Config_t& config); + Config_t& config, Motor::Config_t motor_config); void setup(); void set_error(Error_t error); @@ -116,11 +116,11 @@ 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, - [](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), + make_protocol_property("pre_calibrated", &config_.pre_calibrated, + [](void* ctx) { static_cast(ctx)->check_pre_calibrated(); }, this), make_protocol_property("offset_float", &config_.offset_float), make_protocol_property("enable_phase_interpolation", &config_.enable_phase_interpolation), make_protocol_property("bandwidth", &config_.bandwidth, diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index d5acf252..a3be6acd 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -162,7 +162,7 @@ int odrive_main(void) { // Construct all objects. for (size_t i = 0; i < AXIS_COUNT; ++i) { Encoder *encoder = new Encoder(hw_configs[i].encoder_config, - encoder_configs[i]); + encoder_configs[i], motor_configs[i]); SensorlessEstimator *sensorless_estimator = new SensorlessEstimator(sensorless_configs[i]); Controller *controller = new Controller(controller_configs[i]); Motor *motor = new Motor(hw_configs[i].motor_config, diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index e9d4978d..8bff6d81 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -113,10 +113,10 @@ inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast(~static_c // ODrive specific includes #include #include +#include #include #include #include -#include #include #include #include From e74d626578b0ff7e248196f24a3a41f5198c8522 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 18 Oct 2019 23:55:57 -0400 Subject: [PATCH 235/549] Fix Endstops Homing configuration documentation --- docs/endstops.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/endstops.md b/docs/endstops.md index ca63bd19..0a3be3aa 100644 --- a/docs/endstops.md +++ b/docs/endstops.md @@ -96,9 +96,22 @@ homing_speed | float | 2000.0f ### Performing the Homing Sequence Homing is possible once the ODrive has closed-loop control over the axis. To trigger homing, we must first be in `AXIS_STATE_CLOSED_LOOP_CONTROL`, then call `..controller.home_axis()` This starts the homing sequence, which works as follows: -1. The axis moves towards the `min_endstop` at `homing_speed` -2. The axis presses the `min_endstop` -3. The axis moves away from the `min_endstop` to the home position +1. The axis switches to `INPUT_MODE_VEL_RAMP` +2. The axis ramps up to `homing_speed` in the direction of `min_endstop` +3. The axis presses the `min_endstop` +4. The axis switches to `INPUT_MODE_TRAP_TRAJ` +5. The axis moves to the home position in a controlled manner + +It requires quite a few settings in addition to the endstop settings: + +``` +..controller.config.vel_ramp_rate +..trap_traj.config.vel_limit +..trap_traj.config.accel_limit +..trap_traj.config.decel_limit +``` + +We realize this is a little excessive and we will work towards minimizing the setup, but this works well for smooth and reliable behaviour for now. ### Homing at Startup It is possible to configure the odrive to enter homing immediately after startup. For safety reasons, we require the user to specifically enable closed loop control at startup, even if homing is requested. Thus, to enable homing at startup, the following must be configured: From 0642a69e1a669e986269fd82b1fb15489e27c007 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 24 Oct 2019 16:22:44 +1000 Subject: [PATCH 236/549] AEAT-8800 Adds AEAT-8800 SSI as an encoder using 16bit --- Firmware/MotorControl/encoder.cpp | 12 +++++++++--- Firmware/MotorControl/encoder.hpp | 1 + tools/odrive/enums.py | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 1ede2f47..4c061cc5 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -339,7 +339,9 @@ bool Encoder::abs_spi_init() { spi->Init.TIMode = SPI_TIMODE_DISABLE; spi->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; spi->Init.CRCPolynomial = 10; - + if (config_.mode == MODE_SPI_ABS_AEAT) { + spi->Init.CLKPolarity = SPI_POLARITY_HIGH; + } HAL_SPI_DeInit(spi); HAL_SPI_Init(spi); //stash our configuration @@ -390,7 +392,10 @@ void Encoder::abs_spi_cb() { abs_spi_pos_updated_ = true; } } break; - + case MODE_SPI_ABS_AEAT: { + pos_abs_ = abs_spi_dma_rx_[0]; + abs_spi_pos_updated_ = true; + } break; default: { set_error(ERROR_UNSUPPORTED_ENCODER_MODE); } break; @@ -455,7 +460,8 @@ bool Encoder::update() { } break; case MODE_SPI_ABS_AMS: - case MODE_SPI_ABS_CUI: { + case MODE_SPI_ABS_CUI: + case MODE_SPI_ABS_AEAT: { if (abs_spi_pos_updated_ == false && abs_spi_pos_init_once_) { // Low pass filter the error spi_error_rate_ += current_meas_period * (1.0f - spi_error_rate_); diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 3e140ca0..76d4fcde 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -26,6 +26,7 @@ class Encoder { MODE_SINCOS, MODE_SPI_ABS_CUI = 0x100, MODE_SPI_ABS_AMS = 0x101, + MODE_SPI_ABS_AEAT = 0x102, }; const uint32_t MODE_FLAG_ABS = 0x100; diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 08ea6ec0..b9bf4f85 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -84,3 +84,4 @@ ENCODER_MODE_HALL = 0x01 ENCODER_MODE_SINCOS = 0x02 ENCODER_MODE_SPI_ABS_CUI = 0x100 ENCODER_MODE_SPI_ABS_AMS = 0x101 +ENCODER_MODE_SPI_ABS_AEAT = 0x102 From 5a9fd613852de44676fe3c6ea149fbc7f7e11a99 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 26 Oct 2019 00:35:30 -0400 Subject: [PATCH 237/549] Add links to odrivetool banner --- tools/odrive/shell.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/odrive/shell.py b/tools/odrive/shell.py index c25278e3..754d609a 100644 --- a/tools/odrive/shell.py +++ b/tools/odrive/shell.py @@ -9,6 +9,13 @@ from odrive.utils import start_liveplotter, dump_errors #from odrive.enums import * # pylint: disable=W0614 def print_banner(): + print("Website: https://odriverobotics.com/") + print("Docs: https://docs.odriverobotics.com/") + print("Forums: https://discourse.odriverobotics.com/") + print("Discord: https://discord.gg/k3ZZ3mS") + print("Github: https://github.com/madcowswe/ODrive/") + + print() print('Please connect your ODrive.') print('You can also type help() or quit().') From f127df72f15f6112dd4fa48de25cdeb2c8eb661a Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 26 Oct 2019 14:28:06 -0400 Subject: [PATCH 238/549] Add stack_size vars, reduce stack usage --- Firmware/Board/v3/Inc/freertos_vars.h | 2 ++ Firmware/Board/v3/Src/freertos.c | 6 ++++-- Firmware/MotorControl/axis.cpp | 4 ++-- Firmware/MotorControl/axis.hpp | 1 + Firmware/MotorControl/low_level.cpp | 2 +- Firmware/MotorControl/main.cpp | 11 +++++++++++ Firmware/MotorControl/odrive_main.h | 9 +++++++++ Firmware/communication/communication.cpp | 11 ++++++++++- Firmware/communication/communication.h | 1 + Firmware/communication/interface_can.cpp | 2 +- Firmware/communication/interface_can.hpp | 1 + Firmware/communication/interface_uart.cpp | 3 ++- Firmware/communication/interface_uart.h | 1 + Firmware/communication/interface_usb.cpp | 3 ++- Firmware/communication/interface_usb.h | 1 + 15 files changed, 49 insertions(+), 9 deletions(-) diff --git a/Firmware/Board/v3/Inc/freertos_vars.h b/Firmware/Board/v3/Inc/freertos_vars.h index 6e4c0696..7e86c971 100644 --- a/Firmware/Board/v3/Inc/freertos_vars.h +++ b/Firmware/Board/v3/Inc/freertos_vars.h @@ -11,5 +11,7 @@ extern osSemaphoreId sem_can; extern osThreadId defaultTaskHandle; extern osThreadId usb_irq_thread; +extern const uint32_t stack_size_usb_irq_thread; +extern const uint32_t stack_size_default_task; #endif /* __FREERTOS_H */ \ No newline at end of file diff --git a/Firmware/Board/v3/Src/freertos.c b/Firmware/Board/v3/Src/freertos.c index 5edfd079..42ff9751 100644 --- a/Firmware/Board/v3/Src/freertos.c +++ b/Firmware/Board/v3/Src/freertos.c @@ -89,12 +89,14 @@ osSemaphoreId sem_usb_tx; osSemaphoreId sem_can; osThreadId usb_irq_thread; +const uint32_t stack_size_usb_irq_thread = 1024; // Bytes // Place FreeRTOS heap in core coupled memory for better performance __attribute__((section(".ccmram"))) uint8_t ucHeap[configTOTAL_HEAP_SIZE]; /* USER CODE END Variables */ osThreadId defaultTaskHandle; +const uint32_t stack_size_default_task = 1024; // Bytes /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN FunctionPrototypes */ @@ -150,7 +152,7 @@ void usb_deferred_interrupt_thread(void * ctx) { void init_deferred_interrupts(void) { // Start USB interrupt handler thread - osThreadDef(task_usb_pump, usb_deferred_interrupt_thread, osPriorityAboveNormal, 0, 512); + osThreadDef(task_usb_pump, usb_deferred_interrupt_thread, osPriorityAboveNormal, 0, stack_size_usb_irq_thread / sizeof(StackType_t)); usb_irq_thread = osThreadCreate(osThread(task_usb_pump), NULL); } @@ -206,7 +208,7 @@ void MX_FREERTOS_Init(void) { /* Create the thread(s) */ /* definition and creation of defaultTask */ - osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 256); + osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, stack_size_default_task / sizeof(StackType_t)); defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL); /* USER CODE BEGIN RTOS_THREADS */ diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index a72b05e2..f9406558 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -86,8 +86,8 @@ static void run_state_machine_loop_wrapper(void* ctx) { // @brief Starts run_state_machine_loop in a new thread void Axis::start_thread() { - osThreadDef(thread_def, run_state_machine_loop_wrapper, hw_config_.thread_priority, 0, 4 * 512); - thread_id_ = osThreadCreate(osThread(thread_def), this); + osThreadDef(thread_def, run_state_machine_loop_wrapper, hw_config_.thread_priority, 0, stack_size_ / sizeof(StackType_t)); + thread_id_ = osThreadCreate(osThread(thread_def), this); thread_id_valid_ = true; } diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 25d53d5d..18d76eee 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -234,6 +234,7 @@ public: Endstop& max_endstop_; osThreadId thread_id_; + const uint32_t stack_size_ = 1024; // Bytes volatile bool thread_id_valid_ = false; // variables exposed on protocol diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 1cd17134..8b4a16f5 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -757,7 +757,7 @@ static void analog_polling_thread(void *) } void start_analog_thread() { - osThreadDef(thread_def, analog_polling_thread, osPriorityLow, 0, 128); + osThreadDef(thread_def, analog_polling_thread, osPriorityLow, 0, 512 / sizeof(StackType_t)); osThreadCreate(osThread(thread_def), NULL); } diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index b2055b38..93af6cc4 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -193,6 +193,17 @@ void vApplicationIdleHook(void) { system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread) * sizeof(StackType_t); system_stats_.min_stack_space_usb_irq = uxTaskGetStackHighWaterMark(usb_irq_thread) * sizeof(StackType_t); system_stats_.min_stack_space_startup = uxTaskGetStackHighWaterMark(defaultTaskHandle) * sizeof(StackType_t); + system_stats_.min_stack_space_can = uxTaskGetStackHighWaterMark(odCAN->thread_id_) * sizeof(StackType_t); + + // Actual usage, in bytes, so we don't have to math + system_stats_.stack_usage_axis0 = axes[0]->stack_size_ - system_stats_.min_stack_space_axis0; + system_stats_.stack_usage_axis1 = axes[1]->stack_size_ - system_stats_.min_stack_space_axis1; + system_stats_.stack_usage_comms = stack_size_comm_thread - system_stats_.min_stack_space_comms; + system_stats_.stack_usage_usb = stack_size_usb_thread - system_stats_.min_stack_space_usb; + system_stats_.stack_usage_uart = stack_size_uart_thread - system_stats_.min_stack_space_uart; + system_stats_.stack_usage_usb_irq = stack_size_usb_irq_thread - system_stats_.min_stack_space_usb_irq; + system_stats_.stack_usage_startup = stack_size_default_task - system_stats_.min_stack_space_startup; + system_stats_.stack_usage_can = odCAN->stack_size_ - system_stats_.min_stack_space_can; } } } diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 88d7b902..022871ba 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -55,6 +55,15 @@ typedef struct { uint32_t min_stack_space_usb_irq; uint32_t min_stack_space_startup; uint32_t min_stack_space_can; + + uint32_t stack_usage_axis0; + uint32_t stack_usage_axis1; + uint32_t stack_usage_comms; + uint32_t stack_usage_usb; + uint32_t stack_usage_uart; + uint32_t stack_usage_usb_irq; + uint32_t stack_usage_startup; + uint32_t stack_usage_can; } SystemStats_t; extern SystemStats_t system_stats_; diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 6d96689d..9a436c1b 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -64,6 +64,7 @@ const uint8_t fw_version_revision = FW_VERSION_REVISION; const uint8_t fw_version_unreleased = FW_VERSION_UNRELEASED; // 0 for official releases, 1 otherwise osThreadId comm_thread; +const uint32_t stack_size_comm_thread = 2048; // Bytes volatile bool endpoint_list_valid = false; static uint32_t test_property = 0; @@ -84,7 +85,7 @@ void init_communication(void) { printf("hi!\r\n"); // Start command handling thread - osThreadDef(task_cmd_parse, communication_task, osPriorityNormal, 0, 8000 /* in 32-bit words */); // TODO: fix stack issues + osThreadDef(task_cmd_parse, communication_task, osPriorityNormal, 0, stack_size_comm_thread / sizeof(StackType_t)); comm_thread = osThreadCreate(osThread(task_cmd_parse), NULL); while (!endpoint_list_valid) @@ -135,6 +136,14 @@ static inline auto make_obj_tree() { make_protocol_ro_property("min_stack_space_can", &system_stats_.min_stack_space_can), make_protocol_ro_property("min_stack_space_usb_irq", &system_stats_.min_stack_space_usb_irq), make_protocol_ro_property("min_stack_space_startup", &system_stats_.min_stack_space_startup), + make_protocol_ro_property("stack_usage_axis0", &system_stats_.stack_usage_axis0), + make_protocol_ro_property("stack_usage_axis1", &system_stats_.stack_usage_axis1), + make_protocol_ro_property("stack_usage_comms", &system_stats_.stack_usage_comms), + make_protocol_ro_property("stack_usage_usb", &system_stats_.stack_usage_usb), + make_protocol_ro_property("stack_usage_uart", &system_stats_.stack_usage_uart), + make_protocol_ro_property("stack_usage_usb_irq", &system_stats_.stack_usage_usb_irq), + make_protocol_ro_property("stack_usage_startup", &system_stats_.stack_usage_startup), + make_protocol_ro_property("stack_usage_can", &system_stats_.stack_usage_can), make_protocol_object("usb", make_protocol_ro_property("rx_cnt", &usb_stats_.rx_cnt), make_protocol_ro_property("tx_cnt", &usb_stats_.tx_cnt), diff --git a/Firmware/communication/communication.h b/Firmware/communication/communication.h index b4bab74b..85519b39 100644 --- a/Firmware/communication/communication.h +++ b/Firmware/communication/communication.h @@ -15,6 +15,7 @@ extern "C" { #include extern osThreadId comm_thread; +extern const uint32_t stack_size_comm_thread; extern const uint8_t hw_version_major; extern const uint8_t hw_version_minor; diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 251dec43..00637597 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -78,7 +78,7 @@ bool ODriveCAN::start_can_server() { if (status == HAL_OK) status = HAL_CAN_ActivateNotification(handle_, CAN_IT_RX_FIFO0_MSG_PENDING); - osThreadDef(can_server_thread_def, can_server_thread_wrapper, osPriorityNormal, 0, 512); + osThreadDef(can_server_thread_def, can_server_thread_wrapper, osPriorityNormal, 0, stack_size_ / sizeof(StackType_t)); thread_id_ = osThreadCreate(osThread(can_server_thread_def), this); thread_id_valid_ = true; diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index dcd06232..7a1e9c9a 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -54,6 +54,7 @@ class ODriveCAN { // Thread Relevant Data osThreadId thread_id_; + const uint32_t stack_size_ = 1024; // Bytes Error_t error_ = ERROR_NONE; volatile bool thread_id_valid_ = false; diff --git a/Firmware/communication/interface_uart.cpp b/Firmware/communication/interface_uart.cpp index b3141138..dc8a4ce6 100644 --- a/Firmware/communication/interface_uart.cpp +++ b/Firmware/communication/interface_uart.cpp @@ -22,6 +22,7 @@ static uint32_t dma_last_rcv_idx; // static thread_local uint32_t deadline_ms = 0; osThreadId uart_thread; +const uint32_t stack_size_uart_thread = 2048; // Bytes class UART4Sender : public StreamSink { @@ -98,7 +99,7 @@ void start_uart_server() { dma_last_rcv_idx = UART_RX_BUFFER_SIZE - huart4.hdmarx->Instance->NDTR; // Start UART communication thread - osThreadDef(uart_server_thread_def, uart_server_thread, osPriorityNormal, 0, 1024 /* the ascii protocol needs considerable stack space */); + osThreadDef(uart_server_thread_def, uart_server_thread, osPriorityNormal, 0, stack_size_uart_thread / sizeof(StackType_t) /* the ascii protocol needs considerable stack space */); uart_thread = osThreadCreate(osThread(uart_server_thread_def), NULL); } diff --git a/Firmware/communication/interface_uart.h b/Firmware/communication/interface_uart.h index 8ef39ec4..65033a6f 100644 --- a/Firmware/communication/interface_uart.h +++ b/Firmware/communication/interface_uart.h @@ -11,6 +11,7 @@ extern "C" { #include extern osThreadId uart_thread; +extern const uint32_t stack_size_uart_thread; void start_uart_server(void); diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index 036a8203..7f49c0b5 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -14,6 +14,7 @@ #include osThreadId usb_thread; +const uint32_t stack_size_usb_thread = 2048; // Bytes USBStats_t usb_stats_ = {0}; class USBSender : public PacketSink { @@ -177,6 +178,6 @@ void usb_rx_process_packet(uint8_t *buf, uint32_t len, uint8_t endpoint_pair) { void start_usb_server() { // Start USB communication thread - osThreadDef(usb_server_thread_def, usb_server_thread, osPriorityNormal, 0, 1024); + osThreadDef(usb_server_thread_def, usb_server_thread, osPriorityNormal, 0, stack_size_usb_thread / sizeof(StackType_t)); usb_thread = osThreadCreate(osThread(usb_server_thread_def), NULL); } diff --git a/Firmware/communication/interface_usb.h b/Firmware/communication/interface_usb.h index 0a5b94ff..c78ecc5d 100644 --- a/Firmware/communication/interface_usb.h +++ b/Firmware/communication/interface_usb.h @@ -12,6 +12,7 @@ extern "C" { #include extern osThreadId usb_thread; +extern const uint32_t stack_size_usb_thread; typedef struct { uint32_t rx_cnt; From 4feb34b0af8f071c531ce7af85348c880e6ccbac Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 18 Oct 2019 23:55:57 -0400 Subject: [PATCH 239/549] Fix Endstops Homing configuration documentation --- docs/endstops.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/endstops.md b/docs/endstops.md index 3eae059d..87455825 100644 --- a/docs/endstops.md +++ b/docs/endstops.md @@ -97,9 +97,22 @@ homing_speed | float | 2000.0f ### Performing the Homing Sequence Homing is possible once the ODrive has closed-loop control over the axis. To trigger homing, we must enter `AXIS_STATE_HOMING`. This starts the homing sequence, which works as follows: -1. The axis moves towards the `min_endstop` at `homing_speed` -2. The axis presses the `min_endstop` -3. The axis moves away from the `min_endstop` to the home position +1. The axis switches to `INPUT_MODE_VEL_RAMP` +2. The axis ramps up to `homing_speed` in the direction of `min_endstop` +3. The axis presses the `min_endstop` +4. The axis switches to `INPUT_MODE_TRAP_TRAJ` +5. The axis moves to the home position in a controlled manner + +It requires quite a few settings in addition to the endstop settings: + +``` +..controller.config.vel_ramp_rate +..trap_traj.config.vel_limit +..trap_traj.config.accel_limit +..trap_traj.config.decel_limit +``` + +We realize this is a little excessive and we will work towards minimizing the setup, but this works well for smooth and reliable behaviour for now. ### Homing at Startup It is possible to configure the odrive to enter homing immediately after startup. To enable homing at startup, the following must be configured: From cbae7c5b85e10f3f1ed7bfae52db76b34cc06797 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 28 Oct 2019 20:34:17 -0700 Subject: [PATCH 240/549] change current lim tolerance to absolute margin --- CHANGELOG.md | 1 + Firmware/MotorControl/motor.cpp | 2 +- Firmware/MotorControl/motor.hpp | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac23bac9..bd9beae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * Automatic d axis current for Maximum Torque Per Amp (MTPA) ### Changed +* Changed ratiometric `motor.config.current_lim_tolerance` to absolute `motor.config.current_lim_margin` * Moved `controller.vel_ramp_enable` into `controller.config`. # Releases diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 78dd9d03..6b9922c7 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -360,7 +360,7 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha ictrl.Id_measured += ictrl.I_measured_report_filter_k * (Id - ictrl.Id_measured); // Check for violation of current limit - float I_trip = config_.current_lim_tolerance * effective_current_lim(); + float I_trip = effective_current_lim() * config_.current_lim_margin; if (SQ(Id) + SQ(Iq) > SQ(I_trip)) { set_error(ERROR_CURRENT_LIMIT_VIOLATION); return false; diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index cf2c7c63..155e952f 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -74,7 +74,7 @@ public: // Read out max_allowed_current to see max supported value for current_lim. // float current_lim = 70.0f; //[A] float current_lim = 10.0f; //[A] - float current_lim_tolerance = 1.25f; // multiple of current_lim + float current_lim_margin = 8.0f; // Maximum violation of current_lim // Value used to compute shunt amplifier gains float requested_current_range = 60.0f; // [A] float current_control_bandwidth = 1000.0f; // [rad/s] @@ -248,7 +248,7 @@ public: make_protocol_property("direction", &config_.direction), make_protocol_property("motor_type", &config_.motor_type), make_protocol_property("current_lim", &config_.current_lim), - make_protocol_property("current_lim_tolerance", &config_.current_lim_tolerance), + make_protocol_property("current_lim_margin", &config_.current_lim_margin), make_protocol_property("inverter_temp_limit_lower", &config_.inverter_temp_limit_lower), make_protocol_property("inverter_temp_limit_upper", &config_.inverter_temp_limit_upper), make_protocol_property("requested_current_range", &config_.requested_current_range), From e19e857360e724ce63c5b66cbdbd76f2fdfcc241 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 28 Oct 2019 20:39:23 -0700 Subject: [PATCH 241/549] fix typo --- Firmware/MotorControl/motor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 6b9922c7..62ff6fd1 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -360,7 +360,7 @@ bool Motor::FOC_current(float Id_des, float Iq_des, float I_phase, float pwm_pha ictrl.Id_measured += ictrl.I_measured_report_filter_k * (Id - ictrl.Id_measured); // Check for violation of current limit - float I_trip = effective_current_lim() * config_.current_lim_margin; + float I_trip = effective_current_lim() + config_.current_lim_margin; if (SQ(Id) + SQ(Iq) > SQ(I_trip)) { set_error(ERROR_CURRENT_LIMIT_VIOLATION); return false; From 5b6d01d1a942a7cc2720f7a9fab442fc99509306 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 29 Oct 2019 20:58:29 -0400 Subject: [PATCH 242/549] Fix ColumnLimit --- Firmware/.clang-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/.clang-format b/Firmware/.clang-format index d2eb1915..bad55d06 100644 --- a/Firmware/.clang-format +++ b/Firmware/.clang-format @@ -2,5 +2,5 @@ BasedOnStyle: Google AllowShortCaseLabelsOnASingleLine: 'true' IndentWidth: '4' - +ColumnLimit: '0' ... From 2a22602e821abc6d8189359059494e011828e933 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 2 Nov 2019 14:05:22 -0700 Subject: [PATCH 243/549] add dirty fix for setpoint in cpr --- Firmware/MotorControl/controller.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index f1687494..107fa5ed 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -136,6 +136,13 @@ bool Controller::update(float* current_setpoint_output) { anticogging_calibration(axis_->encoder_.pos_estimate_, axis_->encoder_.vel_estimate_); } + // TODO also enable circular deltas for 2nd order filter, etc. + if (pos_wrap_src_) { + float cpr = *pos_wrap_src_; + // Keep pos setpoint from drifting + input_pos_ = fmodf_pos(input_pos_, cpr); + } + // Update inputs switch (config_.input_mode) { case INPUT_MODE_INACTIVE: { From 545aee8b9ac31ccebcc71b81bcd7f74a2dcf7f1c Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 14 Nov 2019 20:31:15 +0100 Subject: [PATCH 244/549] add unit tests for incremental encoder and NVM --- tools/odrive/tests/enc0_sim_-4096cps.ino.hex | 795 ++++++++++++++++++ tools/odrive/tests/enc1_sim_-4096cps.ino.hex | 795 ++++++++++++++++++ tools/odrive/tests/encoder_test.py | 84 ++ tools/odrive/tests/nvm_test.py | 45 + tools/odrive/{tests.py => tests/old_tests.py} | 62 -- tools/odrive/tests/test_runner.py | 195 +++++ tools/test-rig-rpi.yaml | 64 ++ 7 files changed, 1978 insertions(+), 62 deletions(-) create mode 100644 tools/odrive/tests/enc0_sim_-4096cps.ino.hex create mode 100644 tools/odrive/tests/enc1_sim_-4096cps.ino.hex create mode 100644 tools/odrive/tests/encoder_test.py create mode 100644 tools/odrive/tests/nvm_test.py rename tools/odrive/{tests.py => tests/old_tests.py} (92%) create mode 100644 tools/odrive/tests/test_runner.py create mode 100644 tools/test-rig-rpi.yaml diff --git a/tools/odrive/tests/enc0_sim_-4096cps.ino.hex b/tools/odrive/tests/enc0_sim_-4096cps.ino.hex new file mode 100644 index 00000000..78b4d734 --- /dev/null +++ b/tools/odrive/tests/enc0_sim_-4096cps.ino.hex @@ -0,0 +1,795 @@ +:0200000460009A +:100000004643464200000156000000000101020084 +:1000100000000000000000000000000000000000E0 +:1000200000000000000000000000000000000000D0 +:1000300000000000000000000000000000000000C0 +:1000400000000000010403000000000000000000A8 +:100050000000200000000000000000000000000080 +:100060000000000000000000000000000000000090 +:100070000000000000000000000000000000000080 +:10008000EB04180A063204260000000000000000FD +:10009000050404240000000000000000000000002F +:1000A0000000000000000000000000000000000050 +:1000B0000604000000000000000000000000000036 +:1000C0000000000000000000000000000000000030 +:1000D00020041808000000000000000000000000DC +:1000E0000000000000000000000000000000000010 +:1000F0000000000000000000000000000000000000 +:10010000D8041808000000000000000000000000F3 +:100110000204180804200000000000000000000095 +:1001200000000000000000000000000000000000CF +:10013000600400000000000000000000000000005B +:1001400000000000000000000000000000000000AF +:10015000000000000000000000000000000000009F +:10016000000000000000000000000000000000008F +:10017000000000000000000000000000000000007F +:10018000000000000000000000000000000000006F +:10019000000000000000000000000000000000005F +:1001A000000000000000000000000000000000004F +:1001B000000000000000000000000000000000003F +:1001C000000100000010000001000000000000001D +:1001D000000001000000000000000000000000001E +:1001E000000000000000000000000000000000000F +:1001F00000000000000000000000000000000000FF +:10020000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE +:10021000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEE +:10022000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDE +:10023000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCE +:10024000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBE +:10025000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAE +:10026000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E +:10027000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8E +:10028000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7E +:10029000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6E +:1002A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5E +:1002B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4E +:1002C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3E +:1002D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E +:1002E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1E +:1002F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0E +:10030000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD +:10031000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED +:10032000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD +:10033000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCD +:10034000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD +:10035000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAD +:10036000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D +:10037000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8D +:10038000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7D +:10039000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6D +:1003A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D +:1003B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4D +:1003C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3D +:1003D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2D +:1003E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1D +:1003F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0D +:10040000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC +:10041000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC +:10042000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC +:10043000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCC +:10044000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC +:10045000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAC +:10046000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9C +:10047000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8C +:10048000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7C +:10049000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C +:1004A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5C +:1004B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4C +:1004C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3C +:1004D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2C +:1004E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1C +:1004F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0C +:10050000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB +:10051000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEB +:10052000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB +:10053000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCB +:10054000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBB +:10055000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAB +:10056000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9B +:10057000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8B +:10058000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7B +:10059000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6B +:1005A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B +:1005B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4B +:1005C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3B +:1005D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2B +:1005E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1B +:1005F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0B +:10060000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA +:10061000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEA +:10062000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA +:10063000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCA +:10064000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA +:10065000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAA +:10066000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9A +:10067000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8A +:10068000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7A +:10069000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6A +:1006A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5A +:1006B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4A +:1006C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3A +:1006D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A +:1006E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1A +:1006F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A +:10070000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9 +:10071000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9 +:10072000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9 +:10073000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC9 +:10074000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB9 +:10075000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9 +:10076000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF99 +:10077000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF89 +:10078000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF79 +:10079000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF69 +:1007A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF59 +:1007B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF49 +:1007C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF39 +:1007D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF29 +:1007E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF19 +:1007F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF09 +:10080000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8 +:10081000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8 +:10082000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD8 +:10083000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC8 +:10084000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB8 +:10085000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA8 +:10086000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF98 +:10087000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF88 +:10088000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF78 +:10089000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF68 +:1008A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF58 +:1008B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF48 +:1008C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF38 +:1008D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF28 +:1008E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF18 +:1008F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF08 +:10090000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7 +:10091000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE7 +:10092000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD7 +:10093000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7 +:10094000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB7 +:10095000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA7 +:10096000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF97 +:10097000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF87 +:10098000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF77 +:10099000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF67 +:1009A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57 +:1009B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF47 +:1009C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF37 +:1009D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27 +:1009E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF17 +:1009F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF07 +:100A0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6 +:100A1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE6 +:100A2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD6 +:100A3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC6 +:100A4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB6 +:100A5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA6 +:100A6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF96 +:100A7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF86 +:100A8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF76 +:100A9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF66 +:100AA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 +:100AB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF46 +:100AC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF36 +:100AD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF26 +:100AE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16 +:100AF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF06 +:100B0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5 +:100B1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5 +:100B2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD5 +:100B3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC5 +:100B4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB5 +:100B5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA5 +:100B6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF95 +:100B7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF85 +:100B8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF75 +:100B9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF65 +:100BA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF55 +:100BB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF45 +:100BC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF35 +:100BD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF25 +:100BE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF15 +:100BF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF05 +:100C0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4 +:100C1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE4 +:100C2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD4 +:100C3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC4 +:100C4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB4 +:100C5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA4 +:100C6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF94 +:100C7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF84 +:100C8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF74 +:100C9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF64 +:100CA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF54 +:100CB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF44 +:100CC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF34 +:100CD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF24 +:100CE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF14 +:100CF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF04 +:100D0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3 +:100D1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE3 +:100D2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD3 +:100D3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC3 +:100D4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB3 +:100D5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA3 +:100D6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF93 +:100D7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83 +:100D8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF73 +:100D9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF63 +:100DA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF53 +:100DB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43 +:100DC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF33 +:100DD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF23 +:100DE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF13 +:100DF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF03 +:100E0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2 +:100E1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE2 +:100E2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD2 +:100E3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2 +:100E4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB2 +:100E5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA2 +:100E6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF92 +:100E7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF82 +:100E8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF72 +:100E9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF62 +:100EA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF52 +:100EB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF42 +:100EC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF32 +:100ED000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF22 +:100EE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF12 +:100EF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02 +:100F0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1 +:100F1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE1 +:100F2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD1 +:100F3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC1 +:100F4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB1 +:100F5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA1 +:100F6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF91 +:100F7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81 +:100F8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF71 +:100F9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF61 +:100FA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF51 +:100FB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF41 +:100FC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF31 +:100FD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF21 +:100FE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF11 +:100FF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF01 +:10100000D10020402C100060000000000000000013 +:1010100020100060001000600000000000000000D0 +:10102000000000607031000000000000000001209E +:1010300035100060764B0720764C4FF42A01764A33 +:101040005C64186499639546744A75498A420FD066 +:10105000744B9A420CD2D4430846234423F0030332 +:1010600004330B4450F8041B984242F8041BF9D196 +:101070006D4A6E498A420FD06D4B9A420CD2D443CE +:101080000846234423F0030304330B4450F8041BA5 +:10109000984242F8041BF9D1664A674B9A420BD238 +:1010A000D04311460024034423F0030304331344C4 +:1010B00041F8044B8B42FBD1604A4FF47001604B06 +:1010C000116003F530715F4A43F8042F9942FBD158 +:1010D0005D4B80215D4A03F8011B9342FBD15C4CC0 +:1010E0004FF080375B4E4FF0FF32DFF8A8A10025AC +:1010F000DFF8A491DFF8A481574B4549C3F800A05D +:10110000C4F80471C4F80091C4F8F470C4F8F08015 +:10111000F36923F07F0343F04003F361736A23F024 +:101120007F0343F0400373628A66CA660A674A67B0 +:1011300000F0B6F8494A6320494B4A49106003223F +:101140001D60CAF8381043F8082C4749474A4848F8 +:10115000C3F8082D0B68474A43F08073CAF83C0077 +:1011600045480B601368454943F001031360036869 +:101170000B6000F0E5F8C4F804714148C4F8009130 +:10118000C4F8F470C4F8F08000F03AFA00BF00BF71 +:1011900000BF00BFF16E3B4A41F440513A4BF1664B +:1011A0001560C2F80851C2F81851C2F82851C2F8A7 +:1011B00038519A6BD20708D442F615623349596503 +:1011C0001A659A6B42F001029A632F4A304C936879 +:1011D00043F00113936000F01BFA2368132BFCD932 +:1011E00000F05CF900F0D0F900F016FA00F0DAF83F +:1011F00000F0FEF92368B3F5967FFBD300F0FCF90D +:1012000000F00EFA00F018FA00F012FAFAE700BF48 +:1012100000C00A40ABAAAAAA008007200000000074 +:10122000501600605017000000000020A42D006040 +:10123000D0030020D0030020C032002088ED00E061 +:10124000FC0F00201D05000000E400E0A0E400E029 +:1012500000800D4000C00F4008ED00E014E000E009 +:1012600018E000E095100000FCED00E000002020F8 +:1012700099110000001000E0041000E0A40D00200F +:101280000046C3230040084000400D400000C05607 +:10129000A80D0020001000201B1018200C0D1113A9 +:1012A000F0B5194A0021194B4FF0100E18480124CF +:1012B000184E194D0160194FC2F800E01E6015600C +:1012C000174E184D1F601660174F1D60174E184DB2 +:1012D00017601E60174F1560174E184D1F6016607F +:1012E0001D600460BFF34F8FBFF36F8FC3F8B01161 +:1012F000BFF34F8FBFF36F8F52F8883C43F44033F6 +:1013000042F8883CF0BD00BF9CED00E0A0ED00E09D +:1013100094ED00E0250008031100200021000207E1 +:1013200012000020250008131300202027000B13B3 +:101330001400004033001013150000602F000B074D +:10134000F0B4174A40274FF480314FF480564FF4E1 +:1013500000554FF4404443F24200136913F0020F6A +:1013600006D0946151619061136913F0020FF8D1B6 +:1013700013F4005F01D15561EFE713F4805F01D1F1 +:101380005661EAE7002BE8DA13F4803F01D091615F +:10139000E3E75B0601D45761DFE7F0BC704700BFAD +:1013A00000800D40364A03203649F3EE096A13687F +:1013B00023F00103F0B51360C2F89000D1F8E030DB +:1013C000DFF8D4E0DCB2C3F30B202F4FC0EB135394 +:1013D00007EE904AA4F15501CEF80040B8EEE77A46 +:1013E00003EB830407EE900A03FB01F13B6003EB80 +:1013F0008313B8EEE75A07EE901A091B77EE666A78 +:10140000B8EE677A214D07EE901A0B44C5ED006ADD +:10141000F8EE677A1E4EC7EE265A1E4930601068F5 +:1014200087EEA66A07EE903AF8EE677A87EEA67A1C +:1014300075EE855A76EE056AFCEEE55AFCEEE66A34 +:1014400015EE903A40EA035316EE900A77EE057ACD +:10145000136001EA0041D2F81031FCEEE77A0B4349 +:10146000C2F8103117EE903AD2F81011C3F30B0303 +:101470000B43C2F81031136843F002031360F0BD50 +:1014800080810D4000441F40F8030020F403002039 +:10149000FC0300200000FF0FF0030020304B40F65B +:1014A000617270B5C3F8202140F2044500F0ACF839 +:1014B0002C492D48D1F880202C4C42F003022C4BB3 +:1014C000C1F88020C0F86051226813401BB9D0F8E1 +:1014D000A8319A071AD0244B4FF00041234A516398 +:1014E0001A46D3F8401141F00201C3F84011D2F876 +:1014F00040319B07FBD44FF400301E491B4B4FF08B +:101500000042086019209A6300F09AF81A4D0022F0 +:10151000164B4FF08041144C0A26996328461A60F6 +:101520001146C4F8A8614FF4207200F07DF84FF422 +:1015300081064FF4800040F24313104A10492E6098 +:101540002864C4F85851C2F80412C4F848310D4A4E +:101550004FF4003101231160C4F8403170BD00BF69 +:1015600000800D4000C00F4000002E4000900D4054 +:10157000001C1E008CE200E0003000200010002063 +:10158000F90600000CE100E0114B1249D86E0A4642 +:1015900040F4403030B4D86640F2B765D86EA0242D +:1015A00040F44070D8664D648C64936C1B06FCD488 +:1015B000094B40F2B760A021064A58649964936CC5 +:1015C00013F08003FBD1054A137030BC704700BF95 +:1015D00000C00F4000400C4000800C40A10D0020D6 +:1015E000F8B500BFF8BC08BC9E467047FFFFFFFF80 +:1015F0005FF800F0390500005FF800F055160000B4 +:101600005FF800F0D90100005FF800F0F90D00006C +:101610005FF800F0350500005FF800F06913000086 +:101620005FF800F0010100005FF800F0A51600006F +:101630005FF800F0B91100005FF800F0050100004C +:101640005FF800F05912000045000000FFFFFFFFA7 +:10165000000000000000000000000000000000008A +:10166000000000000000000000000000000000007A +:1016700010B5054C237833B9044B13B10448AFF3CC +:1016800000800123237010BDD00300200000000063 +:1016900054170000084B10B51BB108490848AFF3B8 +:1016A00000800848036803B910BD074B002BFBD02E +:1016B000BDE81040184700BF00000000D403002020 +:1016C00054170000D00300200000000008B5174B9D +:1016D0000121187800F00AFA154B0121187800F062 +:1016E00005FA144B0121187800F000FA124B012181 +:1016F000187800F0FBF9114B0121187800F0F6F989 +:101700000F4B0121187800F0F1F90E4B00211878E9 +:1017100000F0ECF90C4B0021187800F0E7F90B4BC6 +:1017200000211878BDE8084000F0E0B9F40200207C +:10173000FC0200200003002010030020080300200A +:10174000F8020020EC030020EC020020F002002050 +:10175000FFF7BCBF2C4A2D4B2DE9F04714682C4AEB +:10176000DFF8B8A012685746DFF8B490294EDFF8CA +:10177000B480294D93FBF4F393FBF2F4012199F823 +:10178000000000F0ADF9DAF800103268A5FB023273 +:10179000920C04FB02F2DAF800305B1A9A42FAD893 +:1017A000012198F8000000F09BF939683268A5FB28 +:1017B0000232920C04FB02F2DAF800305B1A9A4211 +:1017C000FAD8002199F8000000F08AF939683268E7 +:1017D000A5FB0232920C04FB02F2DAF800305B1A2D +:1017E0009A42FAD8002198F8000000F079F9396897 +:1017F0003268A5FB0232920C04FB02F2DAF80030E8 +:101800005B1A9A42FAD8B9E70C03002000879303C9 +:10181000040300201803002083DE1B43041000E0B3 +:10182000FC020020000300208C4A8D4B90422DE9E1 +:10183000F0438C4D5C699969EF681DD98A4B9842D9 +:1018400040F20181894B40F22766DFF860E20344F1 +:10185000874D1A0AAEFB0232D30903EB830303EB75 +:10186000830202F2E243B34228BF3346A3F5487332 +:10187000A5FB0336F60804E07E4EB04294BF062670 +:101880000E26774A07F01F0ED2F88030B64543F097 +:10189000C003C2F880300AD2724B27F01F071A46E5 +:1018A0003743DF601368002BFCDA07F01F0E14F0DB +:1018B00000732ED1704D714AD5F810C015460CEA50 +:1018C0000202AA420ABF4FF0C0534FF480520022D6 +:1018D00084EA030515F0605F06D024F0605403F03D +:1018E00060535F4D1C436C6181EA020313F4405F57 +:1018F00008D05B4B21F4405111431A469961936C17 +:101900001D07FCD444F00074554A5461936C990649 +:10191000FCD401215A4D0A4601FB02F300FB03F3FC +:10192000AB4209D8072A00F28480013201FB02F39E +:1019300000FB03F3AB42F5D95248534D1844A5FBC5 +:101940000030030D6C2B79D8352B7ED8DFF8608101 +:1019500036234E48DFF820C14D4DDCF80090B0FB37 +:10196000F2F009EA05054545B0FBF1F00BD043F470 +:1019700000534FF480586546CCF80080CCF8003016 +:101980002B68002BFCDADFF8D8C0013ADCF8103005 +:1019900003F00703934207D002F007026546CCF834 +:1019A0001020AB6CDB03FCD40139890284EA01030B +:1019B00013F4E05F0AD02A4B24F4E05401F4E05120 +:1019C0001A460C435C61936C9907FCD4314B324945 +:1019D0000344DB09A1FB0331090B042928BF0421BF +:1019E0004B1E1B0284EA030212F4407F06D024F44B +:1019F000407403F440731A4A1C435461184B24F09A +:101A000000741A465C61936C9B06FCD4B0FBF1F148 +:101A1000224A7645224B1060196008D2114B27F0FC +:101A20001F071A463743DF601368002BFCDABDE856 +:101A3000F083042980D8013101226DE7DFF874803A +:101A40006C23184886E712261BE71748DFF86880E2 +:101A500000FB03F043EA08087CE700BF00A4781FFE +:101A600000C00F40000008400046C32300BA3CDC21 +:101A70001F85EB5100366E0100800D404030008024 +:101A8000FFB19F26808D5B00819F5E1600B29F266E +:101A90007F3001807FD1F0089F10E50018030020FF +:101AA0001403002000643F4D001BB70023B24C001C +:101AB000362000806C200080002000800001074B51 +:101AC0001A181B58D2685868104202D011B9C3F8CE +:101AD00088207047C3F88420704700BF00000020B2 +:101AE000272801D8FFF7EABF704700BF27281CD876 +:101AF00000011A4A012902EB0003105810B415D056 +:101B0000042913D0DC68426822EA040242609A6821 +:101B1000E9B102291ED003295B685DF8044B0CBFB4 +:101B20000F491049116015221A607047DA680129BF +:101B3000446842EA040242609A6808D040F63801DC +:101B400011605B6815225DF8044B1A6070473821FC +:101B50001160F6E704491160F3E700BF00000020C0 +:101B6000383001003800010038F0010004207146CF +:101B7000084203D0EFF3098000F008B8EFF30880C3 +:101B800000F004B8704700BF704700BF1B4B052131 +:101B90001B4A382030B5C2F848110821C2F8380372 +:101BA00083B05A68174C0A4317485A60C3F8841028 +:101BB000C3F888102368834202D91448FFF734FE23 +:101BC0000E4D08240020124A1249C5F884400190A5 +:101BD000019B934205D8019B01330193019B9342E2 +:101BE000F9D9C5F888400190019B8B42EDD8019B43 +:101BF00001330193019B8B42F9D9E6E700C01B40FA +:101C000000801F4018030020FF45C32300A3E111FB +:101C10003F420F003F548900836B30B41BB15368BF +:101C200043F40043536072B6446B9CB1104B226086 +:101C3000D3F8B0410C4217D1D3F8404144F480446A +:101C4000C3F84041D3F8B851D3F840416404F3D508 +:101C5000294209D10023064C8260C360D4F8B03118 +:101C60001943C4F8B0110263426362B630BC7047D6 +:101C700000002E4038B50546036B06E0AA6B1C68D1 +:101C800090476A6B2346944208D0184633B1012B23 +:101C900004D05A681206F1D52B6338BD00232B639C +:101CA0006B6338BDF0B5F1B9224C23490020234BBA +:101CB00001228025A1600A601A464D60E060D3F8D9 +:101CC000BC41886044F001141D4DC3F8BC41D3F8F9 +:101CD000B01141F00101C3F8B0112860D2F8B03161 +:101CE000002BFBD1F0BD0904164B144D0126114CFD +:101CF00041F0800100221E60596000F5805CE264C2 +:101D000000F5005EA36400F54057D5F8B01100F56A +:101D100080462A46986041F48031C3F80CC0C3F86D +:101D200010E05F619E61C5F8B011D2F8B031002BB0 +:101D3000FBD1BAE7003000202020002000002E4018 +:101D40000C040020002000202DE9F04FBB4C83B094 +:101D5000D4F8448118F0010FC4F844815ED0D4F85F +:101D6000AC31002B55D04FF00119DFF8F0B2264608 +:101D7000B34FCA46B96AFA6AC4F8AC31D4F84031F4 +:101D800043F40053C4F84031D4F840319D04F5D5F4 +:101D9000D6F8403123F40053C6F84031C6F8B4A158 +:101DA000D4F8B43113F00113FAD188B240F281654E +:101DB000CBF80030A84200F29680B0F5D06F80F0EA +:101DC000D881B0F5817F00F0578100F2D58080285E +:101DD00000F02681822840F0C78092B202F07F0195 +:101DE000072900F2C18089009648974D0844016890 +:101DF00010062B706B7040F1E581C90301D50123FA +:101E00002B7002219048FFF74DFFD4F8AC31002B26 +:101E1000B0D18A4AD2F8BC31002B44D118F0400F1F +:101E200018D0864BD3F8AC111A46C3F8AC11D3F8CE +:101E3000BC11C3F8BC11D2F8B041804B002CFAD1D0 +:101E40004FF0FF32C3F8B421D3F8843100F074FBB3 +:101E50007E4B1C6018F0807F03D07D4B1B6803B164 +:101E6000984718F0007F03D07A4B1B6803B198475E +:101E700018F0040F02D0714BD3F884316F4BD3F8B4 +:101E8000482112060CD518F0800F09D072490A7843 +:101E9000002A00F08E81531EDBB20B7003B9FBBE2B +:101EA00003B0BDE8F08F6D49C2F8BC310868034249 +:101EB00040F0CC81654A14681C40AFD0630700F144 +:101EC000E281670300F1EF81260700F1E881250335 +:101ED00000F1E181E00600F1DA81E1029ED560487F +:101EE000FFF7C8FE9AE742F22105A84238D06FD822 +:101EF000B0F5086F00F02F81B0F5106F34D1C1F349 +:101F00000741584A584811705849594A0193C6F830 +:101F1000C801C6F8CC11C6F8D02100F00FFB554A15 +:101F2000019B80210120FB6410605160D6F8BC1138 +:101F3000936041F00111BA64C6F8BC11D6F8B02123 +:101F400042F48032C6F8B021444A1360D4F8B0316C +:101F5000002BFBD1D4F8AC31002B7FF40BAF58E74A +:101F6000100C072800F03281C4F8C091D4F8AC31CD +:101F7000002B7FF4FFAE4CE740F20235A84200F0A0 +:101F8000B780B0F5A06FEFD13A4A80200125FB64FD +:101F900050601560D6F8BC01936040F00113BA643C +:101FA000C6F8BC31D6F8B03143F48033C6F8B0314E +:101FB000D4F8B031002BFBD10B0C5B0643F08073DF +:101FC000C6F85431D4F8AC31002B7FF4D3AE20E7FF +:101FD00042F22123984200F0828042F2213398425B +:101FE000C2D1244B01218022196000215A60D4F80B +:101FF000BC21BB6442F001129960F964C4F8BC21B1 +:10200000D4F8B03143F48033C4F8B031D4F8B031EF +:10201000002BFBD1D4F8AC31002B7FF4ABAEF8E64B +:10202000094A0221104613705370FFF73BFED4F8A3 +:10203000AC31002B7FF49EAEEBE600BF00002E40DB +:1020400000300020C0012E402004002004040020A5 +:102050000004002008040020100400200C040020CC +:1020600040320020300400200200CC00C8000200F2 +:102070000200C8002020002092B202F07F03072B4C +:102080003FF672AF12F0800F4FEA8303884A4FF099 +:1020900001011A44136814BF23F4803323F00103B1 +:1020A00013608022834B196000215A60D4F8BC2150 +:1020B000BB6442F001129960F964C4F8BC21D4F801 +:1020C000B03143F48033C4F8B031D4F8B031002BD0 +:1020D000FBD1D4F8AC31002B7FF44CAE99E6764BB3 +:1020E000C1F30741754A1868754B1060197078E79D +:1020F00092B202F07F03072B3FF636AF12F0800F4B +:102100004FEA83036A4A4FF001011A44136814BF6F +:1021100043F4803343F0010313608022654B196060 +:1021200000215A60D4F8BC21BB6442F001129960CE +:10213000F964C4F8BC21D4F8B03143F48033C4F856 +:10214000B031D4F8B031002BFBD1D4F8AC31002B36 +:102150007FF410AE5DE65B4A01215B4B12781846B6 +:102160001A70FFF79FFDD4F8AC31002B7FF402AE5C +:102170004FE6564B586800283FF4F6AE090C1FFA9C +:1021800082FE04E00C33586800283FF4EDAE1D8851 +:102190008D42F7D15D887545F4D1090A120C0329E7 +:1021A0000CBF01781989914228BF1146FFF77AFDCB +:1021B0002BE6D3F8482122F08002C3F8482103B06F +:1021C000BDE8F08FCA077FF51CAE18E6404D012030 +:1021D000FB6029603F4B4049186059603F49D6F881 +:1021E000B0016A6001F5005E40F00102A1F5005007 +:1021F00001F58055BB609860A1F58050D860C6F8A5 +:10220000B02119615D61C3F818E0D4F8B031002B3A +:10221000FBD1284A012048F28001FB6410604FF492 +:1022200080305160D6F8BC11936041F00113BA645C +:10223000C6F8BC31D6F8B0310343C6F8B031CBF89C +:102240000000D4F8B031002BFBD1DEE5204C42F287 +:102250002100002524880D6084427FF42BAE2049A4 +:10226000204C03C90D0C86282060A180A5717FF445 +:1022700021AED2F8481150241B4841F08001C2F829 +:102280004811047016E61948FFF7F4FC19E61848DF +:10229000FFF7F0FC21E61748FFF7ECFC1AE61648BA +:1022A000FFF7E8FC13E61548FFF7E4FC0CE600BF77 +:1022B000C0012E4020200020A80D002088320020E0 +:1022C000800D002030040020200400208002002027 +:1022D000180400200020002080000700282400208F +:1022E000280400208032002010040020003100204B +:1022F00000320020C0310020803100204031002019 +:10230000002AA0F102022DE9F04714BF00274FF088 +:102310000057022A01D9BDE8F0874FEAC01ADFF85A +:1023200040900D4604460AEB0906002140229846DB +:10233000304600F0DFFC012047EA05414AF8091069 +:10234000C6F83880B060B8F1000FE4D0034BA0406D +:102350001C6820431860BDE8F08700BF040400201B +:1023600000300020002AA0F102022DE9F04714BF3E +:1023700000274FF00057022A01D9BDE8F08740221C +:10238000C501DFF8449088461544002104461E46E6 +:1023900005EB090A504600F0ADFC012247EA08416E +:1023A00045F80910CAF83860CAF80820002EE4D0B1 +:1023B00004F11000034B8240186802431A60BDE824 +:1023C000F08700BF04040020003000201204816068 +:1023D000C36142F08002F0B44260012701F58056EB +:1023E00001F5005501F5405401F580420760C660D3 +:1023F000056144618261F0BC704700BF831E022BFF +:1024000000D9704730B4064B00F1100401250A468C +:1024100003EBC01005FA04F130BCFFF7FDBB00BFB1 +:1024200040300020831E022B00D9704710B4054BAA +:1024300001240A4604FA00F103EBC0105DF8044BD6 +:10244000FFF7EABB00300020124A134BD2F82002FB +:1024500020F07F40984210B584B002D800EB800095 +:1024600040000E4C01A90A2200F07EFA01A90023C7 +:10247000204611F8012B01333AB10A2B20F8022F24 +:10248000F7D11623237004B010BD5B00DBB22370BC +:1024900004B010BD00441F407F969800B403002094 +:1024A0004368C269C3F30E43054930B4C3F1400326 +:1024B000044C002521F8123024F8125030BC70472B +:1024C000000C0020F80B0020F8B5154B1B783BB929 +:1024D00003F0FF04134B1B7813B1134D2A8802B984 +:1024E000F8BD124F2346124EC2F58072397811485A +:1024F00006EB411600EB01213046FFF767FF31463E +:102500000420FFF77BFF3B780133DBB2062B98BF3B +:102510003B704FF0000388BF3C702B80F8BD00BFBC +:10252000340B002030040020800C0020350B0020EC +:10253000A00C002034040020704700BF0021E022DE +:102540002048F8B50C46204E204D00F0D3FB204F1C +:102550002146204B6022347028461F4E1C8000F01C +:10256000C9FB23462246102102203C60BC80346017 +:10257000B480FFF7F7FE2246184B40210320FFF7F7 +:10258000BFFE2346224640210420FFF7EBFE2346F0 +:10259000402228461249FFF719FF29460320FFF77A +:1025A00041FF104B4A22104910480860C3F884408C +:1025B000C3F88020D3F8482142F08072C3F8482144 +:1025C000F8BD00BFA00C0020350B0020200C00201F +:1025D000000C0020800C0020F80B0020510E0000A1 +:1025E000380B002000002E4000040020790E00006F +:1025F000024A034B10881B88C01A7047000C002049 +:10260000F80B002010B4EFF3108272B6437F33B999 +:10261000017F012908D0032910D00123437702B993 +:1026200062B65DF8044B7047114C2168A1B11149A5 +:1026300043610B68086083615861EEE70E4C2168C6 +:1026400081B10E4943610B680860836158610C4B8E +:102650004FF080511960E0E7064B416181612060D5 +:102660001860DAE7054B4161816120601860EEE790 +:10267000940D0020900D0020840D0020880D002076 +:1026800004ED00E010B4047F4160022CC26003D06E +:102690005DF8044BFFF7B6BF83685DF8044B18473D +:1026A00070B5EFF3108172B60C4C23688BB10C4EF1 +:1026B00000255A6922607AB1956101B962B65D77E9 +:1026C00018469B689847EFF3108172B62368002B79 +:1026D000EFD101B962B670BD3260EEE7840D002023 +:1026E000880D0020FFF7DCBF184A30B41468002CB6 +:1026F00028D0036821688B420FD2CB1A0021846056 +:10270000C1602360E0601060022330BC0375704735 +:102710000360144611688B4208D3A2685B1A002A32 +:10272000F6D18260C4600360A060EDE7D568CB1A83 +:1027300082600222C560E060C16888602360027523 +:1027400030BC70478460C4601060DDE78C0D0020F1 +:10275000F8B5224E34682CB32368002B3AD11D46BD +:102760001F4F04E03468ECB12368002B32D1A3681A +:1027700003B1DD6020693360036825751B68BB42C7 +:1027800021D1037F4560022BC46020D0FFF73AFFC0 +:102790006368002BE6D023602046FFF7A5FF34686E +:1027A000002CE1D1EFF3108372B60E4A00211068BD +:1027B000116003B962B628B18468FFF795FF20461F +:1027C0000028F9D1F8BD224600219847E0E7836848 +:1027D0009847DDE7013B2360E4E700BF8C0D002054 +:1027E000351000009C0D0020044A054B1168054A75 +:1027F0001960136801331360FFF7AABF041000E0EB +:10280000A40D0020A80D002070B5214C237883B9B9 +:10281000204B01221B7822701BBB1F4B1B78002B07 +:1028200029D11E4B00211A68217012B1EFF30582E5 +:1028300002B170BDEFF3108072B61A68F2B1184C95 +:102840002178D9B90126556926701D60D5B1A961D5 +:1028500000B962B6002593681046557798472570F1 +:1028600070BDFFF7C5FE0028D7D000F015FA0A4B5F +:102870001B78002BD5D000F0FBF9D2E70028D8D187 +:1028800062B670BD074B1D600028E3D1E1E700BFD1 +:10289000A00D0020CA030020C80D0020940D0020C8 +:1028A000980D0020900D0020002852D02DE9F04F07 +:1028B000814683B0274C0120274D284E54E8003F25 +:1028C0002A68316844E80003002BF7D1244F4FF405 +:1028D0007A7E2448D7F800C0BB4607F1C647036894 +:1028E000C1EB0C0107F5DE1707F67F67A7FB03C3F3 +:1028F000BA4601279B0CB1FBF3F30EFB023854E8F8 +:10290000003F2A68316844E80073002BF7D1DBF8F8 +:1029100000C04FF47A7E03680EFB02F2C1EB0C019B +:10292000AAFB033EC8EB02034FEA9E42B1FBF2F161 +:10293000CA18B2F57A7F07D3B9F1010908F57A7898 +:10294000DDD103B0BDE8F08F0190FFF75DFF019886 +:10295000D5E770478C320020A80D0020A40D002080 +:10296000041000E018030020F0B44E1E0025374686 +:1029700000E00135B0FBF2F302FB130000F1370475 +:10298000092800F13000E4B298BFC4B2184607F835 +:10299000014F002BEDD14A1953704DB1013316F898 +:1029A000014F1778E81A3770834202F80149F5DBC6 +:1029B0000846F0BC704700BFA4484FF00F0CA44B72 +:1029C000826F42F47F02F0B582670025D0F8802044 +:1029D0004FF470469F4C4FF4604E29464FF4806789 +:1029E00014432A46C0F88040A3F88C6148F2B82608 +:1029F000A3F88EC1A3F89051B3F8880180B240F0DB +:102A0000F000A3F8880101EB4100914B0131002552 +:102A100040011C4604290344A3F804E0DF805A84E3 +:102A20001A865A805A81DE815A82DA825A83DA8380 +:102A3000E9D1B4F888014FF00F0C874B4FF4704682 +:102A400080B229464FF460472A4640EA0C004FF412 +:102A5000806EA4F88801B4F8880180B240F47060F8 +:102A6000A4F88801A3F88C6148F2B826A3F88EC1B7 +:102A7000A3F89051B3F8880180B240F0F000A3F8B9 +:102A8000880101EB4100744B0131002540011C46D7 +:102A9000042903449F80A3F806E05A841A865A80CA +:102AA0005A81DE815A82DA825A83DA83E9D1B4F814 +:102AB00088014FF00F0C694B4FF4704680B22946E5 +:102AC0004FF460472A4640EA0C004FF4806EA4F8A9 +:102AD0008801B4F8880180B240F47060A4F88801DD +:102AE000A3F88C6148F2B826A3F88EC1A3F89051E0 +:102AF000B3F8880180B240F0F000A3F8880101EB40 +:102B00004100564B0131002540011C460429034475 +:102B10009F80A3F806E05A841A865A805A81DE8183 +:102B20005A82DA825A83DA83E9D1B4F888014FF005 +:102B30000F0C4B4B4FF4704780B229464FF4604660 +:102B40002A4640EA0C004FF4806EA4F88801B4F8DD +:102B5000880180B240F47060A4F88801A3F88C71F9 +:102B600048F2B827A3F88EC1A3F89051B3F88801B2 +:102B700080B240F0F000A3F8880101EB4100384B2F +:102B8000013140011C46042903449E80A3F806E05D +:102B90005A841A865A805A81DF815A82DA825A838D +:102BA000DA83EAD1B4F888310F27002241F2010616 +:102BB0009BB245F6C05E114643F226053B43A4F89E +:102BC0008831B4F888319BB243F47063A4F888313B +:102BD0005001244B01320344042A99815981DF8139 +:102BE0009E82A3F806E0198019829D81F0D100220F +:102BF0000F2741F2010645F6C055114643F226045F +:102C00005001194B01320344042A99815981DF8113 +:102C10009E82DD80198019829C81F1D100220F27CC +:102C200041F2010645F6C055114643F22604500113 +:102C30000E4B01320344042A99815981DF819E821F +:102C4000DD80198019829C81F1D1F0BD00C00F4058 +:102C500000C03D40000003FC00003E4000403E40FC +:102C600000803E4000C01D4000001E4000401E404D +:102C700038B5074B1C784CB1064D55F8043F002B76 +:102C8000FBD09847631E13F0FF04F6D138BD00BF98 +:102C9000C80D0020A80D0020014B00221A707047BB +:102CA000CA03002070B50F4E0F4D761BB61018BF2B +:102CB000002405D0013455F8043B9847A642F9D1C9 +:102CC0000A4E0B4D761B00F063F8B61018BF0024B7 +:102CD00006D0013455F8043B9847A642F9D170BD9F +:102CE00070BD00BF48160060481600604C160060BA +:102CF0004816006070B4840746D0541E002A41D0A4 +:102D0000CDB2034602E0621EE4B3144603F8015B51 +:102D10009A07F8D1032C2ED9CDB245EA05250F2C00 +:102D200045EA054519D903F110022646103E0F2E3B +:102D300042F8105C42F80C5C42F8085C42F8045C13 +:102D400002F11002F2D8A4F1100222F00F0204F0F6 +:102D50000F041032032C13440DD91E462246043AA8 +:102D6000032A46F8045BFAD8221F22F00302043239 +:102D7000134404F003042CB1C9B21C4403F8011B32 +:102D8000A342FBD170BC704714460346C2E700BFA4 +:102D90005FF800F0E1150060000000000000000096 +:042DA000F8B500BFC3 +:102DA40000000042C8801F40B8821F400800000095 +:102DB40000000042C4801F40B4821F400400000091 +:102DC40000C0004224801F4014821F4010000000F5 +:102DD40000C0004228801F4018821F4020000000CD +:102DE40000C000422C801F401C821F404000000095 +:102DF40000C0004234801F4024821F4000010000B4 +:102E04000040004264811F4054831F4000040000BE +:102E14000040004280811F4070831F400000020078 +:102E2400004000427C811F406C831F400000010071 +:102E34000040004268811F4058831F400008000082 +:102E4400004000423C811F402C831F4001000000D1 +:102E54000040004244811F4034831F4004000000AE +:102E64000040004240811F4030831F4002000000A8 +:102E74000040004248811F4038831F400800000082 +:102E84000000004204811F40F4821F40000004003F +:102E94000000004208811F40F8821F400000080023 +:102EA4000000004218811F4008831F40000080007A +:102EB4000000004214811F4004831F4000004000B2 +:102EC4000000004200811F40F0821F400000020009 +:102ED40000000042FC801F40EC821F400000010003 +:102EE4000000004224811F4014831F40000000049E +:102EF4000000004228811F4018831F400000000882 +:102F0400000000421C811F400C831F400000000190 +:102F14000000004220811F4010831F400000000277 +:102F240000000042EC801F40DC821F4000100000C3 +:102F340000000042F0801F40E0821F40002000009B +:102F44000000004234811F4024831F4000000040E1 +:102F54000000004238811F4028831F400000008089 +:102F64000080004294801F4084821F4000000400BF +:102F740000C0004290801F4080821F4000000080FB +:102F840000800042A8801F4098821F4000008000FB +:102F940000800042A4801F4094821F400000400033 +:102FA400004000426C811F405C831F400010000001 +:102FB40000C0004230801F4020821F40800000007B +:102FC40000800042C8811F40B8831F400080000079 +:102FD40000800042C4811F40B4831F4000400000B1 +:102FE40000800042C0811F40B0831F4000200000C9 +:102FF40000800042BC811F40AC831F4000100000D1 +:1030040000800042D0811F40C0831F4000000200A6 +:1030140000800042CC811F40BC831F40000001009F +:1030240000010000840300201200000000060000DC +:103034001C0300200A0000000002000040030020DE +:10304400430000000007000040030020430000008C +:1030540000030000B0030020000000000103090485 +:103064002803002000000000020309049803002044 +:103074000000000003030904B40300200000000062 +:10308400000000000000000000000000010000003B +:1030940002000000170000001200000016000000EB +:1030A400150000001E0000001300000000200000B6 +:1030B400140000000029DE07007B9A170A060002AC +:1030C4000200004001000000180354006500650080 +:1030D4006E00730079006400750069006E006F0073 +:1030E40009024300020100C0320904000001020287 +:1030F4000100052400100105240101010424020635 +:1031040005240600010705820310001009040100CC +:10311400020A0000000705030240000007058402BC +:10312400400000001201000202000040C0168304A7 +:103134007902010203010000160355005300420006 +:103144002000530065007200690061006C000000FB +:10315400040309040C030000000000000000000048 +:10316400000000000000000000000100000000005A +:040000056000100087 +:00000001FF diff --git a/tools/odrive/tests/enc1_sim_-4096cps.ino.hex b/tools/odrive/tests/enc1_sim_-4096cps.ino.hex new file mode 100644 index 00000000..43eabc4b --- /dev/null +++ b/tools/odrive/tests/enc1_sim_-4096cps.ino.hex @@ -0,0 +1,795 @@ +:0200000460009A +:100000004643464200000156000000000101020084 +:1000100000000000000000000000000000000000E0 +:1000200000000000000000000000000000000000D0 +:1000300000000000000000000000000000000000C0 +:1000400000000000010403000000000000000000A8 +:100050000000200000000000000000000000000080 +:100060000000000000000000000000000000000090 +:100070000000000000000000000000000000000080 +:10008000EB04180A063204260000000000000000FD +:10009000050404240000000000000000000000002F +:1000A0000000000000000000000000000000000050 +:1000B0000604000000000000000000000000000036 +:1000C0000000000000000000000000000000000030 +:1000D00020041808000000000000000000000000DC +:1000E0000000000000000000000000000000000010 +:1000F0000000000000000000000000000000000000 +:10010000D8041808000000000000000000000000F3 +:100110000204180804200000000000000000000095 +:1001200000000000000000000000000000000000CF +:10013000600400000000000000000000000000005B +:1001400000000000000000000000000000000000AF +:10015000000000000000000000000000000000009F +:10016000000000000000000000000000000000008F +:10017000000000000000000000000000000000007F +:10018000000000000000000000000000000000006F +:10019000000000000000000000000000000000005F +:1001A000000000000000000000000000000000004F +:1001B000000000000000000000000000000000003F +:1001C000000100000010000001000000000000001D +:1001D000000001000000000000000000000000001E +:1001E000000000000000000000000000000000000F +:1001F00000000000000000000000000000000000FF +:10020000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE +:10021000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEE +:10022000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDE +:10023000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCE +:10024000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBE +:10025000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAE +:10026000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E +:10027000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8E +:10028000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7E +:10029000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6E +:1002A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5E +:1002B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4E +:1002C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3E +:1002D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E +:1002E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1E +:1002F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0E +:10030000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD +:10031000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED +:10032000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD +:10033000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCD +:10034000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD +:10035000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAD +:10036000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D +:10037000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8D +:10038000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7D +:10039000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6D +:1003A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D +:1003B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4D +:1003C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3D +:1003D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2D +:1003E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1D +:1003F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0D +:10040000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC +:10041000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC +:10042000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC +:10043000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCC +:10044000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC +:10045000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAC +:10046000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9C +:10047000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8C +:10048000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7C +:10049000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C +:1004A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5C +:1004B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4C +:1004C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3C +:1004D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2C +:1004E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1C +:1004F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0C +:10050000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB +:10051000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEB +:10052000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB +:10053000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCB +:10054000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBB +:10055000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAB +:10056000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9B +:10057000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8B +:10058000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7B +:10059000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6B +:1005A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B +:1005B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4B +:1005C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3B +:1005D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2B +:1005E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1B +:1005F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0B +:10060000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA +:10061000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEA +:10062000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA +:10063000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCA +:10064000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA +:10065000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAA +:10066000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9A +:10067000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8A +:10068000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7A +:10069000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6A +:1006A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5A +:1006B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4A +:1006C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3A +:1006D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A +:1006E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1A +:1006F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A +:10070000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9 +:10071000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9 +:10072000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9 +:10073000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC9 +:10074000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB9 +:10075000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9 +:10076000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF99 +:10077000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF89 +:10078000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF79 +:10079000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF69 +:1007A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF59 +:1007B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF49 +:1007C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF39 +:1007D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF29 +:1007E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF19 +:1007F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF09 +:10080000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8 +:10081000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8 +:10082000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD8 +:10083000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC8 +:10084000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB8 +:10085000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA8 +:10086000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF98 +:10087000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF88 +:10088000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF78 +:10089000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF68 +:1008A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF58 +:1008B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF48 +:1008C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF38 +:1008D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF28 +:1008E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF18 +:1008F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF08 +:10090000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7 +:10091000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE7 +:10092000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD7 +:10093000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7 +:10094000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB7 +:10095000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA7 +:10096000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF97 +:10097000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF87 +:10098000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF77 +:10099000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF67 +:1009A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57 +:1009B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF47 +:1009C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF37 +:1009D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27 +:1009E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF17 +:1009F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF07 +:100A0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6 +:100A1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE6 +:100A2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD6 +:100A3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC6 +:100A4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB6 +:100A5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA6 +:100A6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF96 +:100A7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF86 +:100A8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF76 +:100A9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF66 +:100AA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 +:100AB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF46 +:100AC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF36 +:100AD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF26 +:100AE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16 +:100AF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF06 +:100B0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5 +:100B1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5 +:100B2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD5 +:100B3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC5 +:100B4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB5 +:100B5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA5 +:100B6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF95 +:100B7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF85 +:100B8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF75 +:100B9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF65 +:100BA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF55 +:100BB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF45 +:100BC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF35 +:100BD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF25 +:100BE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF15 +:100BF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF05 +:100C0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4 +:100C1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE4 +:100C2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD4 +:100C3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC4 +:100C4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB4 +:100C5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA4 +:100C6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF94 +:100C7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF84 +:100C8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF74 +:100C9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF64 +:100CA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF54 +:100CB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF44 +:100CC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF34 +:100CD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF24 +:100CE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF14 +:100CF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF04 +:100D0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3 +:100D1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE3 +:100D2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD3 +:100D3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC3 +:100D4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB3 +:100D5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA3 +:100D6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF93 +:100D7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83 +:100D8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF73 +:100D9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF63 +:100DA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF53 +:100DB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43 +:100DC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF33 +:100DD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF23 +:100DE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF13 +:100DF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF03 +:100E0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2 +:100E1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE2 +:100E2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD2 +:100E3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2 +:100E4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB2 +:100E5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA2 +:100E6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF92 +:100E7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF82 +:100E8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF72 +:100E9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF62 +:100EA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF52 +:100EB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF42 +:100EC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF32 +:100ED000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF22 +:100EE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF12 +:100EF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02 +:100F0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1 +:100F1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE1 +:100F2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD1 +:100F3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC1 +:100F4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB1 +:100F5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA1 +:100F6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF91 +:100F7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81 +:100F8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF71 +:100F9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF61 +:100FA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF51 +:100FB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF41 +:100FC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF31 +:100FD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF21 +:100FE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF11 +:100FF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF01 +:10100000D10020402C100060000000000000000013 +:1010100020100060001000600000000000000000D0 +:10102000000000607031000000000000000001209E +:1010300035100060764B0720764C4FF42A01764A33 +:101040005C64186499639546744A75498A420FD066 +:10105000744B9A420CD2D4430846234423F0030332 +:1010600004330B4450F8041B984242F8041BF9D196 +:101070006D4A6E498A420FD06D4B9A420CD2D443CE +:101080000846234423F0030304330B4450F8041BA5 +:10109000984242F8041BF9D1664A674B9A420BD238 +:1010A000D04311460024034423F0030304331344C4 +:1010B00041F8044B8B42FBD1604A4FF47001604B06 +:1010C000116003F530715F4A43F8042F9942FBD158 +:1010D0005D4B80215D4A03F8011B9342FBD15C4CC0 +:1010E0004FF080375B4E4FF0FF32DFF8A8A10025AC +:1010F000DFF8A491DFF8A481574B4549C3F800A05D +:10110000C4F80471C4F80091C4F8F470C4F8F08015 +:10111000F36923F07F0343F04003F361736A23F024 +:101120007F0343F0400373628A66CA660A674A67B0 +:1011300000F0B6F8494A6320494B4A49106003223F +:101140001D60CAF8381043F8082C4749474A4848F8 +:10115000C3F8082D0B68474A43F08073CAF83C0077 +:1011600045480B601368454943F001031360036869 +:101170000B6000F0E5F8C4F804714148C4F8009130 +:10118000C4F8F470C4F8F08000F03AFA00BF00BF71 +:1011900000BF00BFF16E3B4A41F440513A4BF1664B +:1011A0001560C2F80851C2F81851C2F82851C2F8A7 +:1011B00038519A6BD20708D442F615623349596503 +:1011C0001A659A6B42F001029A632F4A304C936879 +:1011D00043F00113936000F01BFA2368132BFCD932 +:1011E00000F05CF900F0D0F900F016FA00F0DAF83F +:1011F00000F0FEF92368B3F5967FFBD300F0FCF90D +:1012000000F00EFA00F018FA00F012FAFAE700BF48 +:1012100000C00A40ABAAAAAA008007200000000074 +:10122000501600605017000000000020A42D006040 +:10123000D0030020D0030020C032002088ED00E061 +:10124000FC0F00201D05000000E400E0A0E400E029 +:1012500000800D4000C00F4008ED00E014E000E009 +:1012600018E000E095100000FCED00E000002020F8 +:1012700099110000001000E0041000E0A40D00200F +:101280000046C3230040084000400D400000C05607 +:10129000A80D0020001000201B1018200C0D1113A9 +:1012A000F0B5194A0021194B4FF0100E18480124CF +:1012B000184E194D0160194FC2F800E01E6015600C +:1012C000174E184D1F601660174F1D60174E184DB2 +:1012D00017601E60174F1560174E184D1F6016607F +:1012E0001D600460BFF34F8FBFF36F8FC3F8B01161 +:1012F000BFF34F8FBFF36F8F52F8883C43F44033F6 +:1013000042F8883CF0BD00BF9CED00E0A0ED00E09D +:1013100094ED00E0250008031100200021000207E1 +:1013200012000020250008131300202027000B13B3 +:101330001400004033001013150000602F000B074D +:10134000F0B4174A40274FF480314FF480564FF4E1 +:1013500000554FF4404443F24200136913F0020F6A +:1013600006D0946151619061136913F0020FF8D1B6 +:1013700013F4005F01D15561EFE713F4805F01D1F1 +:101380005661EAE7002BE8DA13F4803F01D091615F +:10139000E3E75B0601D45761DFE7F0BC704700BFAD +:1013A00000800D40364A03203649F3EE096A13687F +:1013B00023F00103F0B51360C2F89000D1F8E030DB +:1013C000DFF8D4E0DCB2C3F30B202F4FC0EB135394 +:1013D00007EE904AA4F15501CEF80040B8EEE77A46 +:1013E00003EB830407EE900A03FB01F13B6003EB80 +:1013F0008313B8EEE75A07EE901A091B77EE666A78 +:10140000B8EE677A214D07EE901A0B44C5ED006ADD +:10141000F8EE677A1E4EC7EE265A1E4930601068F5 +:1014200087EEA66A07EE903AF8EE677A87EEA67A1C +:1014300075EE855A76EE056AFCEEE55AFCEEE66A34 +:1014400015EE903A40EA035316EE900A77EE057ACD +:10145000136001EA0041D2F81031FCEEE77A0B4349 +:10146000C2F8103117EE903AD2F81011C3F30B0303 +:101470000B43C2F81031136843F002031360F0BD50 +:1014800080810D4000441F40F8030020F403002039 +:10149000FC0300200000FF0FF0030020304B40F65B +:1014A000617270B5C3F8202140F2044500F0ACF839 +:1014B0002C492D48D1F880202C4C42F003022C4BB3 +:1014C000C1F88020C0F86051226813401BB9D0F8E1 +:1014D000A8319A071AD0244B4FF00041234A516398 +:1014E0001A46D3F8401141F00201C3F84011D2F876 +:1014F00040319B07FBD44FF400301E491B4B4FF08B +:101500000042086019209A6300F09AF81A4D0022F0 +:10151000164B4FF08041144C0A26996328461A60F6 +:101520001146C4F8A8614FF4207200F07DF84FF422 +:1015300081064FF4800040F24313104A10492E6098 +:101540002864C4F85851C2F80412C4F848310D4A4E +:101550004FF4003101231160C4F8403170BD00BF69 +:1015600000800D4000C00F4000002E4000900D4054 +:10157000001C1E008CE200E0003000200010002063 +:10158000F90600000CE100E0114B1249D86E0A4642 +:1015900040F4403030B4D86640F2B765D86EA0242D +:1015A00040F44070D8664D648C64936C1B06FCD488 +:1015B000094B40F2B760A021064A58649964936CC5 +:1015C00013F08003FBD1054A137030BC704700BF95 +:1015D00000C00F4000400C4000800C40A10D0020D6 +:1015E000F8B500BFF8BC08BC9E467047FFFFFFFF80 +:1015F0005FF800F0390500005FF800F055160000B4 +:101600005FF800F0D90100005FF800F0F90D00006C +:101610005FF800F0350500005FF800F06913000086 +:101620005FF800F0010100005FF800F0A51600006F +:101630005FF800F0B91100005FF800F0050100004C +:101640005FF800F05912000045000000FFFFFFFFA7 +:10165000000000000000000000000000000000008A +:10166000000000000000000000000000000000007A +:1016700010B5054C237833B9044B13B10448AFF3CC +:1016800000800123237010BDD00300200000000063 +:1016900054170000084B10B51BB108490848AFF3B8 +:1016A00000800848036803B910BD074B002BFBD02E +:1016B000BDE81040184700BF00000000D403002020 +:1016C00054170000D00300200000000008B5174B9D +:1016D0000121187800F00AFA154B0121187800F062 +:1016E00005FA144B0121187800F000FA124B012181 +:1016F000187800F0FBF9114B0121187800F0F6F989 +:101700000F4B0121187800F0F1F90E4B00211878E9 +:1017100000F0ECF90C4B0021187800F0E7F90B4BC6 +:1017200000211878BDE8084000F0E0B9F40200207C +:10173000FC0200200003002010030020080300200A +:10174000F8020020EC030020EC020020F002002050 +:10175000FFF7BCBF2C4A2D4B2DE9F04714682C4AEB +:10176000DFF8B8A012685746DFF8B490294EDFF8CA +:10177000B480294D93FBF4F393FBF2F4012199F823 +:10178000000000F0ADF9DAF800103268A5FB023273 +:10179000920C04FB02F2DAF800305B1A9A42FAD893 +:1017A000012198F8000000F09BF939683268A5FB28 +:1017B0000232920C04FB02F2DAF800305B1A9A4211 +:1017C000FAD8002199F8000000F08AF939683268E7 +:1017D000A5FB0232920C04FB02F2DAF800305B1A2D +:1017E0009A42FAD8002198F8000000F079F9396897 +:1017F0003268A5FB0232920C04FB02F2DAF80030E8 +:101800005B1A9A42FAD8B9E70C03002000879303C9 +:10181000040300201803002083DE1B43041000E0B3 +:1018200008030020F80200208C4A8D4B90422DE9DD +:10183000F0438C4D5C699969EF681DD98A4B9842D9 +:1018400040F20181894B40F22766DFF860E20344F1 +:10185000874D1A0AAEFB0232D30903EB830303EB75 +:10186000830202F2E243B34228BF3346A3F5487332 +:10187000A5FB0336F60804E07E4EB04294BF062670 +:101880000E26774A07F01F0ED2F88030B64543F097 +:10189000C003C2F880300AD2724B27F01F071A46E5 +:1018A0003743DF601368002BFCDA07F01F0E14F0DB +:1018B00000732ED1704D714AD5F810C015460CEA50 +:1018C0000202AA420ABF4FF0C0534FF480520022D6 +:1018D00084EA030515F0605F06D024F0605403F03D +:1018E00060535F4D1C436C6181EA020313F4405F57 +:1018F00008D05B4B21F4405111431A469961936C17 +:101900001D07FCD444F00074554A5461936C990649 +:10191000FCD401215A4D0A4601FB02F300FB03F3FC +:10192000AB4209D8072A00F28480013201FB02F39E +:1019300000FB03F3AB42F5D95248534D1844A5FBC5 +:101940000030030D6C2B79D8352B7ED8DFF8608101 +:1019500036234E48DFF820C14D4DDCF80090B0FB37 +:10196000F2F009EA05054545B0FBF1F00BD043F470 +:1019700000534FF480586546CCF80080CCF8003016 +:101980002B68002BFCDADFF8D8C0013ADCF8103005 +:1019900003F00703934207D002F007026546CCF834 +:1019A0001020AB6CDB03FCD40139890284EA01030B +:1019B00013F4E05F0AD02A4B24F4E05401F4E05120 +:1019C0001A460C435C61936C9907FCD4314B324945 +:1019D0000344DB09A1FB0331090B042928BF0421BF +:1019E0004B1E1B0284EA030212F4407F06D024F44B +:1019F000407403F440731A4A1C435461184B24F09A +:101A000000741A465C61936C9B06FCD4B0FBF1F148 +:101A1000224A7645224B1060196008D2114B27F0FC +:101A20001F071A463743DF601368002BFCDABDE856 +:101A3000F083042980D8013101226DE7DFF874803A +:101A40006C23184886E712261BE71748DFF86880E2 +:101A500000FB03F043EA08087CE700BF00A4781FFE +:101A600000C00F40000008400046C32300BA3CDC21 +:101A70001F85EB5100366E0100800D404030008024 +:101A8000FFB19F26808D5B00819F5E1600B29F266E +:101A90007F3001807FD1F0089F10E50018030020FF +:101AA0001403002000643F4D001BB70023B24C001C +:101AB000362000806C200080002000800001074B51 +:101AC0001A181B58D2685868104202D011B9C3F8CE +:101AD00088207047C3F88420704700BF00000020B2 +:101AE000272801D8FFF7EABF704700BF27281CD876 +:101AF00000011A4A012902EB0003105810B415D056 +:101B0000042913D0DC68426822EA040242609A6821 +:101B1000E9B102291ED003295B685DF8044B0CBFB4 +:101B20000F491049116015221A607047DA680129BF +:101B3000446842EA040242609A6808D040F63801DC +:101B400011605B6815225DF8044B1A6070473821FC +:101B50001160F6E704491160F3E700BF00000020C0 +:101B6000383001003800010038F0010004207146CF +:101B7000084203D0EFF3098000F008B8EFF30880C3 +:101B800000F004B8704700BF704700BF1B4B052131 +:101B90001B4A382030B5C2F848110821C2F8380372 +:101BA00083B05A68174C0A4317485A60C3F8841028 +:101BB000C3F888102368834202D91448FFF734FE23 +:101BC0000E4D08240020124A1249C5F884400190A5 +:101BD000019B934205D8019B01330193019B9342E2 +:101BE000F9D9C5F888400190019B8B42EDD8019B43 +:101BF00001330193019B8B42F9D9E6E700C01B40FA +:101C000000801F4018030020FF45C32300A3E111FB +:101C10003F420F003F548900836B30B41BB15368BF +:101C200043F40043536072B6446B9CB1104B226086 +:101C3000D3F8B0410C4217D1D3F8404144F480446A +:101C4000C3F84041D3F8B851D3F840416404F3D508 +:101C5000294209D10023064C8260C360D4F8B03118 +:101C60001943C4F8B0110263426362B630BC7047D6 +:101C700000002E4038B50546036B06E0AA6B1C68D1 +:101C800090476A6B2346944208D0184633B1012B23 +:101C900004D05A681206F1D52B6338BD00232B639C +:101CA0006B6338BDF0B5F1B9224C23490020234BBA +:101CB00001228025A1600A601A464D60E060D3F8D9 +:101CC000BC41886044F001141D4DC3F8BC41D3F8F9 +:101CD000B01141F00101C3F8B0112860D2F8B03161 +:101CE000002BFBD1F0BD0904164B144D0126114CFD +:101CF00041F0800100221E60596000F5805CE264C2 +:101D000000F5005EA36400F54057D5F8B01100F56A +:101D100080462A46986041F48031C3F80CC0C3F86D +:101D200010E05F619E61C5F8B011D2F8B031002BB0 +:101D3000FBD1BAE7003000202020002000002E4018 +:101D40000C040020002000202DE9F04FBB4C83B094 +:101D5000D4F8448118F0010FC4F844815ED0D4F85F +:101D6000AC31002B55D04FF00119DFF8F0B2264608 +:101D7000B34FCA46B96AFA6AC4F8AC31D4F84031F4 +:101D800043F40053C4F84031D4F840319D04F5D5F4 +:101D9000D6F8403123F40053C6F84031C6F8B4A158 +:101DA000D4F8B43113F00113FAD188B240F281654E +:101DB000CBF80030A84200F29680B0F5D06F80F0EA +:101DC000D881B0F5817F00F0578100F2D58080285E +:101DD00000F02681822840F0C78092B202F07F0195 +:101DE000072900F2C18089009648974D0844016890 +:101DF00010062B706B7040F1E581C90301D50123FA +:101E00002B7002219048FFF74DFFD4F8AC31002B26 +:101E1000B0D18A4AD2F8BC31002B44D118F0400F1F +:101E200018D0864BD3F8AC111A46C3F8AC11D3F8CE +:101E3000BC11C3F8BC11D2F8B041804B002CFAD1D0 +:101E40004FF0FF32C3F8B421D3F8843100F074FBB3 +:101E50007E4B1C6018F0807F03D07D4B1B6803B164 +:101E6000984718F0007F03D07A4B1B6803B198475E +:101E700018F0040F02D0714BD3F884316F4BD3F8B4 +:101E8000482112060CD518F0800F09D072490A7843 +:101E9000002A00F08E81531EDBB20B7003B9FBBE2B +:101EA00003B0BDE8F08F6D49C2F8BC310868034249 +:101EB00040F0CC81654A14681C40AFD0630700F144 +:101EC000E281670300F1EF81260700F1E881250335 +:101ED00000F1E181E00600F1DA81E1029ED560487F +:101EE000FFF7C8FE9AE742F22105A84238D06FD822 +:101EF000B0F5086F00F02F81B0F5106F34D1C1F349 +:101F00000741584A584811705849594A0193C6F830 +:101F1000C801C6F8CC11C6F8D02100F00FFB554A15 +:101F2000019B80210120FB6410605160D6F8BC1138 +:101F3000936041F00111BA64C6F8BC11D6F8B02123 +:101F400042F48032C6F8B021444A1360D4F8B0316C +:101F5000002BFBD1D4F8AC31002B7FF40BAF58E74A +:101F6000100C072800F03281C4F8C091D4F8AC31CD +:101F7000002B7FF4FFAE4CE740F20235A84200F0A0 +:101F8000B780B0F5A06FEFD13A4A80200125FB64FD +:101F900050601560D6F8BC01936040F00113BA643C +:101FA000C6F8BC31D6F8B03143F48033C6F8B0314E +:101FB000D4F8B031002BFBD10B0C5B0643F08073DF +:101FC000C6F85431D4F8AC31002B7FF4D3AE20E7FF +:101FD00042F22123984200F0828042F2213398425B +:101FE000C2D1244B01218022196000215A60D4F80B +:101FF000BC21BB6442F001129960F964C4F8BC21B1 +:10200000D4F8B03143F48033C4F8B031D4F8B031EF +:10201000002BFBD1D4F8AC31002B7FF4ABAEF8E64B +:10202000094A0221104613705370FFF73BFED4F8A3 +:10203000AC31002B7FF49EAEEBE600BF00002E40DB +:1020400000300020C0012E402004002004040020A5 +:102050000004002008040020100400200C040020CC +:1020600040320020300400200200CC00C8000200F2 +:102070000200C8002020002092B202F07F03072B4C +:102080003FF672AF12F0800F4FEA8303884A4FF099 +:1020900001011A44136814BF23F4803323F00103B1 +:1020A00013608022834B196000215A60D4F8BC2150 +:1020B000BB6442F001129960F964C4F8BC21D4F801 +:1020C000B03143F48033C4F8B031D4F8B031002BD0 +:1020D000FBD1D4F8AC31002B7FF44CAE99E6764BB3 +:1020E000C1F30741754A1868754B1060197078E79D +:1020F00092B202F07F03072B3FF636AF12F0800F4B +:102100004FEA83036A4A4FF001011A44136814BF6F +:1021100043F4803343F0010313608022654B196060 +:1021200000215A60D4F8BC21BB6442F001129960CE +:10213000F964C4F8BC21D4F8B03143F48033C4F856 +:10214000B031D4F8B031002BFBD1D4F8AC31002B36 +:102150007FF410AE5DE65B4A01215B4B12781846B6 +:102160001A70FFF79FFDD4F8AC31002B7FF402AE5C +:102170004FE6564B586800283FF4F6AE090C1FFA9C +:1021800082FE04E00C33586800283FF4EDAE1D8851 +:102190008D42F7D15D887545F4D1090A120C0329E7 +:1021A0000CBF01781989914228BF1146FFF77AFDCB +:1021B0002BE6D3F8482122F08002C3F8482103B06F +:1021C000BDE8F08FCA077FF51CAE18E6404D012030 +:1021D000FB6029603F4B4049186059603F49D6F881 +:1021E000B0016A6001F5005E40F00102A1F5005007 +:1021F00001F58055BB609860A1F58050D860C6F8A5 +:10220000B02119615D61C3F818E0D4F8B031002B3A +:10221000FBD1284A012048F28001FB6410604FF492 +:1022200080305160D6F8BC11936041F00113BA645C +:10223000C6F8BC31D6F8B0310343C6F8B031CBF89C +:102240000000D4F8B031002BFBD1DEE5204C42F287 +:102250002100002524880D6084427FF42BAE2049A4 +:10226000204C03C90D0C86282060A180A5717FF445 +:1022700021AED2F8481150241B4841F08001C2F829 +:102280004811047016E61948FFF7F4FC19E61848DF +:10229000FFF7F0FC21E61748FFF7ECFC1AE61648BA +:1022A000FFF7E8FC13E61548FFF7E4FC0CE600BF77 +:1022B000C0012E4020200020A80D002088320020E0 +:1022C000800D002030040020200400208002002027 +:1022D000180400200020002080000700282400208F +:1022E000280400208032002010040020003100204B +:1022F00000320020C0310020803100204031002019 +:10230000002AA0F102022DE9F04714BF00274FF088 +:102310000057022A01D9BDE8F0874FEAC01ADFF85A +:1023200040900D4604460AEB0906002140229846DB +:10233000304600F0DFFC012047EA05414AF8091069 +:10234000C6F83880B060B8F1000FE4D0034BA0406D +:102350001C6820431860BDE8F08700BF040400201B +:1023600000300020002AA0F102022DE9F04714BF3E +:1023700000274FF00057022A01D9BDE8F08740221C +:10238000C501DFF8449088461544002104461E46E6 +:1023900005EB090A504600F0ADFC012247EA08416E +:1023A00045F80910CAF83860CAF80820002EE4D0B1 +:1023B00004F11000034B8240186802431A60BDE824 +:1023C000F08700BF04040020003000201204816068 +:1023D000C36142F08002F0B44260012701F58056EB +:1023E00001F5005501F5405401F580420760C660D3 +:1023F000056144618261F0BC704700BF831E022BFF +:1024000000D9704730B4064B00F1100401250A468C +:1024100003EBC01005FA04F130BCFFF7FDBB00BFB1 +:1024200040300020831E022B00D9704710B4054BAA +:1024300001240A4604FA00F103EBC0105DF8044BD6 +:10244000FFF7EABB00300020124A134BD2F82002FB +:1024500020F07F40984210B584B002D800EB800095 +:1024600040000E4C01A90A2200F07EFA01A90023C7 +:10247000204611F8012B01333AB10A2B20F8022F24 +:10248000F7D11623237004B010BD5B00DBB22370BC +:1024900004B010BD00441F407F969800B403002094 +:1024A0004368C269C3F30E43054930B4C3F1400326 +:1024B000044C002521F8123024F8125030BC70472B +:1024C000000C0020F80B0020F8B5154B1B783BB929 +:1024D00003F0FF04134B1B7813B1134D2A8802B984 +:1024E000F8BD124F2346124EC2F58072397811485A +:1024F00006EB411600EB01213046FFF767FF31463E +:102500000420FFF77BFF3B780133DBB2062B98BF3B +:102510003B704FF0000388BF3C702B80F8BD00BFBC +:10252000340B002030040020800C0020350B0020EC +:10253000A00C002034040020704700BF0021E022DE +:102540002048F8B50C46204E204D00F0D3FB204F1C +:102550002146204B6022347028461F4E1C8000F01C +:10256000C9FB23462246102102203C60BC80346017 +:10257000B480FFF7F7FE2246184B40210320FFF7F7 +:10258000BFFE2346224640210420FFF7EBFE2346F0 +:10259000402228461249FFF719FF29460320FFF77A +:1025A00041FF104B4A22104910480860C3F884408C +:1025B000C3F88020D3F8482142F08072C3F8482144 +:1025C000F8BD00BFA00C0020350B0020200C00201F +:1025D000000C0020800C0020F80B0020510E0000A1 +:1025E000380B002000002E4000040020790E00006F +:1025F000024A034B10881B88C01A7047000C002049 +:10260000F80B002010B4EFF3108272B6437F33B999 +:10261000017F012908D0032910D00123437702B993 +:1026200062B65DF8044B7047114C2168A1B11149A5 +:1026300043610B68086083615861EEE70E4C2168C6 +:1026400081B10E4943610B680860836158610C4B8E +:102650004FF080511960E0E7064B416181612060D5 +:102660001860DAE7054B4161816120601860EEE790 +:10267000940D0020900D0020840D0020880D002076 +:1026800004ED00E010B4047F4160022CC26003D06E +:102690005DF8044BFFF7B6BF83685DF8044B18473D +:1026A00070B5EFF3108172B60C4C23688BB10C4EF1 +:1026B00000255A6922607AB1956101B962B65D77E9 +:1026C00018469B689847EFF3108172B62368002B79 +:1026D000EFD101B962B670BD3260EEE7840D002023 +:1026E000880D0020FFF7DCBF184A30B41468002CB6 +:1026F00028D0036821688B420FD2CB1A0021846056 +:10270000C1602360E0601060022330BC0375704735 +:102710000360144611688B4208D3A2685B1A002A32 +:10272000F6D18260C4600360A060EDE7D568CB1A83 +:1027300082600222C560E060C16888602360027523 +:1027400030BC70478460C4601060DDE78C0D0020F1 +:10275000F8B5224E34682CB32368002B3AD11D46BD +:102760001F4F04E03468ECB12368002B32D1A3681A +:1027700003B1DD6020693360036825751B68BB42C7 +:1027800021D1037F4560022BC46020D0FFF73AFFC0 +:102790006368002BE6D023602046FFF7A5FF34686E +:1027A000002CE1D1EFF3108372B60E4A00211068BD +:1027B000116003B962B628B18468FFF795FF20461F +:1027C0000028F9D1F8BD224600219847E0E7836848 +:1027D0009847DDE7013B2360E4E700BF8C0D002054 +:1027E000351000009C0D0020044A054B1168054A75 +:1027F0001960136801331360FFF7AABF041000E0EB +:10280000A40D0020A80D002070B5214C237883B9B9 +:10281000204B01221B7822701BBB1F4B1B78002B07 +:1028200029D11E4B00211A68217012B1EFF30582E5 +:1028300002B170BDEFF3108072B61A68F2B1184C95 +:102840002178D9B90126556926701D60D5B1A961D5 +:1028500000B962B6002593681046557798472570F1 +:1028600070BDFFF7C5FE0028D7D000F015FA0A4B5F +:102870001B78002BD5D000F0FBF9D2E70028D8D187 +:1028800062B670BD074B1D600028E3D1E1E700BFD1 +:10289000A00D0020CA030020C80D0020940D0020C8 +:1028A000980D0020900D0020002852D02DE9F04F07 +:1028B000814683B0274C0120274D284E54E8003F25 +:1028C0002A68316844E80003002BF7D1244F4FF405 +:1028D0007A7E2448D7F800C0BB4607F1C647036894 +:1028E000C1EB0C0107F5DE1707F67F67A7FB03C3F3 +:1028F000BA4601279B0CB1FBF3F30EFB023854E8F8 +:10290000003F2A68316844E80073002BF7D1DBF8F8 +:1029100000C04FF47A7E03680EFB02F2C1EB0C019B +:10292000AAFB033EC8EB02034FEA9E42B1FBF2F161 +:10293000CA18B2F57A7F07D3B9F1010908F57A7898 +:10294000DDD103B0BDE8F08F0190FFF75DFF019886 +:10295000D5E770478C320020A80D0020A40D002080 +:10296000041000E018030020F0B44E1E0025374686 +:1029700000E00135B0FBF2F302FB130000F1370475 +:10298000092800F13000E4B298BFC4B2184607F835 +:10299000014F002BEDD14A1953704DB1013316F898 +:1029A000014F1778E81A3770834202F80149F5DBC6 +:1029B0000846F0BC704700BFA4484FF00F0CA44B72 +:1029C000826F42F47F02F0B582670025D0F8802044 +:1029D0004FF470469F4C4FF4604E29464FF4806789 +:1029E00014432A46C0F88040A3F88C6148F2B82608 +:1029F000A3F88EC1A3F89051B3F8880180B240F0DB +:102A0000F000A3F8880101EB4100914B0131002552 +:102A100040011C4604290344A3F804E0DF805A84E3 +:102A20001A865A805A81DE815A82DA825A83DA8380 +:102A3000E9D1B4F888014FF00F0C874B4FF4704682 +:102A400080B229464FF460472A4640EA0C004FF412 +:102A5000806EA4F88801B4F8880180B240F47060F8 +:102A6000A4F88801A3F88C6148F2B826A3F88EC1B7 +:102A7000A3F89051B3F8880180B240F0F000A3F8B9 +:102A8000880101EB4100744B0131002540011C46D7 +:102A9000042903449F80A3F806E05A841A865A80CA +:102AA0005A81DE815A82DA825A83DA83E9D1B4F814 +:102AB00088014FF00F0C694B4FF4704680B22946E5 +:102AC0004FF460472A4640EA0C004FF4806EA4F8A9 +:102AD0008801B4F8880180B240F47060A4F88801DD +:102AE000A3F88C6148F2B826A3F88EC1A3F89051E0 +:102AF000B3F8880180B240F0F000A3F8880101EB40 +:102B00004100564B0131002540011C460429034475 +:102B10009F80A3F806E05A841A865A805A81DE8183 +:102B20005A82DA825A83DA83E9D1B4F888014FF005 +:102B30000F0C4B4B4FF4704780B229464FF4604660 +:102B40002A4640EA0C004FF4806EA4F88801B4F8DD +:102B5000880180B240F47060A4F88801A3F88C71F9 +:102B600048F2B827A3F88EC1A3F89051B3F88801B2 +:102B700080B240F0F000A3F8880101EB4100384B2F +:102B8000013140011C46042903449E80A3F806E05D +:102B90005A841A865A805A81DF815A82DA825A838D +:102BA000DA83EAD1B4F888310F27002241F2010616 +:102BB0009BB245F6C05E114643F226053B43A4F89E +:102BC0008831B4F888319BB243F47063A4F888313B +:102BD0005001244B01320344042A99815981DF8139 +:102BE0009E82A3F806E0198019829D81F0D100220F +:102BF0000F2741F2010645F6C055114643F226045F +:102C00005001194B01320344042A99815981DF8113 +:102C10009E82DD80198019829C81F1D100220F27CC +:102C200041F2010645F6C055114643F22604500113 +:102C30000E4B01320344042A99815981DF819E821F +:102C4000DD80198019829C81F1D1F0BD00C00F4058 +:102C500000C03D40000003FC00003E4000403E40FC +:102C600000803E4000C01D4000001E4000401E404D +:102C700038B5074B1C784CB1064D55F8043F002B76 +:102C8000FBD09847631E13F0FF04F6D138BD00BF98 +:102C9000C80D0020A80D0020014B00221A707047BB +:102CA000CA03002070B50F4E0F4D761BB61018BF2B +:102CB000002405D0013455F8043B9847A642F9D1C9 +:102CC0000A4E0B4D761B00F063F8B61018BF0024B7 +:102CD00006D0013455F8043B9847A642F9D170BD9F +:102CE00070BD00BF48160060481600604C160060BA +:102CF0004816006070B4840746D0541E002A41D0A4 +:102D0000CDB2034602E0621EE4B3144603F8015B51 +:102D10009A07F8D1032C2ED9CDB245EA05250F2C00 +:102D200045EA054519D903F110022646103E0F2E3B +:102D300042F8105C42F80C5C42F8085C42F8045C13 +:102D400002F11002F2D8A4F1100222F00F0204F0F6 +:102D50000F041032032C13440DD91E462246043AA8 +:102D6000032A46F8045BFAD8221F22F00302043239 +:102D7000134404F003042CB1C9B21C4403F8011B32 +:102D8000A342FBD170BC704714460346C2E700BFA4 +:102D90005FF800F0E1150060000000000000000096 +:042DA000F8B500BFC3 +:102DA40000000042C8801F40B8821F400800000095 +:102DB40000000042C4801F40B4821F400400000091 +:102DC40000C0004224801F4014821F4010000000F5 +:102DD40000C0004228801F4018821F4020000000CD +:102DE40000C000422C801F401C821F404000000095 +:102DF40000C0004234801F4024821F4000010000B4 +:102E04000040004264811F4054831F4000040000BE +:102E14000040004280811F4070831F400000020078 +:102E2400004000427C811F406C831F400000010071 +:102E34000040004268811F4058831F400008000082 +:102E4400004000423C811F402C831F4001000000D1 +:102E54000040004244811F4034831F4004000000AE +:102E64000040004240811F4030831F4002000000A8 +:102E74000040004248811F4038831F400800000082 +:102E84000000004204811F40F4821F40000004003F +:102E94000000004208811F40F8821F400000080023 +:102EA4000000004218811F4008831F40000080007A +:102EB4000000004214811F4004831F4000004000B2 +:102EC4000000004200811F40F0821F400000020009 +:102ED40000000042FC801F40EC821F400000010003 +:102EE4000000004224811F4014831F40000000049E +:102EF4000000004228811F4018831F400000000882 +:102F0400000000421C811F400C831F400000000190 +:102F14000000004220811F4010831F400000000277 +:102F240000000042EC801F40DC821F4000100000C3 +:102F340000000042F0801F40E0821F40002000009B +:102F44000000004234811F4024831F4000000040E1 +:102F54000000004238811F4028831F400000008089 +:102F64000080004294801F4084821F4000000400BF +:102F740000C0004290801F4080821F4000000080FB +:102F840000800042A8801F4098821F4000008000FB +:102F940000800042A4801F4094821F400000400033 +:102FA400004000426C811F405C831F400010000001 +:102FB40000C0004230801F4020821F40800000007B +:102FC40000800042C8811F40B8831F400080000079 +:102FD40000800042C4811F40B4831F4000400000B1 +:102FE40000800042C0811F40B0831F4000200000C9 +:102FF40000800042BC811F40AC831F4000100000D1 +:1030040000800042D0811F40C0831F4000000200A6 +:1030140000800042CC811F40BC831F40000001009F +:1030240000010000840300201200000000060000DC +:103034001C0300200A0000000002000040030020DE +:10304400430000000007000040030020430000008C +:1030540000030000B0030020000000000103090485 +:103064002803002000000000020309049803002044 +:103074000000000003030904B40300200000000062 +:10308400000000000000000000000000010000003B +:1030940002000000170000001200000016000000EB +:1030A400150000001E0000001300000000200000B6 +:1030B400140000000029DE07007B9A170A060002AC +:1030C4000200004001000000180354006500650080 +:1030D4006E00730079006400750069006E006F0073 +:1030E40009024300020100C0320904000001020287 +:1030F4000100052400100105240101010424020635 +:1031040005240600010705820310001009040100CC +:10311400020A0000000705030240000007058402BC +:10312400400000001201000202000040C0168304A7 +:103134007902010203010000160355005300420006 +:103144002000530065007200690061006C000000FB +:10315400040309040C030000000000000000000048 +:10316400000000000000000000000100000000005A +:040000056000100087 +:00000001FF diff --git a/tools/odrive/tests/encoder_test.py b/tools/odrive/tests/encoder_test.py new file mode 100644 index 00000000..efde608b --- /dev/null +++ b/tools/odrive/tests/encoder_test.py @@ -0,0 +1,84 @@ + +import test_runner + +import time +from math import pi +import os + +from fibre.utils import Logger +from test_runner import EncoderTestContext, test_assert_eq, program_teensy + +def modpm(val, range): + return ((val + (range / 2)) % range) - (range / 2) + +class TestIncrementalEncoder(): + + def is_compatible(self, enc_ctx: EncoderTestContext): + return True + + def run_delta_test(self, encoder, true_cps, with_cpr): + encoder.config.cpr = with_cpr + + for i in range(100): + now = time.monotonic() + new_shadow_count = encoder.shadow_count + new_count_in_cpr = encoder.count_in_cpr + new_phase = encoder.phase + new_pos_estimate = encoder.pos_estimate + new_pos_cpr = encoder.pos_cpr + + if i > 0: + dt = now - before + test_assert_eq((new_shadow_count - last_shadow_count) / dt, true_cps, accuracy = 0.05) + test_assert_eq(modpm(new_count_in_cpr - last_count_in_cpr, with_cpr) / dt, true_cps, accuracy = 0.3) + #test_assert_eq(modpm(new_phase - last_phase, 2*pi) / dt, 2*pi*true_rps, accuracy = 0.1) + test_assert_eq((new_pos_estimate - last_pos_estimate) / dt, true_cps, accuracy = 0.3) + test_assert_eq(modpm(new_pos_cpr - last_pos_cpr, with_cpr) / dt, true_cps, accuracy = 0.3) + test_assert_eq(encoder.vel_estimate, true_cps, accuracy = 0.05) + + before = now + last_shadow_count = new_shadow_count + last_count_in_cpr = new_count_in_cpr + last_phase = new_phase + last_pos_estimate = new_pos_estimate + last_pos_cpr = new_pos_cpr + + time.sleep(0.01) + + def run_test(self, enc_ctx: EncoderTestContext, logger: Logger): + true_cps = 8192*-0.5 # counts per second generated by the virtual encoder + # TODO: read teensy config from YAML file + if enc_ctx.num == 0: + hexfile = 'enc0_sim_-4096cps.ino.hex' + else: + hexfile = 'enc1_sim_-4096cps.ino.hex' + program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) + time.sleep(1.0) # wait for PLLs to stabilize + + encoder = enc_ctx.handle + + # The true encoder count and PLL output should be roughly the same. + # At 8192 CPR and 0.5 RPM, the delta because of sequential reading is + # around 3.25 counts. The exact value depends on the connection. + # The tracking error of the PLL is below 1 count. + + #logger.debug("check if count_in_cpr == pos_cpr") + #configured_cpr = 8192 + #encoder.config.cpr = configured_cpr + #expected_delta = true_cps/1200 + #for _ in range(1000): + # first = enc_ctx.handle.axis0.encoder.count_in_cpr + # second = enc_ctx.handle.axis0.encoder.pos_cpr + # test_assert_eq(modpm(second - first, configured_cpr), expected_delta, range=abs(true_cps/500)) + # time.sleep(0.001) + + logger.debug("check if variables move at the correct velocity (8192 CPR)...") + self.run_delta_test(encoder, true_cps, 8192) + logger.debug("check if variables move at the correct velocity (65536 CPR)...") + self.run_delta_test(encoder, true_cps, 65536) + encoder.config.cpr = 8192 + + + +if __name__ == '__main__': + test_runner.run(TestIncrementalEncoder()) diff --git a/tools/odrive/tests/nvm_test.py b/tools/odrive/tests/nvm_test.py new file mode 100644 index 00000000..9e416898 --- /dev/null +++ b/tools/odrive/tests/nvm_test.py @@ -0,0 +1,45 @@ + +import test_runner + +import time +from math import pi +import os + +import fibre +from fibre.utils import Logger +from test_runner import ODriveTestContext, test_assert_eq + +class TestStoreAndReboot(): + """ + Stores the current configuration to NVM and reboots. + """ + + def is_compatible(self, odrive: ODriveTestContext): + return True + + def run_with_values(self, values, odrive: ODriveTestContext, logger: Logger): + logger.debug("storing configuration and rebooting...") + + for value in values: + odrive.handle.config.brake_resistance = value + + odrive.handle.save_configuration() + try: + odrive.handle.reboot() + except fibre.ChannelBrokenException: + pass # this is expected + odrive.handle = None + time.sleep(2) + + odrive.make_available(logger) + + logger.debug("verifying configuration after reboot...") + test_assert_eq(odrive.handle.config.brake_resistance, values[-1], accuracy=0.01) + + def run_test(self, odrive: ODriveTestContext, logger): + self.run_with_values([0.5, 1.0, 1.5], odrive, logger) + self.run_with_values([2.5, 3.7], odrive, logger) + self.run_with_values([0.47], odrive, logger) + +if __name__ == '__main__': + test_runner.run(TestStoreAndReboot()) diff --git a/tools/odrive/tests.py b/tools/odrive/tests/old_tests.py similarity index 92% rename from tools/odrive/tests.py rename to tools/odrive/tests/old_tests.py index 264fa62b..cf22ed10 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests/old_tests.py @@ -17,31 +17,10 @@ print = functools.partial(print, flush=True) import abc ABC = abc.ABC -class TestFailed(Exception): - def __init__(self, message): - Exception.__init__(self, message) class PreconditionsNotMet(Exception): pass -class ODriveTestContext(): - def __init__(self, name: str, yaml: dict): - self.handle = None - self.yaml = yaml - self.name = name - self.axes = [] - for axis_idx, axis_yaml in enumerate(yaml['axes']): - axis_name = (name + "." + axis_yaml['name']) if 'name' in axis_yaml else '{}.axis{}'.format(name, axis_idx) - self.axes.append(AxisTestContext(axis_name, axis_yaml, self)) - - def rediscover(self): - """ - Reconnects to the ODrive - """ - self.handle = odrive.find_any( - path="usb", serial_number=self.yaml['serial-number'], timeout=15)#, printer=print) - for axis_idx, axis_ctx in enumerate(self.axes): - axis_ctx.handle = self.handle.__dict__['axis{}'.format(axis_idx)] class AxisTestContext(): def __init__(self, name: str, yaml: dict, odrv_ctx: ODriveTestContext): @@ -51,24 +30,6 @@ class AxisTestContext(): self.lock = threading.Lock() self.odrv_ctx = odrv_ctx -def test_assert_eq(observed, expected, range=None, accuracy=None): - sign = lambda x: 1 if x >= 0 else -1 - - # Comparision with absolute range - if not range is None: - if (observed < expected - range) or (observed > expected + range): - raise TestFailed("value out of range: expected {}+-{} but observed {}".format(expected, range, observed)) - - # Comparision with relative range - elif not accuracy is None: - if sign(observed) != sign(expected) or (abs(observed) < abs(expected) * (1 - accuracy)) or (abs(observed) > abs(expected) * (1 + accuracy)): - raise TestFailed("value out of range: expected {}+-{}% but observed {}".format(expected, accuracy*100.0, observed)) - - # Exact comparision - else: - if observed != expected: - raise TestFailed("value mismatch: expected {} but observed {}".format(expected, observed)) - def get_errors(axis_ctx: AxisTestContext): errors = [] if axis_ctx.handle.motor.error != 0: @@ -398,29 +359,6 @@ class TestClosedLoopControl(AxisTest): time.sleep(0.5) request_state(axis_ctx, AXIS_STATE_IDLE) -class TestStoreAndReboot(ODriveTest): - """ - Stores the current configuration to NVM and reboots. - """ - def run_test(self, odrv_ctx: ODriveTestContext, logger): - logger.debug("storing configuration and rebooting...") - odrv_ctx.handle.save_configuration() - try: - odrv_ctx.handle.reboot() - except fibre.ChannelBrokenException: - pass # this is expected - time.sleep(2) - - odrv_ctx.rediscover() - - logger.debug("verifying configuration after reboot...") - test_assert_eq(odrv_ctx.handle.config.brake_resistance, odrv_ctx.yaml['brake-resistance'], accuracy=0.01) - for axis_ctx in odrv_ctx.axes: - test_assert_eq(axis_ctx.handle.encoder.config.cpr, axis_ctx.yaml['encoder-cpr']) - test_assert_eq(axis_ctx.handle.motor.config.phase_resistance, axis_ctx.yaml['motor-phase-resistance'], accuracy=0.2) - test_assert_eq(axis_ctx.handle.motor.config.phase_inductance, axis_ctx.yaml['motor-phase-inductance'], accuracy=0.5) - - class TestHighVelocity(AxisTest): """ Spins the motor up to it's max speed during a period of 10s. diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py new file mode 100644 index 00000000..d24f4c8d --- /dev/null +++ b/tools/odrive/tests/test_runner.py @@ -0,0 +1,195 @@ +# Provides utilities for standalone test scripts. +# This script is not intended to be run directly. + +import sys, os +sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) + +import odrive +from fibre import Logger, Event +import argparse +import yaml +from inspect import signature +import itertools +import time + + +# Assert utils ----------------------------------------------------------------# + +class TestFailed(Exception): + def __init__(self, message): + Exception.__init__(self, message) + +def test_assert_eq(observed, expected, range=None, accuracy=None): + sign = lambda x: 1 if x >= 0 else -1 + + # Comparision with absolute range + if not range is None: + if (observed < expected - range) or (observed > expected + range): + raise TestFailed("value out of range: expected {}+-{} but observed {}".format(expected, range, observed)) + + # Comparision with relative range + elif not accuracy is None: + if sign(observed) != sign(expected) or (abs(observed) < abs(expected) * (1 - accuracy)) or (abs(observed) > abs(expected) * (1 + accuracy)): + raise TestFailed("value out of range: expected {}+-{}% but observed {}".format(expected, accuracy*100.0, observed)) + + # Exact comparision + else: + if observed != expected: + raise TestFailed("value mismatch: expected {} but observed {}".format(expected, observed)) + + +# Test Components -------------------------------------------------------------# + +class ODriveTestContext(): + def __init__(self, yaml: dict): + self.handle = None + self.yaml = yaml + #self.axes = [AxisTestContext(None), AxisTestContext(None)] + self.encoders = [EncoderTestContext(self, 0, None), EncoderTestContext(self, 1, None)] + + def __repr__(self): + return self.yaml['name'] + + def make_available(self, logger: Logger): + """ + Connects to the ODrive + """ + if not self.handle is None: + return + + logger.debug('waiting for {} ({})'.format(self.yaml['name'], self.yaml['serial-number'])) + self.handle = odrive.find_any( + path="usb", serial_number=self.yaml['serial-number'], timeout=30)#, printer=print) + #for axis_idx, axis_ctx in enumerate(self.axes): + # axis_ctx.handle = self.handle.__dict__['axis{}'.format(axis_idx)] + for encoder_idx, encoder_ctx in enumerate(self.encoders): + encoder_ctx.handle = self.handle.__dict__['axis{}'.format(encoder_idx)].encoder + +class EncoderTestContext(): + def __init__(self, odrv_ctx: ODriveTestContext, num: int, yaml: dict): + self.handle = None + self.odrv_ctx = odrv_ctx + self.num = num + + def __repr__(self): + return str(self.odrv_ctx) + '.encoder' + str(self.num) + + def make_available(self, logger: Logger): + self.odrv_ctx.make_available(logger) + +class CANTestContext(): + def __init__(self, yaml: dict): + self.handle = None + self.yaml = yaml + + def make_available(self, logger: Logger): + if not self.handle is None: + return + + # TODO: read bus name from yaml + import can + self.handle = can.interface.Bus(bustype='socketcan', channel='can0', bitrate=250000) + + +# Helper functions ------------------------------------------------------------# + +def yaml_to_test_objects(test_rig_yaml: dict, logger: Logger): + available_test_objects = {} + + def add_component(component): + available_test_objects[type(component)] = available_test_objects.get(type(component), []) + available_test_objects[type(component)].append(component) + + for component_yaml in test_rig_yaml['components']: + if component_yaml['type'] == 'odrive': + odrv_ctx = ODriveTestContext(component_yaml) + add_component(odrv_ctx) + for enc_ctx in odrv_ctx.encoders: + add_component(enc_ctx) + else: + logger.warn('test rig has unsupported component ' + component_yaml['type']) + continue + + + return available_test_objects + +def run_shell(command_line, logger, timeout=None): + """ + Runs a shell command in the current directory + """ + import shlex + import subprocess + logger.debug("invoke: " + str(command_line)) + if isinstance(command_line, list): + cmd = command_line + else: + cmd = shlex.split(command_line) + result = subprocess.run(cmd, timeout=timeout, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + if result.returncode != 0: + logger.error(result.stdout.decode(sys.stdout.encoding)) + raise TestFailed("command {} failed".format(command_line)) + +def program_teensy(hex_file_path, program_gpio: int, logger: Logger): + """ + Programs the specified hex file onto the Teensy. + To reset the Teensy, a GPIO of the local system must be connected to the + Teensy's "Program" pin. This pin must first be manually made available to + user space: + + echo 26 | sudo tee /sys/class/gpio/export + sudo chmod a+rw /sys/class/gpio/gpio26/* + + """ + + # Put Teensy into program mode by pulling it's program pin down + with open("/sys/class/gpio/gpio{}/value".format(program_gpio), "w") as gpio: + gpio.write("0") + time.sleep(0.1) + with open("/sys/class/gpio/gpio{}/value".format(program_gpio), "w") as gpio: + gpio.write("1") + + run_shell(["teensy_loader_cli", "-mmcu=imxrt1062", "-w", hex_file_path], logger, timeout = 5) + time.sleep(0.5) # give it some time to boot + +def run(test_case): + # Parse arguments + parser = argparse.ArgumentParser(description='ODrive automated test tool\n') + parser.add_argument("--ignore", metavar='DEVICE', action='store', nargs='+', + help="Ignore (disable) one or more components of the test rig") + # TODO: implement + parser.add_argument("--test-rig-yaml", type=argparse.FileType('r'), required=True, + help="test rig YAML file") + parser.set_defaults(ignore=[]) + + args = parser.parse_args() + + # Load objects + test_rig_yaml = yaml.load(args.test_rig_yaml, Loader=yaml.BaseLoader) + logger = Logger() + + available_test_objects = yaml_to_test_objects(test_rig_yaml, logger) + + # Compile a list of list of potential objects that might be compatible with this + # test + possible_parameters = [] + sig = signature(test_case.is_compatible) + for param_name in sig.parameters: + param_type = sig.parameters[param_name].annotation + possible_parameters.append(available_test_objects[param_type]) + + # For each combination, check if the test is compatible with these objects + for param_combination in itertools.product(*possible_parameters): + if not test_case.is_compatible(*param_combination): + continue + + for param in param_combination: + param.make_available(logger) + + logger.notify('* running {} on {}...'.format(type(test_case).__name__, + [str(p) for p in param_combination])) + test_case.run_test(*param_combination, logger) + + + logger.success('All tests passed!') diff --git a/tools/test-rig-rpi.yaml b/tools/test-rig-rpi.yaml new file mode 100644 index 00000000..cc84f4a7 --- /dev/null +++ b/tools/test-rig-rpi.yaml @@ -0,0 +1,64 @@ + +components: + - type: generalpurpose + name: homenet + net: homenet + + - type: generalpurpose + name: rpi + ssh: odrv + net: homenet + can0: main_canbus + uart0: /dev/serial/by-id/[not-yet-used] + + - type: programmer + name: The Blue STLink/v2 + id: '\x53\x3f\x75\x06\x49\x3f\x49\x51\x44\x54\x19\x3f' + + - type: odrive + name: ODrive + board-version: v3.6-58V + serial-number: "20703595524B" + brake-resistance: 0.47 + usb: auto + can: main_canbus + vbus-voltage: 24 # [V] + max-brake-power: 150 # [W] + encoder0: virtual_encoder0 + encoder1: virtual_encoder1 + motor0: D5065-270KV_0 + motor1: D5065-270KV_1 + + - type: motor + name: D5065-270KV_0 + phase-resistance: 0.0245 + phase-inductance: 2.03e-05 + pole-pairs: 7 + direction: 1 + kv: 270 + max-current: 70 + max-voltage: 40 + + - type: motor + name: D5065-270KV_1 + phase-resistance: 0.0245 + phase-inductance: 2.03e-05 + pole-pairs: 7 + direction: 1 + kv: 270 + max-current: 70 + max-voltage: 40 + + - type: encoder + name: real_encoder_0 + cpr: 8192 + max-rpm: 7000 + + - type: encoder + name: real_encoder_1 + cpr: 8192 + max-rpm: 7000 + + - type: teensy + name: teensy + From 27ab4eaf35687571575b9c23d30a3afbd01502a4 Mon Sep 17 00:00:00 2001 From: Alex McNabb Date: Mon, 18 Nov 2019 22:46:57 -0600 Subject: [PATCH 245/549] Alternate procedure to find pole-pair count --- docs/getting-started.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 9cd6ecf2..7c02ab87 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -187,9 +187,9 @@ You can change `odrv0.axis0.motor.config.calibration_current` [A] to the largest This is the resistance of the brake resistor. If you are not using it, you may set it to `0`. Note that there may be some extra resistance in your wiring and in the screw terminals, so if you are getting issues while braking you may want to increase this parameter by around 0.05 ohm. `odrv0.axis0.motor.config.pole_pairs` -This is the number of **magnet poles** in the rotor, **divided by two**. To find this, you can simply count the number of permanent magnets in the rotor, if you can see them. -**Note**: This is **not** the same as the number of coils in the stator. -If you can't see them, try sliding a loose magnet in your hand around the rotor, and counting how many times it stops. This will be the number of _pole pairs_. If you use a magnetic piece of metal instead of a magnet, you will get the number of _magnet poles_. +This is the number of **magnet poles** in the rotor, **divided by two**. To find this, you can simply count the number of permanent magnets in the rotor, if you can see them. +If you can't see them, try sliding a loose magnet in your hand around the rotor, and counting how many times it stops. This will be the number of _pole pairs_. If you use a magnetic piece of metal instead of a magnet, you will get the number of _magnet poles_. Another way of finding this number is with a current limited power supply. Connect any two of the three phases to a power supply outputting around 2A, spin the motor by hand, and count the number of detents. This will be the number of pole pairs. If you can't distinguish the detents from the normal cogging present when the motor is disconnected, increase the current. +**Note**: This is **not** the same as the number of coils in the stator. `odrv0.axis0.motor.config.motor_type` This is the type of motor being used. Currently two types of motors are supported: High-current motors (`MOTOR_TYPE_HIGH_CURRENT`) and gimbal motors (`MOTOR_TYPE_GIMBAL`). From fa0180a6012fb064b88be72cf48e36162edfa8ca Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Sat, 25 May 2019 21:58:33 +0200 Subject: [PATCH 246/549] Revert "dump_errors() should tell you about unknown errors" This reverts commit 5db152b10385787cb47f8f9dae520e28ccb77dda. --- tools/odrive/utils.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 8cbb704a..f5ce0c7f 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -53,12 +53,8 @@ def dump_errors(odrv, clear=False): print(prefix + _VT100Colors['red'] + "Error(s):" + _VT100Colors['default']) errorcodes_tup = [(name, val) for name, val in errorcodes.__dict__.items() if 'ERROR_' in name] for codename, codeval in errorcodes_tup: - if remote_obj.error: - print(" ", end='') - if codeval != 0: - print(codename) - else: - print("UNKNOWN_ERROR!") + if remote_obj.error & codeval != 0: + print(" " + codename) if clear: remote_obj.error = errorcodes.ERROR_NONE else: From 2c9f43c9ff46af72539564e448df1b65b123c939 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 4 Dec 2019 00:09:57 -0500 Subject: [PATCH 247/549] Add safety disarm in the stack overflow hook and increase stack sizes --- Firmware/Board/v3/Src/freertos.c | 4 ++-- Firmware/MotorControl/axis.hpp | 2 +- Firmware/MotorControl/main.cpp | 4 ++++ Firmware/communication/communication.cpp | 2 +- Firmware/communication/interface_uart.cpp | 2 +- Firmware/communication/interface_usb.cpp | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Firmware/Board/v3/Src/freertos.c b/Firmware/Board/v3/Src/freertos.c index 42ff9751..b2d49e55 100644 --- a/Firmware/Board/v3/Src/freertos.c +++ b/Firmware/Board/v3/Src/freertos.c @@ -89,14 +89,14 @@ osSemaphoreId sem_usb_tx; osSemaphoreId sem_can; osThreadId usb_irq_thread; -const uint32_t stack_size_usb_irq_thread = 1024; // Bytes +const uint32_t stack_size_usb_irq_thread = 2048; // Bytes // Place FreeRTOS heap in core coupled memory for better performance __attribute__((section(".ccmram"))) uint8_t ucHeap[configTOTAL_HEAP_SIZE]; /* USER CODE END Variables */ osThreadId defaultTaskHandle; -const uint32_t stack_size_default_task = 1024; // Bytes +const uint32_t stack_size_default_task = 2048; // Bytes /* Private function prototypes -----------------------------------------------*/ /* USER CODE BEGIN FunctionPrototypes */ diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index f9199d8b..a84e76af 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -244,7 +244,7 @@ class Axis { Endstop& max_endstop_; osThreadId thread_id_; - const uint32_t stack_size_ = 1024; // Bytes + const uint32_t stack_size_ = 2048; // Bytes volatile bool thread_id_valid_ = false; // variables exposed on protocol diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 43c3e476..8650b006 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -182,6 +182,10 @@ extern "C" int construct_objects(){ extern "C" { int odrive_main(void); void vApplicationStackOverflowHook(xTaskHandle *pxTask, signed portCHAR *pcTaskName) { + for(auto& axis : axes){ + safety_critical_disarm_motor_pwm(axis->motor_); + } + safety_critical_disarm_brake_resistor(); for (;;); // TODO: safe action } void vApplicationIdleHook(void) { diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 8a22508e..ac4714db 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -64,7 +64,7 @@ const uint8_t fw_version_revision = FW_VERSION_REVISION; const uint8_t fw_version_unreleased = FW_VERSION_UNRELEASED; // 0 for official releases, 1 otherwise osThreadId comm_thread; -const uint32_t stack_size_comm_thread = 2048; // Bytes +const uint32_t stack_size_comm_thread = 4096; // Bytes volatile bool endpoint_list_valid = false; static uint32_t test_property = 0; diff --git a/Firmware/communication/interface_uart.cpp b/Firmware/communication/interface_uart.cpp index dc8a4ce6..f1bb5e0d 100644 --- a/Firmware/communication/interface_uart.cpp +++ b/Firmware/communication/interface_uart.cpp @@ -22,7 +22,7 @@ static uint32_t dma_last_rcv_idx; // static thread_local uint32_t deadline_ms = 0; osThreadId uart_thread; -const uint32_t stack_size_uart_thread = 2048; // Bytes +const uint32_t stack_size_uart_thread = 4096; // Bytes class UART4Sender : public StreamSink { diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index 7f49c0b5..c52fe5ca 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -14,7 +14,7 @@ #include osThreadId usb_thread; -const uint32_t stack_size_usb_thread = 2048; // Bytes +const uint32_t stack_size_usb_thread = 4096; // Bytes USBStats_t usb_stats_ = {0}; class USBSender : public PacketSink { From da39c07da6e2016990e322718955eccdfd0f1807 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 4 Dec 2019 00:13:42 -0500 Subject: [PATCH 248/549] Unknown Error if error code is present but not matched in dump_errors --- tools/odrive/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index f5ce0c7f..2b2901a7 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -50,11 +50,15 @@ def dump_errors(odrv, clear=False): for name, remote_obj, errorcodes in module_decode_map: prefix = ' '*2 + name + ": " if (remote_obj.error != errorcodes.ERROR_NONE): + foundError = False print(prefix + _VT100Colors['red'] + "Error(s):" + _VT100Colors['default']) errorcodes_tup = [(name, val) for name, val in errorcodes.__dict__.items() if 'ERROR_' in name] for codename, codeval in errorcodes_tup: if remote_obj.error & codeval != 0: + foundError = True print(" " + codename) + if not foundError: + print(" " + 'UNKNOWN ERROR!') if clear: remote_obj.error = errorcodes.ERROR_NONE else: From ee9d332fd5cab7bca06b696c8ee35e0dc7aba437 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 27 Sep 2019 13:00:34 +0200 Subject: [PATCH 249/549] support "unsigned int" type in native protocol This fixes a compile error when using enums with large numerical values. The int type of such enums is unsigned int, which was previously not supported. --- Firmware/fibre/cpp/include/fibre/protocol.hpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index c1f3cc68..366f46c2 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -398,7 +398,7 @@ bool default_readwrite_endpoint_handler(endpoint_ref_t* value, const uint8_t* in } template -static inline const char* get_default_json_modifier(); +static constexpr inline const char* get_default_json_modifier(); template<> inline constexpr const char* get_default_json_modifier() { @@ -441,6 +441,14 @@ inline constexpr const char* get_default_json_modifier() { return "\"type\":\"uint32\",\"access\":\"rw\""; } template<> +inline constexpr const char* get_default_json_modifier() { + return "\"type\":\"uint32\",\"access\":\"r\""; // TODO: automatically detect size +} +template<> +inline constexpr const char* get_default_json_modifier() { + return "\"type\":\"uint32\",\"access\":\"rw\""; // TODO: automatically detect size +} +template<> inline constexpr const char* get_default_json_modifier() { return "\"type\":\"uint16\",\"access\":\"r\""; } @@ -537,6 +545,11 @@ template<> struct format_traits_t { using type = void; static constexpr const char * fmt = "%lu"; static constexpr const char * fmtp = "%lu"; }; +// TODO: change all overloads to fundamental int type space +template<> struct format_traits_t { using type = void; + static constexpr const char * fmt = "%ud"; + static constexpr const char * fmtp = "%ud"; +}; template<> struct format_traits_t { using type = void; static constexpr const char * fmt = "%hd"; static constexpr const char * fmtp = "%hd"; From 8b10429738bb1100e7b5a181291f2b3d3694a8eb Mon Sep 17 00:00:00 2001 From: Richard Parsons <503426+Capo01@users.noreply.github.com> Date: Tue, 10 Dec 2019 15:45:23 +1100 Subject: [PATCH 250/549] Included that Zadig has native and CDC interfaces listed --- docs/troubleshooting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 6ba3752e..bd02b737 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -113,7 +113,7 @@ You can also try increasing `.controller.config.vel_limit_tolerance`. The * **Linux**: Type `lsusb` to list all USB devices. Verify that your ODrive is listed. * **Linux**: Make sure you [set up your udev rules](getting-started#downloading-and-installing-tools) correctly. * **Windows**: Right-click on the start menu and open "Device Manager". Verify that your ODrive is listed. - * **Windows**: Use the [Zadig utility](http://zadig.akeo.ie/) to verify the driver is set to `libusb-win32`. + * **Windows**: Use the [Zadig utility](http://zadig.akeo.ie/) to verify the driver is set to `libusb-win32`. Note that there are two options listed in Zadig for Odrive: `ODrive 3.x Native Interface (Interface 2)` and `ODrive 3.x CDC Interface (Interface 0)`. Only the native interface should have `libusb-win32` while the CDC interface should use `WinUSB`. * Ensure that no other ODrive program is running * Run `odrivetools` with the `--verbose` option. * Run `PYUSB_DEBUG=debug odrivetools` to get even more log output. From 6e6363322696064125d3263311a66459cea3e756 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Wed, 11 Dec 2019 22:27:02 -0500 Subject: [PATCH 251/549] Update configuring-vscode.md --- docs/configuring-vscode.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuring-vscode.md b/docs/configuring-vscode.md index 572f9933..69edf772 100644 --- a/docs/configuring-vscode.md +++ b/docs/configuring-vscode.md @@ -40,6 +40,7 @@ An extension called Cortex-Debug has recently been released which is designed sp Note: If developing on Windows, you should have `arm-none-eabi-gdb` and `openOCD` on your PATH. * Make sure you have the Firmware folder as your active folder + * Set `CONFIG_DEBUG=true` in the tup.config file * Flash the board with the newest code (starting debug session doesn't do this) * Debug -> Start Debugging (or press F5) * The processor will reset and halt. From 31f8ba30fba04c49f56a16b79189f544bf61f39d Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 12 Dec 2019 16:20:01 +0100 Subject: [PATCH 252/549] [testing] add initial set of CAN tests --- tools/odrive/tests/can_test.py | 170 ++++++++++++++++++++++++++++++ tools/odrive/tests/nvm_test.py | 2 +- tools/odrive/tests/test_runner.py | 6 +- 3 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 tools/odrive/tests/can_test.py diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py new file mode 100644 index 00000000..344d94fe --- /dev/null +++ b/tools/odrive/tests/can_test.py @@ -0,0 +1,170 @@ + +import test_runner + +import struct +import can +import asyncio +import time + +from fibre.utils import Logger +from odrive.enums import errors +from test_runner import CANTestContext, ODriveTestContext, test_assert_eq + +# Each argument is described as tuple (name, format, scale). +# Struct format codes: https://docs.python.org/2/library/struct.html +command_set = { + 'heartbeat': (0x001, [('error', 'I', 1), ('current_state', 'I', 1)]), # untested + 'estop': (0x002, []), # tested + 'get_motor_error': (0x003, [('motor_error', 'I', 1)]), # untested + 'get_encoder_error': (0x004, [('encoder_error', 'I', 1)]), # untested + 'get_sensorless_error': (0x004, [('sensorless_error', 'I', 1)]), # untested + 'set_node_id': (0x006, [('node_id', 'H', 1)]), # tested + 'set_requested_state': (0x007, [('requested_state', 'I', 1)]), # tested + # 0x008 not yet implemented + 'get_encoder_estimates': (0x009, [('encoder_pos_estimate', 'f', 1), ('encoder_vel_estimate', 'f', 1)]), # untested + 'get_encoder_count': (0x00a, [('encoder_shadow_count', 'i', 1), ('encoder_count', 'i', 1)]), # untested + 'set_controller_modes': (0x00b, [('control_mode', 'i', 1), ('input_mode', 'i', 1)]), # tested + 'set_input_pos': (0x00c, [('input_pos', 'i', 1), ('vel_ff', 'h', 0.1), ('cur_ff', 'h', 0.01)]), # tested + 'set_input_vel': (0x00d, [('input_vel', 'i', 0.01), ('cur_ff', 'h', 0.01)]), # tested + 'set_input_current': (0x00e, [('input_current', 'i', 0.01)]), # tested + 'set_velocity_limit': (0x00f, [('velocity_limit', 'f', 1)]), # tested + 'start_anticogging': (0x010, []), # untested + 'set_traj_vel_limit': (0x011, [('traj_vel_limit', 'f', 1)]), # tested + 'set_traj_accel_limits': (0x012, [('traj_accel_limit', 'f', 1), ('traj_decel_limit', 'f', 1)]), # tested + 'set_traj_a_per_css': (0x013, [('a_per_css', 'f', 1)]), # tested + 'get_iq': (0x014, [('iq_setpoint', 'f', 1), ('iq_measured', 'f', 1)]), # untested + 'get_sensorless_estimates': (0x015, [('sensorless_pos_estimate', 'f', 1), ('sensorless_vel_estimate', 'f', 1)]), # untested + 'reboot': (0x016, []), # untested + 'get_vbus_voltage': (0x017, [('vbus_voltage', 'f', 1)]), # tested + 'clear_errors': (0x018, []), # partially tested +} + +def command(bus, node_id_, cmd_name, **kwargs): + cmd_spec = command_set[cmd_name] + cmd_id = cmd_spec[0] + fmt = '<' + ''.join([f for (n, f, s) in cmd_spec[1]]) # all little endian + + if (sorted([n for (n, f, s) in cmd_spec[1]]) != sorted(kwargs.keys())): + raise Exception("expected arguments: " + str([n for (n, f, s) in cmd_spec[1]])) + + fields = [((kwargs[n] / s) if f == 'f' else int(kwargs[n] / s)) for (n, f, s) in cmd_spec[1]] + data = struct.pack(fmt, *fields) + msg = can.Message(arbitration_id=((node_id_ << 5) | cmd_id), data=data) + bus.send(msg) + +async def request(bus, node_id, cmd_name, timeout = 1.0): + cmd_spec = command_set[cmd_name] + cmd_id = cmd_spec[0] + fmt = '<' + ''.join([f for (n, f, s) in cmd_spec[1]]) # all little endian + + reader = can.AsyncBufferedReader() + notifier = can.Notifier(bus, [reader], timeout = timeout, loop = asyncio.get_event_loop()) + + try: + msg = can.Message(arbitration_id=((node_id << 5) | cmd_id), data=[], is_remote_frame=True) + bus.send(msg) + + # The timeout in can.Notifier only triggers if no new messages are received at all, + # so we need a second monitoring method. + start = time.monotonic() + while True: + msg = await reader.get_message() + if ((msg.arbitration_id == ((node_id << 5) | cmd_id)) and not msg.is_remote_frame): + break + if (time.monotonic() - start) > timeout: + raise TimeoutError() + finally: + notifier.stop() + + fields = struct.unpack(fmt, msg.data[:(struct.calcsize(fmt))]) + return {n: (fields[i] * s) for (i, (n, f, s)) in enumerate(cmd_spec[1])} + + +class TestSimpleCAN(): + def is_compatible(self, canbus: CANTestContext, odrive: ODriveTestContext): + return canbus.yaml['bus'] == odrive.yaml['can'] # check if connected + + def run_test(self, canbus: CANTestContext, odrive: ODriveTestContext, logger: Logger): + node_id = 0 + axis = odrive.handle.axis0 + axis.config.can_node_id = node_id + time.sleep(0.1) + + def my_cmd(cmd_name, **kwargs): command(canbus.handle, node_id, cmd_name, **kwargs) + def my_req(cmd_name, **kwargs): return asyncio.run(request(canbus.handle, node_id, cmd_name, **kwargs)) + def fence(): my_req('get_vbus_voltage') # fence to ensure the CAN command was sent + + test_assert_eq(my_req('get_vbus_voltage')['vbus_voltage'], odrive.handle.vbus_voltage, accuracy=0.01) + + my_cmd('set_node_id', node_id=node_id+20) + asyncio.run(request(canbus.handle, node_id+20, 'get_vbus_voltage')) + test_assert_eq(axis.config.can_node_id, node_id+20) + + # Reset node ID to default value + command(canbus.handle, node_id+20, 'set_node_id', node_id=node_id) + fence() + test_assert_eq(axis.config.can_node_id, node_id) + + my_cmd('clear_errors') + fence() + test_assert_eq(axis.error, 0) + + my_cmd('estop') + fence() + test_assert_eq(axis.error, errors.axis.ERROR_ESTOP_REQUESTED) + + my_cmd('set_requested_state', requested_state=42) # illegal state - should assert axis error + fence() + test_assert_eq(axis.current_state, 1) # idle + test_assert_eq(axis.error, errors.axis.ERROR_ESTOP_REQUESTED | errors.axis.ERROR_INVALID_STATE) + + my_cmd('clear_errors') + fence() + test_assert_eq(axis.error, 0) + + my_cmd('set_controller_modes', control_mode=1, input_mode=5) # current conrol, traprzoidal trajectory + fence() + test_assert_eq(axis.controller.config.control_mode, 1) + test_assert_eq(axis.controller.config.input_mode, 5) + + # Reset to safe values + my_cmd('set_controller_modes', control_mode=3, input_mode=1) # position control, passthrough + fence() + test_assert_eq(axis.controller.config.control_mode, 3) + test_assert_eq(axis.controller.config.input_mode, 1) + + my_cmd('set_input_pos', input_pos=1, vel_ff=2, cur_ff=3) + fence() + test_assert_eq(axis.controller.input_pos, 1.0, range=0.1) + test_assert_eq(axis.controller.input_vel, 2.0, range=0.01) + test_assert_eq(axis.controller.input_current, 3.0, range=0.001) + + my_cmd('set_input_vel', input_vel=-10.0, cur_ff=30.1234) + fence() + test_assert_eq(axis.controller.input_vel, -10.0, range=0.01) + test_assert_eq(axis.controller.input_current, 30.1234, range=0.01) + + my_cmd('set_input_current', input_current=3.1415) + fence() + test_assert_eq(axis.controller.input_current, 3.1415, range=0.01) + + my_cmd('set_velocity_limit', velocity_limit=23456.78) + fence() + test_assert_eq(axis.controller.config.vel_limit, 23456.78, range=0.001) + + my_cmd('set_traj_vel_limit', traj_vel_limit=123.456) + fence() + test_assert_eq(axis.trap_traj.config.vel_limit, 123.456, range=0.0001) + + my_cmd('set_traj_accel_limits', traj_accel_limit=98.231, traj_decel_limit=-12.234) + fence() + test_assert_eq(axis.trap_traj.config.accel_limit, 98.231, range=0.0001) + test_assert_eq(axis.trap_traj.config.decel_limit, -12.234, range=0.0001) + + my_cmd('set_traj_a_per_css', a_per_css=55.086) + fence() + test_assert_eq(axis.controller.config.inertia, 55.086, range=0.0001) + + +if __name__ == '__main__': + test_runner.run(TestSimpleCAN()) diff --git a/tools/odrive/tests/nvm_test.py b/tools/odrive/tests/nvm_test.py index 9e416898..99da037f 100644 --- a/tools/odrive/tests/nvm_test.py +++ b/tools/odrive/tests/nvm_test.py @@ -36,7 +36,7 @@ class TestStoreAndReboot(): logger.debug("verifying configuration after reboot...") test_assert_eq(odrive.handle.config.brake_resistance, values[-1], accuracy=0.01) - def run_test(self, odrive: ODriveTestContext, logger): + def run_test(self, odrive: ODriveTestContext, logger: Logger): self.run_with_values([0.5, 1.0, 1.5], odrive, logger) self.run_with_values([2.5, 3.7], odrive, logger) self.run_with_values([0.47], odrive, logger) diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index d24f4c8d..879b4e4c 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -88,7 +88,7 @@ class CANTestContext(): # TODO: read bus name from yaml import can - self.handle = can.interface.Bus(bustype='socketcan', channel='can0', bitrate=250000) + self.handle = can.interface.Bus(bustype='socketcan', channel=self.yaml['id'], bitrate=250000) # Helper functions ------------------------------------------------------------# @@ -106,6 +106,10 @@ def yaml_to_test_objects(test_rig_yaml: dict, logger: Logger): add_component(odrv_ctx) for enc_ctx in odrv_ctx.encoders: add_component(enc_ctx) + elif component_yaml['type'] == 'generalpurpose': + for (k, v) in [(k, v) for (k, v) in component_yaml.items() if k.startswith("can")]: + can_ctx = CANTestContext({'id': k, 'bus': v}) + add_component(can_ctx) else: logger.warn('test rig has unsupported component ' + component_yaml['type']) continue From 8b04201e5279e82175d13c6c4e2401dc808c506d Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 12 Dec 2019 20:43:37 +0100 Subject: [PATCH 253/549] [testing] add PWM input test --- tools/odrive/tests/pwm_input_test.py | 130 +++++ tools/odrive/tests/pwm_sim.ino.hex | 801 +++++++++++++++++++++++++++ tools/odrive/tests/test_runner.py | 1 + 3 files changed, 932 insertions(+) create mode 100644 tools/odrive/tests/pwm_input_test.py create mode 100644 tools/odrive/tests/pwm_sim.ino.hex diff --git a/tools/odrive/tests/pwm_input_test.py b/tools/odrive/tests/pwm_input_test.py new file mode 100644 index 00000000..e70f8e1e --- /dev/null +++ b/tools/odrive/tests/pwm_input_test.py @@ -0,0 +1,130 @@ + +import test_runner + +import time +import math +import os + +import fibre +from fibre.utils import Logger +from odrive.enums import errors +from test_runner import ODriveTestContext, test_assert_eq, program_teensy + +#def modpm(val, lower_bound, upper_bound): +# return ((val - lower_bound) % (upper_bound - lower_bound)) - lower_bound + +def modpm(val, range): + return ((val + (range / 2)) % range) - (range / 2) + +class TestPwmInput(): + """ + Verifies the PWM input. + + The Teensy generates a PWM signal that goes from 0% (1ms high) to 100% (2ms high) + in 1 second and then resumes at 0%. + + This test takes about 1min. + + Note: this test is currently only written for ODrive 3.6 (or similar GPIO layout). + """ + + def is_compatible(self, odrive: ODriveTestContext): + return True + + def run_delta_test(self, attr, with_min, with_max, timeout = 5.0): + rounds_per_s = 1.0 + units_per_s = (with_max - with_min) * rounds_per_s + step_size = units_per_s * 0.02 # 20ms per step + min_val = math.inf + max_val = -math.inf + cumulative_delta = 0 + + rate = units_per_s + rate_gain = 1 / 0.5 # 200ms time constant + + # Could do something fancy like fit a piecewise linear function + + start = time.monotonic() + i = 0 + while True: + now = time.monotonic() + new_val = attr.get_value() + min_val = min(min_val, new_val) + max_val = max(max_val, new_val) + + if i > 0: + dt = now - before + delta = modpm(new_val - last_val, with_max - with_min) + cumulative_delta += delta + + # low pass filter rate + rate += (delta / dt - rate) * min(rate_gain * dt, 1) + #print("rate", rate) + + # After 500ms verify the rate + if now - start > 0.5: + # 40% seems like a very large range. With a smaller range + # the test tends to fail. May want to investigate if this + # is only because of the non-realtimeness of the tester + # of if it's an actual problem. Looks fine on software oscilloscope. + test_assert_eq(rate, units_per_s, accuracy=0.4) + + before = now + last_val = new_val + + if (now - start > timeout): + break + time.sleep(0.005) # PWM time resolution is 20ms, so let's read a bit slower. + i += 1 + + # Check the total incement during this time + test_assert_eq(cumulative_delta, timeout * units_per_s, accuracy=0.1) + + # Check that the minimum and maximum values were observed + test_assert_eq(min_val, with_min, range = step_size) + test_assert_eq(max_val, with_max, range = step_size) + + def run_test(self, odrive: ODriveTestContext, logger: Logger): + # TODO: test each GPIO separately + hexfile = 'pwm_sim.ino.hex' + program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) + time.sleep(1.0) # wait for PLLs to stabilize + + logger.debug("Set up PWM input...") + odrive.handle.erase_configuration() + odrive.handle.config.enable_uart = False + odrive.handle.config.gpio1_pwm_mapping.endpoint = odrive.handle.axis0.controller._remote_attributes['input_pos'] + odrive.handle.config.gpio1_pwm_mapping.min = -50 + odrive.handle.config.gpio1_pwm_mapping.max = 200 + odrive.handle.config.gpio2_pwm_mapping.endpoint = odrive.handle.axis1.controller._remote_attributes['input_pos'] + odrive.handle.config.gpio2_pwm_mapping.min = 20 + odrive.handle.config.gpio2_pwm_mapping.max = 400 + odrive.handle.config.gpio3_pwm_mapping.endpoint = odrive.handle.axis0.controller._remote_attributes['input_vel'] + odrive.handle.config.gpio3_pwm_mapping.min = -1000 + odrive.handle.config.gpio3_pwm_mapping.max = 0 + odrive.handle.config.gpio4_pwm_mapping.endpoint = odrive.handle.axis1.controller._remote_attributes['input_vel'] + odrive.handle.config.gpio4_pwm_mapping.min = -20000 + odrive.handle.config.gpio4_pwm_mapping.max = 20000 + + # Save and reboot + odrive.handle.save_configuration() + try: + odrive.handle.reboot() + except fibre.ChannelBrokenException: + pass # this is expected + odrive.handle = None + time.sleep(2) + odrive.make_available(logger) + + logger.debug("Check if PWM on GPIO1 works...") + self.run_delta_test(odrive.handle.axis0.controller._remote_attributes['input_pos'], -50, 200) + logger.debug("Check if PWM on GPIO2 works...") + self.run_delta_test(odrive.handle.axis1.controller._remote_attributes['input_pos'], 20, 400) + logger.debug("Check if PWM on GPIO3 works...") + self.run_delta_test(odrive.handle.axis0.controller._remote_attributes['input_vel'], -1000, 0) + logger.debug("Check if PWM on GPIO4 works...") + self.run_delta_test(odrive.handle.axis1.controller._remote_attributes['input_vel'], -20000, 20000) + + +if __name__ == '__main__': + test_runner.run(TestPwmInput()) diff --git a/tools/odrive/tests/pwm_sim.ino.hex b/tools/odrive/tests/pwm_sim.ino.hex new file mode 100644 index 00000000..efd5bf65 --- /dev/null +++ b/tools/odrive/tests/pwm_sim.ino.hex @@ -0,0 +1,801 @@ +:0200000460009A +:100000004643464200000156000000000101020084 +:1000100000000000000000000000000000000000E0 +:1000200000000000000000000000000000000000D0 +:1000300000000000000000000000000000000000C0 +:1000400000000000010403000000000000000000A8 +:100050000000200000000000000000000000000080 +:100060000000000000000000000000000000000090 +:100070000000000000000000000000000000000080 +:10008000EB04180A063204260000000000000000FD +:10009000050404240000000000000000000000002F +:1000A0000000000000000000000000000000000050 +:1000B0000604000000000000000000000000000036 +:1000C0000000000000000000000000000000000030 +:1000D00020041808000000000000000000000000DC +:1000E0000000000000000000000000000000000010 +:1000F0000000000000000000000000000000000000 +:10010000D8041808000000000000000000000000F3 +:100110000204180804200000000000000000000095 +:1001200000000000000000000000000000000000CF +:10013000600400000000000000000000000000005B +:1001400000000000000000000000000000000000AF +:10015000000000000000000000000000000000009F +:10016000000000000000000000000000000000008F +:10017000000000000000000000000000000000007F +:10018000000000000000000000000000000000006F +:10019000000000000000000000000000000000005F +:1001A000000000000000000000000000000000004F +:1001B000000000000000000000000000000000003F +:1001C000000100000010000001000000000000001D +:1001D000000001000000000000000000000000001E +:1001E000000000000000000000000000000000000F +:1001F00000000000000000000000000000000000FF +:10020000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE +:10021000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEE +:10022000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDE +:10023000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCE +:10024000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBE +:10025000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAE +:10026000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E +:10027000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8E +:10028000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7E +:10029000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6E +:1002A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5E +:1002B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4E +:1002C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3E +:1002D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E +:1002E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1E +:1002F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0E +:10030000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD +:10031000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED +:10032000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD +:10033000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCD +:10034000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD +:10035000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAD +:10036000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D +:10037000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8D +:10038000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7D +:10039000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6D +:1003A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D +:1003B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4D +:1003C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3D +:1003D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2D +:1003E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1D +:1003F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0D +:10040000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC +:10041000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC +:10042000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC +:10043000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCC +:10044000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC +:10045000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAC +:10046000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9C +:10047000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8C +:10048000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7C +:10049000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C +:1004A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5C +:1004B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4C +:1004C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3C +:1004D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2C +:1004E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1C +:1004F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0C +:10050000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB +:10051000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEB +:10052000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB +:10053000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCB +:10054000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBB +:10055000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAB +:10056000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9B +:10057000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8B +:10058000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7B +:10059000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6B +:1005A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B +:1005B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4B +:1005C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3B +:1005D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2B +:1005E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1B +:1005F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0B +:10060000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA +:10061000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEA +:10062000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA +:10063000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCA +:10064000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA +:10065000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAA +:10066000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9A +:10067000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8A +:10068000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7A +:10069000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6A +:1006A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5A +:1006B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4A +:1006C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3A +:1006D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A +:1006E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1A +:1006F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A +:10070000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9 +:10071000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9 +:10072000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9 +:10073000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC9 +:10074000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB9 +:10075000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9 +:10076000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF99 +:10077000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF89 +:10078000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF79 +:10079000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF69 +:1007A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF59 +:1007B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF49 +:1007C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF39 +:1007D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF29 +:1007E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF19 +:1007F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF09 +:10080000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8 +:10081000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8 +:10082000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD8 +:10083000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC8 +:10084000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB8 +:10085000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA8 +:10086000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF98 +:10087000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF88 +:10088000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF78 +:10089000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF68 +:1008A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF58 +:1008B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF48 +:1008C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF38 +:1008D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF28 +:1008E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF18 +:1008F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF08 +:10090000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7 +:10091000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE7 +:10092000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD7 +:10093000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7 +:10094000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB7 +:10095000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA7 +:10096000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF97 +:10097000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF87 +:10098000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF77 +:10099000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF67 +:1009A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57 +:1009B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF47 +:1009C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF37 +:1009D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27 +:1009E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF17 +:1009F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF07 +:100A0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6 +:100A1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE6 +:100A2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD6 +:100A3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC6 +:100A4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB6 +:100A5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA6 +:100A6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF96 +:100A7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF86 +:100A8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF76 +:100A9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF66 +:100AA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 +:100AB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF46 +:100AC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF36 +:100AD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF26 +:100AE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16 +:100AF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF06 +:100B0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5 +:100B1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5 +:100B2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD5 +:100B3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC5 +:100B4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB5 +:100B5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA5 +:100B6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF95 +:100B7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF85 +:100B8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF75 +:100B9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF65 +:100BA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF55 +:100BB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF45 +:100BC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF35 +:100BD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF25 +:100BE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF15 +:100BF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF05 +:100C0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4 +:100C1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE4 +:100C2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD4 +:100C3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC4 +:100C4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB4 +:100C5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA4 +:100C6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF94 +:100C7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF84 +:100C8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF74 +:100C9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF64 +:100CA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF54 +:100CB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF44 +:100CC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF34 +:100CD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF24 +:100CE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF14 +:100CF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF04 +:100D0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3 +:100D1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE3 +:100D2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD3 +:100D3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC3 +:100D4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB3 +:100D5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA3 +:100D6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF93 +:100D7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83 +:100D8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF73 +:100D9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF63 +:100DA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF53 +:100DB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43 +:100DC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF33 +:100DD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF23 +:100DE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF13 +:100DF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF03 +:100E0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2 +:100E1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE2 +:100E2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD2 +:100E3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2 +:100E4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB2 +:100E5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA2 +:100E6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF92 +:100E7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF82 +:100E8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF72 +:100E9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF62 +:100EA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF52 +:100EB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF42 +:100EC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF32 +:100ED000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF22 +:100EE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF12 +:100EF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02 +:100F0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1 +:100F1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE1 +:100F2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD1 +:100F3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC1 +:100F4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB1 +:100F5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA1 +:100F6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF91 +:100F7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81 +:100F8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF71 +:100F9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF61 +:100FA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF51 +:100FB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF41 +:100FC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF31 +:100FD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF21 +:100FE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF11 +:100FF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF01 +:10100000D10020402C100060000000000000000013 +:1010100020100060001000600000000000000000D0 +:1010200000000060D031000000000000000001203E +:1010300035100060764B0720764C4FF42A01764A33 +:101040005C64186499639546744A75498A420FD066 +:10105000744B9A420CD2D4430846234423F0030332 +:1010600004330B4450F8041B984242F8041BF9D196 +:101070006D4A6E498A420FD06D4B9A420CD2D443CE +:101080000846234423F0030304330B4450F8041BA5 +:10109000984242F8041BF9D1664A674B9A420BD238 +:1010A000D04311460024034423F0030304331344C4 +:1010B00041F8044B8B42FBD1604A4FF47001604B06 +:1010C000116003F530715F4A43F8042F9942FBD158 +:1010D0005D4B80215D4A03F8011B9342FBD15C4CC0 +:1010E0004FF080375B4E4FF0FF32DFF8A8A10025AC +:1010F000DFF8A491DFF8A481574B4549C3F800A05D +:10110000C4F80471C4F80091C4F8F470C4F8F08015 +:10111000F36923F07F0343F04003F361736A23F024 +:101120007F0343F0400373628A66CA660A674A67B0 +:1011300000F0B6F8494A6320494B4A49106003223F +:101140001D60CAF8381043F8082C4749474A4848F8 +:10115000C3F8082D0B68474A43F08073CAF83C0077 +:1011600045480B601368454943F001031360036869 +:101170000B6000F0E5F8C4F804714148C4F8009130 +:10118000C4F8F470C4F8F08000F04AFA00BF00BF61 +:1011900000BF00BFF16E3B4A41F440513A4BF1664B +:1011A0001560C2F80851C2F81851C2F82851C2F8A7 +:1011B00038519A6BD20708D442F615623349596503 +:1011C0001A659A6B42F001029A632F4A304C936879 +:1011D00043F00113936000F01BFA2368132BFCD932 +:1011E00000F05CF900F0D0F900F00EFA00F0DAF847 +:1011F00000F002FA2368B3F5967FFBD300F018FAEB +:1012000000F0FEF900F01CFA00F016FAFAE700BF51 +:1012100000C00A40ABAAAAAA008007200000000074 +:1012200050160060C017000000000020142E00605F +:10123000C0030020C0030020C032002088ED00E081 +:10124000FC0F00208905000000E400E0A0E400E0BD +:1012500000800D4000C00F4008ED00E014E000E009 +:1012600018E000E001110000FCED00E0000020208B +:1012700005120000001000E0041000E0840D0020C2 +:101280000046C3230040084000400D400000C05607 +:10129000880D0020001000201B1018200C0D1113C9 +:1012A000F0B5194A0021194B4FF0100E18480124CF +:1012B000184E194D0160194FC2F800E01E6015600C +:1012C000174E184D1F601660174F1D60174E184DB2 +:1012D00017601E60174F1560174E184D1F6016607F +:1012E0001D600460BFF34F8FBFF36F8FC3F8B01161 +:1012F000BFF34F8FBFF36F8F52F8883C43F44033F6 +:1013000042F8883CF0BD00BF9CED00E0A0ED00E09D +:1013100094ED00E0250008031100200021000207E1 +:1013200012000020250008131300202027000B13B3 +:101330001400004033001013150000602F000B074D +:10134000F0B4174A40274FF480314FF480564FF4E1 +:1013500000554FF4404443F24200136913F0020F6A +:1013600006D0946151619061136913F0020FF8D1B6 +:1013700013F4005F01D15561EFE713F4805F01D1F1 +:101380005661EAE7002BE8DA13F4803F01D091615F +:10139000E3E75B0601D45761DFE7F0BC704700BFAD +:1013A00000800D40364A03203649F3EE096A13687F +:1013B00023F00103F0B51360C2F89000D1F8E030DB +:1013C000DFF8D4E0DCB2C3F30B202F4FC0EB135394 +:1013D00007EE904AA4F15501CEF80040B8EEE77A46 +:1013E00003EB830407EE900A03FB01F13B6003EB80 +:1013F0008313B8EEE75A07EE901A091B77EE666A78 +:10140000B8EE677A214D07EE901A0B44C5ED006ADD +:10141000F8EE677A1E4EC7EE265A1E4930601068F5 +:1014200087EEA66A07EE903AF8EE677A87EEA67A1C +:1014300075EE855A76EE056AFCEEE55AFCEEE66A34 +:1014400015EE903A40EA035316EE900A77EE057ACD +:10145000136001EA0041D2F81031FCEEE77A0B4349 +:10146000C2F8103117EE903AD2F81011C3F30B0303 +:101470000B43C2F81031136843F002031360F0BD50 +:1014800080810D4000441F40E8030020E403002059 +:10149000EC0300200000FF0FE0030020304B40F67B +:1014A000617270B5C3F8202140F2044500F0B4F831 +:1014B0002C492D48D1F880202C4C42F003022C4BB3 +:1014C000C1F88020C0F86051226813401BB9D0F8E1 +:1014D000A8319A071AD0244B4FF00041234A516398 +:1014E0001A46D3F8401141F00201C3F84011D2F876 +:1014F00040319B07FBD44FF400301E491B4B4FF08B +:101500000042086019209A6300F08EF81A4D0022FC +:10151000164B4FF08041144C0A26996328461A60F6 +:101520001146C4F8A8614FF4207200F061F84FF43E +:1015300081064FF4800040F24313104A10492E6098 +:101540002864C4F85851C2F80412C4F848310D4A4E +:101550004FF4003101231160C4F8403170BD00BF69 +:1015600000800D4000C00F4000002E4000900D4054 +:10157000001C1E008CE200E0003000200010002063 +:10158000650700000CE100E0114B1249D86E0A46D5 +:1015900040F4403030B4D86640F2B765D86EA0242D +:1015A00040F44070D8664D648C64936C1B06FCD488 +:1015B000094B40F2B760A021064A58649964936CC5 +:1015C00013F08003FBD1054A137030BC704700BF95 +:1015D00000C00F4000400C4000800C40810D0020F6 +:1015E000F8B500BFF8BC08BC9E467047FFFFFFFF80 +:1015F0005FF800F0111700005FF800F0A50500008B +:101600005FF800F0C90000005FF800F0D51300009B +:101610005FF800F0A10500005FF800F0650E000023 +:101620005FF800F0450200005FF800F0C51200000E +:101630005FF800F0C11600005FF800F0251200000E +:101640005FF800F0D100000045000000FFFFFFFF41 +:10165000000000000000000000000000000000008A +:10166000000000000000000000000000000000007A +:1016700010B5054C237833B9044B13B10448AFF3CC +:1016800000800123237010BDC00300200000000073 +:10169000C4170000084B10B51BB108490848AFF348 +:1016A00000800848036803B910BD074B002BFBD02E +:1016B000BDE81040184700BF00000000C403002030 +:1016C000C4170000C00300200000000008B50D4B47 +:1016D0000121187800F040FA0B4B0121187800F036 +:1016E0003BFA0A4B0121187800F036FA084B012129 +:1016F000187800F031FA074B01211878BDE808404E +:1017000000F02ABAEC020020F0020020F4020020CF +:10171000F8020020FC020020FFF7D8BF0000000004 +:101720002DE9F04F504D83B09FED507A0121D5ED5A +:10173000007ADFF850B167EE877ADFF84CA19BF8AA +:101740000000DFF84891FDEEE77ADFF84481484F6A +:10175000484E17EE904A00F0F9F99AF8000001217E +:1017600000F0F4F999F80000012104F57A7400F012 +:10177000EDF998F80000012100F0E8F9387801212E +:1017800000F0E4F93C483D49D0F800E03368A1FBA3 +:101790000331890C04FB01F10368374ACEEB0303E4 +:1017A0009942F9D89BF800000021019200F0CEF98F +:1017B0009AF80000002100F0C9F999F80000002112 +:1017C00000F0C4F998F80000002100F0BFF9387863 +:1017D000002100F0BBF9019A284942F21070176805 +:1017E0003368001BA1FB03139B0C03FB00F0136881 +:1017F0002149DB1B9842FAD8086842F2107332681C +:101800001E4CA4FB0242920C03FB02F20B681B1A53 +:101810009A42FBD81A4BF7EE006A95ED007A93EDE9 +:10182000005AB7EEC77AB7EEC55A9FED0D4BA5EE3D +:10183000047BB7EEC77BB4EEE67A85ED007AF1EE75 +:1018400010FA0ADDF0EE667A37EE677AB4EEE77AE0 +:10185000F1EE10FAF8DC85ED007A03B0BDE8F08F08 +:101860007B14AE47E17A943FDC03002000007A4409 +:10187000FC02002008030020041000E083DE1B436C +:1018800000030020EC020020F0020020F4020020FF +:10189000F80200208C4A8D4B90422DE9F0438C4D8C +:1018A0005C699969EF681DD98A4B984240F20181C1 +:1018B000894B40F22766DFF860E20344874D1A0A3D +:1018C000AEFB0232D30903EB830303EB830202F284 +:1018D000E243B34228BF3346A3F54873A5FB033662 +:1018E000F60804E07E4EB04294BF06260E26774AE4 +:1018F00007F01F0ED2F88030B64543F0C003C2F89F +:1019000080300AD2724B27F01F071A463743DF6038 +:101910001368002BFCDA07F01F0E14F000732ED1B1 +:10192000704D714AD5F810C015460CEA0202AA4261 +:101930000ABF4FF0C0534FF48052002284EA0305DF +:1019400015F0605F06D024F0605403F060535F4DE3 +:101950001C436C6181EA020313F4405F08D05B4BC7 +:1019600021F4405111431A469961936C1D07FCD430 +:1019700044F00074554A5461936C9906FCD40121DB +:101980005A4D0A4601FB02F300FB03F3AB4209D8B0 +:10199000072A00F28480013201FB02F300FB03F30B +:1019A000AB42F5D95248534D1844A5FB0030030D06 +:1019B0006C2B79D8352B7ED8DFF8608136234E48E2 +:1019C000DFF820C14D4DDCF80090B0FBF2F009EAE1 +:1019D00005054545B0FBF1F00BD043F400534FF43F +:1019E00080586546CCF80080CCF800302B68002B7E +:1019F000FCDADFF8D8C0013ADCF8103003F0070356 +:101A0000934207D002F007026546CCF81020AB6C79 +:101A1000DB03FCD40139890284EA010313F4E05F9B +:101A20000AD02A4B24F4E05401F4E0511A460C4346 +:101A30005C61936C9907FCD4314B32490344DB0958 +:101A4000A1FB0331090B042928BF04214B1E1B02F3 +:101A500084EA030212F4407F06D024F4407403F4B5 +:101A600040731A4A1C435461184B24F000741A4600 +:101A70005C61936C9B06FCD4B0FBF1F1224A764585 +:101A8000224B1060196008D2114B27F01F071A462D +:101A90003743DF601368002BFCDABDE8F0830429CC +:101AA00080D8013101226DE7DFF874806C2318487B +:101AB00086E712261BE71748DFF8688000FB03F073 +:101AC00043EA08087CE700BF00A4781F00C00F406D +:101AD000000008400046C32300BA3CDC1F85EB51E0 +:101AE00000366E0100800D4040300080FFB19F261F +:101AF000808D5B00819F5E1600B29F267F30018043 +:101B00007FD1F0089F10E5000803002004030020A7 +:101B100000643F4D001BB70023B24C00362000800C +:101B20006C200080002000800001074B1A181B5811 +:101B3000D2685868104202D011B9C3F888207047A3 +:101B4000C3F88420704700BF00000020272801D878 +:101B5000FFF7EABF704700BF27281CD800011A4AC8 +:101B6000012902EB0003105810B415D0042913D03A +:101B7000DC68426822EA040242609A68E9B10229FC +:101B80001ED003295B685DF8044B0CBF0F49104958 +:101B9000116015221A607047DA680129446842EA28 +:101BA000040242609A6808D040F6380111605B6810 +:101BB00015225DF8044B1A60704738211160F6E772 +:101BC00004491160F3E700BF000000203830010035 +:101BD0003800010038F0010004207146084203D0AB +:101BE000EFF3098000F008B8EFF3088000F004B8C4 +:101BF000704700BF704700BF1B4B05211B4A3820B0 +:101C000030B5C2F848110821C2F8380383B05A68C9 +:101C1000174C0A4317485A60C3F88410C3F8881059 +:101C20002368834202D91448FFF734FE0E4D08247E +:101C30000020124A1249C5F884400190019B93424A +:101C400005D8019B01330193019B9342F9D9C5F853 +:101C500088400190019B8B42EDD8019B0133019399 +:101C6000019B8B42F9D9E6E700C01B4000801F4072 +:101C700008030020FF45C32300A3E1113F420F00EA +:101C80003F548900836B30B41BB1536843F4004365 +:101C9000536072B6446B9CB1104B2260D3F8B041D4 +:101CA0000C4217D1D3F8404144F48044C3F840417A +:101CB000D3F8B851D3F840416404F3D5294209D18F +:101CC0000023064C8260C360D4F8B0311943C4F8D5 +:101CD000B0110263426362B630BC704700002E4010 +:101CE00038B50546036B06E0AA6B1C6890476A6B23 +:101CF0002346944208D0184633B1012B04D05A68C9 +:101D00001206F1D52B6338BD00232B636B6338BDFE +:101D1000F0B5F1B9224C23490020234B0122802544 +:101D2000A1600A601A464D60E060D3F8BC4188604B +:101D300044F001141D4DC3F8BC41D3F8B01141F07B +:101D40000101C3F8B0112860D2F8B031002BFBD1EB +:101D5000F0BD0904164B144D0126114C41F08001D1 +:101D600000221E60596000F5805CE26400F5005EB0 +:101D7000A36400F54057D5F8B01100F580462A4617 +:101D8000986041F48031C3F80CC0C3F810E05F6183 +:101D90009E61C5F8B011D2F8B031002BFBD1BAE783 +:101DA000003000202020002000002E40FC030020F6 +:101DB000002000202DE9F04FBB4C83B0D4F84481C3 +:101DC00018F0010FC4F844815ED0D4F8AC31002B78 +:101DD00055D04FF00119DFF8F0B22646B34FCA468E +:101DE000B96AFA6AC4F8AC31D4F8403143F400530C +:101DF000C4F84031D4F840319D04F5D5D6F84031CF +:101E000023F40053C6F84031C6F8B4A1D4F8B43175 +:101E100013F00113FAD188B240F28165CBF800309B +:101E2000A84200F29680B0F5D06F80F0D881B0F56E +:101E3000817F00F0578100F2D580802800F0268154 +:101E4000822840F0C78092B202F07F01072900F299 +:101E5000C18089009648974D0844016810062B7090 +:101E60006B7040F1E581C90301D501232B7002217C +:101E70009048FFF74DFFD4F8AC31002BB0D18A4A1F +:101E8000D2F8BC31002B44D118F0400F18D0864B4B +:101E9000D3F8AC111A46C3F8AC11D3F8BC11C3F88F +:101EA000BC11D2F8B041804B002CFAD14FF0FF3278 +:101EB000C3F8B421D3F8843100F074FB7E4B1C606E +:101EC00018F0807F03D07D4B1B6803B1984718F052 +:101ED000007F03D07A4B1B6803B1984718F0040FBA +:101EE00002D0714BD3F884316F4BD3F848211206DE +:101EF0000CD518F0800F09D072490A78002A00F03A +:101F00008E81531EDBB20B7003B9FBBE03B0BDE87C +:101F1000F08F6D49C2F8BC310868034240F0CC81B3 +:101F2000654A14681C40AFD0630700F1E281670383 +:101F300000F1EF81260700F1E881250300F1E1813E +:101F4000E00600F1DA81E1029ED56048FFF7C8FEA5 +:101F50009AE742F22105A84238D06FD8B0F5086F51 +:101F600000F02F81B0F5106F34D1C1F30741584A0A +:101F7000584811705849594A0193C6F8C801C6F823 +:101F8000CC11C6F8D02100F00FFB554A019B8021EF +:101F90000120FB6410605160D6F8BC11936041F0E1 +:101FA0000111BA64C6F8BC11D6F8B02142F48032EF +:101FB000C6F8B021444A1360D4F8B031002BFBD1ED +:101FC000D4F8AC31002B7FF40BAF58E7100C072886 +:101FD00000F03281C4F8C091D4F8AC31002B7FF40A +:101FE000FFAE4CE740F20235A84200F0B780B0F5F2 +:101FF000A06FEFD13A4A80200125FB645060156044 +:10200000D6F8BC01936040F00113BA64C6F8BC3145 +:10201000D6F8B03143F48033C6F8B031D4F8B031DB +:10202000002BFBD10B0C5B0643F08073C6F85431D8 +:10203000D4F8AC31002B7FF4D3AE20E742F2212359 +:10204000984200F0828042F221339842C2D1244B60 +:1020500001218022196000215A60D4F8BC21BB64A0 +:1020600042F001129960F964C4F8BC21D4F8B0318F +:1020700043F48033C4F8B031D4F8B031002BFBD135 +:10208000D4F8AC31002B7FF4ABAEF8E6094A02215C +:10209000104613705370FFF73BFED4F8AC31002BA1 +:1020A0007FF49EAEEBE600BF00002E400030002023 +:1020B000C0012E4010040020F4030020F003002093 +:1020C000F803002000040020FC0300204032002020 +:1020D000200400200200CC00C80002000200C8005A +:1020E0002020002092B202F07F03072B3FF672AF50 +:1020F00012F0800F4FEA8303884A4FF001011A441F +:10210000136814BF23F4803323F00103136080228B +:10211000834B196000215A60D4F8BC21BB6442F0A3 +:1021200001129960F964C4F8BC21D4F8B03143F4C9 +:102130008033C4F8B031D4F8B031002BFBD1D4F8DF +:10214000AC31002B7FF44CAE99E6764BC1F30741DE +:10215000754A1868754B1060197078E792B202F0F2 +:102160007F03072B3FF636AF12F0800F4FEA830351 +:102170006A4A4FF001011A44136814BF43F48033D4 +:1021800043F0010313608022654B196000215A60FF +:10219000D4F8BC21BB6442F001129960F964C4F820 +:1021A000BC21D4F8B03143F48033C4F8B031D4F852 +:1021B000B031002BFBD1D4F8AC31002B7FF410AE42 +:1021C0005DE65B4A01215B4B127818461A70FFF7F7 +:1021D0009FFDD4F8AC31002B7FF402AE4FE6564B96 +:1021E000586800283FF4F6AE090C1FFA82FE04E09E +:1021F0000C33586800283FF4EDAE1D888D42F7D1AE +:102200005D887545F4D1090A120C03290CBF0178C9 +:102210001989914228BF1146FFF77AFD2BE6D3F8C2 +:10222000482122F08002C3F8482103B0BDE8F08FB6 +:10223000CA077FF51CAE18E6404D0120FB602960FF +:102240003F4B4049186059603F49D6F8B0016A6079 +:1022500001F5005E40F00102A1F5005001F5805546 +:10226000BB609860A1F58050D860C6F8B0211961B4 +:102270005D61C3F818E0D4F8B031002BFBD1284AD7 +:10228000012048F28001FB6410604FF480305160FF +:10229000D6F8BC11936041F00113BA64C6F8BC31A2 +:1022A000D6F8B0310343C6F8B031CBF80000D4F80B +:1022B000B031002BFBD1DEE5204C42F2210000259D +:1022C00024880D6084427FF42BAE2049204C03C942 +:1022D0000D0C86282060A180A5717FF421AED2F874 +:1022E000481150241B4841F08001C2F84811047085 +:1022F00016E61948FFF7F4FC19E61848FFF7F0FC5A +:1023000021E61748FFF7ECFC1AE61648FFF7E8FC51 +:1023100013E61548FFF7E4FC0CE600BFC0012E40B1 +:1023200020200020880D002088320020600D002031 +:102330002004002010040020800200200804002057 +:10234000002000208000070018240020180400202E +:1023500080320020000400200031002000320020E4 +:10236000C03100208031002040310020002AA0F13F +:1023700002022DE9F04714BF00274FF00057022A50 +:1023800001D9BDE8F0874FEAC01ADFF840900D464A +:1023900004460AEB0906002140229846304600F028 +:1023A000DFFC012047EA05414AF80910C6F83880E9 +:1023B000B060B8F1000FE4D0034BA0401C6820438C +:1023C0001860BDE8F08700BFF40300200030002053 +:1023D000002AA0F102022DE9F04714BF00274FF0B8 +:1023E0000057022A01D9BDE8F0874022C501DFF875 +:1023F000449088461544002104461E4605EB090A10 +:10240000504600F0ADFC012247EA084145F80910AA +:10241000CAF83860CAF80820002EE4D004F1100091 +:10242000034B8240186802431A60BDE8F08700BF82 +:10243000F40300200030002012048160C36142F0E8 +:102440008002F0B44260012701F5805601F5005585 +:1024500001F5405401F580420760C66005614461A2 +:102460008261F0BC704700BF831E022B00D9704709 +:1024700030B4064B00F1100401250A4603EBC010EE +:1024800005FA04F130BCFFF7FDBB00BF403000206F +:10249000831E022B00D9704710B4054B01240A4655 +:1024A00004FA00F103EBC0105DF8044BFFF7EABB40 +:1024B00000300020124A134BD2F8200220F07F4057 +:1024C000984210B584B002D800EB800040000E4C5A +:1024D00001A90A2200F07EFA01A90023204611F882 +:1024E000012B01333AB10A2B20F8022FF7D1162322 +:1024F000237004B010BD5B00DBB2237004B010BDCC +:1025000000441F407F969800A40300204368C269DE +:10251000C3F30E43054930B4C3F14003044C002516 +:1025200021F8123024F8125030BC7047F00B002014 +:10253000E80B0020F8B5154B1B783BB903F0FF04FE +:10254000134B1B7813B1134D2A8802B9F8BD124FF3 +:102550002346124EC2F580723978114806EB4116B7 +:1025600000EB01213046FFF767FF31460420FFF7FB +:102570007BFF3B780133DBB2062B98BF3B704FF0FB +:10258000000388BF3C702B80F8BD00BF240B0020E7 +:1025900020040020600C0020250B0020800C00206F +:1025A00024040020704700BF0021E0222048F8B535 +:1025B0000C46204E204D00F0D3FB204F2146204BEF +:1025C0006022347028461F4E1C8000F0C9FB234651 +:1025D0002246102102203C60BC803460B480FFF7AA +:1025E000F7FE2246184B40210320FFF7BFFE23468B +:1025F000224640210420FFF7EBFE234640222846D6 +:102600001249FFF719FF29460320FFF741FF104B3E +:102610004A22104910480860C3F88440C3F880205B +:10262000D3F8482142F08072C3F84821F8BD00BFBA +:10263000800C0020250B0020000C0020F00B002057 +:10264000600C0020E80B0020BD0E0000280B0020CD +:1026500000002E40F0030020E50E0000024A034B6C +:1026600010881B88C01A7047F00B0020E80B002070 +:1026700010B4EFF3108272B6437F33B9017F0129A2 +:1026800008D0032910D00123437702B962B65DF860 +:10269000044B7047114C2168A1B1114943610B688B +:1026A000086083615861EEE70E4C216881B10E49E4 +:1026B00043610B680860836158610C4B4FF0805197 +:1026C0001960E0E7064B4161816120601860DAE73C +:1026D000054B4161816120601860EEE7740D0020B8 +:1026E000700D0020640D0020680D002004ED00E056 +:1026F00010B4047F4160022CC26003D05DF8044B2B +:10270000FFF7B6BF83685DF8044B184770B5EFF369 +:10271000108172B60C4C23688BB10C4E00255A699F +:1027200022607AB1956101B962B65D7718469B68FF +:102730009847EFF3108172B62368002BEFD101B9EF +:1027400062B670BD3260EEE7640D0020680D0020B7 +:10275000FFF7DCBF184A30B41468002C28D0036897 +:1027600021688B420FD2CB1A00218460C1602360A4 +:10277000E0601060022330BC0375704703601446AC +:1027800011688B4208D3A2685B1A002AF6D18260D6 +:10279000C4600360A060EDE7D568CB1A82600222B6 +:1027A000C560E060C16888602360027530BC704716 +:1027B0008460C4601060DDE76C0D0020F8B5224E27 +:1027C00034682CB32368002B3AD11D461F4F04E018 +:1027D0003468ECB12368002B32D1A36803B1DD600B +:1027E00020693360036825751B68BB4221D1037FD4 +:1027F0004560022BC46020D0FFF73AFF6368002BCE +:10280000E6D023602046FFF7A5FF3468002CE1D115 +:10281000EFF3108372B60E4A00211068116003B9FD +:1028200062B628B18468FFF795FF20460028F9D1E9 +:10283000F8BD224600219847E0E783689847DDE726 +:10284000013B2360E4E700BF6C0D0020A1100000F5 +:102850007C0D0020044A054B1168054A1960136875 +:1028600001331360FFF7AABF041000E0840D0020BD +:10287000880D002070B5214C237883B9204B0122AC +:102880001B7822701BBB1F4B1B78002B29D11E4BC2 +:1028900000211A68217012B1EFF3058202B170BDF8 +:1028A000EFF3108072B61A68F2B1184C2178D9B9DA +:1028B0000126556926701D60D5B1A96100B962B6BF +:1028C00000259368104655779847257070BDFFF72F +:1028D000C5FE0028D7D000F015FA0A4B1B78002B54 +:1028E000D5D000F0FBF9D2E70028D8D162B670BD90 +:1028F000074B1D600028E3D1E1E700BF800D0020F9 +:10290000BA030020A80D0020740D0020780D0020CF +:10291000700D0020002852D02DE9F04F814683B081 +:10292000274C0120274D284E54E8003F2A68316883 +:1029300044E80003002BF7D1244F4FF47A7E24485B +:10294000D7F800C0BB4607F1C6470368C1EB0C01CE +:1029500007F5DE1707F67F67A7FB03C3BA46012713 +:102960009B0CB1FBF3F30EFB023854E8003F2A68DE +:10297000316844E80073002BF7D1DBF800C04FF456 +:102980007A7E03680EFB02F2C1EB0C01AAFB033E48 +:10299000C8EB02034FEA9E42B1FBF2F1CA18B2F54E +:1029A0007A7F07D3B9F1010908F57A78DDD103B050 +:1029B000BDE8F08F0190FFF75DFF0198D5E7704704 +:1029C0008C320020880D0020840D0020041000E0CF +:1029D00008030020F0B44E1E0025374600E0013504 +:1029E000B0FBF2F302FB130000F13704092800F1F9 +:1029F0003000E4B298BFC4B2184607F8014F002B6C +:102A0000EDD14A1953704DB1013316F8014F1778C3 +:102A1000E81A3770834202F80149F5DB0846F0BC3A +:102A2000704700BFA4484FF00F0CA44B826F42F4D4 +:102A30007F02F0B582670025D0F880204FF4704601 +:102A40009F4C4FF4604E29464FF4806714432A464A +:102A5000C0F88040A3F88C6148F2B826A3F88EC174 +:102A6000A3F89051B3F8880180B240F0F000A3F8C9 +:102A7000880101EB4100914B0131002540011C46CA +:102A800004290344A3F804E0DF805A841A865A809C +:102A90005A81DE815A82DA825A83DA83E9D1B4F824 +:102AA00088014FF00F0C874B4FF4704680B22946D7 +:102AB0004FF460472A4640EA0C004FF4806EA4F8B9 +:102AC0008801B4F8880180B240F47060A4F88801ED +:102AD000A3F88C6148F2B826A3F88EC1A3F89051F0 +:102AE000B3F8880180B240F0F000A3F8880101EB50 +:102AF0004100744B0131002540011C460429034468 +:102B00009F80A3F806E05A841A865A805A81DE8193 +:102B10005A82DA825A83DA83E9D1B4F888014FF015 +:102B20000F0C694B4FF4704680B229464FF4604752 +:102B30002A4640EA0C004FF4806EA4F88801B4F8ED +:102B4000880180B240F47060A4F88801A3F88C6119 +:102B500048F2B826A3F88EC1A3F89051B3F88801C3 +:102B600080B240F0F000A3F8880101EB4100564B21 +:102B70000131002540011C46042903449F80A3F82D +:102B800006E05A841A865A805A81DE815A82DA8295 +:102B90005A83DA83E9D1B4F888014FF00F0C4B4B1C +:102BA0004FF4704780B229464FF460462A4640EA07 +:102BB0000C004FF4806EA4F88801B4F8880180B24C +:102BC00040F47060A4F88801A3F88C7148F2B8272B +:102BD000A3F88EC1A3F89051B3F8880180B240F0F9 +:102BE000F000A3F8880101EB4100384B01314001AE +:102BF0001C46042903449E80A3F806E05A841A86E2 +:102C00005A805A81DF815A82DA825A83DA83EAD182 +:102C1000B4F888310F27002241F201069BB245F635 +:102C2000C05E114643F226053B43A4F88831B4F850 +:102C300088319BB243F47063A4F888315001244B6F +:102C400001320344042A99815981DF819E82A3F8CD +:102C500006E0198019829D81F0D100220F2741F2F0 +:102C6000010645F6C055114643F226045001194BA2 +:102C700001320344042A99815981DF819E82DD80DB +:102C8000198019829C81F1D100220F2741F201069F +:102C900045F6C055114643F2260450010E4B013251 +:102CA0000344042A99815981DF819E82DD80198045 +:102CB00019829C81F1D1F0BD00C00F4000C03D40A1 +:102CC000000003FC00003E4000403E4000803E40CB +:102CD00000C01D4000001E4000401E4038B5074B9C +:102CE0001C784CB1064D55F8043F002BFBD098479B +:102CF000631E13F0FF04F6D138BD00BFA80D0020FD +:102D0000880D0020014B00221A707047BA03002082 +:102D100070B50F4E0F4D761BB61018BF002405D0AE +:102D2000013455F8043B9847A642F9D10A4E0B4DA1 +:102D3000761B00F065F8B61018BF002406D00134E9 +:102D400055F8043B9847A642F9D170BD70BD00BF4D +:102D500048160060481600604C1600604816006077 +:102D600070B4840746D0541E002A41D0CDB2034629 +:102D700002E0621EE4B3144603F8015B9A07F8D13F +:102D8000032C2ED9CDB245EA05250F2C45EA054581 +:102D900019D903F110022646103E0F2E42F8105C9E +:102DA00042F80C5C42F8085C42F8045C02F1100244 +:102DB000F2D8A4F1100222F00F0204F00F04103236 +:102DC000032C13440DD91E462246043A032A46F822 +:102DD000045BFAD8221F22F003020432134404F0E9 +:102DE00003042CB1C9B21C4403F8011BA342FBD15C +:102DF00070BC704714460346C2E700BF00000000E5 +:102E00005FF800F0E1150060000000000000000025 +:042E1000F8B500BF52 +:102E140000000042C8801F40B8821F400800000024 +:102E240000000042C4801F40B4821F400400000020 +:102E340000C0004224801F4014821F401000000084 +:102E440000C0004228801F4018821F40200000005C +:102E540000C000422C801F401C821F404000000024 +:102E640000C0004234801F4024821F400001000043 +:102E74000040004264811F4054831F40000400004E +:102E84000040004280811F4070831F400000020008 +:102E9400004000427C811F406C831F400000010001 +:102EA4000040004268811F4058831F400008000012 +:102EB400004000423C811F402C831F400100000061 +:102EC4000040004244811F4034831F40040000003E +:102ED4000040004240811F4030831F400200000038 +:102EE4000040004248811F4038831F400800000012 +:102EF4000000004204811F40F4821F4000000400CF +:102F04000000004208811F40F8821F4000000800B2 +:102F14000000004218811F4008831F400000800009 +:102F24000000004214811F4004831F400000400041 +:102F34000000004200811F40F0821F400000020098 +:102F440000000042FC801F40EC821F400000010092 +:102F54000000004224811F4014831F40000000042D +:102F64000000004228811F4018831F400000000811 +:102F7400000000421C811F400C831F400000000120 +:102F84000000004220811F4010831F400000000207 +:102F940000000042EC801F40DC821F400010000053 +:102FA40000000042F0801F40E0821F40002000002B +:102FB4000000004234811F4024831F400000004071 +:102FC4000000004238811F4028831F400000008019 +:102FD4000080004294801F4084821F40000004004F +:102FE40000C0004290801F4080821F40000000808B +:102FF40000800042A8801F4098821F40000080008B +:1030040000800042A4801F4094821F4000004000C2 +:10301400004000426C811F405C831F400010000090 +:1030240000C0004230801F4020821F40800000000A +:1030340000800042C8811F40B8831F400080000008 +:1030440000800042C4811F40B4831F400040000040 +:1030540000800042C0811F40B0831F400020000058 +:1030640000800042BC811F40AC831F400010000060 +:1030740000800042D0811F40C0831F400000020036 +:1030840000800042CC811F40BC831F40000001002F +:10309400000100007403002012000000000600007C +:1030A4000C0300200A00000000020000300300208E +:1030B400430000000007000030030020430000002C +:1030C40000030000A0030020000000000103090425 +:1030D40018030020000000000203090488030020F4 +:1030E4000000000003030904A40300200000000002 +:1030F4000000000000000000000000000D000000BF +:103104000E0000000F00000010000000110000007D +:103114000000803F0029DE07007B9A170A060002A0 +:10312400020000400100000018035400650065001F +:103134006E00730079006400750069006E006F0012 +:1031440009024300020100C0320904000001020226 +:1031540001000524001001052401010104240206D4 +:10316400052406000107058203100010090401006C +:10317400020A00000007050302400000070584025C +:10318400400000001201000202000040C016830447 +:1031940079020102030100001603550053004200A6 +:1031A4002000530065007200690061006C0000009B +:1031B400040309040C0300000000000000000000E8 +:1031C40000000000000000000000010000000000FA +:040000056000100087 +:00000001FF diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index 879b4e4c..5fba9bc3 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -144,6 +144,7 @@ def program_teensy(hex_file_path, program_gpio: int, logger: Logger): echo 26 | sudo tee /sys/class/gpio/export sudo chmod a+rw /sys/class/gpio/gpio26/* + echo out > /sys/class/gpio/gpio26/direction """ From 595bfdabbaa6ea43f30eaba2ff647d49c82947f7 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Tue, 17 Dec 2019 18:52:13 -0800 Subject: [PATCH 254/549] add back manual udev rule setup to getting started --- docs/getting-started.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/getting-started.md b/docs/getting-started.md index 9cd6ecf2..a2d50cc6 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -17,6 +17,7 @@ permalink: / - [Configure M0](#configure-m0) - [Position control of M0](#position-control-of-m0) - [Other control modes](#other-control-modes) +- [Watchdog Timer](#watchdog-timer) - [What's next?](#whats-next) @@ -126,6 +127,12 @@ Try step 5 again ### Linux 1. [Install Python 3](https://www.python.org/downloads/). (for example, on Ubuntu, `sudo apt install python3 python3-pip`) 2. Install the ODrive tools by opening a terminal and typing `sudo pip3 install odrive` Enter + * This should automatically add the udev rules. If this fails for some reason you can add them manually: + ```bash + echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="0d[0-9][0-9]", MODE="0666"' | sudo tee /etc/udev/rules.d/91-odrive.rules + sudo udevadm control --reload-rules + sudo udevadm trigger + ``` 3. (needed on Ubuntu, maybe other distros too) Add odrivetool into the path, by adding `~/.local/bin/` into `~/.bash_profile`, for example by running `nano ~/.bashrc`, scrolling to the bottom, pasting `PATH=$PATH:~/.local/bin/`, and then saving and closing, and close and reopen the terminal window. ## Firmware From 5541df081d1d7a08a1a69d32671924443d457caa Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 22 Dec 2019 16:49:50 -0500 Subject: [PATCH 255/549] Add endstop, estop, and DC Bus over power errors to enums --- tools/odrive/enums.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index b9bf4f85..310eb7bb 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -29,6 +29,10 @@ class errors: ERROR_CONTROLLER_FAILED = 0x200 ERROR_POS_CTRL_DURING_SENSORLESS = 0x400 ERROR_WATCHDOG_TIMER_EXPIRED = 0x800 + ERROR_MIN_ENDSTOP_PRESSED = 0x1000 + ERROR_MAX_ENDSTOP_PRESSED = 0x2000 + ERROR_ESTOP_REQUESTED = 0x4000 + ERROR_DC_BUS_OVER_POWER = 0x8000 class motor: ERROR_NONE = 0 From 77efd6b7619c91148a8e795d3afe832704d3a578 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 22 Dec 2019 17:20:10 -0500 Subject: [PATCH 256/549] Allow infinite negative current by default --- Firmware/MotorControl/odrive_main.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 022871ba..5e1caf37 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -93,7 +93,7 @@ struct BoardConfig_t { // Date: Sun, 22 Dec 2019 17:42:16 -0500 Subject: [PATCH 257/549] Add endstop test config --- tools/odrive/tests/endstop_test.py | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tools/odrive/tests/endstop_test.py diff --git a/tools/odrive/tests/endstop_test.py b/tools/odrive/tests/endstop_test.py new file mode 100644 index 00000000..a823549c --- /dev/null +++ b/tools/odrive/tests/endstop_test.py @@ -0,0 +1,34 @@ +import odrive +from odrive.enums import * +from odrive.utils import * + +print("finding an odrive...") +odrv0 = odrive.find_any() +print('Odrive found') + +odrv0.axis1.controller.config.vel_limit = 50000 +odrv0.axis1.controller.config.control_mode = CTRL_MODE_POSITION_CONTROL +odrv0.axis1.controller.config.input_mode = INPUT_MODE_PASSTHROUGH +odrv0.axis1.encoder.config.cpr = 2400 +odrv0.axis1.encoder.config.bandwidth = 1000 +odrv0.axis1.motor.config.calibration_current = 5 +odrv0.axis1.motor.config.current_lim = 5 +odrv0.axis1.controller.config.homing_speed = 5000 +odrv0.config.brake_resistance = 0 + +odrv0.axis0.min_endstop.config.gpio_num = 6 +odrv0.axis0.min_endstop.config.enabled = True +odrv0.axis0.min_endstop.config.offset = -1000 +odrv0.axis0.max_endstop.config.gpio_num = 5 +odrv0.axis0.max_endstop.config.enabled = True + +odrv0.axis1.min_endstop.config.gpio_num = 8 +odrv0.axis1.min_endstop.config.enabled = True +odrv0.axis1.min_endstop.config.offset = -1000 +odrv0.axis1.max_endstop.config.gpio_num = 7 +odrv0.axis1.max_endstop.config.enabled = True + +odrv0.axis1.config.startup_encoder_offset_calibration = True +odrv0.axis1.config.startup_motor_calibration = True +odrv0.axis1.config.startup_homing = True +odrv0.axis1.config.startup_closed_loop_control = True From 96e01cc4c2fc007025767e657ded6a3edb5ac8dd Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 22 Dec 2019 18:49:53 -0500 Subject: [PATCH 258/549] Add pullup config to endstops to account for NC configs --- Firmware/MotorControl/endstop.cpp | 2 +- Firmware/MotorControl/endstop.hpp | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index 72bcae3a..f8fc4480 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -43,7 +43,7 @@ void Endstop::set_enabled(bool enable) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = gpio_pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = config_.is_active_high ? GPIO_PULLDOWN : GPIO_PULLUP; + GPIO_InitStruct.Pull = config_.pullup ? GPIO_PULLUP : GPIO_PULLDOWN; HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); } } diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index ad9ef5ef..eb1e3c8e 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -4,14 +4,15 @@ class Endstop { public: struct Config_t { - float offset = 0; + float offset = 0; float debounce_ms = 50.0f; uint16_t gpio_num = 0; - bool enabled = false; + bool enabled = false; bool is_active_high = false; + bool pullup = false; }; - Endstop(Endstop::Config_t& config); + explicit Endstop(Endstop::Config_t& config); Endstop::Config_t& config_; Axis* axis_ = nullptr; @@ -34,6 +35,7 @@ class Endstop { [](void* ctx) { static_cast(ctx)->update_config(); }, this), make_protocol_property("offset", &config_.offset), make_protocol_property("is_active_high", &config_.is_active_high), + make_protocol_property("pullup", &config_.pullup), make_protocol_property("debounce_ms", &config_.debounce_ms))); } From 45573eb86712b41092eeb1a797475d1cb13c05a9 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 31 Dec 2019 22:16:46 -0500 Subject: [PATCH 259/549] Fix error reading encoder errors --- tools/odrive/enums.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 4d401755..5a3f1b76 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -59,9 +59,9 @@ class errors: ERROR_UNSUPPORTED_ENCODER_MODE = 0x08 ERROR_ILLEGAL_HALL_STATE = 0x10 ERROR_INDEX_NOT_FOUND_YET = 0x20 - ERROR_ABS_SPI_TIMEOUT = 0x40, - ERROR_ABS_SPI_COM_FAIL = 0x80, - ERROR_ABS_SPI_NOT_READY = 0x100, + ERROR_ABS_SPI_TIMEOUT = 0x40 + ERROR_ABS_SPI_COM_FAIL = 0x80 + ERROR_ABS_SPI_NOT_READY = 0x100 class controller: ERROR_NONE = 0 From bb59c2640e5c0fd1a4e11c9b9f8b536547e37f7e Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 31 Dec 2019 23:16:02 -0500 Subject: [PATCH 260/549] Add optional doctest test runner for unit testing --- Firmware/Tests/test_runner.cpp | 66 +++++++++++++++++++--------------- Firmware/Tupfile.lua | 8 ++++- Firmware/tup.config.default | 1 + 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index d5a211f9..cae2441c 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -9,17 +9,16 @@ #define DOCTEST_CONFIG_NO_POSIX_SIGNALS // #define DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS - #include using std::cout; using std::endl; struct can_Message_t { - uint32_t id = 0x000; // 11-bit max is 0x7ff, 29-bit max is 0x1FFFFFFF - bool isExt = false; - bool rtr = false; - uint8_t len = 8; + uint32_t id = 0x000; // 11-bit max is 0x7ff, 29-bit max is 0x1FFFFFFF + bool isExt = false; + bool rtr = false; + uint8_t len = 8; uint8_t buf[8] = {0, 0, 0, 0, 0, 0, 0, 0}; }; @@ -44,7 +43,7 @@ enum InputMode_t { template T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel) { uint64_t tempVal = 0; - uint64_t mask = (1ULL << length) - 1; + uint64_t mask = (1ULL << length) - 1; if (isIntel) { std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); @@ -60,18 +59,17 @@ T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, return retVal; } -template +template float can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { T retVal = can_getSignal(msg, startBit, length, isIntel); return (retVal * factor) + offset; } -template -void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, const uint8_t length, const bool isIntel){ +template +void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, const uint8_t length, const bool isIntel) { uint64_t mask = (1ULL << length) - 1; uint64_t valAsBits = 0; std::memcpy(&valAsBits, &val, sizeof(T)); - if (isIntel) { uint64_t data = 0; @@ -96,7 +94,7 @@ void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, con template void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { - T scaledVal = (val - offset) / factor; + T scaledVal = (val - offset) / factor; can_setSignal(msg, scaledVal, startBit, length, isIntel); } @@ -117,9 +115,9 @@ TEST_CASE("fake") { TEST_SUITE("CAN Functions") { TEST_CASE("reverse") { can_Message_t rxmsg; - rxmsg.id = 0x000; + rxmsg.id = 0x000; rxmsg.isExt = false; - rxmsg.len = 8; + rxmsg.len = 8; rxmsg.buf[0] = 0x12; rxmsg.buf[1] = 0x34; @@ -148,7 +146,7 @@ TEST_SUITE("CAN Functions") { CHECK(floatVal == 1234.6789f); can_Message_t msg; - msg.id = 0x00E; + msg.id = 0x00E; msg.buf[0] = 0x96; msg.buf[1] = 0x00; msg.buf[2] = 0x00; @@ -189,10 +187,9 @@ TEST_SUITE("CAN Functions") { } } - -TEST_SUITE("delta_enc"){ +TEST_SUITE("delta_enc") { // Modulo (as opposed to remainder), per https://stackoverflow.com/a/19288271 - int mod(int dividend, int divisor){ + int mod(int dividend, int divisor) { int r = dividend % divisor; return (r < 0) ? (r + divisor) : r; } @@ -205,8 +202,7 @@ TEST_SUITE("delta_enc"){ return delta_enc; } - TEST_CASE("mod"){ - + TEST_CASE("mod") { int cpr = 1000; // Check moves around 0 @@ -218,14 +214,13 @@ TEST_SUITE("delta_enc"){ CHECK(getDelta(50, 500, cpr) == -450); CHECK(getDelta(500, 50, cpr) == 450); - // Test moving a distance larger than cpr / 2 CHECK(getDelta(950, 450, cpr) == 500); CHECK(getDelta(451, 950, cpr) == -499); CHECK(getDelta(450, 950, cpr) == 500); - + // Test handling around mid-point - CHECK(getDelta(501, 499, cpr) == 2); + CHECK(getDelta(501, 499, cpr) == 2); CHECK(getDelta(499, 501, cpr) == -2); CHECK(getDelta(550, 450, cpr) == 100); CHECK(getDelta(450, 550, cpr) == -100); @@ -235,7 +230,7 @@ TEST_SUITE("delta_enc"){ TEST_SUITE("velLimiter") { // Velocity limiting in current mode #include -using doctest::Approx; + using doctest::Approx; auto limitVel(float vel_limit, float vel_estimate, float vel_gain, float Iq) { float Imax = (vel_limit - vel_estimate) * vel_gain; @@ -256,21 +251,21 @@ using doctest::Approx; CHECK(limitVel(1000.0f, 0.0f, 1.0f, -1.0f) == -1.0f); } - TEST_CASE("Accelerating"){ + TEST_CASE("Accelerating") { CHECK(limitVel(200000.0f, 195000.0f, 5.0E-4f, 30.0f) == 2.5f); CHECK(limitVel(200000.0f, 205000.0f, 5.0E-4f, 30.0f) == -2.5f); CHECK(limitVel(200000.0f, -195000.0f, 5.0E-4, -30.0f) == -2.5f); CHECK(limitVel(200000.0f, -205000.0f, 5.0E-4f, -30.0f) == 2.5f); } - TEST_CASE("Decelerating"){ + TEST_CASE("Decelerating") { CHECK(limitVel(200000.0f, 195000.0f, 5.0E-4f, -30.0f) == -30.0f); CHECK(limitVel(200000.0f, 205000.0f, 5.0E-4f, -30.0f) == -30.0f); CHECK(limitVel(200000.0f, -195000.0f, 5.0E-4, 30.0f) == 30.0f); CHECK(limitVel(200000.0f, -205000.0f, 5.0E-4f, 30.0f) == 30.0f); } - TEST_CASE("Over-Center"){ + TEST_CASE("Over-Center") { CHECK(limitVel(20000.0f, 1000.0f, 5.0E-4f, 30.0f) == 9.5f); CHECK(limitVel(20000.0f, -1000.0f, 5.0E-4f, 30.0f) == Approx(10.5f)); } @@ -279,7 +274,7 @@ using doctest::Approx; TEST_SUITE("vel_ramp") { float vel_ramp_old(float input_vel_, float vel_setpoint_, float vel_ramp_rate) { float max_step_size = 0.000125f * vel_ramp_rate; - float full_step = input_vel_ - vel_setpoint_; + float full_step = input_vel_ - vel_setpoint_; float step; if (std::abs(full_step) > max_step_size) { step = std::copysignf(max_step_size, full_step); @@ -289,11 +284,19 @@ TEST_SUITE("vel_ramp") { return step; } - float vel_ramp_new(float input_vel_, float vel_setpoint_, float vel_ramp_rate){ + float vel_ramp_new(float input_vel_, float vel_setpoint_, float vel_ramp_rate) { float max_step_size = 0.000125f * vel_ramp_rate; - float full_step = input_vel_ - vel_setpoint_; + float full_step = input_vel_ - vel_setpoint_; return std::clamp(full_step, -max_step_size, max_step_size); } + + uint8_t parity(uint16_t v) { + v ^= v >> 8; + v ^= v >> 4; + v ^= v >> 2; + v ^= v >> 1; + return v & 1; + } TEST_CASE("Blah") { float vel_setpoint = 0.0f; @@ -316,4 +319,9 @@ TEST_SUITE("vel_ramp") { input_vel = 0.1234f; CHECK(vel_ramp_old(input_vel, vel_setpoint, vel_ramp_rate) == vel_ramp_new(input_vel, vel_setpoint, vel_ramp_rate)); } + + TEST_CASE("Parity") { + CHECK(parity(0x0DDF) == 0); + CHECK(parity(0x8DDF) == 1); + } } \ No newline at end of file diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 65d10538..16e3aa2a 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -187,4 +187,10 @@ build{ '.', "C:/Tools/doctest/doctest" } -} \ No newline at end of file +} + +if tup.getconfig('DOCTEST') == 'true' then + TEST_INCLUDES = '-IC:/Tools/doctest/doctest' + tup.frule{inputs='Tests/test_runner.cpp', command='g++ -std=gnu++17 '..TEST_INCLUDES..' %f -o %o', outputs='Tests/test_runner.exe'} + tup.frule{inputs='Tests/test_runner.exe', command='%f'} +end \ No newline at end of file diff --git a/Firmware/tup.config.default b/Firmware/tup.config.default index 5c2c4822..b2d49106 100644 --- a/Firmware/tup.config.default +++ b/Firmware/tup.config.default @@ -4,6 +4,7 @@ CONFIG_USB_PROTOCOL=native CONFIG_UART_PROTOCOL=ascii CONFIG_DEBUG=false +CONFIG_DOCTEST=false # Uncomment this to error on compilation warnings #CONFIG_STRICT=true From 8ee478f62999a674d325d5a26c6c10addc832f69 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 31 Dec 2019 23:16:22 -0500 Subject: [PATCH 261/549] remove test_other.cpp --- Firmware/Tests/test_other.cpp | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Firmware/Tests/test_other.cpp diff --git a/Firmware/Tests/test_other.cpp b/Firmware/Tests/test_other.cpp deleted file mode 100644 index d42680b5..00000000 --- a/Firmware/Tests/test_other.cpp +++ /dev/null @@ -1 +0,0 @@ -#include \ No newline at end of file From 2dfff2a7d6ecfb2601207891b68421b2a7483625 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 1 Jan 2020 01:52:03 -0500 Subject: [PATCH 262/549] Only access abs_spi_dma_rx_ once --- Firmware/MotorControl/encoder.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index a226e905..c1223d07 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -373,11 +373,12 @@ void Encoder::abs_spi_cb(){ switch (config_.mode) { case MODE_SPI_ABS_AMS: { uint8_t parity_calc, parity_bit; - parity_calc = parity(abs_spi_dma_rx_[0]&0x7FFF); - parity_bit = abs_spi_dma_rx_[0] >>15; + auto rawVal = abs_spi_dma_rx_[0]; + parity_calc = parity(rawVal & 0x7FFF); + parity_bit = rawVal >>15; if (parity_calc == parity_bit) { - pos_abs_ = abs_spi_dma_rx_[0] & 0x3FFF; + pos_abs_ = rawVal & 0x3FFF; // We are going to ignore values all high or low // This might happen in normal operation, but its unlikely // The filter will handle these cases From 27b3c69e8eaa7381ed846d39e5bbd2ba451f71b6 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 1 Jan 2020 01:52:21 -0500 Subject: [PATCH 263/549] Fix parity calculation --- Firmware/MotorControl/encoder.cpp | 8 ++------ Firmware/Tests/test_runner.cpp | 9 ++++++--- Firmware/Tupfile.lua | 2 +- ODrive_Workspace.code-workspace | 9 ++++++++- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index c1223d07..6de18e9c 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -366,7 +366,7 @@ uint8_t parity(uint16_t v){ v ^= v >> 4; v ^= v >> 2; v ^= v >> 1; - return v & 1; + return (~v) & 1; } void Encoder::abs_spi_cb(){ HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); @@ -379,11 +379,7 @@ void Encoder::abs_spi_cb(){ if (parity_calc == parity_bit) { pos_abs_ = rawVal & 0x3FFF; - // We are going to ignore values all high or low - // This might happen in normal operation, but its unlikely - // The filter will handle these cases - if (pos_abs_ != 0 && pos_abs_ != 0x3FFF) - abs_spi_pos_updated_ = true; + abs_spi_pos_updated_ = true; } } break; case MODE_SPI_ABS_AEAT: { diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index cae2441c..7d38c29f 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -295,7 +295,7 @@ TEST_SUITE("vel_ramp") { v ^= v >> 4; v ^= v >> 2; v ^= v >> 1; - return v & 1; + return (~v) & 1; } TEST_CASE("Blah") { @@ -321,7 +321,10 @@ TEST_SUITE("vel_ramp") { } TEST_CASE("Parity") { - CHECK(parity(0x0DDF) == 0); - CHECK(parity(0x8DDF) == 1); + CHECK(parity(0x0DDF & 0x7FFF) == 1); + CHECK(parity(0x8DDF & 0x7FFF) == 1); + CHECK(parity(0x5BFF & 0x7FFF) == 0); + CHECK(parity(0x0 & 0x7FFF) == 1); + CHECK(__builtin_parity(0x5BFF & 0x7FFF) == 1); } } \ No newline at end of file diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 16e3aa2a..d329e4e7 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -191,6 +191,6 @@ build{ if tup.getconfig('DOCTEST') == 'true' then TEST_INCLUDES = '-IC:/Tools/doctest/doctest' - tup.frule{inputs='Tests/test_runner.cpp', command='g++ -std=gnu++17 '..TEST_INCLUDES..' %f -o %o', outputs='Tests/test_runner.exe'} + tup.frule{inputs='Tests/test_runner.cpp', command='g++ -O3 -std=gnu++17 '..TEST_INCLUDES..' %f -o %o', outputs='Tests/test_runner.exe'} tup.frule{inputs='Tests/test_runner.exe', command='%f'} end \ No newline at end of file diff --git a/ODrive_Workspace.code-workspace b/ODrive_Workspace.code-workspace index 6e9385a0..2012b56e 100644 --- a/ODrive_Workspace.code-workspace +++ b/ODrive_Workspace.code-workspace @@ -57,7 +57,14 @@ "fstream": "cpp", "iomanip": "cpp", "optional": "cpp", - "sstream": "cpp" + "sstream": "cpp", + "atomic": "cpp", + "iterator": "cpp", + "memory_resource": "cpp", + "ratio": "cpp", + "string": "cpp", + "mutex": "cpp", + "thread": "cpp" } } } From bb31f042da7c453998f62a57750fa06a3a1497f5 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 1 Jan 2020 01:52:21 -0500 Subject: [PATCH 264/549] Revert "Fix parity calculation" This reverts commit 27b3c69e8eaa7381ed846d39e5bbd2ba451f71b6. --- Firmware/MotorControl/encoder.cpp | 8 ++++++-- Firmware/Tests/test_runner.cpp | 9 +++------ Firmware/Tupfile.lua | 2 +- ODrive_Workspace.code-workspace | 9 +-------- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 6de18e9c..c1223d07 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -366,7 +366,7 @@ uint8_t parity(uint16_t v){ v ^= v >> 4; v ^= v >> 2; v ^= v >> 1; - return (~v) & 1; + return v & 1; } void Encoder::abs_spi_cb(){ HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); @@ -379,7 +379,11 @@ void Encoder::abs_spi_cb(){ if (parity_calc == parity_bit) { pos_abs_ = rawVal & 0x3FFF; - abs_spi_pos_updated_ = true; + // We are going to ignore values all high or low + // This might happen in normal operation, but its unlikely + // The filter will handle these cases + if (pos_abs_ != 0 && pos_abs_ != 0x3FFF) + abs_spi_pos_updated_ = true; } } break; case MODE_SPI_ABS_AEAT: { diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index 7d38c29f..cae2441c 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -295,7 +295,7 @@ TEST_SUITE("vel_ramp") { v ^= v >> 4; v ^= v >> 2; v ^= v >> 1; - return (~v) & 1; + return v & 1; } TEST_CASE("Blah") { @@ -321,10 +321,7 @@ TEST_SUITE("vel_ramp") { } TEST_CASE("Parity") { - CHECK(parity(0x0DDF & 0x7FFF) == 1); - CHECK(parity(0x8DDF & 0x7FFF) == 1); - CHECK(parity(0x5BFF & 0x7FFF) == 0); - CHECK(parity(0x0 & 0x7FFF) == 1); - CHECK(__builtin_parity(0x5BFF & 0x7FFF) == 1); + CHECK(parity(0x0DDF) == 0); + CHECK(parity(0x8DDF) == 1); } } \ No newline at end of file diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index d329e4e7..16e3aa2a 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -191,6 +191,6 @@ build{ if tup.getconfig('DOCTEST') == 'true' then TEST_INCLUDES = '-IC:/Tools/doctest/doctest' - tup.frule{inputs='Tests/test_runner.cpp', command='g++ -O3 -std=gnu++17 '..TEST_INCLUDES..' %f -o %o', outputs='Tests/test_runner.exe'} + tup.frule{inputs='Tests/test_runner.cpp', command='g++ -std=gnu++17 '..TEST_INCLUDES..' %f -o %o', outputs='Tests/test_runner.exe'} tup.frule{inputs='Tests/test_runner.exe', command='%f'} end \ No newline at end of file diff --git a/ODrive_Workspace.code-workspace b/ODrive_Workspace.code-workspace index 2012b56e..6e9385a0 100644 --- a/ODrive_Workspace.code-workspace +++ b/ODrive_Workspace.code-workspace @@ -57,14 +57,7 @@ "fstream": "cpp", "iomanip": "cpp", "optional": "cpp", - "sstream": "cpp", - "atomic": "cpp", - "iterator": "cpp", - "memory_resource": "cpp", - "ratio": "cpp", - "string": "cpp", - "mutex": "cpp", - "thread": "cpp" + "sstream": "cpp" } } } From 878566cba9648c761126eae28f48472f2beb6b26 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 1 Jan 2020 19:48:54 -0500 Subject: [PATCH 265/549] Correct parity calculation tests --- Firmware/MotorControl/encoder.cpp | 1 + Firmware/Tests/test_runner.cpp | 5 +++-- Firmware/Tupfile.lua | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index c1223d07..544a0d39 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -368,6 +368,7 @@ uint8_t parity(uint16_t v){ v ^= v >> 1; return v & 1; } + void Encoder::abs_spi_cb(){ HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); switch (config_.mode) { diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index cae2441c..aa2476f9 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -321,7 +321,8 @@ TEST_SUITE("vel_ramp") { } TEST_CASE("Parity") { - CHECK(parity(0x0DDF) == 0); - CHECK(parity(0x8DDF) == 1); + CHECK(parity(0x0DDF & 0x7FFF) == 0); + CHECK(parity(0x8DDF & 0x7FFF) == 0); + CHECK(parity(0x5BFF & 0x7FFF) == 1); } } \ No newline at end of file diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 16e3aa2a..d329e4e7 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -191,6 +191,6 @@ build{ if tup.getconfig('DOCTEST') == 'true' then TEST_INCLUDES = '-IC:/Tools/doctest/doctest' - tup.frule{inputs='Tests/test_runner.cpp', command='g++ -std=gnu++17 '..TEST_INCLUDES..' %f -o %o', outputs='Tests/test_runner.exe'} + tup.frule{inputs='Tests/test_runner.cpp', command='g++ -O3 -std=gnu++17 '..TEST_INCLUDES..' %f -o %o', outputs='Tests/test_runner.exe'} tup.frule{inputs='Tests/test_runner.exe', command='%f'} end \ No newline at end of file From c9d35415dbd1eaa9fbf8d43f649764bacad26d0f Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 1 Jan 2020 21:37:04 -0500 Subject: [PATCH 266/549] Configure motors before encoders to avoid SPI collision --- Firmware/MotorControl/axis.cpp | 1 - Firmware/MotorControl/main.cpp | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index f9406558..58c46663 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -75,7 +75,6 @@ static void step_cb_wrapper(void* ctx) { // @brief Sets up all components of the axis, // such as gate driver and encoder hardware. void Axis::setup() { - encoder_.setup(); motor_.setup(); } diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index d31bbd13..3cc077ea 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -235,6 +235,10 @@ int odrive_main(void) { axes[i]->setup(); } + for(auto axis : axes){ + axis->encoder_.setup(); + } + // Start PWM and enable adc interrupts/callbacks start_adc_pwm(); From 727759219862434b34e3a48fed6b83080b183a73 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 1 Jan 2020 21:37:31 -0500 Subject: [PATCH 267/549] Set default GPIO pin for absolute encoders to pin 1 --- Firmware/MotorControl/encoder.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index bcc11b40..6e439e80 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -50,7 +50,7 @@ public: bool find_idx_on_lockin_only = false; // Only be sensitive during lockin scan constant vel state bool idx_search_unidirectional = false; // Only allow index search in known direction bool ignore_illegal_hall_state = false; // dont error on bad states like 000 or 111 - uint16_t abs_spi_cs_gpio_pin = 0; + uint16_t abs_spi_cs_gpio_pin = 1; }; Encoder(const EncoderHardwareConfig_t& hw_config, From 4ee55aaed4627050a2bd5594f25088042d4cac8c Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 1 Jan 2020 21:37:55 -0500 Subject: [PATCH 268/549] Only configure encoder SPI at startup --- Firmware/MotorControl/encoder.cpp | 40 ++++++++++--------------------- Firmware/MotorControl/encoder.hpp | 4 ++-- 2 files changed, 14 insertions(+), 30 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 544a0d39..c0208db0 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -22,7 +22,8 @@ void Encoder::setup() { HAL_TIM_Encoder_Start(hw_config_.timer, TIM_CHANNEL_ALL); set_idx_subscribe(); - if(config_.mode & MODE_FLAG_ABS){ + mode_ = config_.mode; + if(mode_ & MODE_FLAG_ABS){ abs_spi_cs_pin_init(); abs_spi_init(); if (axis_->controller_.config_.anticogging.pre_calibrated) { @@ -94,7 +95,7 @@ void Encoder::update_pll_gains() { void Encoder::check_pre_calibrated() { if (!is_ready_) config_.pre_calibrated = false; - if (config_.mode == MODE_INCREMENTAL && !index_found_) + if (mode_ == MODE_INCREMENTAL && !index_found_) config_.pre_calibrated = false; } @@ -286,7 +287,7 @@ static bool decode_hall(uint8_t hall_state, int32_t* hall_cnt) { } void Encoder::sample_now() { - switch (config_.mode) { + switch (mode_) { case MODE_INCREMENTAL: { tim_cnt_sample_ = (int16_t)hw_config_.timer->Instance->CNT; } break; @@ -313,13 +314,9 @@ void Encoder::sample_now() { } bool Encoder::abs_spi_init(){ - if ((config_.mode & MODE_FLAG_ABS) == 0x0) + if ((mode_ & MODE_FLAG_ABS) == 0x0) return false; - uint32_t cr1,cr2; - cr1 = hw_config_.spi->Instance->CR1; - cr2 = hw_config_.spi->Instance->CR2; - SPI_HandleTypeDef * spi = hw_config_.spi; spi->Init.Mode = SPI_MODE_MASTER; spi->Init.Direction = SPI_DIRECTION_2LINES; @@ -332,29 +329,20 @@ bool Encoder::abs_spi_init(){ spi->Init.TIMode = SPI_TIMODE_DISABLE; spi->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; spi->Init.CRCPolynomial = 10; - if (config_.mode == MODE_SPI_ABS_AEAT) { + if (mode_ == MODE_SPI_ABS_AEAT) { spi->Init.CLKPolarity = SPI_POLARITY_HIGH; } HAL_SPI_DeInit(spi); HAL_SPI_Init(spi); - //stash our configuration - abs_spi_cr1 = hw_config_.spi->Instance->CR1; - abs_spi_cr2 = hw_config_.spi->Instance->CR2; - - hw_config_.spi->Instance->CR1 = cr1; - hw_config_.spi->Instance->CR2 = cr2; return true; } bool Encoder::abs_spi_start_transaction(){ - if (config_.mode & MODE_FLAG_ABS){ + if (mode_ & MODE_FLAG_ABS){ if(hw_config_.spi->State != HAL_SPI_STATE_READY){ set_error(ERROR_ABS_SPI_NOT_READY); return false; } - //apply the stashed configuration - hw_config_.spi->Instance->CR1 = abs_spi_cr1; - hw_config_.spi->Instance->CR2 = abs_spi_cr2; HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_RESET); HAL_SPI_TransmitReceive_DMA(hw_config_.spi,(uint8_t*)abs_spi_dma_tx_,(uint8_t*)abs_spi_dma_rx_,1); } @@ -371,20 +359,16 @@ uint8_t parity(uint16_t v){ void Encoder::abs_spi_cb(){ HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); - switch (config_.mode) { + switch (mode_) { case MODE_SPI_ABS_AMS: { uint8_t parity_calc, parity_bit; auto rawVal = abs_spi_dma_rx_[0]; parity_calc = parity(rawVal & 0x7FFF); - parity_bit = rawVal >>15; + parity_bit = rawVal >> 15; if (parity_calc == parity_bit) { pos_abs_ = rawVal & 0x3FFF; - // We are going to ignore values all high or low - // This might happen in normal operation, but its unlikely - // The filter will handle these cases - if (pos_abs_ != 0 && pos_abs_ != 0x3FFF) - abs_spi_pos_updated_ = true; + abs_spi_pos_updated_ = true; } } break; case MODE_SPI_ABS_AEAT: { @@ -420,7 +404,7 @@ bool Encoder::update() { // update internal encoder state. int32_t delta_enc = 0; - switch (config_.mode) { + switch (mode_) { case MODE_INCREMENTAL: { //TODO: use count_in_cpr_ instead as shadow_count_ can overflow //or use 64 bit @@ -488,7 +472,7 @@ bool Encoder::update() { count_in_cpr_ += delta_enc; count_in_cpr_ = mod(count_in_cpr_, config_.cpr); - if(config_.mode & MODE_FLAG_ABS) + if(mode_ & MODE_FLAG_ABS) count_in_cpr_ = pos_abs_; //// run pll (for now pll is in units of encoder counts) diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 6e439e80..fddf5142 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -112,6 +112,7 @@ public: uint16_t abs_spi_dma_rx_[2]; bool abs_spi_pos_updated_ = false; bool abs_spi_pos_init_once_ = false; + Mode_t mode_ = MODE_INCREMENTAL; GPIO_TypeDef* abs_spi_cs_port_; uint16_t abs_spi_cs_pin_; uint32_t abs_spi_cr1; @@ -140,8 +141,7 @@ public: make_protocol_ro_property("spi_error_rate", &spi_error_rate_), make_protocol_object("config", - make_protocol_property("mode", &config_.mode, - [](void* ctx) { static_cast(ctx)->abs_spi_init(); }, this), + make_protocol_property("mode", &config_.mode), make_protocol_property("use_index", &config_.use_index, [](void* ctx) { static_cast(ctx)->set_idx_subscribe(); }, this), make_protocol_property("find_idx_on_lockin_only", &config_.find_idx_on_lockin_only, From 62654ed966999497e0589be4f1619c4e4b5f40bb Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 1 Jan 2020 21:38:17 -0500 Subject: [PATCH 269/549] Fix avoid transients at startup --- Firmware/MotorControl/axis.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 58c46663..585366e6 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -319,6 +319,7 @@ bool Axis::run_closed_loop_control_loop() { // To avoid any transient on startup, we intialize the setpoint to be the current position controller_.pos_setpoint_ = *controller_.pos_estimate_src_; + controller_.input_pos_ = *controller_.pos_estimate_src_; // Avoid integrator windup issues controller_.vel_integrator_current_ = 0.0f; From 37ee8ae184e320acfc25c6bebf74d1b8da30d26f Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 1 Jan 2020 22:13:08 -0500 Subject: [PATCH 270/549] Fix compile errors --- Firmware/MotorControl/controller.hpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index e69fcffb..afbb4edd 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -95,12 +95,6 @@ public: Config_t& config_; Axis* axis_ = nullptr; // set by Axis constructor - // TODO: anticogging overhaul: - // - expose selected (all?) variables on protocol - // - make calibration user experience similar to motor & encoder calibration - // - use python tools to Fourier transform and write back the smoothed map or Fourier coefficients - // - make the calibration persistent - Error_t error_ = ERROR_NONE; float* pos_estimate_src_ = nullptr; @@ -114,7 +108,6 @@ public: // float vel_setpoint = 800.0f; float vel_integrator_current_ = 0.0f; // [A] float current_setpoint_ = 0.0f; // [A] - float vel_ramp_target_ = 0.0f; float input_pos_ = 0.0f; float input_vel_ = 0.0f; @@ -142,7 +135,6 @@ public: make_protocol_ro_property("current_setpoint", ¤t_setpoint_), make_protocol_ro_property("trajectory_done", &trajectory_done_), make_protocol_property("vel_integrator_current", &vel_integrator_current_), - make_protocol_property("vel_ramp_target", &vel_ramp_target_), make_protocol_property("anticogging_valid", &anticogging_valid_), make_protocol_property("gain_scheduling_width", &config_.gain_scheduling_width), make_protocol_object("config", @@ -157,7 +149,6 @@ public: make_protocol_property("vel_integrator_gain", &config_.vel_integrator_gain), make_protocol_property("vel_limit", &config_.vel_limit), make_protocol_property("vel_limit_tolerance", &config_.vel_limit_tolerance), - make_protocol_property("vel_ramp_enable", &config_.vel_ramp_enable), make_protocol_property("vel_ramp_rate", &config_.vel_ramp_rate), make_protocol_property("current_ramp_rate", &config_.current_ramp_rate), make_protocol_property("homing_speed", &config_.homing_speed), From 75808abce4809489090cb0c08def5b2c24901191 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 1 Jan 2020 22:13:44 -0500 Subject: [PATCH 271/549] Fix merge issue with ACIM code --- Firmware/MotorControl/controller.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index b0ddab90..c88fe4a7 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -305,7 +305,7 @@ bool Controller::update(float* current_setpoint_output) { } v_err = vel_des - *vel_estimate_src; - Iq += (config_.vel_gain * gain_scheduling_multiplier) * v_err; + Iq += (vel_gain * gain_scheduling_multiplier) * v_err; // Velocity integral action before limiting Iq += vel_integrator_current_; @@ -317,7 +317,7 @@ bool Controller::update(float* current_setpoint_output) { set_error(ERROR_INVALID_ESTIMATE); return false; } - Iq = limitVel(config_.vel_limit, *vel_estimate_src, config_.vel_gain, Iq); + Iq = limitVel(config_.vel_limit, *vel_estimate_src, vel_gain, Iq); } // Current limiting @@ -343,7 +343,7 @@ bool Controller::update(float* current_setpoint_output) { // TODO make decayfactor configurable vel_integrator_current_ *= 0.99f; } else { - vel_integrator_current_ += ((config_.vel_integrator_gain * gain_scheduling_multiplier) * current_meas_period) * v_err; + vel_integrator_current_ += ((vel_integrator_gain * gain_scheduling_multiplier) * current_meas_period) * v_err; } } From 1bdea53ec8fa0711b56c50b49116078a0eacbffd Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 8 Jan 2020 22:11:50 -0500 Subject: [PATCH 272/549] Improve testability --- Firmware/Drivers/DRV8301/drv8301.c | 2 +- Firmware/MotorControl/axis.cpp | 2 +- Firmware/MotorControl/low_level.cpp | 2 +- Firmware/MotorControl/odrive_main.h | 2 +- Firmware/MotorControl/trapTraj.cpp | 2 +- Firmware/MotorControl/{utils.c => utils.cpp} | 4 +- Firmware/MotorControl/{utils.h => utils.hpp} | 2 +- Firmware/Tests/test_can.cpp | 91 ++++++++++ Firmware/Tests/test_runner.cpp | 171 +------------------ Firmware/Tupfile.lua | 6 +- Firmware/communication/ascii_protocol.cpp | 2 +- Firmware/communication/can_helpers.hpp | 88 ++++++++++ Firmware/communication/communication.cpp | 2 +- Firmware/communication/interface_can.cpp | 2 +- Firmware/communication/interface_can.hpp | 81 +-------- Firmware/communication/interface_uart.cpp | 2 +- Firmware/communication/interface_usb.cpp | 2 +- ODrive_Workspace.code-workspace | 3 +- 18 files changed, 200 insertions(+), 266 deletions(-) rename Firmware/MotorControl/{utils.c => utils.cpp} (98%) rename Firmware/MotorControl/{utils.h => utils.hpp} (98%) create mode 100644 Firmware/Tests/test_can.cpp create mode 100644 Firmware/communication/can_helpers.hpp diff --git a/Firmware/Drivers/DRV8301/drv8301.c b/Firmware/Drivers/DRV8301/drv8301.c index 0e90a652..c65cc4d3 100644 --- a/Firmware/Drivers/DRV8301/drv8301.c +++ b/Firmware/Drivers/DRV8301/drv8301.c @@ -45,7 +45,7 @@ // drivers #include "drv8301.h" -#include "utils.h" +#include "utils.hpp" // ************************************************************************** diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 585366e6..b48ff66d 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -4,7 +4,7 @@ #include "gpio.h" #include "odrive_main.h" -#include "utils.h" +#include "utils.hpp" #include "communication/interface_can.hpp" Axis::Axis(int axis_num, diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index a3c341f5..908ffb9f 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include "odrive_main.h" diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index dec12035..d5832838 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -127,7 +127,7 @@ inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast(~static_c // ODrive specific includes -#include +#include #include #include #include diff --git a/Firmware/MotorControl/trapTraj.cpp b/Firmware/MotorControl/trapTraj.cpp index f1e41aa5..2067c35e 100644 --- a/Firmware/MotorControl/trapTraj.cpp +++ b/Firmware/MotorControl/trapTraj.cpp @@ -1,6 +1,6 @@ #include #include "odrive_main.h" -#include "utils.h" +#include "utils.hpp" // A sign function where input 0 has positive sign (not 0) float sign_hard(float val) { diff --git a/Firmware/MotorControl/utils.c b/Firmware/MotorControl/utils.cpp similarity index 98% rename from Firmware/MotorControl/utils.c rename to Firmware/MotorControl/utils.cpp index 3278d614..579a47a7 100644 --- a/Firmware/MotorControl/utils.c +++ b/Firmware/MotorControl/utils.cpp @@ -1,5 +1,5 @@ -#include +#include #include #include #include @@ -151,7 +151,7 @@ float fast_atan2(float y, float x) { // p(x) = coeffs[0] * x^deg + ... + coeffs[deg], for some degree "deg" float horner_fma(float x, const float *coeffs, size_t count) { float result = 0.0f; - for (int idx = 0; idx < count; ++idx) + for (size_t idx = 0; idx < count; ++idx) result = fmaf(result, x, coeffs[idx]); return result; } diff --git a/Firmware/MotorControl/utils.h b/Firmware/MotorControl/utils.hpp similarity index 98% rename from Firmware/MotorControl/utils.h rename to Firmware/MotorControl/utils.hpp index 3145c19a..574aa800 100644 --- a/Firmware/MotorControl/utils.h +++ b/Firmware/MotorControl/utils.hpp @@ -58,7 +58,7 @@ extern "C" { #ifdef M_PI #undef M_PI #endif -#define M_PI 3.14159265358979323846f +#define M_PI (3.14159265358979323846f) #define MACRO_MAX(x, y) (((x) > (y)) ? (x) : (y)) #define MACRO_MIN(x, y) (((x) < (y)) ? (x) : (y)) diff --git a/Firmware/Tests/test_can.cpp b/Firmware/Tests/test_can.cpp new file mode 100644 index 00000000..29f96a79 --- /dev/null +++ b/Firmware/Tests/test_can.cpp @@ -0,0 +1,91 @@ + +#define DOCTEST_IMPLEMENT +#include +#include +#include + +#include "communication/can_helpers.hpp" + +enum InputMode_t { + INPUT_MODE_INACTIVE, + INPUT_MODE_PASSTHROUGH, + INPUT_MODE_VEL_RAMP, + INPUT_MODE_POS_FILTER, + INPUT_MODE_MIX_CHANNELS, + INPUT_MODE_TRAP_TRAJ, +}; + +TEST_SUITE("CAN Functions") { + TEST_CASE("reverse") { + can_Message_t rxmsg; + rxmsg.id = 0x000; + rxmsg.isExt = false; + rxmsg.len = 8; + + rxmsg.buf[0] = 0x12; + rxmsg.buf[1] = 0x34; + + std::reverse(std::begin(rxmsg.buf), std::end(rxmsg.buf)); + CHECK(rxmsg.buf[0] == 0x00); + CHECK(rxmsg.buf[6] == 0x34); + CHECK(rxmsg.buf[7] == 0x12); + } + + TEST_CASE("getSignal") { + can_Message_t rxmsg; + + auto val = 0x1234; + std::memcpy(rxmsg.buf, &val, sizeof(val)); + + val = can_getSignal(rxmsg, 0, 16, true, 1, 0); + CHECK(val == 0x1234); + + val = can_getSignal(rxmsg, 0, 16, false, 1, 0); + CHECK(val == 0x3412); + + float myFloat = 1234.6789f; + std::memcpy(rxmsg.buf, &myFloat, sizeof(myFloat)); + auto floatVal = can_getSignal(rxmsg, 0, 32, true, 1, 0); + CHECK(floatVal == 1234.6789f); + + can_Message_t msg; + msg.id = 0x00E; + msg.buf[0] = 0x96; + msg.buf[1] = 0x00; + msg.buf[2] = 0x00; + msg.buf[3] = 0x00; + CHECK(can_getSignal(msg, 0, 32, true, 0.01f, 0.0f) == 1.50f); + } + + TEST_CASE("setSignal") { + can_Message_t txmsg; + + can_setSignal(txmsg, 0x1234, 0, 16, true, 1.0f, 0.0f); + CHECK(can_getSignal(txmsg, 0, 16, true, 1.0f, 0.0f) == 0x1234); + + can_setSignal(txmsg, 0xABCD, 16, 16, true, 1.0f, 0.0f); + CHECK(can_getSignal(txmsg, 0, 16, true, 1.0f, 0.0f) == 0x1234); + CHECK(can_getSignal(txmsg, 16, 16, true, 1.0f, 0.0f) == 0xABCD); + + can_setSignal(txmsg, 1234.5678f, 32, 32, true, 1.0f, 0.0f); + CHECK(can_getSignal(txmsg, 0, 16, true, 1.0f, 0.0f) == 0x1234); + CHECK(can_getSignal(txmsg, 16, 16, true, 1.0f, 0.0f) == 0xABCD); + CHECK(can_getSignal(txmsg, 32, 32, true, 1.0f, 0.0f)); + + can_setSignal(txmsg, 0x1234, 0, 16, false, 1.0f, 0.0f); + CHECK(can_getSignal(txmsg, 0, 16, false, 1.0f, 0.0f) == 0x1234); + CHECK(can_getSignal(txmsg, 16, 16, true, 1.0f, 0.0f) == 0xABCD); + CHECK(can_getSignal(txmsg, 32, 32, true, 1.0f, 0.0f)); + + can_setSignal(txmsg, 234981.0f, 12, 32, false, 2.0f, 1.1f); + CHECK(can_getSignal(txmsg, 12, 32, false, 2.0f, 1.1f) == 234981.0f); + } + + TEST_CASE("getSignal enums") { + can_Message_t rxmsg; + rxmsg.buf[0] = INPUT_MODE_MIX_CHANNELS; + rxmsg.buf[1] = INPUT_MODE_PASSTHROUGH; + CHECK(static_cast(can_getSignal(rxmsg, 0, 8, true, 1, 0)) == INPUT_MODE_MIX_CHANNELS); + CHECK(static_cast(can_getSignal(rxmsg, 8, 8, true, 1, 0)) == INPUT_MODE_PASSTHROUGH); + } +} \ No newline at end of file diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index aa2476f9..e2314477 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -11,181 +11,14 @@ #include +#include + using std::cout; using std::endl; -struct can_Message_t { - uint32_t id = 0x000; // 11-bit max is 0x7ff, 29-bit max is 0x1FFFFFFF - bool isExt = false; - bool rtr = false; - uint8_t len = 8; - uint8_t buf[8] = {0, 0, 0, 0, 0, 0, 0, 0}; -}; -struct can_Signal_t { - const uint8_t startBit; - const uint8_t length; - const bool isIntel; - const float factor; - const float offset; -}; -enum InputMode_t { - INPUT_MODE_INACTIVE, - INPUT_MODE_PASSTHROUGH, - INPUT_MODE_VEL_RAMP, - INPUT_MODE_POS_FILTER, - INPUT_MODE_MIX_CHANNELS, - INPUT_MODE_TRAP_TRAJ, -}; -// Fetch a specific signal from the message -template -T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel) { - uint64_t tempVal = 0; - uint64_t mask = (1ULL << length) - 1; - - if (isIntel) { - std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); - tempVal = (tempVal >> startBit) & mask; - } else { - std::reverse(std::begin(msg.buf), std::end(msg.buf)); - std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); - tempVal = (tempVal >> (64 - startBit - length)) & mask; - } - - T retVal; - std::memcpy(&retVal, &tempVal, sizeof(T)); - return retVal; -} - -template -float can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { - T retVal = can_getSignal(msg, startBit, length, isIntel); - return (retVal * factor) + offset; -} - -template -void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, const uint8_t length, const bool isIntel) { - uint64_t mask = (1ULL << length) - 1; - uint64_t valAsBits = 0; - std::memcpy(&valAsBits, &val, sizeof(T)); - - if (isIntel) { - uint64_t data = 0; - std::memcpy(&data, msg.buf, sizeof(data)); - - data &= ~(mask << startBit); - data |= valAsBits << startBit; - - std::memcpy(msg.buf, &data, sizeof(data)); - } else { - uint64_t data = 0; - std::reverse(std::begin(msg.buf), std::end(msg.buf)); - std::memcpy(&data, msg.buf, sizeof(data)); - - data &= ~(mask << (64 - startBit - length)); - data |= valAsBits << (64 - startBit - length); - - std::memcpy(msg.buf, &data, sizeof(data)); - std::reverse(std::begin(msg.buf), std::end(msg.buf)); - } -} - -template -void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { - T scaledVal = (val - offset) / factor; - can_setSignal(msg, scaledVal, startBit, length, isIntel); -} - -template -float can_getSignal(can_Message_t msg, const can_Signal_t& signal) { - return can_getSignal(msg, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); -} - -template -void can_setSignal(can_Message_t& msg, const T& val, const can_Signal_t& signal) { - can_setSignal(msg, val, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); -} - -TEST_CASE("fake") { - cout << endl; -} - -TEST_SUITE("CAN Functions") { - TEST_CASE("reverse") { - can_Message_t rxmsg; - rxmsg.id = 0x000; - rxmsg.isExt = false; - rxmsg.len = 8; - - rxmsg.buf[0] = 0x12; - rxmsg.buf[1] = 0x34; - - std::reverse(std::begin(rxmsg.buf), std::end(rxmsg.buf)); - CHECK(rxmsg.buf[0] == 0x00); - CHECK(rxmsg.buf[6] == 0x34); - CHECK(rxmsg.buf[7] == 0x12); - } - - TEST_CASE("getSignal") { - can_Message_t rxmsg; - - auto val = 0x1234; - std::memcpy(rxmsg.buf, &val, sizeof(val)); - - val = can_getSignal(rxmsg, 0, 16, true, 1, 0); - CHECK(val == 0x1234); - - val = can_getSignal(rxmsg, 0, 16, false, 1, 0); - CHECK(val == 0x3412); - - float myFloat = 1234.6789f; - std::memcpy(rxmsg.buf, &myFloat, sizeof(myFloat)); - auto floatVal = can_getSignal(rxmsg, 0, 32, true, 1, 0); - CHECK(floatVal == 1234.6789f); - - can_Message_t msg; - msg.id = 0x00E; - msg.buf[0] = 0x96; - msg.buf[1] = 0x00; - msg.buf[2] = 0x00; - msg.buf[3] = 0x00; - CHECK(can_getSignal(msg, 0, 32, true, 0.01f, 0.0f) == 1.50f); - } - - TEST_CASE("setSignal") { - can_Message_t txmsg; - - can_setSignal(txmsg, 0x1234, 0, 16, true, 1.0f, 0.0f); - CHECK(can_getSignal(txmsg, 0, 16, true, 1.0f, 0.0f) == 0x1234); - - can_setSignal(txmsg, 0xABCD, 16, 16, true, 1.0f, 0.0f); - CHECK(can_getSignal(txmsg, 0, 16, true, 1.0f, 0.0f) == 0x1234); - CHECK(can_getSignal(txmsg, 16, 16, true, 1.0f, 0.0f) == 0xABCD); - - can_setSignal(txmsg, 1234.5678f, 32, 32, true, 1.0f, 0.0f); - CHECK(can_getSignal(txmsg, 0, 16, true, 1.0f, 0.0f) == 0x1234); - CHECK(can_getSignal(txmsg, 16, 16, true, 1.0f, 0.0f) == 0xABCD); - CHECK(can_getSignal(txmsg, 32, 32, true, 1.0f, 0.0f)); - - can_setSignal(txmsg, 0x1234, 0, 16, false, 1.0f, 0.0f); - CHECK(can_getSignal(txmsg, 0, 16, false, 1.0f, 0.0f) == 0x1234); - CHECK(can_getSignal(txmsg, 16, 16, true, 1.0f, 0.0f) == 0xABCD); - CHECK(can_getSignal(txmsg, 32, 32, true, 1.0f, 0.0f)); - - can_setSignal(txmsg, 234981.0f, 12, 32, false, 2.0f, 1.1f); - CHECK(can_getSignal(txmsg, 12, 32, false, 2.0f, 1.1f) == 234981.0f); - } - - TEST_CASE("getSignal enums") { - can_Message_t rxmsg; - rxmsg.buf[0] = INPUT_MODE_MIX_CHANNELS; - rxmsg.buf[1] = INPUT_MODE_PASSTHROUGH; - CHECK(static_cast(can_getSignal(rxmsg, 0, 8, true, 1, 0)) == INPUT_MODE_MIX_CHANNELS); - CHECK(static_cast(can_getSignal(rxmsg, 8, 8, true, 1, 0)) == INPUT_MODE_PASSTHROUGH); - } -} TEST_SUITE("delta_enc") { // Modulo (as opposed to remainder), per https://stackoverflow.com/a/19288271 diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index d329e4e7..63ca9117 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -157,7 +157,7 @@ build{ packages={'stm_platform'}, sources={ 'Drivers/DRV8301/drv8301.c', - 'MotorControl/utils.c', + 'MotorControl/utils.cpp', 'MotorControl/arm_sin_f32.c', 'MotorControl/arm_cos_f32.c', 'MotorControl/low_level.cpp', @@ -190,7 +190,7 @@ build{ } if tup.getconfig('DOCTEST') == 'true' then - TEST_INCLUDES = '-IC:/Tools/doctest/doctest' - tup.frule{inputs='Tests/test_runner.cpp', command='g++ -O3 -std=gnu++17 '..TEST_INCLUDES..' %f -o %o', outputs='Tests/test_runner.exe'} + TEST_INCLUDES = '-I. -I./MotorControl -I./fibre/cpp/include -I./Drivers/DRV8301 -IC:/Tools/doctest/doctest' + tup.frule{inputs='Tests/*.cpp', command='g++ -O3 -std=gnu++17 '..TEST_INCLUDES..' %f -o %o', outputs='Tests/test_runner.exe'} tup.frule{inputs='Tests/test_runner.exe', command='%f'} end \ No newline at end of file diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 3a617649..54e6cccc 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -11,7 +11,7 @@ #include "../build/version.h" // autogenerated based on Git state #include "communication.h" #include "ascii_protocol.hpp" -#include +#include #include /* Private macros ------------------------------------------------------------*/ diff --git a/Firmware/communication/can_helpers.hpp b/Firmware/communication/can_helpers.hpp new file mode 100644 index 00000000..b5e40385 --- /dev/null +++ b/Firmware/communication/can_helpers.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include + +struct can_Message_t { + uint32_t id = 0x000; // 11-bit max is 0x7ff, 29-bit max is 0x1FFFFFFF + bool isExt = false; + bool rtr = false; + uint8_t len = 8; + uint8_t buf[8] = {0, 0, 0, 0, 0, 0, 0, 0}; +} ; + +struct can_Signal_t { + const uint8_t startBit; + const uint8_t length; + const bool isIntel; + const float factor; + const float offset; +}; + + +#include +template +T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel) { + uint64_t tempVal = 0; + uint64_t mask = (1ULL << length) - 1; + + if (isIntel) { + std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); + tempVal = (tempVal >> startBit) & mask; + } else { + std::reverse(std::begin(msg.buf), std::end(msg.buf)); + std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); + tempVal = (tempVal >> (64 - startBit - length)) & mask; + } + + T retVal; + std::memcpy(&retVal, &tempVal, sizeof(T)); + return retVal; +} + +template +float can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { + T retVal = can_getSignal(msg, startBit, length, isIntel); + return (retVal * factor) + offset; +} + +template +void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { + T scaledVal = (val - offset) / factor; + uint64_t valAsBits = 0; + std::memcpy(&valAsBits, &scaledVal, sizeof(scaledVal)); + + uint64_t mask = (1ULL << length) - 1; + + if (isIntel) { + uint64_t data = 0; + std::memcpy(&data, msg.buf, sizeof(data)); + + data &= ~(mask << startBit); + data |= valAsBits << startBit; + + std::memcpy(msg.buf, &data, sizeof(data)); + } else { + uint64_t data = 0; + std::reverse(std::begin(msg.buf), std::end(msg.buf)); + std::memcpy(&data, msg.buf, sizeof(data)); + + data &= ~(mask << (64 - startBit - length)); + data |= valAsBits << (64 - startBit - length); + + std::memcpy(msg.buf, &data, sizeof(data)); + std::reverse(std::begin(msg.buf), std::end(msg.buf)); + } +} + +template +float can_getSignal(can_Message_t msg, const can_Signal_t& signal) { + return can_getSignal(msg, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); +} + +template +void can_setSignal(can_Message_t& msg, const T& val, const can_Signal_t& signal) { + can_setSignal(msg, val, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); +} \ No newline at end of file diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index a5cd4bcb..f90bbbb1 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -10,7 +10,7 @@ #include "odrive_main.h" #include "freertos_vars.h" -#include "utils.h" +#include "utils.hpp" #include "../build/version.h" // autogenerated based on Git state diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index 00637597..bf7f9ff0 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -2,7 +2,7 @@ #include "fibre/crc.hpp" #include "freertos_vars.h" -#include "utils.h" +#include "utils.hpp" #include #include diff --git a/Firmware/communication/interface_can.hpp b/Firmware/communication/interface_can.hpp index 7a1e9c9a..ffb29bb7 100644 --- a/Firmware/communication/interface_can.hpp +++ b/Firmware/communication/interface_can.hpp @@ -5,26 +5,11 @@ #include #include "fibre/protocol.hpp" #include "odrive_main.h" +#include "can_helpers.hpp" #define CAN_CLK_HZ (42000000) #define CAN_CLK_MHZ (42) -struct can_Message_t { - uint32_t id = 0x000; // 11-bit max is 0x7ff, 29-bit max is 0x1FFFFFFF - bool isExt = false; - bool rtr = false; - uint8_t len = 8; - uint8_t buf[8] = {0, 0, 0, 0, 0, 0, 0, 0}; -} ; - -struct can_Signal_t { - const uint8_t startBit; - const uint8_t length; - const bool isIntel; - const float factor; - const float offset; -}; - // Anonymous enum for defining the most common CAN baud rates enum { CAN_BAUD_125K = 125000, @@ -87,70 +72,6 @@ class ODriveCAN { void set_baud_rate(uint32_t baudRate); }; -#include -template -T can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel) { - uint64_t tempVal = 0; - uint64_t mask = (1ULL << length) - 1; - - if (isIntel) { - std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); - tempVal = (tempVal >> startBit) & mask; - } else { - std::reverse(std::begin(msg.buf), std::end(msg.buf)); - std::memcpy(&tempVal, msg.buf, sizeof(tempVal)); - tempVal = (tempVal >> (64 - startBit - length)) & mask; - } - - T retVal; - std::memcpy(&retVal, &tempVal, sizeof(T)); - return retVal; -} - -template -float can_getSignal(can_Message_t msg, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { - T retVal = can_getSignal(msg, startBit, length, isIntel); - return (retVal * factor) + offset; -} - -template -void can_setSignal(can_Message_t& msg, const T& val, const uint8_t startBit, const uint8_t length, const bool isIntel, const float factor, const float offset) { - T scaledVal = (val - offset) / factor; - uint64_t valAsBits = 0; - std::memcpy(&valAsBits, &scaledVal, sizeof(scaledVal)); - - uint64_t mask = (1ULL << length) - 1; - - if (isIntel) { - uint64_t data = 0; - std::memcpy(&data, msg.buf, sizeof(data)); - - data &= ~(mask << startBit); - data |= valAsBits << startBit; - - std::memcpy(msg.buf, &data, sizeof(data)); - } else { - uint64_t data = 0; - std::reverse(std::begin(msg.buf), std::end(msg.buf)); - std::memcpy(&data, msg.buf, sizeof(data)); - - data &= ~(mask << (64 - startBit - length)); - data |= valAsBits << (64 - startBit - length); - - std::memcpy(msg.buf, &data, sizeof(data)); - std::reverse(std::begin(msg.buf), std::end(msg.buf)); - } -} - -template -float can_getSignal(can_Message_t msg, const can_Signal_t& signal) { - return can_getSignal(msg, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); -} - -template -void can_setSignal(can_Message_t& msg, const T& val, const can_Signal_t& signal) { - can_setSignal(msg, val, signal.startBit, signal.length, signal.isIntel, signal.factor, signal.offset); -} DEFINE_ENUM_FLAG_OPERATORS(ODriveCAN::Error_t) diff --git a/Firmware/communication/interface_uart.cpp b/Firmware/communication/interface_uart.cpp index f1bb5e0d..ae08d90c 100644 --- a/Firmware/communication/interface_uart.cpp +++ b/Firmware/communication/interface_uart.cpp @@ -3,7 +3,7 @@ #include "ascii_protocol.hpp" -#include +#include #include #include diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index c52fe5ca..59ee5f2a 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -2,7 +2,7 @@ #include "interface_usb.h" #include "ascii_protocol.hpp" -#include +#include #include #include diff --git a/ODrive_Workspace.code-workspace b/ODrive_Workspace.code-workspace index cf197f8d..8c1fe5c3 100644 --- a/ODrive_Workspace.code-workspace +++ b/ODrive_Workspace.code-workspace @@ -61,7 +61,8 @@ "fstream": "cpp", "iomanip": "cpp", "optional": "cpp", - "sstream": "cpp" + "sstream": "cpp", + "utils.h": "c" } } } From 930de28de9deb276643394000dac907018cca074 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 8 Jan 2020 22:59:57 -0500 Subject: [PATCH 273/549] Clean up static analysis warnings --- Firmware/MotorControl/main.cpp | 2 +- Firmware/MotorControl/odrive_main.h | 2 +- Firmware/communication/interface_i2c.cpp | 2 +- Firmware/communication/interface_usb.cpp | 2 +- Firmware/fibre/cpp/include/fibre/protocol.hpp | 50 ++++++++++--------- 5 files changed, 30 insertions(+), 28 deletions(-) diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 57a0fef0..f3c7d4b4 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -21,7 +21,7 @@ Endstop::Config_t min_endstop_configs[AXIS_COUNT]; Endstop::Config_t max_endstop_configs[AXIS_COUNT]; bool user_config_loaded_; -SystemStats_t system_stats_ = { 0 }; +SystemStats_t system_stats_; Axis *axes[AXIS_COUNT]; ODriveCAN *odCAN = nullptr; diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index d5832838..ff8199c9 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -71,7 +71,7 @@ extern SystemStats_t system_stats_; } struct PWMMapping_t { - endpoint_ref_t endpoint = { 0 }; + endpoint_ref_t endpoint; float min = 0; float max = 0; }; diff --git a/Firmware/communication/interface_i2c.cpp b/Firmware/communication/interface_i2c.cpp index 4eb0fdd2..d94ed58f 100644 --- a/Firmware/communication/interface_i2c.cpp +++ b/Firmware/communication/interface_i2c.cpp @@ -8,7 +8,7 @@ #define I2C_RX_BUFFER_PREAMBLE_SIZE 4 #define I2C_TX_BUFFER_SIZE 128 -I2CStats_t i2c_stats_ = {0}; +I2CStats_t i2c_stats_; static uint8_t i2c_rx_buffer[I2C_RX_BUFFER_PREAMBLE_SIZE + I2C_RX_BUFFER_SIZE]; static uint8_t i2c_tx_buffer[I2C_TX_BUFFER_SIZE]; diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index 59ee5f2a..21273dee 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -15,7 +15,7 @@ osThreadId usb_thread; const uint32_t stack_size_usb_thread = 4096; // Bytes -USBStats_t usb_stats_ = {0}; +USBStats_t usb_stats_; class USBSender : public PacketSink { public: diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index 366f46c2..934d397a 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -77,9 +77,9 @@ constexpr uint32_t PROTOCOL_SERVER_TIMEOUT_MS = 10; typedef struct { - uint16_t json_crc; - uint16_t node_id; - uint16_t endpoint_id; + uint16_t json_crc = 0; + uint16_t node_id = 0; + uint16_t endpoint_id = 0; } endpoint_ref_t; #include @@ -101,8 +101,9 @@ template<> inline size_t write_le(float value, 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"); - const uint32_t * value_as_uint32 = reinterpret_cast(&value); - return write_le(*value_as_uint32, buffer); + uint32_t value_as_uint32; + std::memcpy(&value_as_uint32, &value, sizeof(uint32_t)); + return write_le(value_as_uint32, buffer); } template @@ -116,8 +117,9 @@ 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); + uint32_t value_as_uint32; + std::memcpy(&value_as_uint32, &value, sizeof(uint32_t)); + return read_le(&value_as_uint32, buffer); } // @brief Reads a value of type T from the buffer. @@ -183,19 +185,19 @@ public: class StreamToPacketSegmenter : public StreamSink { public: - StreamToPacketSegmenter(PacketSink& output) : + explicit StreamToPacketSegmenter(PacketSink& output) : output_(output) { }; - int process_bytes(const uint8_t *buffer, size_t length, size_t* processed_bytes); + int process_bytes(const uint8_t *buffer, size_t length, size_t* processed_bytes) override; size_t get_free_space() { return SIZE_MAX; } private: - uint8_t header_buffer_[3]; + uint8_t header_buffer_[3] = {0}; size_t header_index_ = 0; - uint8_t packet_buffer_[RX_BUF_SIZE]; + uint8_t packet_buffer_[RX_BUF_SIZE] = {0}; size_t packet_index_ = 0; size_t packet_length_ = 0; PacketSink& output_; @@ -204,13 +206,13 @@ private: class StreamBasedPacketSink : public PacketSink { public: - StreamBasedPacketSink(StreamSink& output) : + explicit StreamBasedPacketSink(StreamSink& output) : output_(output) { }; //size_t get_mtu() { return SIZE_MAX; } - int process_packet(const uint8_t *buffer, size_t length); + int process_packet(const uint8_t *buffer, size_t length) override; private: StreamSink& output_; @@ -220,10 +222,10 @@ private: // A single call to process_bytes may result in multiple packets being sent. class PacketBasedStreamSink : public StreamSink { public: - PacketBasedStreamSink(PacketSink& packet_sink) : _packet_sink(packet_sink) {} + explicit PacketBasedStreamSink(PacketSink& packet_sink) : _packet_sink(packet_sink) {} ~PacketBasedStreamSink() {} - int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) { + int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) override { // Loop to ensure all bytes get sent while (length) { size_t chunk = length; @@ -253,7 +255,7 @@ public: buffer_length_(length) {} // Returns 0 on success and -1 if the buffer could not accept everything because it became full - int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) { + int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) override { size_t chunk = length < buffer_length_ ? length : buffer_length_; memcpy(buffer_, buffer, chunk); buffer_ += chunk; @@ -279,7 +281,7 @@ public: follow_up_stream_(follow_up_stream) {} // Returns 0 on success and -1 if the buffer could not accept everything because it became full - int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) { + int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) override { if (skip_ < length) { buffer += skip_; length -= skip_; @@ -295,7 +297,7 @@ public: } } - size_t get_free_space() { return skip_ + follow_up_stream_.get_free_space(); } + size_t get_free_space() override { return skip_ + follow_up_stream_.get_free_space(); } private: size_t skip_; @@ -308,17 +310,17 @@ private: // on the data that is sent to it. class CRC16Calculator : public StreamSink { public: - CRC16Calculator(uint16_t crc16_init) : + explicit CRC16Calculator(uint16_t crc16_init) : crc16_(crc16_init) {} - int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) { + int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) override{ crc16_ = calc_crc16(crc16_, buffer, length); if (processed_bytes) *processed_bytes += length; return 0; } - size_t get_free_space() { return SIZE_MAX; } + size_t get_free_space() override { return SIZE_MAX; } uint16_t get_crc16() { return crc16_; } private: @@ -500,17 +502,17 @@ static inline int write_string(const char* str, StreamSink* output) { */ class BidirectionalPacketBasedChannel : public PacketSink { public: - BidirectionalPacketBasedChannel(PacketSink& output) : + explicit BidirectionalPacketBasedChannel(PacketSink& output) : output_(output) { } //size_t get_mtu() { // return SIZE_MAX; //} - int process_packet(const uint8_t* buffer, size_t length); + int process_packet(const uint8_t* buffer, size_t length) override; private: PacketSink& output_; - uint8_t tx_buf_[TX_BUF_SIZE]; + uint8_t tx_buf_[TX_BUF_SIZE] = {0}; }; From f6a29fc047c85d5f711b18e07be08e60160cefba Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 8 Jan 2020 23:08:37 -0500 Subject: [PATCH 274/549] Use g++ in vscode --- Firmware/.vscode/c_cpp_properties.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index 9e671e1b..9c1aec97 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -17,7 +17,7 @@ "__GNUC__" ], "intelliSenseMode": "gcc-x64", - "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-g++.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++17" }, @@ -30,14 +30,14 @@ "STM32F405xx", "USE_HAL_DRIVER", "HW_VERSION_MAJOR=3", - "HW_VERSION_MINOR=4", - "HW_VERSION_VOLTAGE=24", + "HW_VERSION_MINOR=6", + "HW_VERSION_VOLTAGE=56", "__weak=\"__attribute__((weak))\"", "__packed=\"__attribute__((__packed__))\"", "__GNUC__" ], "intelliSenseMode": "gcc-x64", - "compilerPath": "arm-none-eabi-gcc -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-none-eabi-g++ -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++17" }, From 89588dbf979fc97a1fe7a25b4b709c12fdec3457 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 22 Jan 2020 17:47:16 -0500 Subject: [PATCH 275/549] Fix writing of floats --- Firmware/fibre/cpp/include/fibre/protocol.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index 934d397a..016d7064 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -117,9 +117,7 @@ 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"); - uint32_t value_as_uint32; - std::memcpy(&value_as_uint32, &value, sizeof(uint32_t)); - return read_le(&value_as_uint32, buffer); + return read_le(reinterpret_cast(value), buffer); } // @brief Reads a value of type T from the buffer. From 66370993086ea2513e4488e9d6b5300db6377123 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sat, 25 Jan 2020 15:48:37 +0100 Subject: [PATCH 276/549] [testing] add motor calibration test --- tools/odrive/tests/motor_calibration_test.py | 70 ++++++++++ tools/odrive/tests/test_runner.py | 137 ++++++++++++++----- tools/test-rig-rpi.yaml | 16 +-- 3 files changed, 175 insertions(+), 48 deletions(-) create mode 100644 tools/odrive/tests/motor_calibration_test.py diff --git a/tools/odrive/tests/motor_calibration_test.py b/tools/odrive/tests/motor_calibration_test.py new file mode 100644 index 00000000..e755f832 --- /dev/null +++ b/tools/odrive/tests/motor_calibration_test.py @@ -0,0 +1,70 @@ + +import test_runner + +import time +from math import pi +import os + +from fibre.utils import Logger +from test_runner import AxisTestContext, MotorTestContext, test_assert_eq, test_assert_no_error, request_state +from odrive.enums import * + +def modpm(val, range): + return ((val + (range / 2)) % range) - (range / 2) + +class TestMotorCalibration(): + """ + Runs the motor calibration and checks if the measurements match the expectation. + """ + + def is_compatible(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext): + return axis_ctx.yaml == motor_ctx.yaml['name'] # check if connected + + def run_test(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, logger: Logger): + # reset old calibration values + axis_ctx.handle.motor.config.phase_resistance = 0.0 + axis_ctx.handle.motor.config.phase_inductance = 0.0 + + axis_ctx.handle.clear_errors() + + # run calibration + request_state(axis_ctx, AXIS_STATE_MOTOR_CALIBRATION) + time.sleep(6) + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_no_error(axis_ctx) + + # check if measurements match expectation + test_assert_eq(axis_ctx.handle.motor.config.phase_resistance, float(motor_ctx.yaml['phase-resistance']), accuracy=0.2) + test_assert_eq(axis_ctx.handle.motor.config.phase_inductance, float(motor_ctx.yaml['phase-inductance']), accuracy=0.5) + + +class TestDisconnectedMotorCalibration(): + """ + Tests if the motor calibration fails as expected if the phases are floating. + """ + + def is_compatible(self, axis_ctx: AxisTestContext): + return axis_ctx.yaml == 'floating' + + def run_test(self, axis_ctx: AxisTestContext, logger: Logger): + axis = axis_ctx.handle + + # reset old calibration values + axis_ctx.handle.motor.config.phase_resistance = 0.0 + axis_ctx.handle.motor.config.phase_inductance = 0.0 + + axis_ctx.handle.clear_errors() + + # run test + request_state(axis_ctx, AXIS_STATE_MOTOR_CALIBRATION) + time.sleep(6) + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_eq(axis_ctx.handle.error, errors.axis.ERROR_MOTOR_FAILED) + test_assert_eq(axis_ctx.handle.motor.error, errors.motor.ERROR_PHASE_RESISTANCE_OUT_OF_RANGE) + + +if __name__ == '__main__': + test_runner.run([ + TestMotorCalibration(), + TestDisconnectedMotorCalibration() + ]) diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index 5fba9bc3..0c4ec395 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -46,6 +46,7 @@ class ODriveTestContext(): self.yaml = yaml #self.axes = [AxisTestContext(None), AxisTestContext(None)] self.encoders = [EncoderTestContext(self, 0, None), EncoderTestContext(self, 1, None)] + self.axes = [AxisTestContext(self, 0, None), AxisTestContext(self, 1, None)] def __repr__(self): return self.yaml['name'] @@ -64,6 +65,32 @@ class ODriveTestContext(): # axis_ctx.handle = self.handle.__dict__['axis{}'.format(axis_idx)] for encoder_idx, encoder_ctx in enumerate(self.encoders): encoder_ctx.handle = self.handle.__dict__['axis{}'.format(encoder_idx)].encoder + # TODO: distinguish between axis and motor context + for axis_idx, axis_ctx in enumerate(self.axes): + axis_ctx.handle = self.handle.__dict__['axis{}'.format(axis_idx)] + +class MotorTestContext(): + def __init__(self, yaml: dict): + self.yaml = yaml + + def __repr__(self): + return self.yaml['name'] + + def make_available(self, logger: Logger): + pass + +class AxisTestContext(): + def __init__(self, odrv_ctx: ODriveTestContext, num: int, yaml: dict): + self.handle = None + self.yaml = odrv_ctx.yaml[f'motor{num}'] # TODO: this is bad naming + self.odrv_ctx = odrv_ctx + self.num = num + + def __repr__(self): + return str(self.odrv_ctx) + '.axis' + str(self.num) + + def make_available(self, logger: Logger): + self.odrv_ctx.make_available(logger) class EncoderTestContext(): def __init__(self, odrv_ctx: ODriveTestContext, num: int, yaml: dict): @@ -93,6 +120,35 @@ class CANTestContext(): # Helper functions ------------------------------------------------------------# +def request_state(axis_ctx: AxisTestContext, state, expect_success=True): + axis_ctx.handle.requested_state = state + time.sleep(0.001) + if expect_success: + test_assert_eq(axis_ctx.handle.current_state, state) + else: + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_eq(axis_ctx.handle.error, AXIS_ERROR_INVALID_STATE) + axis_ctx.handle.error = AXIS_ERROR_NONE # reset error + +def get_errors(axis_ctx: AxisTestContext): + errors = [] + if axis_ctx.handle.motor.error != 0: + errors.append("motor failed with error 0x{:04X}".format(axis_ctx.handle.motor.error)) + if axis_ctx.handle.encoder.error != 0: + errors.append("encoder failed with error 0x{:04X}".format(axis_ctx.handle.encoder.error)) + if axis_ctx.handle.sensorless_estimator.error != 0: + errors.append("sensorless_estimator failed with error 0x{:04X}".format(axis_ctx.handle.sensorless_estimator.error)) + if axis_ctx.handle.error != 0: + errors.append("axis failed with error 0x{:04X}".format(axis_ctx.handle.error)) + elif len(errors) > 0: + errors.append("and by the way: axis reports no error even though there is one") + return errors + +def test_assert_no_error(axis_ctx: AxisTestContext): + errors = get_errors(axis_ctx) + if len(errors) > 0: + raise TestFailed("\n".join(errors)) + def yaml_to_test_objects(test_rig_yaml: dict, logger: Logger): available_test_objects = {} @@ -106,10 +162,15 @@ def yaml_to_test_objects(test_rig_yaml: dict, logger: Logger): add_component(odrv_ctx) for enc_ctx in odrv_ctx.encoders: add_component(enc_ctx) + for axis_ctx in odrv_ctx.axes: + add_component(axis_ctx) elif component_yaml['type'] == 'generalpurpose': for (k, v) in [(k, v) for (k, v) in component_yaml.items() if k.startswith("can")]: can_ctx = CANTestContext({'id': k, 'bus': v}) add_component(can_ctx) + elif component_yaml['type'] == 'motor': + motor_ctx = MotorTestContext(component_yaml) + add_component(motor_ctx) else: logger.warn('test rig has unsupported component ' + component_yaml['type']) continue @@ -158,43 +219,49 @@ def program_teensy(hex_file_path, program_gpio: int, logger: Logger): run_shell(["teensy_loader_cli", "-mmcu=imxrt1062", "-w", hex_file_path], logger, timeout = 5) time.sleep(0.5) # give it some time to boot -def run(test_case): - # Parse arguments - parser = argparse.ArgumentParser(description='ODrive automated test tool\n') - parser.add_argument("--ignore", metavar='DEVICE', action='store', nargs='+', - help="Ignore (disable) one or more components of the test rig") - # TODO: implement - parser.add_argument("--test-rig-yaml", type=argparse.FileType('r'), required=True, - help="test rig YAML file") - parser.set_defaults(ignore=[]) +def run(test_cases): + if not isinstance(test_cases, list): + test_cases = [test_cases] - args = parser.parse_args() + for test_case in test_cases: + # Compile a list of list of potential objects that might be compatible with this + # test + possible_parameters = [] + sig = signature(test_case.is_compatible) + for param_name in sig.parameters: + param_type = sig.parameters[param_name].annotation + possible_parameters.append(available_test_objects[param_type]) - # Load objects - test_rig_yaml = yaml.load(args.test_rig_yaml, Loader=yaml.BaseLoader) - logger = Logger() - - available_test_objects = yaml_to_test_objects(test_rig_yaml, logger) - - # Compile a list of list of potential objects that might be compatible with this - # test - possible_parameters = [] - sig = signature(test_case.is_compatible) - for param_name in sig.parameters: - param_type = sig.parameters[param_name].annotation - possible_parameters.append(available_test_objects[param_type]) - - # For each combination, check if the test is compatible with these objects - for param_combination in itertools.product(*possible_parameters): - if not test_case.is_compatible(*param_combination): - continue - - for param in param_combination: - param.make_available(logger) - - logger.notify('* running {} on {}...'.format(type(test_case).__name__, - [str(p) for p in param_combination])) - test_case.run_test(*param_combination, logger) + # For each combination, check if the test is compatible with these objects + for param_combination in itertools.product(*possible_parameters): + if not test_case.is_compatible(*param_combination): + continue + + for param in param_combination: + param.make_available(logger) + logger.notify('* running {} on {}...'.format(type(test_case).__name__, + [str(p) for p in param_combination])) + test_case.run_test(*param_combination, logger) logger.success('All tests passed!') + + +# Load test engine ------------------------------------------------------------# + +# Parse arguments +parser = argparse.ArgumentParser(description='ODrive automated test tool\n') +parser.add_argument("--ignore", metavar='DEVICE', action='store', nargs='+', + help="Ignore (disable) one or more components of the test rig") + # TODO: implement +parser.add_argument("--test-rig-yaml", type=argparse.FileType('r'), required=True, + help="test rig YAML file") +parser.set_defaults(ignore=[]) + +args = parser.parse_args() + +# Load objects +test_rig_yaml = yaml.load(args.test_rig_yaml, Loader=yaml.BaseLoader) +logger = Logger() + +available_test_objects = yaml_to_test_objects(test_rig_yaml, logger) diff --git a/tools/test-rig-rpi.yaml b/tools/test-rig-rpi.yaml index cc84f4a7..7078aad2 100644 --- a/tools/test-rig-rpi.yaml +++ b/tools/test-rig-rpi.yaml @@ -27,22 +27,12 @@ components: encoder0: virtual_encoder0 encoder1: virtual_encoder1 motor0: D5065-270KV_0 - motor1: D5065-270KV_1 + motor1: floating - type: motor name: D5065-270KV_0 - phase-resistance: 0.0245 - phase-inductance: 2.03e-05 - pole-pairs: 7 - direction: 1 - kv: 270 - max-current: 70 - max-voltage: 40 - - - type: motor - name: D5065-270KV_1 - phase-resistance: 0.0245 - phase-inductance: 2.03e-05 + phase-resistance: 0.039 + phase-inductance: 1.57e-05 pole-pairs: 7 direction: 1 kv: 270 From 73a167272cde18bc03611faa980e344b2420e713 Mon Sep 17 00:00:00 2001 From: Brandon Lewis Date: Tue, 10 Dec 2019 14:48:44 -0800 Subject: [PATCH 277/549] Add a Dockerfile which builds in an Ubuntu container --- Dockerfile | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..c0d0e423 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM ubuntu + +# Prepare the build environment and dependencies +RUN apt-get update +RUN apt-get -y install software-properties-common +RUN add-apt-repository ppa:team-gcc-arm-embedded/ppa +RUN add-apt-repository ppa:jonathonf/tup +RUN apt-get update +RUN apt-get -y upgrade +RUN apt-get -y install gcc-arm-embedded openocd tup python3.7 build-essential git + +# Build step below does not know about debian's python naming schemme +RUN ln -s /usr/bin/python3.7 /usr/bin/python + +# Copy the firmware tree into the container +RUN mkdir ODrive +COPY . ODrive +WORKDIR ODrive/Firmware + +# Hack around Tup's dependency on FUSE +RUN tup generate build.sh +RUN ./build.sh From c34cf83c63383d23aee9a8e28c8e05891f3adc63 Mon Sep 17 00:00:00 2001 From: Brandon Lewis Date: Tue, 10 Dec 2019 15:58:51 -0800 Subject: [PATCH 278/549] Use the tag for the specific LTS release --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c0d0e423..9739928c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu +FROM ubuntu:bionic # Prepare the build environment and dependencies RUN apt-get update From c5f06845c2b5eb3242654451ca640d615ac8362e Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Mon, 25 Mar 2019 20:04:45 +0100 Subject: [PATCH 279/549] Allow for regen current before braking --- Firmware/MotorControl/low_level.cpp | 8 ++++---- Firmware/MotorControl/odrive_main.h | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 82e54049..2b834e55 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -585,10 +585,10 @@ void update_brake_current() { Ibus_sum += axes[i]->motor_.current_control_.Ibus; } } - float brake_current = -Ibus_sum; - // Clip negative values to 0.0f - if (brake_current < 0.0f) brake_current = 0.0f; - float brake_duty = brake_current * board_config.brake_resistance / vbus_voltage; + + // Don't start braking until -Ibus > regen_current_allowed + float brake_current = std::max(-Ibus_sum - board_config.max_regen_current, 0.0f); + float brake_duty = std::max(brake_current * std::abs(board_config.brake_resistance) / vbus_voltage, 0.0f); // Duty limit at 90% to allow bootstrap caps to charge // If brake_duty is NaN, this expression will also evaluate to false diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 8bff6d81..d8e569da 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -71,6 +71,7 @@ struct BoardConfig_t { bool enable_uart = true; bool enable_i2c_instead_of_can = false; bool enable_ascii_protocol_on_usb = true; + float max_regen_current = 0.0f; #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 5 && HW_VERSION_VOLTAGE >= 48 float brake_resistance = 2.0f; // [ohm] #else From b23ef3214541fd5347d339e9656dcd06cc1c1594 Mon Sep 17 00:00:00 2001 From: Brandon Lewis Date: Thu, 30 Jan 2020 12:43:23 -0800 Subject: [PATCH 280/549] Add a simple script to run the docker build --- dockerbuild.sh | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100755 dockerbuild.sh diff --git a/dockerbuild.sh b/dockerbuild.sh new file mode 100755 index 00000000..5f1964ee --- /dev/null +++ b/dockerbuild.sh @@ -0,0 +1,47 @@ +function cleanup { + echo "Removing previous build artifacts" + rm -rf build + docker rm build-cont +} + +function gc { + cleanup + docker rmi build-img + docker image prune +} + +function build { + cleanup + + echo "Building the firmware" + docker build -t build-img . + + echo "Create container" + docker create --name build-cont build-img:latest + + echo "Extract build artifacts" + docker cp build-cont:ODrive/Firmware/build . +} + +function usage { + echo "usage: $0 build | cleanup | gc" + echo + echo "build -- build in docker and extract the artifacts." + echo "cleanup -- remove build artifacts from previous build" + echo "gc -- remove all build images and containers" +} + +case $1 in + build) + build + ;; + cleanup) + cleanup + ;; + gc) + gc + ;; + *) + usage + ;; +esac From b7a42525df89f5b0d3cb790600aac0bdba76237a Mon Sep 17 00:00:00 2001 From: Brandon Lewis Date: Thu, 30 Jan 2020 12:46:22 -0800 Subject: [PATCH 281/549] Add note to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf7ea37f..1918483f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * Unit Testing with Doctest has been started for select algorithms, see [Firmware/Tests/test_runner.cpp](Firmware/Tests/test_runner.cpp) * Added support for Flylint VSCode Extension for static code analysis * Using an STM32F405 .svd file allows CortexDebug to view registers during debugging +* Added scripts for building via docker. ### Changed * Changed ratiometric `motor.config.current_lim_tolerance` to absolute `motor.config.current_lim_margin` From 55ac5eb31c721a1b1b81540d2918e594f80f7c63 Mon Sep 17 00:00:00 2001 From: Brandon Lewis Date: Thu, 30 Jan 2020 13:01:02 -0800 Subject: [PATCH 282/549] prefix container and image names with odrive --- dockerbuild.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dockerbuild.sh b/dockerbuild.sh index 5f1964ee..779b9bf1 100755 --- a/dockerbuild.sh +++ b/dockerbuild.sh @@ -1,12 +1,12 @@ function cleanup { echo "Removing previous build artifacts" rm -rf build - docker rm build-cont + docker rm odrive-build-cont } function gc { cleanup - docker rmi build-img + docker rmi odrive-build-img docker image prune } @@ -14,17 +14,17 @@ function build { cleanup echo "Building the firmware" - docker build -t build-img . + docker build -t odrive-build-img . echo "Create container" - docker create --name build-cont build-img:latest + docker create --name odrive-build-cont odrive-build-img:latest echo "Extract build artifacts" - docker cp build-cont:ODrive/Firmware/build . + docker cp odrive-build-cont:ODrive/Firmware/build . } function usage { - echo "usage: $0 build | cleanup | gc" + echo "usage: $0 (build | cleanup | gc)" echo echo "build -- build in docker and extract the artifacts." echo "cleanup -- remove build artifacts from previous build" From a7dfb6ca060b6d3a483426797d0cdee2bec9f4f9 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 30 Jan 2020 13:31:20 -0800 Subject: [PATCH 283/549] add max regen current to protocl --- Firmware/communication/communication.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 79982e64..fe426a9d 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -152,6 +152,7 @@ static inline auto make_obj_tree() { ), make_protocol_object("config", make_protocol_property("brake_resistance", &board_config.brake_resistance), + make_protocol_property("max_regen_current ", &board_config.max_regen_current ), // TODO: changing this currently requires a reboot - fix this make_protocol_property("enable_uart", &board_config.enable_uart), make_protocol_property("enable_i2c_instead_of_can" , &board_config.enable_i2c_instead_of_can), // requires a reboot From 8cbc09e168186ccd1454daf5111727d027b6f11f Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 30 Jan 2020 16:16:39 -0800 Subject: [PATCH 284/549] feed watchdog on enter closed loop --- Firmware/MotorControl/axis.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 8ed9a4a3..6a3d0444 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -419,6 +419,7 @@ void Axis::run_state_machine_loop() { goto invalid_state_label; if (!encoder_.is_ready_) goto invalid_state_label; + watchdog_feed(); status = run_closed_loop_control_loop(); } break; From 1c9476318bed8e31a026e547d86aa62308273a42 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Wed, 5 Feb 2020 19:24:04 -0800 Subject: [PATCH 285/549] Update getting-started.md --- docs/getting-started.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 69337ad0..909a245d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -194,9 +194,10 @@ You can change `odrv0.axis0.motor.config.calibration_current` [A] to the largest This is the resistance of the brake resistor. If you are not using it, you may set it to `0`. Note that there may be some extra resistance in your wiring and in the screw terminals, so if you are getting issues while braking you may want to increase this parameter by around 0.05 ohm. `odrv0.axis0.motor.config.pole_pairs` -This is the number of **magnet poles** in the rotor, **divided by two**. To find this, you can simply count the number of permanent magnets in the rotor, if you can see them. -If you can't see them, try sliding a loose magnet in your hand around the rotor, and counting how many times it stops. This will be the number of _pole pairs_. If you use a magnetic piece of metal instead of a magnet, you will get the number of _magnet poles_. Another way of finding this number is with a current limited power supply. Connect any two of the three phases to a power supply outputting around 2A, spin the motor by hand, and count the number of detents. This will be the number of pole pairs. If you can't distinguish the detents from the normal cogging present when the motor is disconnected, increase the current. -**Note**: This is **not** the same as the number of coils in the stator. +This is the number of **magnet poles** in the rotor, **divided by two**. To find this, you can simply count the number of permanent magnets in the rotor, if you can see them. +**Note**: This is **not** the same as the number of coils in the stator. +A good way to find the number of pole pairs is with a current limited power supply. Connect any two of the three phases to a power supply outputting around 2A, spin the motor by hand, and count the number of detents. This will be the number of pole pairs. If you can't distinguish the detents from the normal cogging present when the motor is disconnected, increase the current. +Another way is sliding a loose magnet in your hand around the rotor, and counting how many times it stops. This will be the number of _pole pairs_. If you use a ferrous piece of metal instead of a magnet, you will get the number of _magnet poles_. `odrv0.axis0.motor.config.motor_type` This is the type of motor being used. Currently two types of motors are supported: High-current motors (`MOTOR_TYPE_HIGH_CURRENT`) and gimbal motors (`MOTOR_TYPE_GIMBAL`). From b2812f167c8ddb8f2005850a4e22945dfdc2c252 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Wed, 5 Feb 2020 19:38:07 -0800 Subject: [PATCH 286/549] execute property write hooks --- CHANGELOG.md | 1 + Firmware/fibre/cpp/include/fibre/protocol.hpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd9beae1..e9842158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * AC Induction Motor support. * Tracking of rotor flux through rotor time constant * Automatic d axis current for Maximum Torque Per Amp (MTPA) +* ASCII "w" commands now execute write hooks. ### Changed * Changed ratiometric `motor.config.current_lim_tolerance` to absolute `motor.config.current_lim_margin` diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index c1f3cc68..77b031f2 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -812,7 +812,11 @@ public: // special-purpose function - to be moved bool set_string(char * buffer, size_t length) final { - return from_string(buffer, length, property_, 0); + bool wrote = from_string(buffer, length, property_, 0); + if (wrote && written_hook_ != nullptr) { + written_hook_(ctx_); + } + return wrote; } bool set_from_float(float value) final { From d2239d15b67b86a6bcc05aac498f60e379e00cd7 Mon Sep 17 00:00:00 2001 From: camrbuss Date: Thu, 27 Feb 2020 21:39:54 -0700 Subject: [PATCH 287/549] doc - Additional states and control modes --- .gitignore | 6 +++--- docs/commands.md | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index ae3506ce..23806ff0 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,7 @@ target/ .tup tup.config -/ruby-bundle -/_site -/.bundle +docs/ruby-bundle +docs/_site +docs/.bundle diff --git a/docs/commands.md b/docs/commands.md index 1dce7336..320337c4 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -36,6 +36,11 @@ The current state of an axis is indicated by `.current_state`. The user ca 8. `AXIS_STATE_CLOSED_LOOP_CONTROL` Run closed loop control. * The action depends on the [control mode](#control-mode). * Can only be entered if the motor is calibrated (`.motor.is_calibrated`) and the encoder is ready (`.encoder.is_ready`). + 9. `AXIS_STATE_LOCKIN_SPIN` Run lockin spin. + * Can only be entered if the motor is calibrated (`.motor.is_calibrated`) or the motor direction is unspecified (`.motor.config.direction == 1`) + 10. `AXIS_STATE_ENCODER_DIR_FIND` Run encoder direction search. + * Can only be entered if the motor is calibrated (`.motor.is_calibrated`). + ### Startup Procedure @@ -58,6 +63,7 @@ Possible values are: * `CTRL_MODE_POSITION_CONTROL` * `CTRL_MODE_VELOCITY_CONTROL` * `CTRL_MODE_CURRENT_CONTROL` +* `CTRL_MODE_TRAJECTORY_CONTROL` * `CTRL_MODE_VOLTAGE_CONTROL` - this one is not normally used. # Control Commands From da97449baece08668a4531aaf223d97b39a9bf4d Mon Sep 17 00:00:00 2001 From: camrbuss Date: Fri, 28 Feb 2020 21:12:56 -0700 Subject: [PATCH 288/549] doc - encoder updates --- docs/encoders.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/encoders.md b/docs/encoders.md index cb038850..b3d76ca0 100644 --- a/docs/encoders.md +++ b/docs/encoders.md @@ -118,7 +118,7 @@ must reflect the number of counts odrive receives after one complete turn of the You will probably never be able to properly debug if you have problems unless you use an oscilloscope. If you have one, try the following: Connect to the AB pins, see if you get square waves as you turn the motor. Connect to the I pin, see if you get a pulse on a complete rotation. Sometimes this is hard to see. -If you are using SPI, have a lot at the signal on the CLK, and CS pins. There are many examples on the net for how these should behave. +If you are using SPI, use a logic analyzer and connect a wire to the CLK, MISO, and CS pins. Set a trigger for the CS pin and insure that the encoder position is being sent and is increasing/decreasing as you spin the motor. There is extremely cheap [Sigrok](https://sigrok.org/) supported hardware available for protocol analysis. ## Encoder Noise Noise is found in all circuits, life is just about figuring out if it is preventing your system from working. Lots of users have no problems with noise interfering with their odrive operation, others will tell you "_I've been using the same encoder as you with no problems_". Power to 'em, that may be true, but it doesn't mean it will work for you. If you are concerned about noise, there are several possible sources: @@ -152,9 +152,11 @@ The acronym I and Z mean the same thing, connect those as well if you are using #### Using SPI. TobinHall has written a [branch](https://github.com/TobinHall/ODrive/tree/Non-Blocking_Absolute_SPI) that supports the SPI option on the AS5047/AS5048. Use his build to flash firmware on your ODrive and connect MISO, SCK, and CS to the labeled pins on the odrive -Tie MOSI to 3.3v, connect to the SCK, CLK, MISO, GND and 3.2v pins on the ODrive. (note for SPI users, the acronym SCK and CLK mean the same thing, the acronym CSn and CS mean the same thing.) +Wetmelon's Razor's Edge [branch](https://github.com/Wetmelon/ODrive/tree/RazorsEdge) also supports SPI without manually pulling MOSI high and using a standard SPI wiring + +Tie MOSI to 3.3v, connect to the SCK, CLK, MISO, GND and 3.3v pins on the ODrive. (note for SPI users, the acronym SCK and CLK mean the same thing, the acronym CSn and CS mean the same thing.) Add these commands to your calibration / startup script: * `.encoder.config.abs_spi_cs_gpio_pin = 4` or which ever GPIO pin you choose -* `.encoder.config.mode = 257` -* `.axis0.encoder.config.cpr = 2**14` +* `.encoder.config.mode = 257` 256 - CUI Encoder, 257 - AMS Encoder, 258 - AEAT Encoder +* `.axis0.encoder.config.cpr = 2**14` Resolution of the encoder `2**14` or `2**16` is common From 0befb119a5931eb12e40919ca1f2e2ac49855d56 Mon Sep 17 00:00:00 2001 From: camrbuss Date: Sun, 1 Mar 2020 13:11:48 -0700 Subject: [PATCH 289/549] doc - added controller details --- docs/control.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/control.md b/docs/control.md index 8af90a91..985f8993 100644 --- a/docs/control.md +++ b/docs/control.md @@ -30,6 +30,14 @@ voltage_cmd = current_error * current_gain + voltage_integral (+ voltage_feedfor ``` For more detail refer to [controller.cpp](https://github.com/madcowswe/ODrive/blob/master/Firmware/MotorControl/controller.cpp#L86). + +### Controller Details: +The ultimate output of the controller is the voltage applied to the gate of each FET to deliver current through each coil of the motor. The current through the motor linearly relates to the torque output of the motor. This means that the inputs to the cascaded controller are theoretically the position (angle), velocity (angle/time), and acceleration (angle/time/time) of the motor. Note that when thinking about the controller from the perpective of the physics of the motor you would expect to see the time in the Velocity and Current loops, but it is absent because the time difference between iterations is always 125 microseconds (8kHz). Because the time difference between controller loops is a constant and can simply be wrapped into the controller gains. + +The output of each stage of the controller is clamped before being fed into the next stage. So after the `vel_cmd` is calculated from the position controller, the `vel_cmd` is clamped to the velocity limit. The `current_cmd` output of the velocity controller is then clamped and fed to the current controller. Oddly enough the controller class does not contain the current controller, but instead the current controller is housed in the motor class due to the complexity of the motor driver schema. + +The feedforward terms available when using the position or velocity control mode are meant to enable better performance when the dynamics of a system are known and the host controller can predict the motion based on the load. A perfect example of this is the use of the trajectory controller that sets the position, velocity, and current based on the desired position, velocity, and acceleration. If you take a trapezoidal velocity profile for example, you can imagine on the ramp upward the velocity will be increasing over time, while the current is a non-zero constant. At the flat portion of the profile the velocity will be a non-zero constant, but the acceleration will be zero. This trajectory controller use case uses the cascaded controller with multiple inputs to achieve the desired motion with the best performance. + ## Tuning Tuning the motor controller is an essential step to unlock the full potential of the ODrive. Tuning allows for the controller to quickly respond to disturbances or changes in the system (such as an external force being applied or a change in the setpoint) without becoming unstable. Correctly setting the three tuning parameters (called gains) ensures that ODrive can control your motors in the most effective way possible. The three values are: * `.controller.config.pos_gain = 20.0` [(counts/s) / counts] From 2e05041d52ede12e33d8a66b0d2847c6cbafc667 Mon Sep 17 00:00:00 2001 From: camrbuss Date: Sun, 1 Mar 2020 13:18:36 -0700 Subject: [PATCH 290/549] Gemfile.lock is ignored by Github, rm for local --- .gitignore | 1 + docs/Gemfile.lock | 251 ---------------------------------------------- 2 files changed, 1 insertion(+), 251 deletions(-) delete mode 100644 docs/Gemfile.lock diff --git a/.gitignore b/.gitignore index 23806ff0..20743ca8 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,5 @@ tup.config docs/ruby-bundle docs/_site docs/.bundle +docs/Gemfile.lock diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock deleted file mode 100644 index 860837ad..00000000 --- a/docs/Gemfile.lock +++ /dev/null @@ -1,251 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - activesupport (4.2.9) - i18n (~> 0.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.11.1) - colorator (1.1.0) - commonmarker (0.17.9) - ruby-enum (~> 0.5) - concurrent-ruby (1.0.5) - em-websocket (0.5.1) - eventmachine (>= 0.12.9) - http_parser.rb (~> 0.6.0) - ethon (0.11.0) - ffi (>= 1.3.0) - eventmachine (1.2.5) - execjs (2.7.0) - faraday (0.14.0) - multipart-post (>= 1.2, < 3) - ffi (1.9.24) - forwardable-extended (2.6.0) - gemoji (3.0.0) - github-pages (181) - activesupport (= 4.2.9) - github-pages-health-check (= 1.4.0) - jekyll (= 3.7.4) - jekyll-avatar (= 0.5.0) - jekyll-coffeescript (= 1.1.1) - jekyll-commonmark-ghpages (= 0.1.5) - jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.9.3) - jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.9.4) - jekyll-mentions (= 1.3.0) - jekyll-optional-front-matter (= 0.3.0) - jekyll-paginate (= 1.1.0) - jekyll-readme-index (= 0.2.0) - jekyll-redirect-from (= 0.13.0) - jekyll-relative-links (= 0.5.3) - jekyll-remote-theme (= 0.2.3) - jekyll-sass-converter (= 1.5.2) - jekyll-seo-tag (= 2.4.0) - jekyll-sitemap (= 1.2.0) - jekyll-swiss (= 0.4.0) - jekyll-theme-architect (= 0.1.1) - jekyll-theme-cayman (= 0.1.1) - jekyll-theme-dinky (= 0.1.1) - jekyll-theme-hacker (= 0.1.1) - jekyll-theme-leap-day (= 0.1.1) - jekyll-theme-merlot (= 0.1.1) - jekyll-theme-midnight (= 0.1.1) - jekyll-theme-minimal (= 0.1.1) - jekyll-theme-modernist (= 0.1.1) - jekyll-theme-primer (= 0.5.3) - jekyll-theme-slate (= 0.1.1) - jekyll-theme-tactile (= 0.1.1) - jekyll-theme-time-machine (= 0.1.1) - jekyll-titles-from-headings (= 0.5.1) - jemoji (= 0.9.0) - kramdown (= 1.16.2) - liquid (= 4.0.0) - listen (= 3.1.5) - mercenary (~> 0.3) - minima (= 2.4.0) - nokogiri (>= 1.8.5, < 2.0) - rouge (= 2.2.1) - terminal-table (~> 1.4) - github-pages-health-check (1.4.0) - addressable (~> 2.3) - net-dns (~> 0.8) - octokit (~> 4.0) - public_suffix (~> 2.0) - typhoeus (~> 1.3) - html-pipeline (2.7.1) - activesupport (>= 2) - nokogiri (>= 1.8.5) - http_parser.rb (0.6.0) - i18n (0.9.5) - concurrent-ruby (~> 1.0) - jekyll (3.7.4) - addressable (~> 2.4) - colorator (~> 1.0) - em-websocket (~> 0.5) - i18n (~> 0.7) - jekyll-sass-converter (~> 1.0) - jekyll-watch (~> 2.0) - kramdown (~> 1.14) - liquid (~> 4.0) - mercenary (~> 0.3.3) - pathutil (~> 0.9) - rouge (>= 1.7, < 4) - safe_yaml (~> 1.0) - jekyll-avatar (0.5.0) - jekyll (~> 3.0) - jekyll-coffeescript (1.1.1) - coffee-script (~> 2.2) - coffee-script-source (~> 1.11.1) - jekyll-commonmark (1.2.0) - commonmarker (~> 0.14) - jekyll (>= 3.0, < 4.0) - jekyll-commonmark-ghpages (0.1.5) - commonmarker (~> 0.17.6) - jekyll-commonmark (~> 1) - rouge (~> 2) - jekyll-default-layout (0.1.4) - jekyll (~> 3.0) - jekyll-feed (0.9.3) - jekyll (~> 3.3) - jekyll-gist (1.5.0) - octokit (~> 4.2) - jekyll-github-metadata (2.9.4) - jekyll (~> 3.1) - octokit (~> 4.0, != 4.4.0) - jekyll-mentions (1.3.0) - activesupport (~> 4.0) - html-pipeline (~> 2.3) - jekyll (~> 3.0) - jekyll-optional-front-matter (0.3.0) - jekyll (~> 3.0) - jekyll-paginate (1.1.0) - jekyll-readme-index (0.2.0) - jekyll (~> 3.0) - jekyll-redirect-from (0.13.0) - jekyll (~> 3.3) - jekyll-relative-links (0.5.3) - jekyll (~> 3.3) - jekyll-remote-theme (0.2.3) - jekyll (~> 3.5) - rubyzip (>= 1.2.2, < 3.0) - typhoeus (>= 0.7, < 2.0) - jekyll-sass-converter (1.5.2) - sass (~> 3.4) - jekyll-seo-tag (2.4.0) - jekyll (~> 3.3) - jekyll-sitemap (1.2.0) - jekyll (~> 3.3) - jekyll-swiss (0.4.0) - jekyll-theme-architect (0.1.1) - jekyll (~> 3.5) - jekyll-seo-tag (~> 2.0) - jekyll-theme-cayman (0.1.1) - jekyll (~> 3.5) - jekyll-seo-tag (~> 2.0) - jekyll-theme-dinky (0.1.1) - jekyll (~> 3.5) - jekyll-seo-tag (~> 2.0) - jekyll-theme-hacker (0.1.1) - jekyll (~> 3.5) - jekyll-seo-tag (~> 2.0) - jekyll-theme-leap-day (0.1.1) - jekyll (~> 3.5) - jekyll-seo-tag (~> 2.0) - jekyll-theme-merlot (0.1.1) - jekyll (~> 3.5) - jekyll-seo-tag (~> 2.0) - jekyll-theme-midnight (0.1.1) - jekyll (~> 3.5) - jekyll-seo-tag (~> 2.0) - jekyll-theme-minimal (0.1.1) - jekyll (~> 3.5) - jekyll-seo-tag (~> 2.0) - jekyll-theme-modernist (0.1.1) - jekyll (~> 3.5) - jekyll-seo-tag (~> 2.0) - jekyll-theme-primer (0.5.3) - jekyll (~> 3.5) - jekyll-github-metadata (~> 2.9) - jekyll-seo-tag (~> 2.0) - jekyll-theme-slate (0.1.1) - jekyll (~> 3.5) - jekyll-seo-tag (~> 2.0) - jekyll-theme-tactile (0.1.1) - jekyll (~> 3.5) - jekyll-seo-tag (~> 2.0) - jekyll-theme-time-machine (0.1.1) - jekyll (~> 3.5) - jekyll-seo-tag (~> 2.0) - jekyll-titles-from-headings (0.5.1) - jekyll (~> 3.3) - jekyll-watch (2.0.0) - listen (~> 3.0) - jemoji (0.9.0) - activesupport (~> 4.0, >= 4.2.9) - gemoji (~> 3.0) - html-pipeline (~> 2.2) - jekyll (~> 3.0) - kramdown (1.16.2) - liquid (4.0.0) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) - mercenary (0.3.6) - mini_portile2 (2.3.0) - minima (2.4.0) - jekyll (~> 3.5) - jekyll-feed (~> 0.9) - jekyll-seo-tag (~> 2.1) - minitest (5.11.3) - multipart-post (2.0.0) - net-dns (0.8.0) - nokogiri (>= 1.8.5) - mini_portile2 (~> 2.3.0) - octokit (4.8.0) - sawyer (~> 0.8.0, >= 0.5.3) - pathutil (0.16.1) - forwardable-extended (~> 2.6) - public_suffix (2.0.5) - rb-fsevent (0.10.3) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) - rouge (2.2.1) - ruby-enum (0.7.2) - i18n - ruby_dep (1.5.0) - rubyzip (1.2.2) - safe_yaml (1.0.4) - sass (3.5.6) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sawyer (0.8.1) - addressable (>= 2.3.5, < 2.6) - faraday (~> 0.8, < 1.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - typhoeus (1.3.0) - ethon (>= 0.9.0) - tzinfo (1.2.5) - thread_safe (~> 0.1) - unicode-display_width (1.3.0) - -PLATFORMS - ruby - -DEPENDENCIES - github-pages - jekyll-redirect-from - -BUNDLED WITH - 1.16.1 From bffd10ad271c11cfc0baa0bb0a23cba452afebb8 Mon Sep 17 00:00:00 2001 From: camrbuss Date: Sun, 1 Mar 2020 15:46:54 -0700 Subject: [PATCH 291/549] doc - updated developer documentation --- docs/configuring-vscode.md | 9 +++++---- docs/developer-guide.md | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/configuring-vscode.md b/docs/configuring-vscode.md index 69edf772..dd23df1d 100644 --- a/docs/configuring-vscode.md +++ b/docs/configuring-vscode.md @@ -10,14 +10,15 @@ Before doing the VSCode setup, make sure you've installed all of your [prerequis 1. Open VSCode 1. Install extensions. This can be done directly from VSCode (Ctrl+Shift+X) * Required extensions: - * C/C++ - * Cortex-Debug + * C/C++ `ext install ms-vscode.cpptools` + * Cortex-Debug `ext install marus25.cortex-debug` + * Cortex-Debug: Device Support Pack - STM32F4 `ext install marus25.cortex-debug-dp-stm32f4` * Recommended Extensions: * Include Autocomplete * Path Autocomplete * Auto Comment Blocks -1. Create an environment variable named `ARM_GCC_ROOT` whose value is the location of the `GNU Arm Embedded Toolchain` (.e.g `C:\Program Files (x86)\GNU Tools Arm Embedded\7 2018-q2-update`) that you installed in the prerequisites section of the developer's guide. -1. Restart VSCode +1. Create an environment variable named `ARM_GCC_ROOT` whose value is the location of the `GNU Arm Embedded Toolchain` (.e.g `C:\Program Files (x86)\GNU Tools Arm Embedded\7 2018-q2-update`) that you installed in the prerequisites section of the developer's guide. This is not strictly needed for Linux or Mac, and you can alternatively use the `Cortex-debug: Arm Toolchain Path` setting in VSCode extension settings. +1. Relaunch VSCode 1. Open the VSCode Workspace file, which is located in the root of the ODrive repository. It is called `ODrive_Workspace.code-workspace`. The first time you open it, VSCode will install some dependencies. If it fails, you may need to [change your proxy settings](https://code.visualstudio.com/docs/getstarted/settings). You should now be ready to compile and test the ODrive project. diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 6f351070..36790201 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -98,7 +98,7 @@ __CONFIG_BOARD_VERSION__: The board version you're using. Can be `v3.1`, `v3.2`, __CONFIG_USB_PROTOCOL__: Defines which protocol the ODrive should use on the USB interface. * `native`: The native ODrive protocol. Use this if you want to use the python tools in this repo. Can maybe work with macOS. - * `native-stream`: Like the native ODrive protocol, but the ODrive will treat the USB connection exactly as if it was a UART connection. __ Maybe need to use this if you're on macOS__. This is necessary because macOS doesn't grant our python tools sufficient low-level access to treat the device as the USB device that it is. + * `native-stream`: Like the native ODrive protocol, but the ODrive will treat the USB connection exactly as if it was a UART connection. __You may need to use this if you're on macOS__. This is necessary because macOS doesn't grant our python tools sufficient low-level access to treat the device as the USB device that it is. * `none`: Disable USB. The device will still show up when plugged in but it will ignore any commands. **Note**: There is a second USB interface that is always a serial port. @@ -108,6 +108,8 @@ __CONFIG_UART_PROTOCOL__: Defines which protocol the ODrive should use on the UA * `ascii`: The ASCII protocol. Use this option if you control the ODrive with an Arduino. The ODrive Arduino library is not yet updated to the native protocol. * `none`: Disable UART. +__CONFIG_DEBUG__: Defines wether debugging will be enabled when compiling the firmware; specifically the `-g -gdwarf-2` flags. Note that printf debugging will only function if your tup.config specifies the `USB_PROTOCOL` or `UART_PROTOCOL` as stdout and `DEBUG_PRINT` is defined. See the IDE specific documentation for more information. + You can also modify the compile-time defaults for all `.config` parameters. You will find them if you search for `AxisConfig`, `MotorConfig`, etc.

    From a498a1f984b1bc8701e0f403e0e349f59dfc2d6e Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 9 Mar 2020 21:08:49 -0400 Subject: [PATCH 292/549] Convert endstop debounce_ms to a uint32 to avoid potential overflow bug --- Firmware/MotorControl/endstop.cpp | 4 ++-- Firmware/MotorControl/endstop.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index f8fc4480..d4be0f41 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -10,14 +10,14 @@ void Endstop::update() { GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); bool last_pin_state = pin_state_; pin_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); - float now = axis_->loop_counter_ * current_meas_period; + uint32_t now = static_cast(axis_->loop_counter_ * current_meas_period); if (pin_state_ != last_pin_state) { debounce_timer_ = now; } if (config_.enabled) { if ((now - debounce_timer_) >= (config_.debounce_ms * 0.001f)) { // Debounce timer expired, take the new pin state endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state - debounce_timer_ = now - (config_.debounce_ms * 0.001f); // Ensure timer doesn't have overflow issues + debounce_timer_ = config_.debounce_ms; // Ensure timer doesn't have overflow issues } else { endstop_state_ = endstop_state_; // Do nothing } diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index eb1e3c8e..b5c6e6d8 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -5,7 +5,7 @@ class Endstop { public: struct Config_t { float offset = 0; - float debounce_ms = 50.0f; + uint32_t debounce_ms = 50; uint16_t gpio_num = 0; bool enabled = false; bool is_active_high = false; @@ -42,6 +42,6 @@ class Endstop { private: bool pin_state_ = false; float pos_when_pressed_ = 0.0f; - volatile float debounce_timer_ = 0; + uint32_t debounce_timer_ = 0; }; #endif \ No newline at end of file From 17bcb43dd64a50c5527cf92639433a75ce2cf311 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 9 Mar 2020 21:15:45 -0400 Subject: [PATCH 293/549] Improve debounce resolution --- Firmware/MotorControl/endstop.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index d4be0f41..bdafa395 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -10,12 +10,12 @@ void Endstop::update() { GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); bool last_pin_state = pin_state_; pin_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); - uint32_t now = static_cast(axis_->loop_counter_ * current_meas_period); + uint32_t now = static_cast(axis_->loop_counter_ * current_meas_period * 1000); if (pin_state_ != last_pin_state) { debounce_timer_ = now; } if (config_.enabled) { - if ((now - debounce_timer_) >= (config_.debounce_ms * 0.001f)) { // Debounce timer expired, take the new pin state + if ((now - debounce_timer_) >= config_.debounce_ms) { // Debounce timer expired, take the new pin state endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state debounce_timer_ = config_.debounce_ms; // Ensure timer doesn't have overflow issues } else { From 7d00c4181df8072d4b0ce0c2d45f692c03a3f97f Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 9 Mar 2020 21:16:43 -0400 Subject: [PATCH 294/549] revert timer overflow check --- Firmware/MotorControl/endstop.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index bdafa395..db8cf271 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -17,7 +17,7 @@ void Endstop::update() { if (config_.enabled) { if ((now - debounce_timer_) >= config_.debounce_ms) { // Debounce timer expired, take the new pin state endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state - debounce_timer_ = config_.debounce_ms; // Ensure timer doesn't have overflow issues + debounce_timer_ = now - config_.debounce_ms; // Ensure timer doesn't have overflow issues } else { endstop_state_ = endstop_state_; // Do nothing } From 42b6effedd49dbfffa82321fc98918c832589114 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 26 Mar 2020 17:20:32 -0400 Subject: [PATCH 295/549] Implement a better brake resistor current handler --- Firmware/.vscode/c_cpp_properties.json | 2 +- Firmware/.vscode/settings.json | 3 --- Firmware/MotorControl/low_level.cpp | 7 ++++++- Firmware/MotorControl/odrive_main.h | 1 + Firmware/communication/communication.cpp | 1 + 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index c4a2f37c..e8523423 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -24,7 +24,7 @@ "USE_HAL_DRIVER", "HW_VERSION_MAJOR=3", "HW_VERSION_MINOR=6", - "HW_VERSION_VOLTAGE=56", + "HW_VERSION_VOLTAGE=24", "USB_PROTOCOL_NATIVE", "__weak=\"__attribute__((weak))\"", "__packed=\"__attribute__((__packed__))\"", diff --git a/Firmware/.vscode/settings.json b/Firmware/.vscode/settings.json index 39c28f82..b0396fc7 100644 --- a/Firmware/.vscode/settings.json +++ b/Firmware/.vscode/settings.json @@ -1,9 +1,6 @@ { "C_Cpp.intelliSenseEngine": "Default", "C_Cpp.intelliSenseEngineFallback": "Disabled", - "files.exclude": { - "build": true - }, "files.associations": { "memory": "cpp", "utility": "cpp", diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 6125c99c..91fc9c94 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -592,9 +592,14 @@ void update_brake_current() { } float brake_current = -Ibus_sum; // Clip negative values to 0.0f - if (brake_current < 0.0f) brake_current = 0.0f; + if (brake_current < 0.0f) + brake_current = 0.0f; float brake_duty = brake_current * board_config.brake_resistance / vbus_voltage; + brake_duty = std::max((vbus_voltage - board_config.nominal_voltage) / (VBUS_OVERVOLTAGE_LEVEL/0.9f - board_config.nominal_voltage), brake_duty); + // Clamp the duty cycle + brake_duty = brake_duty < 0.0f ? 0.0f : (brake_duty > 0.9f ? 0.9f : brake_duty); + // Duty limit at 90% to allow bootstrap caps to charge // If brake_duty is NaN, this expression will also evaluate to false if ((brake_duty >= 0.0f) && (brake_duty <= 0.9f)) { diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 677fb996..6d2b312d 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -76,6 +76,7 @@ struct BoardConfig_t { #else float brake_resistance = 0.47f; // [ohm] #endif + float nominal_voltage = VBUS_OVERVOLTAGE_LEVEL; float dc_bus_undervoltage_trip_level = 8.0f; // Date: Fri, 27 Mar 2020 21:05:16 -0400 Subject: [PATCH 296/549] Move gpio helper functions --- Firmware/MotorControl/utils.hpp | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Firmware/MotorControl/utils.hpp b/Firmware/MotorControl/utils.hpp index 574aa800..fdcb73cc 100644 --- a/Firmware/MotorControl/utils.hpp +++ b/Firmware/MotorControl/utils.hpp @@ -113,4 +113,50 @@ float our_arm_cos_f32(float x); } #endif + +#include "gpio.h" +constexpr GPIO_TypeDef* get_gpio_port_by_pin(uint16_t GPIO_pin){ + switch(GPIO_pin){ + case 1: return GPIO_1_GPIO_Port; break; + case 2: return GPIO_2_GPIO_Port; break; + case 3: return GPIO_3_GPIO_Port; break; + case 4: return GPIO_4_GPIO_Port; break; +#ifdef GPIO_5_GPIO_Port + case 5: return GPIO_5_GPIO_Port; break; +#endif +#ifdef GPIO_6_GPIO_Port + case 6: return GPIO_6_GPIO_Port; break; +#endif +#ifdef GPIO_7_GPIO_Port + case 7: return GPIO_7_GPIO_Port; break; +#endif +#ifdef GPIO_8_GPIO_Port + case 8: return GPIO_8_GPIO_Port; break; +#endif + default: return GPIO_1_GPIO_Port; + } +} + +constexpr uint16_t get_gpio_pin_by_pin(uint16_t GPIO_pin){ + switch(GPIO_pin){ + case 1: return GPIO_1_Pin; break; + case 2: return GPIO_2_Pin; break; + case 3: return GPIO_3_Pin; break; + case 4: return GPIO_4_Pin; break; +#ifdef GPIO_5_Pin + case 5: return GPIO_5_Pin; break; +#endif +#ifdef GPIO_6_Pin + case 6: return GPIO_6_Pin; break; +#endif +#ifdef GPIO_7_Pin + case 7: return GPIO_7_Pin; break; +#endif +#ifdef GPIO_8_Pin + case 8: return GPIO_8_Pin; break; +#endif + default: return GPIO_1_Pin; + } +} + #endif //__UTILS_H From 33317eb4df3a3b5f813988376a63c77670829da4 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 27 Mar 2020 21:05:27 -0400 Subject: [PATCH 297/549] Add Timer class --- Firmware/MotorControl/timer.hpp | 40 +++++++++++++++++++++++++++++++++ Firmware/Tests/test_timer.cpp | 30 +++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 Firmware/MotorControl/timer.hpp create mode 100644 Firmware/Tests/test_timer.cpp diff --git a/Firmware/MotorControl/timer.hpp b/Firmware/MotorControl/timer.hpp new file mode 100644 index 00000000..e1b6b0b0 --- /dev/null +++ b/Firmware/MotorControl/timer.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +class Timer { + public: + void setTimeout(const float timeout) { + timeout_ = timeout; + } + + void setInterval(const float interval) { + interval_ = interval; + } + + void start() { + running_ = true; + } + + void stop() { + running_ = false; + } + + void update() { + if (running_) + timer_ = std::min(timer_ + interval_, timeout_); + } + + void reset() { + timer_ = 0.0f; + } + + bool expired() { + return timer_ >= timeout_; + } + + private: + float timer_ = 0.0f; + float timeout_ = 0.0f; + float interval_ = 0.0f; + bool running_ = false; +}; diff --git a/Firmware/Tests/test_timer.cpp b/Firmware/Tests/test_timer.cpp new file mode 100644 index 00000000..bd34b7c6 --- /dev/null +++ b/Firmware/Tests/test_timer.cpp @@ -0,0 +1,30 @@ +#define DOCTEST_IMPLEMENT +#include +#include "MotorControl/timer.hpp" + +TEST_CASE("Timer"){ + Timer myTimer; + myTimer.setTimeout(10); + myTimer.setInterval(1); + CHECK(!myTimer.expired()); + + myTimer.start(); + CHECK(!myTimer.expired()); + for(int i = 0; i < 9; ++i){ + myTimer.update(); + CHECK(!myTimer.expired()); + } + + myTimer.update(); + CHECK(myTimer.expired()); + + myTimer.stop(); + CHECK(myTimer.expired()); + + myTimer.start(); + CHECK(myTimer.expired()); + + myTimer.reset(); + CHECK(!myTimer.expired()); + +} \ No newline at end of file From f98da473f14dbe2d5f2e11df31b5f0eb0509e1a8 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 27 Mar 2020 21:11:13 -0400 Subject: [PATCH 298/549] Remove utils import from drv8301.c --- Firmware/Board/v3/Inc/gpio.h | 2 -- Firmware/Board/v3/Src/gpio.c | 42 ------------------------------ Firmware/Drivers/DRV8301/drv8301.c | 1 - 3 files changed, 45 deletions(-) diff --git a/Firmware/Board/v3/Inc/gpio.h b/Firmware/Board/v3/Inc/gpio.h index 403271b6..81f90be9 100644 --- a/Firmware/Board/v3/Inc/gpio.h +++ b/Firmware/Board/v3/Inc/gpio.h @@ -76,8 +76,6 @@ bool GPIO_subscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin, void GPIO_unsubscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin); void GPIO_set_to_analog(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin); -uint16_t get_gpio_pin_by_pin(uint16_t GPIO_pin); -GPIO_TypeDef* get_gpio_port_by_pin(uint16_t GPIO_pin); #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR <= 4 #define GPIO_COUNT 5 diff --git a/Firmware/Board/v3/Src/gpio.c b/Firmware/Board/v3/Src/gpio.c index 08aa1450..635091ee 100644 --- a/Firmware/Board/v3/Src/gpio.c +++ b/Firmware/Board/v3/Src/gpio.c @@ -278,49 +278,7 @@ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_pin) { } } -GPIO_TypeDef* get_gpio_port_by_pin(uint16_t GPIO_pin){ - switch(GPIO_pin){ - case 1: return GPIO_1_GPIO_Port; break; - case 2: return GPIO_2_GPIO_Port; break; - case 3: return GPIO_3_GPIO_Port; break; - case 4: return GPIO_4_GPIO_Port; break; -#ifdef GPIO_5_GPIO_Port - case 5: return GPIO_5_GPIO_Port; break; -#endif -#ifdef GPIO_6_GPIO_Port - case 6: return GPIO_6_GPIO_Port; break; -#endif -#ifdef GPIO_7_GPIO_Port - case 7: return GPIO_7_GPIO_Port; break; -#endif -#ifdef GPIO_8_GPIO_Port - case 8: return GPIO_8_GPIO_Port; break; -#endif - default: return GPIO_1_GPIO_Port; - } -} -uint16_t get_gpio_pin_by_pin(uint16_t GPIO_pin){ - switch(GPIO_pin){ - case 1: return GPIO_1_Pin; break; - case 2: return GPIO_2_Pin; break; - case 3: return GPIO_3_Pin; break; - case 4: return GPIO_4_Pin; break; -#ifdef GPIO_5_Pin - case 5: return GPIO_5_Pin; break; -#endif -#ifdef GPIO_6_Pin - case 6: return GPIO_6_Pin; break; -#endif -#ifdef GPIO_7_Pin - case 7: return GPIO_7_Pin; break; -#endif -#ifdef GPIO_8_Pin - case 8: return GPIO_8_Pin; break; -#endif - default: return GPIO_1_Pin; - } -} /* USER CODE END 2 */ diff --git a/Firmware/Drivers/DRV8301/drv8301.c b/Firmware/Drivers/DRV8301/drv8301.c index c65cc4d3..0ecb1469 100644 --- a/Firmware/Drivers/DRV8301/drv8301.c +++ b/Firmware/Drivers/DRV8301/drv8301.c @@ -45,7 +45,6 @@ // drivers #include "drv8301.h" -#include "utils.hpp" // ************************************************************************** From 8a0a22eb6f7181072292f5e7c5bf83f50fb42264 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 27 Mar 2020 21:11:39 -0400 Subject: [PATCH 299/549] Revamp Endstop timers --- Firmware/MotorControl/endstop.cpp | 35 ++++++++++++++++------------- Firmware/MotorControl/endstop.hpp | 6 +++-- Firmware/MotorControl/odrive_main.h | 5 ++++- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index db8cf271..2f8c6410 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -3,24 +3,25 @@ Endstop::Endstop(Endstop::Config_t& config) : config_(config) { update_config(); + debounceTimer_.setInterval(current_meas_period); } + void Endstop::update() { - uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); - GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); - bool last_pin_state = pin_state_; - pin_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); - uint32_t now = static_cast(axis_->loop_counter_ * current_meas_period * 1000); - if (pin_state_ != last_pin_state) { - debounce_timer_ = now; - } + debounceTimer_.update(); if (config_.enabled) { - if ((now - debounce_timer_) >= config_.debounce_ms) { // Debounce timer expired, take the new pin state + bool last_pin_state = pin_state_; + + uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); + GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); + pin_state_ = HAL_GPIO_ReadPin(gpio_port, gpio_pin); + + // If the pin state has changed, reset the timer + if (pin_state_ != last_pin_state) + debounceTimer_.reset(); + + if (debounceTimer_.expired()) endstop_state_ = config_.is_active_high ? pin_state_ : !pin_state_; // endstop_state is the logical state - debounce_timer_ = now - config_.debounce_ms; // Ensure timer doesn't have overflow issues - } else { - endstop_state_ = endstop_state_; // Do nothing - } } else { endstop_state_ = false; } @@ -30,11 +31,13 @@ bool Endstop::get_state() { return endstop_state_; } -void Endstop::update_config(){ +void Endstop::update_config() { set_enabled(config_.enabled); + debounceTimer_.setInterval(config_.debounce_ms * 0.001f); } void Endstop::set_enabled(bool enable) { + debounceTimer_.reset(); if (config_.gpio_num != 0) { uint16_t gpio_pin = get_gpio_pin_by_pin(config_.gpio_num); GPIO_TypeDef* gpio_port = get_gpio_port_by_pin(config_.gpio_num); @@ -45,6 +48,8 @@ void Endstop::set_enabled(bool enable) { GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = config_.pullup ? GPIO_PULLUP : GPIO_PULLDOWN; HAL_GPIO_Init(gpio_port, &GPIO_InitStruct); - } + debounceTimer_.start(); + } else + debounceTimer_.stop(); } } \ No newline at end of file diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index b5c6e6d8..02f116b7 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -1,6 +1,7 @@ #ifndef __ENDSTOP_HPP #define __ENDSTOP_HPP +#include "timer.hpp" class Endstop { public: struct Config_t { @@ -36,12 +37,13 @@ class Endstop { make_protocol_property("offset", &config_.offset), make_protocol_property("is_active_high", &config_.is_active_high), make_protocol_property("pullup", &config_.pullup), - make_protocol_property("debounce_ms", &config_.debounce_ms))); + make_protocol_property("debounce_ms", &config_.debounce_ms, + [](void* ctx) { static_cast(ctx)->update_config(); }, this))); } private: bool pin_state_ = false; float pos_when_pressed_ = 0.0f; - uint32_t debounce_timer_ = 0; + Timer debounceTimer_; }; #endif \ No newline at end of file diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index ff8199c9..72097f36 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -33,9 +33,12 @@ extern "C" { //default timeout waiting for phase measurement signals #define PH_CURRENT_MEAS_TIMEOUT 2 // [ms] -//TODO clean this up +// Period in [s] static const float current_meas_period = CURRENT_MEAS_PERIOD; + +// Frequency in [Hz] static const int current_meas_hz = CURRENT_MEAS_HZ; + // extern const float elec_rad_per_enc; extern uint32_t _reboot_cookie; extern bool user_config_loaded_; From fb461034980f264c96e32af11e2fb60aa9bf22c4 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 27 Mar 2020 21:22:04 -0400 Subject: [PATCH 300/549] Add doctest, fix drv8301 warning --- Firmware/Drivers/DRV8301/drv8301.c | 1 + Firmware/MotorControl/axis.cpp | 1 + Firmware/MotorControl/gpio_utils.hpp | 46 + Firmware/MotorControl/odrive_main.h | 1 + Firmware/MotorControl/utils.hpp | 46 - Firmware/Tupfile.lua | 4 +- Firmware/communication/communication.cpp | 1 + Firmware/doctest/doctest.h | 5956 ++++++++++++++++++++++ Firmware/doctest/parts/doctest.cpp | 3344 ++++++++++++ Firmware/doctest/parts/doctest_fwd.h | 2604 ++++++++++ 10 files changed, 11956 insertions(+), 48 deletions(-) create mode 100644 Firmware/MotorControl/gpio_utils.hpp create mode 100644 Firmware/doctest/doctest.h create mode 100644 Firmware/doctest/parts/doctest.cpp create mode 100644 Firmware/doctest/parts/doctest_fwd.h diff --git a/Firmware/Drivers/DRV8301/drv8301.c b/Firmware/Drivers/DRV8301/drv8301.c index 0ecb1469..c65cc4d3 100644 --- a/Firmware/Drivers/DRV8301/drv8301.c +++ b/Firmware/Drivers/DRV8301/drv8301.c @@ -45,6 +45,7 @@ // drivers #include "drv8301.h" +#include "utils.hpp" // ************************************************************************** diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 6134a0d0..5635c70c 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -5,6 +5,7 @@ #include "odrive_main.h" #include "utils.hpp" +#include "gpio_utils.hpp" #include "communication/interface_can.hpp" Axis::Axis(int axis_num, diff --git a/Firmware/MotorControl/gpio_utils.hpp b/Firmware/MotorControl/gpio_utils.hpp new file mode 100644 index 00000000..fb70f46e --- /dev/null +++ b/Firmware/MotorControl/gpio_utils.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "gpio.h" +constexpr GPIO_TypeDef* get_gpio_port_by_pin(uint16_t GPIO_pin){ + switch(GPIO_pin){ + case 1: return GPIO_1_GPIO_Port; break; + case 2: return GPIO_2_GPIO_Port; break; + case 3: return GPIO_3_GPIO_Port; break; + case 4: return GPIO_4_GPIO_Port; break; +#ifdef GPIO_5_GPIO_Port + case 5: return GPIO_5_GPIO_Port; break; +#endif +#ifdef GPIO_6_GPIO_Port + case 6: return GPIO_6_GPIO_Port; break; +#endif +#ifdef GPIO_7_GPIO_Port + case 7: return GPIO_7_GPIO_Port; break; +#endif +#ifdef GPIO_8_GPIO_Port + case 8: return GPIO_8_GPIO_Port; break; +#endif + default: return GPIO_1_GPIO_Port; + } +} + +constexpr uint16_t get_gpio_pin_by_pin(uint16_t GPIO_pin){ + switch(GPIO_pin){ + case 1: return GPIO_1_Pin; break; + case 2: return GPIO_2_Pin; break; + case 3: return GPIO_3_Pin; break; + case 4: return GPIO_4_Pin; break; +#ifdef GPIO_5_Pin + case 5: return GPIO_5_Pin; break; +#endif +#ifdef GPIO_6_Pin + case 6: return GPIO_6_Pin; break; +#endif +#ifdef GPIO_7_Pin + case 7: return GPIO_7_Pin; break; +#endif +#ifdef GPIO_8_Pin + case 8: return GPIO_8_Pin; break; +#endif + default: return GPIO_1_Pin; + } +} diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 72097f36..cee9d871 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -131,6 +131,7 @@ inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast(~static_c // ODrive specific includes #include +#include #include #include #include diff --git a/Firmware/MotorControl/utils.hpp b/Firmware/MotorControl/utils.hpp index fdcb73cc..574aa800 100644 --- a/Firmware/MotorControl/utils.hpp +++ b/Firmware/MotorControl/utils.hpp @@ -113,50 +113,4 @@ float our_arm_cos_f32(float x); } #endif - -#include "gpio.h" -constexpr GPIO_TypeDef* get_gpio_port_by_pin(uint16_t GPIO_pin){ - switch(GPIO_pin){ - case 1: return GPIO_1_GPIO_Port; break; - case 2: return GPIO_2_GPIO_Port; break; - case 3: return GPIO_3_GPIO_Port; break; - case 4: return GPIO_4_GPIO_Port; break; -#ifdef GPIO_5_GPIO_Port - case 5: return GPIO_5_GPIO_Port; break; -#endif -#ifdef GPIO_6_GPIO_Port - case 6: return GPIO_6_GPIO_Port; break; -#endif -#ifdef GPIO_7_GPIO_Port - case 7: return GPIO_7_GPIO_Port; break; -#endif -#ifdef GPIO_8_GPIO_Port - case 8: return GPIO_8_GPIO_Port; break; -#endif - default: return GPIO_1_GPIO_Port; - } -} - -constexpr uint16_t get_gpio_pin_by_pin(uint16_t GPIO_pin){ - switch(GPIO_pin){ - case 1: return GPIO_1_Pin; break; - case 2: return GPIO_2_Pin; break; - case 3: return GPIO_3_Pin; break; - case 4: return GPIO_4_Pin; break; -#ifdef GPIO_5_Pin - case 5: return GPIO_5_Pin; break; -#endif -#ifdef GPIO_6_Pin - case 6: return GPIO_6_Pin; break; -#endif -#ifdef GPIO_7_Pin - case 7: return GPIO_7_Pin; break; -#endif -#ifdef GPIO_8_Pin - case 8: return GPIO_8_Pin; break; -#endif - default: return GPIO_1_Pin; - } -} - #endif //__UTILS_H diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 63ca9117..8b66326a 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -185,12 +185,12 @@ build{ 'MotorControl', 'fibre/cpp/include', '.', - "C:/Tools/doctest/doctest" + "doctest" } } if tup.getconfig('DOCTEST') == 'true' then - TEST_INCLUDES = '-I. -I./MotorControl -I./fibre/cpp/include -I./Drivers/DRV8301 -IC:/Tools/doctest/doctest' + TEST_INCLUDES = '-I. -I./MotorControl -I./fibre/cpp/include -I./Drivers/DRV8301 -I./doctest' tup.frule{inputs='Tests/*.cpp', command='g++ -O3 -std=gnu++17 '..TEST_INCLUDES..' %f -o %o', outputs='Tests/test_runner.exe'} tup.frule{inputs='Tests/test_runner.exe', command='%f'} end \ No newline at end of file diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index f90bbbb1..e5f38e1d 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -11,6 +11,7 @@ #include "odrive_main.h" #include "freertos_vars.h" #include "utils.hpp" +#include "gpio_utils.hpp" #include "../build/version.h" // autogenerated based on Git state diff --git a/Firmware/doctest/doctest.h b/Firmware/doctest/doctest.h new file mode 100644 index 00000000..4f3a0e33 --- /dev/null +++ b/Firmware/doctest/doctest.h @@ -0,0 +1,5956 @@ +// ====================================================================== lgtm [cpp/missing-header-guard] +// == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! == +// ====================================================================== +// +// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD +// +// Copyright (c) 2016-2019 Viktor Kirilov +// +// Distributed under the MIT Software License +// See accompanying file LICENSE.txt or copy at +// https://opensource.org/licenses/MIT +// +// The documentation can be found at the library's page: +// https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= +// +// The library is heavily influenced by Catch - https://github.com/catchorg/Catch2 +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt +// +// The concept of subcases (sections in Catch) and expression decomposition are from there. +// Some parts of the code are taken directly: +// - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<> +// - the Approx() helper class for floating point comparison +// - colors in the console +// - breaking into a debugger +// - signal / SEH handling +// - timer +// - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste) +// +// The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/martinmoene/lest/blob/master/LICENSE.txt +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= + +#ifndef DOCTEST_LIBRARY_INCLUDED +#define DOCTEST_LIBRARY_INCLUDED + +// ================================================================================================= +// == VERSION ====================================================================================== +// ================================================================================================= + +#define DOCTEST_VERSION_MAJOR 2 +#define DOCTEST_VERSION_MINOR 3 +#define DOCTEST_VERSION_PATCH 7 +#define DOCTEST_VERSION_STR "2.3.7" + +#define DOCTEST_VERSION \ + (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) + +// ================================================================================================= +// == COMPILER VERSION ============================================================================= +// ================================================================================================= + +// ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect + +#define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH)) + +// GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl... +#if defined(_MSC_VER) && defined(_MSC_FULL_VER) +#if _MSC_VER == _MSC_FULL_VER / 10000 +#define DOCTEST_MSVC DOCTEST_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000) +#else // MSVC +#define DOCTEST_MSVC \ + DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000) +#endif // MSVC +#endif // MSVC +#if defined(__clang__) && defined(__clang_minor__) +#define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__) +#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && \ + !defined(__INTEL_COMPILER) +#define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#endif // GCC + +#ifndef DOCTEST_MSVC +#define DOCTEST_MSVC 0 +#endif // DOCTEST_MSVC +#ifndef DOCTEST_CLANG +#define DOCTEST_CLANG 0 +#endif // DOCTEST_CLANG +#ifndef DOCTEST_GCC +#define DOCTEST_GCC 0 +#endif // DOCTEST_GCC + +// ================================================================================================= +// == COMPILER WARNINGS HELPERS ==================================================================== +// ================================================================================================= + +#if DOCTEST_CLANG +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push") +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop") +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING(w) +#else // DOCTEST_CLANG +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_CLANG + +#if DOCTEST_GCC +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push") +#define DOCTEST_GCC_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(GCC diagnostic ignored w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop") +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING(w) +#else // DOCTEST_GCC +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH +#define DOCTEST_GCC_SUPPRESS_WARNING(w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_GCC + +#if DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push)) +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(w) +#else // DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_MSVC + +// ================================================================================================= +// == COMPILER WARNINGS ============================================================================ +// ================================================================================================= + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") +DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") +DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration +DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression +DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated +DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant +DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding +DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe +// static analysis +DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' +DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable +DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... +DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtr... +DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' + +// 4548 - expression before comma has no effect; expected expression with side - effect +// 4265 - class has virtual functions, but destructor is not virtual +// 4986 - exception specification does not match previous declaration +// 4350 - behavior change: 'member1' called instead of 'member2' +// 4668 - 'x' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' +// 4365 - conversion from 'int' to 'unsigned long', signed/unsigned mismatch +// 4774 - format string expected in argument 'x' is not a string literal +// 4820 - padding in structs + +// only 4 should be disabled globally: +// - 4514 # unreferenced inline function has been removed +// - 4571 # SEH related +// - 4710 # function not inlined +// - 4711 # function 'x' selected for automatic inline expansion + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ + DOCTEST_MSVC_SUPPRESS_WARNING(4548) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4265) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4986) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4350) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4668) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4365) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4774) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4623) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5039) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5105) + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP + +// ================================================================================================= +// == FEATURE DETECTION ============================================================================ +// ================================================================================================= + +// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support +// MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx +// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html +// MSVC version table: +// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering +// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019) +// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017) +// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) +// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) +// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) +// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) +// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) +// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) + +#if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH) +#define DOCTEST_CONFIG_WINDOWS_SEH +#endif // MSVC +#if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH) +#undef DOCTEST_CONFIG_WINDOWS_SEH +#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH + +#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \ + !defined(__EMSCRIPTEN__) +#define DOCTEST_CONFIG_POSIX_SIGNALS +#endif // _WIN32 +#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS) +#undef DOCTEST_CONFIG_POSIX_SIGNALS +#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // no exceptions +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS) +#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT) +#define DOCTEST_CONFIG_IMPLEMENT +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +#if defined(_WIN32) || defined(__CYGWIN__) +#if DOCTEST_MSVC +#define DOCTEST_SYMBOL_EXPORT __declspec(dllexport) +#define DOCTEST_SYMBOL_IMPORT __declspec(dllimport) +#else // MSVC +#define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport)) +#define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport)) +#endif // MSVC +#else // _WIN32 +#define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default"))) +#define DOCTEST_SYMBOL_IMPORT +#endif // _WIN32 + +#ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#ifdef DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT +#else // DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT +#endif // DOCTEST_CONFIG_IMPLEMENT +#else // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#define DOCTEST_INTERFACE +#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL + +#define DOCTEST_EMPTY + +#if DOCTEST_MSVC +#define DOCTEST_NOINLINE __declspec(noinline) +#define DOCTEST_UNUSED +#define DOCTEST_ALIGNMENT(x) +#else // MSVC +#define DOCTEST_NOINLINE __attribute__((noinline)) +#define DOCTEST_UNUSED __attribute__((unused)) +#define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x))) +#endif // MSVC + +// ================================================================================================= +// == FEATURE DETECTION END ======================================================================== +// ================================================================================================= + +// internal macros for string concatenation and anonymous variable name generation +#define DOCTEST_CAT_IMPL(s1, s2) s1##s2 +#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2) +#ifdef __COUNTER__ // not standard and may be missing for some compilers +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__) +#else // __COUNTER__ +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__) +#endif // __COUNTER__ + +#define DOCTEST_TOSTR(x) #x + +#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x& +#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x +#endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE + +// not using __APPLE__ because... this is how Catch does it +#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED +#define DOCTEST_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define DOCTEST_PLATFORM_IPHONE +#elif defined(_WIN32) +#define DOCTEST_PLATFORM_WINDOWS +#else // DOCTEST_PLATFORM +#define DOCTEST_PLATFORM_LINUX +#endif // DOCTEST_PLATFORM + +#define DOCTEST_GLOBAL_NO_WARNINGS(var) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-variable") \ + static int var DOCTEST_UNUSED // NOLINT(fuchsia-statically-constructed-objects,cert-err58-cpp) +#define DOCTEST_GLOBAL_NO_WARNINGS_END() DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#ifndef DOCTEST_BREAK_INTO_DEBUGGER +// should probably take a look at https://github.com/scottt/debugbreak +#ifdef DOCTEST_PLATFORM_MAC +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) +#elif DOCTEST_MSVC +#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() +#elif defined(__MINGW32__) +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wredundant-decls") +extern "C" __declspec(dllimport) void __stdcall DebugBreak(); +DOCTEST_GCC_SUPPRESS_WARNING_POP +#define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak() +#else // linux +#define DOCTEST_BREAK_INTO_DEBUGGER() ((void)0) +#endif // linux +#endif // DOCTEST_BREAK_INTO_DEBUGGER + +// this is kept here for backwards compatibility since the config option was changed +#ifdef DOCTEST_CONFIG_USE_IOSFWD +#define DOCTEST_CONFIG_USE_STD_HEADERS +#endif // DOCTEST_CONFIG_USE_IOSFWD + +#ifdef DOCTEST_CONFIG_USE_STD_HEADERS +#include +#include +#include +#else // DOCTEST_CONFIG_USE_STD_HEADERS + +#if DOCTEST_CLANG +// to detect if libc++ is being used with clang (the _LIBCPP_VERSION identifier) +#include +#endif // clang + +#ifdef _LIBCPP_VERSION +#define DOCTEST_STD_NAMESPACE_BEGIN _LIBCPP_BEGIN_NAMESPACE_STD +#define DOCTEST_STD_NAMESPACE_END _LIBCPP_END_NAMESPACE_STD +#else // _LIBCPP_VERSION +#define DOCTEST_STD_NAMESPACE_BEGIN namespace std { +#define DOCTEST_STD_NAMESPACE_END } +#endif // _LIBCPP_VERSION + +// Forward declaring 'X' in namespace std is not permitted by the C++ Standard. +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643) + +DOCTEST_STD_NAMESPACE_BEGIN // NOLINT (cert-dcl58-cpp) +typedef decltype(nullptr) nullptr_t; +template +struct char_traits; +template <> +struct char_traits; +template +class basic_ostream; +typedef basic_ostream> ostream; +template +class tuple; +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +template +class allocator; +template +class basic_string; +using string = basic_string, allocator>; +#endif // VS 2019 +DOCTEST_STD_NAMESPACE_END + +DOCTEST_MSVC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_USE_STD_HEADERS + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#include +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + +namespace doctest { + +DOCTEST_INTERFACE extern bool is_running_in_test; + +// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length +// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for: +// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128) +// - if small - capacity left before going on the heap - using the lowest 5 bits +// - if small - 2 bits are left unused - the second and third highest ones +// - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator) +// and the "is small" bit remains "0" ("as well as the capacity left") so its OK +// Idea taken from this lecture about the string implementation of facebook/folly - fbstring +// https://www.youtube.com/watch?v=kPR8h4-qZdk +// TODO: +// - optimizations - like not deleting memory unnecessarily in operator= and etc. +// - resize/reserve/clear +// - substr +// - replace +// - back/front +// - iterator stuff +// - find & friends +// - push_back/pop_back +// - assign/insert/erase +// - relational operators as free functions - taking const char* as one of the params +class DOCTEST_INTERFACE String +{ + static const unsigned len = 24; //!OCLINT avoid private static members + static const unsigned last = len - 1; //!OCLINT avoid private static members + + struct view // len should be more than sizeof(view) - because of the final byte for flags + { + char* ptr; + unsigned size; + unsigned capacity; + }; + + union + { + char buf[len]; + view data; + }; + + bool isOnStack() const { return (buf[last] & 128) == 0; } + void setOnHeap(); + void setLast(unsigned in = last); + + void copy(const String& other); + +public: + String(); + ~String(); + + // cppcheck-suppress noExplicitConstructor + String(const char* in); + String(const char* in, unsigned in_size); + + String(const String& other); + String& operator=(const String& other); + + String& operator+=(const String& other); + String operator+(const String& other) const; + + String(String&& other); + String& operator=(String&& other); + + char operator[](unsigned i) const; + char& operator[](unsigned i); + + // the only functions I'm willing to leave in the interface - available for inlining + const char* c_str() const { return const_cast(this)->c_str(); } // NOLINT + char* c_str() { + if(isOnStack()) + return reinterpret_cast(buf); + return data.ptr; + } + + unsigned size() const; + unsigned capacity() const; + + int compare(const char* other, bool no_case = false) const; + int compare(const String& other, bool no_case = false) const; +}; + +DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs); + +DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in); + +namespace Color { + enum Enum + { + None = 0, + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White + }; + + DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code); +} // namespace Color + +namespace assertType { + enum Enum + { + // macro traits + + is_warn = 1, + is_check = 2 * is_warn, + is_require = 2 * is_check, + + is_normal = 2 * is_require, + is_throws = 2 * is_normal, + is_throws_as = 2 * is_throws, + is_throws_with = 2 * is_throws_as, + is_nothrow = 2 * is_throws_with, + + is_false = 2 * is_nothrow, + is_unary = 2 * is_false, // not checked anywhere - used just to distinguish the types + + is_eq = 2 * is_unary, + is_ne = 2 * is_eq, + + is_lt = 2 * is_ne, + is_gt = 2 * is_lt, + + is_ge = 2 * is_gt, + is_le = 2 * is_ge, + + // macro types + + DT_WARN = is_normal | is_warn, + DT_CHECK = is_normal | is_check, + DT_REQUIRE = is_normal | is_require, + + DT_WARN_FALSE = is_normal | is_false | is_warn, + DT_CHECK_FALSE = is_normal | is_false | is_check, + DT_REQUIRE_FALSE = is_normal | is_false | is_require, + + DT_WARN_THROWS = is_throws | is_warn, + DT_CHECK_THROWS = is_throws | is_check, + DT_REQUIRE_THROWS = is_throws | is_require, + + DT_WARN_THROWS_AS = is_throws_as | is_warn, + DT_CHECK_THROWS_AS = is_throws_as | is_check, + DT_REQUIRE_THROWS_AS = is_throws_as | is_require, + + DT_WARN_THROWS_WITH = is_throws_with | is_warn, + DT_CHECK_THROWS_WITH = is_throws_with | is_check, + DT_REQUIRE_THROWS_WITH = is_throws_with | is_require, + + DT_WARN_THROWS_WITH_AS = is_throws_with | is_throws_as | is_warn, + DT_CHECK_THROWS_WITH_AS = is_throws_with | is_throws_as | is_check, + DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require, + + DT_WARN_NOTHROW = is_nothrow | is_warn, + DT_CHECK_NOTHROW = is_nothrow | is_check, + DT_REQUIRE_NOTHROW = is_nothrow | is_require, + + DT_WARN_EQ = is_normal | is_eq | is_warn, + DT_CHECK_EQ = is_normal | is_eq | is_check, + DT_REQUIRE_EQ = is_normal | is_eq | is_require, + + DT_WARN_NE = is_normal | is_ne | is_warn, + DT_CHECK_NE = is_normal | is_ne | is_check, + DT_REQUIRE_NE = is_normal | is_ne | is_require, + + DT_WARN_GT = is_normal | is_gt | is_warn, + DT_CHECK_GT = is_normal | is_gt | is_check, + DT_REQUIRE_GT = is_normal | is_gt | is_require, + + DT_WARN_LT = is_normal | is_lt | is_warn, + DT_CHECK_LT = is_normal | is_lt | is_check, + DT_REQUIRE_LT = is_normal | is_lt | is_require, + + DT_WARN_GE = is_normal | is_ge | is_warn, + DT_CHECK_GE = is_normal | is_ge | is_check, + DT_REQUIRE_GE = is_normal | is_ge | is_require, + + DT_WARN_LE = is_normal | is_le | is_warn, + DT_CHECK_LE = is_normal | is_le | is_check, + DT_REQUIRE_LE = is_normal | is_le | is_require, + + DT_WARN_UNARY = is_normal | is_unary | is_warn, + DT_CHECK_UNARY = is_normal | is_unary | is_check, + DT_REQUIRE_UNARY = is_normal | is_unary | is_require, + + DT_WARN_UNARY_FALSE = is_normal | is_false | is_unary | is_warn, + DT_CHECK_UNARY_FALSE = is_normal | is_false | is_unary | is_check, + DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require, + }; +} // namespace assertType + +DOCTEST_INTERFACE const char* assertString(assertType::Enum at); +DOCTEST_INTERFACE const char* failureString(assertType::Enum at); +DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file); + +struct DOCTEST_INTERFACE TestCaseData +{ + const char* m_file; // the file in which the test was registered + unsigned m_line; // the line where the test was registered + const char* m_name; // name of the test case + const char* m_test_suite; // the test suite in which the test was added + const char* m_description; + bool m_skip; + bool m_may_fail; + bool m_should_fail; + int m_expected_failures; + double m_timeout; +}; + +struct DOCTEST_INTERFACE AssertData +{ + // common - for all asserts + const TestCaseData* m_test_case; + assertType::Enum m_at; + const char* m_file; + int m_line; + const char* m_expr; + bool m_failed; + + // exception-related - for all asserts + bool m_threw; + String m_exception; + + // for normal asserts + String m_decomp; + + // for specific exception-related asserts + bool m_threw_as; + const char* m_exception_type; + const char* m_exception_string; +}; + +struct DOCTEST_INTERFACE MessageData +{ + String m_string; + const char* m_file; + int m_line; + assertType::Enum m_severity; +}; + +struct DOCTEST_INTERFACE SubcaseSignature +{ + String m_name; + const char* m_file; + int m_line; + + bool operator<(const SubcaseSignature& other) const; +}; + +struct DOCTEST_INTERFACE IContextScope +{ + IContextScope(); + virtual ~IContextScope(); + virtual void stringify(std::ostream*) const = 0; +}; + +struct ContextOptions //!OCLINT too many fields +{ + std::ostream* cout; // stdout stream - std::cout by default + std::ostream* cerr; // stderr stream - std::cerr by default + String binary_name; // the test binary name + + // == parameters from the command line + String out; // output filename + String order_by; // how tests should be ordered + unsigned rand_seed; // the seed for rand ordering + + unsigned first; // the first (matching) test to be executed + unsigned last; // the last (matching) test to be executed + + int abort_after; // stop tests after this many failed assertions + int subcase_filter_levels; // apply the subcase filters for the first N levels + + bool success; // include successful assertions in output + bool case_sensitive; // if filtering should be case sensitive + bool exit; // if the program should be exited after the tests are ran/whatever + bool duration; // print the time duration of each test case + bool no_throw; // to skip exceptions-related assertion macros + bool no_exitcode; // if the framework should return 0 as the exitcode + bool no_run; // to not run the tests at all (can be done with an "*" exclude) + bool no_version; // to not print the version of the framework + bool no_colors; // if output to the console should be colorized + bool force_colors; // forces the use of colors even when a tty cannot be detected + bool no_breaks; // to not break into the debugger + bool no_skip; // don't skip test cases which are marked to be skipped + bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x): + bool no_path_in_filenames; // if the path to files should be removed from the output + bool no_line_numbers; // if source code line numbers should be omitted from the output + bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!! + + bool help; // to print the help + bool version; // to print the version + bool count; // if only the count of matching tests is to be retrieved + bool list_test_cases; // to list all tests matching the filters + bool list_test_suites; // to list all suites matching the filters + bool list_reporters; // lists all registered reporters +}; + +namespace detail { +#if defined(DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || defined(DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS) + template + struct enable_if + {}; + + template + struct enable_if + { typedef TYPE type; }; +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format off + template struct remove_reference { typedef T type; }; + template struct remove_reference { typedef T type; }; + template struct remove_reference { typedef T type; }; + + template struct remove_const { typedef T type; }; + template struct remove_const { typedef T type; }; + // clang-format on + + template + struct deferred_false + // cppcheck-suppress unusedStructMember + { static const bool value = false; }; + + namespace has_insertion_operator_impl { + typedef char no; + typedef char yes[2]; + + struct any_t + { + template + // cppcheck-suppress noExplicitConstructor + any_t(const DOCTEST_REF_WRAP(T)); + }; + + yes& testStreamable(std::ostream&); + no testStreamable(no); + + no operator<<(const std::ostream&, const any_t&); + + template + struct has_insertion_operator + { + static std::ostream& s; + static const DOCTEST_REF_WRAP(T) t; + static const bool value = sizeof(decltype(testStreamable(s << t))) == sizeof(yes); + }; + } // namespace has_insertion_operator_impl + + template + struct has_insertion_operator : has_insertion_operator_impl::has_insertion_operator + {}; + + DOCTEST_INTERFACE void my_memcpy(void* dest, const void* src, unsigned num); + + DOCTEST_INTERFACE std::ostream* getTlsOss(); // returns a thread-local ostringstream + DOCTEST_INTERFACE String getTlsOssResult(); + + template + struct StringMakerBase + { + template + static String convert(const DOCTEST_REF_WRAP(T)) { + return "{?}"; + } + }; + + template <> + struct StringMakerBase + { + template + static String convert(const DOCTEST_REF_WRAP(T) in) { + *getTlsOss() << in; + return getTlsOssResult(); + } + }; + + DOCTEST_INTERFACE String rawMemoryToString(const void* object, unsigned size); + + template + String rawMemoryToString(const DOCTEST_REF_WRAP(T) object) { + return rawMemoryToString(&object, sizeof(object)); + } + + template + const char* type_to_string() { + return "<>"; + } +} // namespace detail + +template +struct StringMaker : public detail::StringMakerBase::value> +{}; + +template +struct StringMaker +{ + template + static String convert(U* p) { + if(p) + return detail::rawMemoryToString(p); + return "NULL"; + } +}; + +template +struct StringMaker +{ + static String convert(R C::*p) { + if(p) + return detail::rawMemoryToString(p); + return "NULL"; + } +}; + +template +String toString(const DOCTEST_REF_WRAP(T) value) { + return StringMaker::convert(value); +} + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +DOCTEST_INTERFACE String toString(char* in); +DOCTEST_INTERFACE String toString(const char* in); +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +DOCTEST_INTERFACE String toString(bool in); +DOCTEST_INTERFACE String toString(float in); +DOCTEST_INTERFACE String toString(double in); +DOCTEST_INTERFACE String toString(double long in); + +DOCTEST_INTERFACE String toString(char in); +DOCTEST_INTERFACE String toString(char signed in); +DOCTEST_INTERFACE String toString(char unsigned in); +DOCTEST_INTERFACE String toString(int short in); +DOCTEST_INTERFACE String toString(int short unsigned in); +DOCTEST_INTERFACE String toString(int in); +DOCTEST_INTERFACE String toString(int unsigned in); +DOCTEST_INTERFACE String toString(int long in); +DOCTEST_INTERFACE String toString(int long unsigned in); +DOCTEST_INTERFACE String toString(int long long in); +DOCTEST_INTERFACE String toString(int long long unsigned in); +DOCTEST_INTERFACE String toString(std::nullptr_t in); + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +DOCTEST_INTERFACE String toString(const std::string& in); +#endif // VS 2019 + +class DOCTEST_INTERFACE Approx +{ +public: + explicit Approx(double value); + + Approx operator()(double value) const; + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + explicit Approx(const T& value, + typename detail::enable_if::value>::type* = + static_cast(nullptr)) { + *this = Approx(static_cast(value)); + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + Approx& epsilon(double newEpsilon); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + typename detail::enable_if::value, Approx&>::type epsilon( + const T& newEpsilon) { + m_epsilon = static_cast(newEpsilon); + return *this; + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + Approx& scale(double newScale); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + typename detail::enable_if::value, Approx&>::type scale( + const T& newScale) { + m_scale = static_cast(newScale); + return *this; + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format off + DOCTEST_INTERFACE friend bool operator==(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator==(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator!=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator!=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator<=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator<=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator>=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator>=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator< (double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator< (const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs); + + DOCTEST_INTERFACE friend String toString(const Approx& in); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#define DOCTEST_APPROX_PREFIX \ + template friend typename detail::enable_if::value, bool>::type + + DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(double(lhs), rhs); } + DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); } + DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); } + DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); } + DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) && lhs != rhs; } +#undef DOCTEST_APPROX_PREFIX +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format on + +private: + double m_epsilon; + double m_scale; + double m_value; +}; + +DOCTEST_INTERFACE String toString(const Approx& in); + +DOCTEST_INTERFACE const ContextOptions* getContextOptions(); + +#if !defined(DOCTEST_CONFIG_DISABLE) + +namespace detail { + // clang-format off +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + template struct decay_array { typedef T type; }; + template struct decay_array { typedef T* type; }; + template struct decay_array { typedef T* type; }; + + template struct not_char_pointer { enum { value = 1 }; }; + template<> struct not_char_pointer { enum { value = 0 }; }; + template<> struct not_char_pointer { enum { value = 0 }; }; + + template struct can_use_op : public not_char_pointer::type> {}; +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + // clang-format on + + struct DOCTEST_INTERFACE TestFailureException + { + }; + + DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at); + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + [[noreturn]] +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + DOCTEST_INTERFACE void throwException(); + + struct DOCTEST_INTERFACE Subcase + { + SubcaseSignature m_signature; + bool m_entered = false; + + Subcase(const String& name, const char* file, int line); + ~Subcase(); + + operator bool() const; + }; + + template + String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, + const DOCTEST_REF_WRAP(R) rhs) { + return toString(lhs) + op + toString(rhs); + } + +#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \ + template \ + DOCTEST_NOINLINE Result operator op(const DOCTEST_REF_WRAP(R) rhs) { \ + bool res = op_macro(lhs, rhs); \ + if(m_at & assertType::is_false) \ + res = !res; \ + if(!res || doctest::getContextOptions()->success) \ + return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \ + return Result(res); \ + } + + // more checks could be added - like in Catch: + // https://github.com/catchorg/Catch2/pull/1480/files + // https://github.com/catchorg/Catch2/pull/1481/files +#define DOCTEST_FORBIT_EXPRESSION(rt, op) \ + template \ + rt& operator op(const R&) { \ + static_assert(deferred_false::value, \ + "Expression Too Complex Please Rewrite As Binary Comparison!"); \ + return *this; \ + } + + struct DOCTEST_INTERFACE Result + { + bool m_passed; + String m_decomp; + + Result(bool passed, const String& decomposition = String()); + + // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence + DOCTEST_FORBIT_EXPRESSION(Result, &) + DOCTEST_FORBIT_EXPRESSION(Result, ^) + DOCTEST_FORBIT_EXPRESSION(Result, |) + DOCTEST_FORBIT_EXPRESSION(Result, &&) + DOCTEST_FORBIT_EXPRESSION(Result, ||) + DOCTEST_FORBIT_EXPRESSION(Result, ==) + DOCTEST_FORBIT_EXPRESSION(Result, !=) + DOCTEST_FORBIT_EXPRESSION(Result, <) + DOCTEST_FORBIT_EXPRESSION(Result, >) + DOCTEST_FORBIT_EXPRESSION(Result, <=) + DOCTEST_FORBIT_EXPRESSION(Result, >=) + DOCTEST_FORBIT_EXPRESSION(Result, =) + DOCTEST_FORBIT_EXPRESSION(Result, +=) + DOCTEST_FORBIT_EXPRESSION(Result, -=) + DOCTEST_FORBIT_EXPRESSION(Result, *=) + DOCTEST_FORBIT_EXPRESSION(Result, /=) + DOCTEST_FORBIT_EXPRESSION(Result, %=) + DOCTEST_FORBIT_EXPRESSION(Result, <<=) + DOCTEST_FORBIT_EXPRESSION(Result, >>=) + DOCTEST_FORBIT_EXPRESSION(Result, &=) + DOCTEST_FORBIT_EXPRESSION(Result, ^=) + DOCTEST_FORBIT_EXPRESSION(Result, |=) + }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH + DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") + DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-compare") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wdouble-promotion") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wconversion") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal") + + DOCTEST_GCC_SUPPRESS_WARNING_PUSH + DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") + DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-compare") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wdouble-promotion") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal") + + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH + // https://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389 + DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch + DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch + DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch + //DOCTEST_MSVC_SUPPRESS_WARNING(4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + // clang-format off +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE bool +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE typename enable_if::value || can_use_op::value, bool>::type + inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } + inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } + inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); } + inline bool gt(const char* lhs, const char* rhs) { return String(lhs) > String(rhs); } + inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); } + inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + // clang-format on + +#define DOCTEST_RELATIONAL_OP(name, op) \ + template \ + DOCTEST_COMPARISON_RETURN_TYPE name(const DOCTEST_REF_WRAP(L) lhs, \ + const DOCTEST_REF_WRAP(R) rhs) { \ + return lhs op rhs; \ + } + + DOCTEST_RELATIONAL_OP(eq, ==) + DOCTEST_RELATIONAL_OP(ne, !=) + DOCTEST_RELATIONAL_OP(lt, <) + DOCTEST_RELATIONAL_OP(gt, >) + DOCTEST_RELATIONAL_OP(le, <=) + DOCTEST_RELATIONAL_OP(ge, >=) + +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) l == r +#define DOCTEST_CMP_NE(l, r) l != r +#define DOCTEST_CMP_GT(l, r) l > r +#define DOCTEST_CMP_LT(l, r) l < r +#define DOCTEST_CMP_GE(l, r) l >= r +#define DOCTEST_CMP_LE(l, r) l <= r +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) eq(l, r) +#define DOCTEST_CMP_NE(l, r) ne(l, r) +#define DOCTEST_CMP_GT(l, r) gt(l, r) +#define DOCTEST_CMP_LT(l, r) lt(l, r) +#define DOCTEST_CMP_GE(l, r) ge(l, r) +#define DOCTEST_CMP_LE(l, r) le(l, r) +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + + template + // cppcheck-suppress copyCtorAndEqOperator + struct Expression_lhs + { + L lhs; + assertType::Enum m_at; + + explicit Expression_lhs(L in, assertType::Enum at) + : lhs(in) + , m_at(at) {} + + DOCTEST_NOINLINE operator Result() { + bool res = !!lhs; + if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional + res = !res; + + if(!res || getContextOptions()->success) + return Result(res, toString(lhs)); + return Result(res); + } + + // clang-format off + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>, " > ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<, " < ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional + // clang-format on + + // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &&) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ||) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, =) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, +=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, -=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, *=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, /=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, %=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |=) + // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the + // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression... + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>) + }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + struct DOCTEST_INTERFACE ExpressionDecomposer + { + assertType::Enum m_at; + + ExpressionDecomposer(assertType::Enum at); + + // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table) + // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now... + // https://github.com/catchorg/Catch2/issues/870 + // https://github.com/catchorg/Catch2/issues/565 + template + Expression_lhs operator<<(const DOCTEST_REF_WRAP(L) operand) { + return Expression_lhs(operand, m_at); + } + }; + + struct DOCTEST_INTERFACE TestSuite + { + const char* m_test_suite; + const char* m_description; + bool m_skip; + bool m_may_fail; + bool m_should_fail; + int m_expected_failures; + double m_timeout; + + TestSuite& operator*(const char* in); + + template + TestSuite& operator*(const T& in) { + in.fill(*this); + return *this; + } + }; + + typedef void (*funcType)(); + + struct DOCTEST_INTERFACE TestCase : public TestCaseData + { + funcType m_test; // a function pointer to the test case + + const char* m_type; // for templated test cases - gets appended to the real name + int m_template_id; // an ID used to distinguish between the different versions of a templated test case + String m_full_name; // contains the name (only for templated test cases!) + the template type + + TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, + const char* type = "", int template_id = -1); + + TestCase(const TestCase& other); + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function + TestCase& operator=(const TestCase& other); + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + TestCase& operator*(const char* in); + + template + TestCase& operator*(const T& in) { + in.fill(*this); + return *this; + } + + bool operator<(const TestCase& other) const; + }; + + // forward declarations of functions used by the macros + DOCTEST_INTERFACE int regTest(const TestCase& tc); + DOCTEST_INTERFACE int setTestSuite(const TestSuite& ts); + DOCTEST_INTERFACE bool isDebuggerActive(); + + template + int instantiationHelper(const T&) { return 0; } + + namespace binaryAssertComparison { + enum Enum + { + eq = 0, + ne, + gt, + lt, + ge, + le + }; + } // namespace binaryAssertComparison + + // clang-format off + template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L), const DOCTEST_REF_WRAP(R) ) const { return false; } }; + +#define DOCTEST_BINARY_RELATIONAL_OP(n, op) \ + template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } }; + // clang-format on + + DOCTEST_BINARY_RELATIONAL_OP(0, eq) + DOCTEST_BINARY_RELATIONAL_OP(1, ne) + DOCTEST_BINARY_RELATIONAL_OP(2, gt) + DOCTEST_BINARY_RELATIONAL_OP(3, lt) + DOCTEST_BINARY_RELATIONAL_OP(4, ge) + DOCTEST_BINARY_RELATIONAL_OP(5, le) + + struct DOCTEST_INTERFACE ResultBuilder : public AssertData + { + ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type = "", const char* exception_string = ""); + + void setResult(const Result& res); + + template + DOCTEST_NOINLINE void binary_assert(const DOCTEST_REF_WRAP(L) lhs, + const DOCTEST_REF_WRAP(R) rhs) { + m_failed = !RelationalComparator()(lhs, rhs); + if(m_failed || getContextOptions()->success) + m_decomp = stringifyBinaryExpr(lhs, ", ", rhs); + } + + template + DOCTEST_NOINLINE void unary_assert(const DOCTEST_REF_WRAP(L) val) { + m_failed = !val; + + if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional + m_failed = !m_failed; + + if(m_failed || getContextOptions()->success) + m_decomp = toString(val); + } + + void translateException(); + + bool log(); + void react() const; + }; + + namespace assertAction { + enum Enum + { + nothing = 0, + dbgbreak = 1, + shouldthrow = 2 + }; + } // namespace assertAction + + DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad); + + DOCTEST_INTERFACE void decomp_assert(assertType::Enum at, const char* file, int line, + const char* expr, Result result); + +#define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \ + do { \ + if(!is_running_in_test) { \ + if(failed) { \ + ResultBuilder rb(at, file, line, expr); \ + rb.m_failed = failed; \ + rb.m_decomp = decomp; \ + failed_out_of_a_testing_context(rb); \ + if(isDebuggerActive() && !getContextOptions()->no_breaks) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + if(checkIfShouldThrow(at)) \ + throwException(); \ + } \ + return; \ + } \ + } while(false) + +#define DOCTEST_ASSERT_IN_TESTS(decomp) \ + ResultBuilder rb(at, file, line, expr); \ + rb.m_failed = failed; \ + if(rb.m_failed || getContextOptions()->success) \ + rb.m_decomp = decomp; \ + if(rb.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + if(rb.m_failed && checkIfShouldThrow(at)) \ + throwException() + + template + DOCTEST_NOINLINE void binary_assert(assertType::Enum at, const char* file, int line, + const char* expr, const DOCTEST_REF_WRAP(L) lhs, + const DOCTEST_REF_WRAP(R) rhs) { + bool failed = !RelationalComparator()(lhs, rhs); + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + } + + template + DOCTEST_NOINLINE void unary_assert(assertType::Enum at, const char* file, int line, + const char* expr, const DOCTEST_REF_WRAP(L) val) { + bool failed = !val; + + if(at & assertType::is_false) //!OCLINT bitwise operator in conditional + failed = !failed; + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(toString(val)); + DOCTEST_ASSERT_IN_TESTS(toString(val)); + } + + struct DOCTEST_INTERFACE IExceptionTranslator + { + IExceptionTranslator(); + virtual ~IExceptionTranslator(); + virtual bool translate(String&) const = 0; + }; + + template + class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class + { + public: + explicit ExceptionTranslator(String (*translateFunction)(T)) + : m_translateFunction(translateFunction) {} + + bool translate(String& res) const override { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + try { + throw; // lgtm [cpp/rethrow-no-exception] + // cppcheck-suppress catchExceptionByValue + } catch(T ex) { // NOLINT + res = m_translateFunction(ex); //!OCLINT parameter reassignment + return true; + } catch(...) {} //!OCLINT - empty catch statement +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + ((void)res); // to silence -Wunused-parameter + return false; + } + + private: + String (*m_translateFunction)(T); + }; + + DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et); + + template + struct StringStreamBase + { + template + static void convert(std::ostream* s, const T& in) { + *s << toString(in); + } + + // always treat char* as a string in this context - no matter + // if DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING is defined + static void convert(std::ostream* s, const char* in) { *s << String(in); } + }; + + template <> + struct StringStreamBase + { + template + static void convert(std::ostream* s, const T& in) { + *s << in; + } + }; + + template + struct StringStream : public StringStreamBase::value> + {}; + + template + void toStream(std::ostream* s, const T& value) { + StringStream::convert(s, value); + } + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + DOCTEST_INTERFACE void toStream(std::ostream* s, char* in); + DOCTEST_INTERFACE void toStream(std::ostream* s, const char* in); +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + DOCTEST_INTERFACE void toStream(std::ostream* s, bool in); + DOCTEST_INTERFACE void toStream(std::ostream* s, float in); + DOCTEST_INTERFACE void toStream(std::ostream* s, double in); + DOCTEST_INTERFACE void toStream(std::ostream* s, double long in); + + DOCTEST_INTERFACE void toStream(std::ostream* s, char in); + DOCTEST_INTERFACE void toStream(std::ostream* s, char signed in); + DOCTEST_INTERFACE void toStream(std::ostream* s, char unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int short in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int short unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in); + + // ContextScope base class used to allow implementing methods of ContextScope + // that don't depend on the template parameter in doctest.cpp. + class DOCTEST_INTERFACE ContextScopeBase : public IContextScope { + protected: + ContextScopeBase(); + + void destroy(); + }; + + template class ContextScope : public ContextScopeBase + { + const L &lambda_; + + public: + explicit ContextScope(const L &lambda) : lambda_(lambda) {} + + ContextScope(ContextScope &&other) : lambda_(other.lambda_) {} + + void stringify(std::ostream* s) const override { lambda_(s); } + + ~ContextScope() override { destroy(); } + }; + + struct DOCTEST_INTERFACE MessageBuilder : public MessageData + { + std::ostream* m_stream; + + MessageBuilder(const char* file, int line, assertType::Enum severity); + MessageBuilder() = delete; + ~MessageBuilder(); + + template + MessageBuilder& operator<<(const T& in) { + toStream(m_stream, in); + return *this; + } + + bool log(); + void react(); + }; + + template + ContextScope MakeContextScope(const L &lambda) { + return ContextScope(lambda); + } +} // namespace detail + +#define DOCTEST_DEFINE_DECORATOR(name, type, def) \ + struct name \ + { \ + type data; \ + name(type in = def) \ + : data(in) {} \ + void fill(detail::TestCase& state) const { state.DOCTEST_CAT(m_, name) = data; } \ + void fill(detail::TestSuite& state) const { state.DOCTEST_CAT(m_, name) = data; } \ + } + +DOCTEST_DEFINE_DECORATOR(test_suite, const char*, ""); +DOCTEST_DEFINE_DECORATOR(description, const char*, ""); +DOCTEST_DEFINE_DECORATOR(skip, bool, true); +DOCTEST_DEFINE_DECORATOR(timeout, double, 0); +DOCTEST_DEFINE_DECORATOR(may_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(should_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0); + +template +int registerExceptionTranslator(String (*translateFunction)(T)) { + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") + static detail::ExceptionTranslator exceptionTranslator(translateFunction); + DOCTEST_CLANG_SUPPRESS_WARNING_POP + detail::registerExceptionTranslatorImpl(&exceptionTranslator); + return 0; +} + +} // namespace doctest + +// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro +// introduces an anonymous namespace in which getCurrentTestSuite gets overridden +namespace doctest_detail_test_suite_ns { +DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite(); +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +#else // DOCTEST_CONFIG_DISABLE +template +int registerExceptionTranslator(String (*)(T)) { + return 0; +} +#endif // DOCTEST_CONFIG_DISABLE + +namespace detail { + typedef void (*assert_handler)(const AssertData&); + struct ContextState; +} // namespace detail + +class DOCTEST_INTERFACE Context +{ + detail::ContextState* p; + + void parseArgs(int argc, const char* const* argv, bool withDefaults = false); + +public: + explicit Context(int argc = 0, const char* const* argv = nullptr); + + ~Context(); + + void applyCommandLine(int argc, const char* const* argv); + + void addFilter(const char* filter, const char* value); + void clearFilters(); + void setOption(const char* option, int value); + void setOption(const char* option, const char* value); + + bool shouldExit(); + + void setAsDefaultForAssertsOutOfTestCases(); + + void setAssertHandler(detail::assert_handler ah); + + int run(); +}; + +namespace TestCaseFailureReason { + enum Enum + { + None = 0, + AssertFailure = 1, // an assertion has failed in the test case + Exception = 2, // test case threw an exception + Crash = 4, // a crash... + TooManyFailedAsserts = 8, // the abort-after option + Timeout = 16, // see the timeout decorator + ShouldHaveFailedButDidnt = 32, // see the should_fail decorator + ShouldHaveFailedAndDid = 64, // see the should_fail decorator + DidntFailExactlyNumTimes = 128, // see the expected_failures decorator + FailedExactlyNumTimes = 256, // see the expected_failures decorator + CouldHaveFailedAndDid = 512 // see the may_fail decorator + }; +} // namespace TestCaseFailureReason + +struct DOCTEST_INTERFACE CurrentTestCaseStats +{ + int numAssertsCurrentTest; + int numAssertsFailedCurrentTest; + double seconds; + int failure_flags; // use TestCaseFailureReason::Enum +}; + +struct DOCTEST_INTERFACE TestCaseException +{ + String error_string; + bool is_crash; +}; + +struct DOCTEST_INTERFACE TestRunStats +{ + unsigned numTestCases; + unsigned numTestCasesPassingFilters; + unsigned numTestSuitesPassingFilters; + unsigned numTestCasesFailed; + int numAsserts; + int numAssertsFailed; +}; + +struct QueryData +{ + const TestRunStats* run_stats = nullptr; + const TestCaseData** data = nullptr; + unsigned num_data = 0; +}; + +struct DOCTEST_INTERFACE IReporter +{ + // The constructor has to accept "const ContextOptions&" as a single argument + // which has most of the options for the run + a pointer to the stdout stream + // Reporter(const ContextOptions& in) + + // called when a query should be reported (listing test cases, printing the version, etc.) + virtual void report_query(const QueryData&) = 0; + + // called when the whole test run starts + virtual void test_run_start() = 0; + // called when the whole test run ends (caching a pointer to the input doesn't make sense here) + virtual void test_run_end(const TestRunStats&) = 0; + + // called when a test case is started (safe to cache a pointer to the input) + virtual void test_case_start(const TestCaseData&) = 0; + // called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input) + virtual void test_case_reenter(const TestCaseData&) = 0; + // called when a test case has ended + virtual void test_case_end(const CurrentTestCaseStats&) = 0; + + // called when an exception is thrown from the test case (or it crashes) + virtual void test_case_exception(const TestCaseException&) = 0; + + // called whenever a subcase is entered (don't cache pointers to the input) + virtual void subcase_start(const SubcaseSignature&) = 0; + // called whenever a subcase is exited (don't cache pointers to the input) + virtual void subcase_end() = 0; + + // called for each assert (don't cache pointers to the input) + virtual void log_assert(const AssertData&) = 0; + // called for each message (don't cache pointers to the input) + virtual void log_message(const MessageData&) = 0; + + // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator + // or isn't in the execution range (between first and last) (safe to cache a pointer to the input) + virtual void test_case_skipped(const TestCaseData&) = 0; + + // doctest will not be managing the lifetimes of reporters given to it but this would still be nice to have + virtual ~IReporter(); + + // can obtain all currently active contexts and stringify them if one wishes to do so + static int get_num_active_contexts(); + static const IContextScope* const* get_active_contexts(); + + // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown + static int get_num_stringified_contexts(); + static const String* get_stringified_contexts(); +}; + +namespace detail { + typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&); + + DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter); + + template + IReporter* reporterCreator(const ContextOptions& o) { + return new Reporter(o); + } +} // namespace detail + +template +int registerReporter(const char* name, int priority, bool isReporter) { + detail::registerReporterImpl(name, priority, detail::reporterCreator, isReporter); + return 0; +} +} // namespace doctest + +// if registering is not disabled +#if !defined(DOCTEST_CONFIG_DISABLE) + +// common code in asserts - for convenience +#define DOCTEST_ASSERT_LOG_AND_REACT(b) \ + if(b.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + b.react() + +#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x) x; +#else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x) \ + try { \ + x; \ + } catch(...) { _DOCTEST_RB.translateException(); } +#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +#ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS +#define DOCTEST_CAST_TO_VOID(x) \ + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast") \ + static_cast(x); \ + DOCTEST_GCC_SUPPRESS_WARNING_POP +#else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS +#define DOCTEST_CAST_TO_VOID(x) x; +#endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS + +// registers the test by initializing a dummy var with a function +#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \ + global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + doctest::detail::regTest( \ + doctest::detail::TestCase( \ + f, __FILE__, __LINE__, \ + doctest_detail_test_suite_ns::getCurrentTestSuite()) * \ + decorators); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \ + namespace { \ + struct der : public base \ + { \ + void f(); \ + }; \ + static void func() { \ + der v; \ + v.f(); \ + } \ + DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators) \ + } \ + inline DOCTEST_NOINLINE void der::f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \ + static void f(); \ + DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators) \ + static void f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \ + static doctest::detail::funcType proxy() { return f; } \ + DOCTEST_REGISTER_FUNCTION(inline const, proxy(), decorators) \ + static void f() + +// for registering tests +#define DOCTEST_TEST_CASE(decorators) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + +// for registering tests in classes - requires C++17 for inline variables! +#if __cplusplus >= 201703L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 12, 0) && _MSVC_LANG >= 201703L) +#define DOCTEST_TEST_CASE_CLASS(decorators) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_PROXY_), \ + decorators) +#else // DOCTEST_TEST_CASE_CLASS +#define DOCTEST_TEST_CASE_CLASS(...) \ + TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER +#endif // DOCTEST_TEST_CASE_CLASS + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \ + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), c, \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + +// for converting types to strings without the header and demangling +#define DOCTEST_TYPE_TO_STRING_IMPL(...) \ + template <> \ + inline const char* type_to_string<__VA_ARGS__>() { \ + return "<" #__VA_ARGS__ ">"; \ + } +#define DOCTEST_TYPE_TO_STRING(...) \ + namespace doctest { namespace detail { \ + DOCTEST_TYPE_TO_STRING_IMPL(__VA_ARGS__) \ + } \ + } \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \ + template \ + static void func(); \ + namespace { \ + template \ + struct iter; \ + template \ + struct iter> \ + { \ + iter(const char* file, unsigned line, int index) { \ + doctest::detail::regTest(doctest::detail::TestCase(func, file, line, \ + doctest_detail_test_suite_ns::getCurrentTestSuite(), \ + doctest::detail::type_to_string(), \ + int(line) * 1000 + index) \ + * dec); \ + iter>(file, line, index + 1); \ + } \ + }; \ + template <> \ + struct iter> \ + { \ + iter(const char*, unsigned, int) {} \ + }; \ + } \ + template \ + static void func() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \ + DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)) + +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = \ + doctest::detail::instantiationHelper(DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0));\ + DOCTEST_GLOBAL_NO_WARNINGS_END() + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(anon, anon, std::tuple<__VA_ARGS__>) \ + template \ + static void anon() + +#define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) + +// for subcases +#define DOCTEST_SUBCASE(name) \ + if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ + doctest::detail::Subcase(name, __FILE__, __LINE__)) + +// for grouping tests in test suites by using code blocks +#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \ + namespace ns_name { namespace doctest_detail_test_suite_ns { \ + static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() { \ + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \ + static doctest::detail::TestSuite data; \ + static bool inited = false; \ + DOCTEST_MSVC_SUPPRESS_WARNING_POP \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP \ + if(!inited) { \ + data* decorators; \ + inited = true; \ + } \ + return data; \ + } \ + } \ + } \ + namespace ns_name + +#define DOCTEST_TEST_SUITE(decorators) \ + DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUITE_)) + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(decorators) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * ""); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for registering exception translators +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \ + inline doctest::String translatorName(signature); \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)) = \ + doctest::registerExceptionTranslator(translatorName); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() \ + doctest::String translatorName(signature) + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ + DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_), \ + signature) + +// for registering reporters +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \ + doctest::registerReporter(name, priority, true); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for registering listeners +#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \ + doctest::registerReporter(name, priority, false); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for logging +#define DOCTEST_INFO(expression) \ + DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), \ + DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), expression) + +#define DOCTEST_INFO_IMPL(lambda_name, mb_name, s_name, expression) \ + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4626) \ + auto lambda_name = [&](std::ostream* s_name) { \ + doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \ + mb_name.m_stream = s_name; \ + mb_name << expression; \ + }; \ + DOCTEST_MSVC_SUPPRESS_WARNING_POP \ + auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope(lambda_name) + +#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := " << x) + +#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, x) \ + do { \ + doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \ + mb << x; \ + DOCTEST_ASSERT_LOG_AND_REACT(mb); \ + } while((void)0, 0) + +// clang-format off +#define DOCTEST_ADD_MESSAGE_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) +#define DOCTEST_ADD_FAIL_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) +// clang-format on + +#define DOCTEST_MESSAGE(x) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, x) +#define DOCTEST_FAIL_CHECK(x) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, x) +#define DOCTEST_FAIL(x) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, x) + +#define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility. + +#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.setResult( \ + doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ + << __VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB) \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ + do { \ + DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \ + } while((void)0, 0) + +#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +// necessary for _MESSAGE +#define DOCTEST_ASSERT_IMPLEMENT_2 DOCTEST_ASSERT_IMPLEMENT_1 + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ + doctest::detail::decomp_assert( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, \ + doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ + << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__) +#define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK, __VA_ARGS__) +#define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE, __VA_ARGS__) +#define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__) + +// clang-format off +#define DOCTEST_WARN_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } while((void)0, 0) +#define DOCTEST_CHECK_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } while((void)0, 0) +#define DOCTEST_REQUIRE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } while((void)0, 0) +#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } while((void)0, 0) +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } while((void)0, 0) +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } while((void)0, 0) +// clang-format on + +#define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \ + do { \ + if(!doctest::getContextOptions()->no_throw) { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #expr, #__VA_ARGS__, message); \ + try { \ + DOCTEST_CAST_TO_VOID(expr) \ + } catch(const doctest::detail::remove_const< \ + doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) { \ + _DOCTEST_RB.translateException(); \ + _DOCTEST_RB.m_threw_as = true; \ + } catch(...) { _DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } \ + } while((void)0, 0) + +#define DOCTEST_ASSERT_THROWS_WITH(expr, assert_type, ...) \ + do { \ + if(!doctest::getContextOptions()->no_throw) { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #expr, "", __VA_ARGS__); \ + try { \ + DOCTEST_CAST_TO_VOID(expr) \ + } catch(...) { _DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } \ + } while((void)0, 0) + +#define DOCTEST_ASSERT_NOTHROW(expr, assert_type) \ + do { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #expr); \ + try { \ + DOCTEST_CAST_TO_VOID(expr) \ + } catch(...) { _DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } while((void)0, 0) + +// clang-format off +#define DOCTEST_WARN_THROWS(expr) DOCTEST_ASSERT_THROWS_WITH(expr, DT_WARN_THROWS, "") +#define DOCTEST_CHECK_THROWS(expr) DOCTEST_ASSERT_THROWS_WITH(expr, DT_CHECK_THROWS, "") +#define DOCTEST_REQUIRE_THROWS(expr) DOCTEST_ASSERT_THROWS_WITH(expr, DT_REQUIRE_THROWS, "") + +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, DT_WARN_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, DT_CHECK_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__) + +#define DOCTEST_WARN_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_WARN_NOTHROW) +#define DOCTEST_CHECK_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_CHECK_NOTHROW) +#define DOCTEST_REQUIRE_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_REQUIRE_NOTHROW) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS(expr); } while((void)0, 0) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS(expr); } while((void)0, 0) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS(expr); } while((void)0, 0) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_AS(expr, ex); } while((void)0, 0) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_AS(expr, ex); } while((void)0, 0) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } while((void)0, 0) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_WITH(expr, with); } while((void)0, 0) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_WITH(expr, with); } while((void)0, 0) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } while((void)0, 0) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } while((void)0, 0) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } while((void)0, 0) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } while((void)0, 0) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_NOTHROW(expr); } while((void)0, 0) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_NOTHROW(expr); } while((void)0, 0) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_NOTHROW(expr); } while((void)0, 0) +// clang-format on + +#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \ + do { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY( \ + _DOCTEST_RB.binary_assert( \ + __VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } while((void)0, 0) + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + do { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.unary_assert(__VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } while((void)0, 0) + +#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \ + doctest::detail::binary_assert( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__) + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \ + #__VA_ARGS__, __VA_ARGS__) + +#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__) +#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__) +#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__) +#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__) +#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__) +#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__) +#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__) +#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__) +#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__) +#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__) +#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__) +#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__) +#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__) +#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__) +#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__) +#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__) +#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__) +#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__) + +#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__) +#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__) + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS + +#undef DOCTEST_WARN_THROWS +#undef DOCTEST_CHECK_THROWS +#undef DOCTEST_REQUIRE_THROWS +#undef DOCTEST_WARN_THROWS_AS +#undef DOCTEST_CHECK_THROWS_AS +#undef DOCTEST_REQUIRE_THROWS_AS +#undef DOCTEST_WARN_THROWS_WITH +#undef DOCTEST_CHECK_THROWS_WITH +#undef DOCTEST_REQUIRE_THROWS_WITH +#undef DOCTEST_WARN_THROWS_WITH_AS +#undef DOCTEST_CHECK_THROWS_WITH_AS +#undef DOCTEST_REQUIRE_THROWS_WITH_AS +#undef DOCTEST_WARN_NOTHROW +#undef DOCTEST_CHECK_NOTHROW +#undef DOCTEST_REQUIRE_NOTHROW + +#undef DOCTEST_WARN_THROWS_MESSAGE +#undef DOCTEST_CHECK_THROWS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_MESSAGE +#undef DOCTEST_WARN_THROWS_AS_MESSAGE +#undef DOCTEST_CHECK_THROWS_AS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_AS_MESSAGE +#undef DOCTEST_WARN_THROWS_WITH_MESSAGE +#undef DOCTEST_CHECK_THROWS_WITH_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_WITH_MESSAGE +#undef DOCTEST_WARN_THROWS_WITH_AS_MESSAGE +#undef DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE +#undef DOCTEST_WARN_NOTHROW_MESSAGE +#undef DOCTEST_CHECK_NOTHROW_MESSAGE +#undef DOCTEST_REQUIRE_NOTHROW_MESSAGE + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#define DOCTEST_WARN_THROWS(expr) ((void)0) +#define DOCTEST_CHECK_THROWS(expr) ((void)0) +#define DOCTEST_REQUIRE_THROWS(expr) ((void)0) +#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_WARN_NOTHROW(expr) ((void)0) +#define DOCTEST_CHECK_NOTHROW(expr) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW(expr) ((void)0) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) + +#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#undef DOCTEST_REQUIRE +#undef DOCTEST_REQUIRE_FALSE +#undef DOCTEST_REQUIRE_MESSAGE +#undef DOCTEST_REQUIRE_FALSE_MESSAGE +#undef DOCTEST_REQUIRE_EQ +#undef DOCTEST_REQUIRE_NE +#undef DOCTEST_REQUIRE_GT +#undef DOCTEST_REQUIRE_LT +#undef DOCTEST_REQUIRE_GE +#undef DOCTEST_REQUIRE_LE +#undef DOCTEST_REQUIRE_UNARY +#undef DOCTEST_REQUIRE_UNARY_FALSE + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +// ================================================================================================= +// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING! == +// == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY! == +// ================================================================================================= +#else // DOCTEST_CONFIG_DISABLE + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \ + namespace { \ + template \ + struct der : public base \ + { void f(); }; \ + } \ + template \ + inline void der::f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name) \ + template \ + static inline void f() + +// for registering tests +#define DOCTEST_TEST_CASE(name) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + +// for registering tests in classes +#define DOCTEST_TEST_CASE_CLASS(name) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(x, name) \ + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), x, \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + +// for converting types to strings without the header and demangling +#define DOCTEST_TYPE_TO_STRING(...) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) +#define DOCTEST_TYPE_TO_STRING_IMPL(...) + +// for typed tests +#define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \ + template \ + inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \ + template \ + inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for subcases +#define DOCTEST_SUBCASE(name) + +// for a testsuite block +#define DOCTEST_TEST_SUITE(name) namespace + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(name) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ + template \ + static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature) + +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) +#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) + +#define DOCTEST_INFO(x) ((void)0) +#define DOCTEST_CAPTURE(x) ((void)0) +#define DOCTEST_ADD_MESSAGE_AT(file, line, x) ((void)0) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) ((void)0) +#define DOCTEST_ADD_FAIL_AT(file, line, x) ((void)0) +#define DOCTEST_MESSAGE(x) ((void)0) +#define DOCTEST_FAIL_CHECK(x) ((void)0) +#define DOCTEST_FAIL(x) ((void)0) + +#define DOCTEST_WARN(...) ((void)0) +#define DOCTEST_CHECK(...) ((void)0) +#define DOCTEST_REQUIRE(...) ((void)0) +#define DOCTEST_WARN_FALSE(...) ((void)0) +#define DOCTEST_CHECK_FALSE(...) ((void)0) +#define DOCTEST_REQUIRE_FALSE(...) ((void)0) + +#define DOCTEST_WARN_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_CHECK_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_REQUIRE_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) ((void)0) + +#define DOCTEST_WARN_THROWS(expr) ((void)0) +#define DOCTEST_CHECK_THROWS(expr) ((void)0) +#define DOCTEST_REQUIRE_THROWS(expr) ((void)0) +#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_WARN_NOTHROW(expr) ((void)0) +#define DOCTEST_CHECK_NOTHROW(expr) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW(expr) ((void)0) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) + +#define DOCTEST_WARN_EQ(...) ((void)0) +#define DOCTEST_CHECK_EQ(...) ((void)0) +#define DOCTEST_REQUIRE_EQ(...) ((void)0) +#define DOCTEST_WARN_NE(...) ((void)0) +#define DOCTEST_CHECK_NE(...) ((void)0) +#define DOCTEST_REQUIRE_NE(...) ((void)0) +#define DOCTEST_WARN_GT(...) ((void)0) +#define DOCTEST_CHECK_GT(...) ((void)0) +#define DOCTEST_REQUIRE_GT(...) ((void)0) +#define DOCTEST_WARN_LT(...) ((void)0) +#define DOCTEST_CHECK_LT(...) ((void)0) +#define DOCTEST_REQUIRE_LT(...) ((void)0) +#define DOCTEST_WARN_GE(...) ((void)0) +#define DOCTEST_CHECK_GE(...) ((void)0) +#define DOCTEST_REQUIRE_GE(...) ((void)0) +#define DOCTEST_WARN_LE(...) ((void)0) +#define DOCTEST_CHECK_LE(...) ((void)0) +#define DOCTEST_REQUIRE_LE(...) ((void)0) + +#define DOCTEST_WARN_UNARY(...) ((void)0) +#define DOCTEST_CHECK_UNARY(...) ((void)0) +#define DOCTEST_REQUIRE_UNARY(...) ((void)0) +#define DOCTEST_WARN_UNARY_FALSE(...) ((void)0) +#define DOCTEST_CHECK_UNARY_FALSE(...) ((void)0) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) ((void)0) + +#endif // DOCTEST_CONFIG_DISABLE + +// clang-format off +// KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS +#define DOCTEST_FAST_WARN_EQ DOCTEST_WARN_EQ +#define DOCTEST_FAST_CHECK_EQ DOCTEST_CHECK_EQ +#define DOCTEST_FAST_REQUIRE_EQ DOCTEST_REQUIRE_EQ +#define DOCTEST_FAST_WARN_NE DOCTEST_WARN_NE +#define DOCTEST_FAST_CHECK_NE DOCTEST_CHECK_NE +#define DOCTEST_FAST_REQUIRE_NE DOCTEST_REQUIRE_NE +#define DOCTEST_FAST_WARN_GT DOCTEST_WARN_GT +#define DOCTEST_FAST_CHECK_GT DOCTEST_CHECK_GT +#define DOCTEST_FAST_REQUIRE_GT DOCTEST_REQUIRE_GT +#define DOCTEST_FAST_WARN_LT DOCTEST_WARN_LT +#define DOCTEST_FAST_CHECK_LT DOCTEST_CHECK_LT +#define DOCTEST_FAST_REQUIRE_LT DOCTEST_REQUIRE_LT +#define DOCTEST_FAST_WARN_GE DOCTEST_WARN_GE +#define DOCTEST_FAST_CHECK_GE DOCTEST_CHECK_GE +#define DOCTEST_FAST_REQUIRE_GE DOCTEST_REQUIRE_GE +#define DOCTEST_FAST_WARN_LE DOCTEST_WARN_LE +#define DOCTEST_FAST_CHECK_LE DOCTEST_CHECK_LE +#define DOCTEST_FAST_REQUIRE_LE DOCTEST_REQUIRE_LE + +#define DOCTEST_FAST_WARN_UNARY DOCTEST_WARN_UNARY +#define DOCTEST_FAST_CHECK_UNARY DOCTEST_CHECK_UNARY +#define DOCTEST_FAST_REQUIRE_UNARY DOCTEST_REQUIRE_UNARY +#define DOCTEST_FAST_WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE +#define DOCTEST_FAST_CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE +#define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE + +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INVOKE +// clang-format on + +// BDD style macros +// clang-format off +#define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE(" Scenario: " name) +#define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS(" Scenario: " name) +#define DOCTEST_SCENARIO_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(" Scenario: " name, T, __VA_ARGS__) +#define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(" Scenario: " name, T, id) + +#define DOCTEST_GIVEN(name) DOCTEST_SUBCASE(" Given: " name) +#define DOCTEST_WHEN(name) DOCTEST_SUBCASE(" When: " name) +#define DOCTEST_AND_WHEN(name) DOCTEST_SUBCASE("And when: " name) +#define DOCTEST_THEN(name) DOCTEST_SUBCASE(" Then: " name) +#define DOCTEST_AND_THEN(name) DOCTEST_SUBCASE(" And: " name) +// clang-format on + +// == SHORT VERSIONS OF THE MACROS +#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES) + +#define TEST_CASE DOCTEST_TEST_CASE +#define TEST_CASE_CLASS DOCTEST_TEST_CASE_CLASS +#define TEST_CASE_FIXTURE DOCTEST_TEST_CASE_FIXTURE +#define TYPE_TO_STRING DOCTEST_TYPE_TO_STRING +#define TEST_CASE_TEMPLATE DOCTEST_TEST_CASE_TEMPLATE +#define TEST_CASE_TEMPLATE_DEFINE DOCTEST_TEST_CASE_TEMPLATE_DEFINE +#define TEST_CASE_TEMPLATE_INVOKE DOCTEST_TEST_CASE_TEMPLATE_INVOKE +#define TEST_CASE_TEMPLATE_APPLY DOCTEST_TEST_CASE_TEMPLATE_APPLY +#define SUBCASE DOCTEST_SUBCASE +#define TEST_SUITE DOCTEST_TEST_SUITE +#define TEST_SUITE_BEGIN DOCTEST_TEST_SUITE_BEGIN +#define TEST_SUITE_END DOCTEST_TEST_SUITE_END +#define REGISTER_EXCEPTION_TRANSLATOR DOCTEST_REGISTER_EXCEPTION_TRANSLATOR +#define REGISTER_REPORTER DOCTEST_REGISTER_REPORTER +#define REGISTER_LISTENER DOCTEST_REGISTER_LISTENER +#define INFO DOCTEST_INFO +#define CAPTURE DOCTEST_CAPTURE +#define ADD_MESSAGE_AT DOCTEST_ADD_MESSAGE_AT +#define ADD_FAIL_CHECK_AT DOCTEST_ADD_FAIL_CHECK_AT +#define ADD_FAIL_AT DOCTEST_ADD_FAIL_AT +#define MESSAGE DOCTEST_MESSAGE +#define FAIL_CHECK DOCTEST_FAIL_CHECK +#define FAIL DOCTEST_FAIL +#define TO_LVALUE DOCTEST_TO_LVALUE + +#define WARN DOCTEST_WARN +#define WARN_FALSE DOCTEST_WARN_FALSE +#define WARN_THROWS DOCTEST_WARN_THROWS +#define WARN_THROWS_AS DOCTEST_WARN_THROWS_AS +#define WARN_THROWS_WITH DOCTEST_WARN_THROWS_WITH +#define WARN_THROWS_WITH_AS DOCTEST_WARN_THROWS_WITH_AS +#define WARN_NOTHROW DOCTEST_WARN_NOTHROW +#define CHECK DOCTEST_CHECK +#define CHECK_FALSE DOCTEST_CHECK_FALSE +#define CHECK_THROWS DOCTEST_CHECK_THROWS +#define CHECK_THROWS_AS DOCTEST_CHECK_THROWS_AS +#define CHECK_THROWS_WITH DOCTEST_CHECK_THROWS_WITH +#define CHECK_THROWS_WITH_AS DOCTEST_CHECK_THROWS_WITH_AS +#define CHECK_NOTHROW DOCTEST_CHECK_NOTHROW +#define REQUIRE DOCTEST_REQUIRE +#define REQUIRE_FALSE DOCTEST_REQUIRE_FALSE +#define REQUIRE_THROWS DOCTEST_REQUIRE_THROWS +#define REQUIRE_THROWS_AS DOCTEST_REQUIRE_THROWS_AS +#define REQUIRE_THROWS_WITH DOCTEST_REQUIRE_THROWS_WITH +#define REQUIRE_THROWS_WITH_AS DOCTEST_REQUIRE_THROWS_WITH_AS +#define REQUIRE_NOTHROW DOCTEST_REQUIRE_NOTHROW + +#define WARN_MESSAGE DOCTEST_WARN_MESSAGE +#define WARN_FALSE_MESSAGE DOCTEST_WARN_FALSE_MESSAGE +#define WARN_THROWS_MESSAGE DOCTEST_WARN_THROWS_MESSAGE +#define WARN_THROWS_AS_MESSAGE DOCTEST_WARN_THROWS_AS_MESSAGE +#define WARN_THROWS_WITH_MESSAGE DOCTEST_WARN_THROWS_WITH_MESSAGE +#define WARN_THROWS_WITH_AS_MESSAGE DOCTEST_WARN_THROWS_WITH_AS_MESSAGE +#define WARN_NOTHROW_MESSAGE DOCTEST_WARN_NOTHROW_MESSAGE +#define CHECK_MESSAGE DOCTEST_CHECK_MESSAGE +#define CHECK_FALSE_MESSAGE DOCTEST_CHECK_FALSE_MESSAGE +#define CHECK_THROWS_MESSAGE DOCTEST_CHECK_THROWS_MESSAGE +#define CHECK_THROWS_AS_MESSAGE DOCTEST_CHECK_THROWS_AS_MESSAGE +#define CHECK_THROWS_WITH_MESSAGE DOCTEST_CHECK_THROWS_WITH_MESSAGE +#define CHECK_THROWS_WITH_AS_MESSAGE DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE +#define CHECK_NOTHROW_MESSAGE DOCTEST_CHECK_NOTHROW_MESSAGE +#define REQUIRE_MESSAGE DOCTEST_REQUIRE_MESSAGE +#define REQUIRE_FALSE_MESSAGE DOCTEST_REQUIRE_FALSE_MESSAGE +#define REQUIRE_THROWS_MESSAGE DOCTEST_REQUIRE_THROWS_MESSAGE +#define REQUIRE_THROWS_AS_MESSAGE DOCTEST_REQUIRE_THROWS_AS_MESSAGE +#define REQUIRE_THROWS_WITH_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_MESSAGE +#define REQUIRE_THROWS_WITH_AS_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE +#define REQUIRE_NOTHROW_MESSAGE DOCTEST_REQUIRE_NOTHROW_MESSAGE + +#define SCENARIO DOCTEST_SCENARIO +#define SCENARIO_CLASS DOCTEST_SCENARIO_CLASS +#define SCENARIO_TEMPLATE DOCTEST_SCENARIO_TEMPLATE +#define SCENARIO_TEMPLATE_DEFINE DOCTEST_SCENARIO_TEMPLATE_DEFINE +#define GIVEN DOCTEST_GIVEN +#define WHEN DOCTEST_WHEN +#define AND_WHEN DOCTEST_AND_WHEN +#define THEN DOCTEST_THEN +#define AND_THEN DOCTEST_AND_THEN + +#define WARN_EQ DOCTEST_WARN_EQ +#define CHECK_EQ DOCTEST_CHECK_EQ +#define REQUIRE_EQ DOCTEST_REQUIRE_EQ +#define WARN_NE DOCTEST_WARN_NE +#define CHECK_NE DOCTEST_CHECK_NE +#define REQUIRE_NE DOCTEST_REQUIRE_NE +#define WARN_GT DOCTEST_WARN_GT +#define CHECK_GT DOCTEST_CHECK_GT +#define REQUIRE_GT DOCTEST_REQUIRE_GT +#define WARN_LT DOCTEST_WARN_LT +#define CHECK_LT DOCTEST_CHECK_LT +#define REQUIRE_LT DOCTEST_REQUIRE_LT +#define WARN_GE DOCTEST_WARN_GE +#define CHECK_GE DOCTEST_CHECK_GE +#define REQUIRE_GE DOCTEST_REQUIRE_GE +#define WARN_LE DOCTEST_WARN_LE +#define CHECK_LE DOCTEST_CHECK_LE +#define REQUIRE_LE DOCTEST_REQUIRE_LE +#define WARN_UNARY DOCTEST_WARN_UNARY +#define CHECK_UNARY DOCTEST_CHECK_UNARY +#define REQUIRE_UNARY DOCTEST_REQUIRE_UNARY +#define WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE +#define CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE +#define REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE + +// KEPT FOR BACKWARDS COMPATIBILITY +#define FAST_WARN_EQ DOCTEST_FAST_WARN_EQ +#define FAST_CHECK_EQ DOCTEST_FAST_CHECK_EQ +#define FAST_REQUIRE_EQ DOCTEST_FAST_REQUIRE_EQ +#define FAST_WARN_NE DOCTEST_FAST_WARN_NE +#define FAST_CHECK_NE DOCTEST_FAST_CHECK_NE +#define FAST_REQUIRE_NE DOCTEST_FAST_REQUIRE_NE +#define FAST_WARN_GT DOCTEST_FAST_WARN_GT +#define FAST_CHECK_GT DOCTEST_FAST_CHECK_GT +#define FAST_REQUIRE_GT DOCTEST_FAST_REQUIRE_GT +#define FAST_WARN_LT DOCTEST_FAST_WARN_LT +#define FAST_CHECK_LT DOCTEST_FAST_CHECK_LT +#define FAST_REQUIRE_LT DOCTEST_FAST_REQUIRE_LT +#define FAST_WARN_GE DOCTEST_FAST_WARN_GE +#define FAST_CHECK_GE DOCTEST_FAST_CHECK_GE +#define FAST_REQUIRE_GE DOCTEST_FAST_REQUIRE_GE +#define FAST_WARN_LE DOCTEST_FAST_WARN_LE +#define FAST_CHECK_LE DOCTEST_FAST_CHECK_LE +#define FAST_REQUIRE_LE DOCTEST_FAST_REQUIRE_LE + +#define FAST_WARN_UNARY DOCTEST_FAST_WARN_UNARY +#define FAST_CHECK_UNARY DOCTEST_FAST_CHECK_UNARY +#define FAST_REQUIRE_UNARY DOCTEST_FAST_REQUIRE_UNARY +#define FAST_WARN_UNARY_FALSE DOCTEST_FAST_WARN_UNARY_FALSE +#define FAST_CHECK_UNARY_FALSE DOCTEST_FAST_CHECK_UNARY_FALSE +#define FAST_REQUIRE_UNARY_FALSE DOCTEST_FAST_REQUIRE_UNARY_FALSE + +#define TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE + +#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES + +#if !defined(DOCTEST_CONFIG_DISABLE) + +// this is here to clear the 'current test suite' for the current translation unit - at the top +DOCTEST_TEST_SUITE_END(); + +// add stringification for primitive/fundamental types +namespace doctest { namespace detail { + DOCTEST_TYPE_TO_STRING_IMPL(bool) + DOCTEST_TYPE_TO_STRING_IMPL(float) + DOCTEST_TYPE_TO_STRING_IMPL(double) + DOCTEST_TYPE_TO_STRING_IMPL(long double) + DOCTEST_TYPE_TO_STRING_IMPL(char) + DOCTEST_TYPE_TO_STRING_IMPL(signed char) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned char) +#if !DOCTEST_MSVC || defined(_NATIVE_WCHAR_T_DEFINED) + DOCTEST_TYPE_TO_STRING_IMPL(wchar_t) +#endif // not MSVC or wchar_t support enabled + DOCTEST_TYPE_TO_STRING_IMPL(short int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned short int) + DOCTEST_TYPE_TO_STRING_IMPL(int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned int) + DOCTEST_TYPE_TO_STRING_IMPL(long int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned long int) + DOCTEST_TYPE_TO_STRING_IMPL(long long int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned long long int) +}} // namespace doctest::detail + +#endif // DOCTEST_CONFIG_DISABLE + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_LIBRARY_INCLUDED + +#ifndef DOCTEST_SINGLE_HEADER +#define DOCTEST_SINGLE_HEADER +#endif // DOCTEST_SINGLE_HEADER + +#if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER) + +#ifndef DOCTEST_SINGLE_HEADER +#include "doctest_fwd.h" +#endif // DOCTEST_SINGLE_HEADER + +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros") + +#ifndef DOCTEST_LIBRARY_IMPLEMENTATION +#define DOCTEST_LIBRARY_IMPLEMENTATION + +DOCTEST_CLANG_SUPPRESS_WARNING_POP + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") +DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration +DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data +DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression +DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated +DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant +DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled +DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified +DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal +DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch +DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding in structs +DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe +DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C +DOCTEST_MSVC_SUPPRESS_WARNING(5045) // Spectre mitigation stuff +DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning) +// static analysis +DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' +DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable +DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... +DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtor... +DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN + +// required includes - will go only in one translation unit! +#include +#include +#include +// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/onqtam/doctest/pull/37 +#ifdef __BORLANDC__ +#include +#endif // __BORLANDC__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef DOCTEST_CONFIG_POSIX_SIGNALS +#include +#endif // DOCTEST_CONFIG_POSIX_SIGNALS +#include +#include +#include + +#ifdef DOCTEST_PLATFORM_MAC +#include +#include +#include +#endif // DOCTEST_PLATFORM_MAC + +#ifdef DOCTEST_PLATFORM_WINDOWS + +// defines for a leaner windows.h +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// not sure what AfxWin.h is for - here I do what Catch does +#ifdef __AFXDLL +#include +#else +#include +#endif +#include + +#else // DOCTEST_PLATFORM_WINDOWS + +#include +#include + +#endif // DOCTEST_PLATFORM_WINDOWS + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END + +// counts the number of elements in a C array +#define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0])) + +#ifdef DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled +#else // DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled +#endif // DOCTEST_CONFIG_DISABLE + +#ifndef DOCTEST_CONFIG_OPTIONS_PREFIX +#define DOCTEST_CONFIG_OPTIONS_PREFIX "dt-" +#endif + +#ifndef DOCTEST_THREAD_LOCAL +#define DOCTEST_THREAD_LOCAL thread_local +#endif + +#ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS +#define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX +#else +#define DOCTEST_OPTIONS_PREFIX_DISPLAY "" +#endif + +namespace doctest { + +bool is_running_in_test = false; + +namespace { + using namespace detail; + // case insensitive strcmp + int stricmp(const char* a, const char* b) { + for(;; a++, b++) { + const int d = tolower(*a) - tolower(*b); + if(d != 0 || !*a) + return d; + } + } + + template + String fpToString(T value, int precision) { + std::ostringstream oss; + oss << std::setprecision(precision) << std::fixed << value; + std::string d = oss.str(); + size_t i = d.find_last_not_of('0'); + if(i != std::string::npos && i != d.size() - 1) { + if(d[i] == '.') + i++; + d = d.substr(0, i + 1); + } + return d.c_str(); + } + + struct Endianness + { + enum Arch + { + Big, + Little + }; + + static Arch which() { + int x = 1; + // casting any data pointer to char* is allowed + auto ptr = reinterpret_cast(&x); + if(*ptr) + return Little; + return Big; + } + }; +} // namespace + +namespace detail { + void my_memcpy(void* dest, const void* src, unsigned num) { memcpy(dest, src, num); } + + String rawMemoryToString(const void* object, unsigned size) { + // Reverse order for little endian architectures + int i = 0, end = static_cast(size), inc = 1; + if(Endianness::which() == Endianness::Little) { + i = end - 1; + end = inc = -1; + } + + unsigned const char* bytes = static_cast(object); + std::ostringstream oss; + oss << "0x" << std::setfill('0') << std::hex; + for(; i != end; i += inc) + oss << std::setw(2) << static_cast(bytes[i]); + return oss.str().c_str(); + } + + DOCTEST_THREAD_LOCAL std::ostringstream g_oss; // NOLINT(cert-err58-cpp) + + std::ostream* getTlsOss() { + g_oss.clear(); // there shouldn't be anything worth clearing in the flags + g_oss.str(""); // the slow way of resetting a string stream + //g_oss.seekp(0); // optimal reset - as seen here: https://stackoverflow.com/a/624291/3162383 + return &g_oss; + } + + String getTlsOssResult() { + //g_oss << std::ends; // needed - as shown here: https://stackoverflow.com/a/624291/3162383 + return g_oss.str().c_str(); + } + +#ifndef DOCTEST_CONFIG_DISABLE + +namespace timer_large_integer +{ + +#if defined(DOCTEST_PLATFORM_WINDOWS) + typedef ULONGLONG type; +#else // DOCTEST_PLATFORM_WINDOWS + using namespace std; + typedef uint64_t type; +#endif // DOCTEST_PLATFORM_WINDOWS +} + +typedef timer_large_integer::type ticks_t; + +#ifdef DOCTEST_CONFIG_GETCURRENTTICKS + ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); } +#elif defined(DOCTEST_PLATFORM_WINDOWS) + ticks_t getCurrentTicks() { + static LARGE_INTEGER hz = {0}, hzo = {0}; + if(!hz.QuadPart) { + QueryPerformanceFrequency(&hz); + QueryPerformanceCounter(&hzo); + } + LARGE_INTEGER t; + QueryPerformanceCounter(&t); + return ((t.QuadPart - hzo.QuadPart) * LONGLONG(1000000)) / hz.QuadPart; + } +#else // DOCTEST_PLATFORM_WINDOWS + ticks_t getCurrentTicks() { + timeval t; + gettimeofday(&t, nullptr); + return static_cast(t.tv_sec) * 1000000 + static_cast(t.tv_usec); + } +#endif // DOCTEST_PLATFORM_WINDOWS + + struct Timer + { + void start() { m_ticks = getCurrentTicks(); } + unsigned int getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + //unsigned int getElapsedMilliseconds() const { + // return static_cast(getElapsedMicroseconds() / 1000); + //} + double getElapsedSeconds() const { return (getCurrentTicks() - m_ticks) / 1000000.0; } + + private: + ticks_t m_ticks = 0; + }; + + // this holds both parameters from the command line and runtime data for tests + struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats + { + std::atomic numAssertsCurrentTest_atomic; + std::atomic numAssertsFailedCurrentTest_atomic; + + std::vector> filters = decltype(filters)(9); // 9 different filters + + std::vector reporters_currently_used; + + const TestCase* currentTest = nullptr; + + assert_handler ah = nullptr; + + Timer timer; + + std::vector stringifiedContexts; // logging from INFO() due to an exception + + // stuff for subcases + std::vector subcasesStack; + std::set subcasesPassed; + int subcasesCurrentMaxLevel; + bool should_reenter; + std::atomic shouldLogCurrentException; + + void resetRunData() { + numTestCases = 0; + numTestCasesPassingFilters = 0; + numTestSuitesPassingFilters = 0; + numTestCasesFailed = 0; + numAsserts = 0; + numAssertsFailed = 0; + numAssertsCurrentTest = 0; + numAssertsFailedCurrentTest = 0; + } + + void finalizeTestCaseData() { + seconds = timer.getElapsedSeconds(); + + // update the non-atomic counters + numAsserts += numAssertsCurrentTest_atomic; + numAssertsFailed += numAssertsFailedCurrentTest_atomic; + numAssertsCurrentTest = numAssertsCurrentTest_atomic; + numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic; + + if(numAssertsFailedCurrentTest) + failure_flags |= TestCaseFailureReason::AssertFailure; + + if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 && + Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout) + failure_flags |= TestCaseFailureReason::Timeout; + + if(currentTest->m_should_fail) { + if(failure_flags) { + failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid; + } else { + failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt; + } + } else if(failure_flags && currentTest->m_may_fail) { + failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid; + } else if(currentTest->m_expected_failures > 0) { + if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) { + failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes; + } else { + failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes; + } + } + + bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) || + (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) || + (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags); + + // if any subcase has failed - the whole test case has failed + if(failure_flags && !ok_to_fail) + numTestCasesFailed++; + } + }; + + ContextState* g_cs = nullptr; + + // used to avoid locks for the debug output + // TODO: figure out if this is indeed necessary/correct - seems like either there still + // could be a race or that there wouldn't be a race even if using the context directly + DOCTEST_THREAD_LOCAL bool g_no_colors; + +#endif // DOCTEST_CONFIG_DISABLE +} // namespace detail + +void String::setOnHeap() { *reinterpret_cast(&buf[last]) = 128; } +void String::setLast(unsigned in) { buf[last] = char(in); } + +void String::copy(const String& other) { + using namespace std; + if(other.isOnStack()) { + memcpy(buf, other.buf, len); + } else { + setOnHeap(); + data.size = other.data.size; + data.capacity = data.size + 1; + data.ptr = new char[data.capacity]; + memcpy(data.ptr, other.data.ptr, data.size + 1); + } +} + +String::String() { + buf[0] = '\0'; + setLast(); +} + +String::~String() { + if(!isOnStack()) + delete[] data.ptr; +} + +String::String(const char* in) + : String(in, strlen(in)) {} + +String::String(const char* in, unsigned in_size) { + using namespace std; + if(in_size <= last) { + memcpy(buf, in, in_size + 1); + setLast(last - in_size); + } else { + setOnHeap(); + data.size = in_size; + data.capacity = data.size + 1; + data.ptr = new char[data.capacity]; + memcpy(data.ptr, in, in_size + 1); + } +} + +String::String(const String& other) { copy(other); } + +String& String::operator=(const String& other) { + if(this != &other) { + if(!isOnStack()) + delete[] data.ptr; + + copy(other); + } + + return *this; +} + +String& String::operator+=(const String& other) { + const unsigned my_old_size = size(); + const unsigned other_size = other.size(); + const unsigned total_size = my_old_size + other_size; + using namespace std; + if(isOnStack()) { + if(total_size < len) { + // append to the current stack space + memcpy(buf + my_old_size, other.c_str(), other_size + 1); + setLast(last - total_size); + } else { + // alloc new chunk + char* temp = new char[total_size + 1]; + // copy current data to new location before writing in the union + memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed + // update data in union + setOnHeap(); + data.size = total_size; + data.capacity = data.size + 1; + data.ptr = temp; + // transfer the rest of the data + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } + } else { + if(data.capacity > total_size) { + // append to the current heap block + data.size = total_size; + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } else { + // resize + data.capacity *= 2; + if(data.capacity <= total_size) + data.capacity = total_size + 1; + // alloc new chunk + char* temp = new char[data.capacity]; + // copy current data to new location before releasing it + memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed + // release old chunk + delete[] data.ptr; + // update the rest of the union members + data.size = total_size; + data.ptr = temp; + // transfer the rest of the data + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } + } + + return *this; +} + +String String::operator+(const String& other) const { return String(*this) += other; } + +String::String(String&& other) { + using namespace std; + memcpy(buf, other.buf, len); + other.buf[0] = '\0'; + other.setLast(); +} + +String& String::operator=(String&& other) { + using namespace std; + if(this != &other) { + if(!isOnStack()) + delete[] data.ptr; + memcpy(buf, other.buf, len); + other.buf[0] = '\0'; + other.setLast(); + } + return *this; +} + +char String::operator[](unsigned i) const { + return const_cast(this)->operator[](i); // NOLINT +} + +char& String::operator[](unsigned i) { + if(isOnStack()) + return reinterpret_cast(buf)[i]; + return data.ptr[i]; +} + +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized") +unsigned String::size() const { + if(isOnStack()) + return last - (unsigned(buf[last]) & 31); // using "last" would work only if "len" is 32 + return data.size; +} +DOCTEST_GCC_SUPPRESS_WARNING_POP + +unsigned String::capacity() const { + if(isOnStack()) + return len; + return data.capacity; +} + +int String::compare(const char* other, bool no_case) const { + if(no_case) + return doctest::stricmp(c_str(), other); + return std::strcmp(c_str(), other); +} + +int String::compare(const String& other, bool no_case) const { + return compare(other.c_str(), no_case); +} + +// clang-format off +bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; } +bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } +bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; } +bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; } +bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; } +bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; } +// clang-format on + +std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); } + +namespace { + void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;) +} // namespace + +namespace Color { + std::ostream& operator<<(std::ostream& s, Color::Enum code) { + color_to_stream(s, code); + return s; + } +} // namespace Color + +// clang-format off +const char* assertString(assertType::Enum at) { + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4062) // enum 'x' in switch of enum 'y' is not handled + switch(at) { //!OCLINT missing default in switch statements + case assertType::DT_WARN : return "WARN"; + case assertType::DT_CHECK : return "CHECK"; + case assertType::DT_REQUIRE : return "REQUIRE"; + + case assertType::DT_WARN_FALSE : return "WARN_FALSE"; + case assertType::DT_CHECK_FALSE : return "CHECK_FALSE"; + case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE"; + + case assertType::DT_WARN_THROWS : return "WARN_THROWS"; + case assertType::DT_CHECK_THROWS : return "CHECK_THROWS"; + case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS"; + + case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS"; + case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS"; + case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS"; + + case assertType::DT_WARN_THROWS_WITH : return "WARN_THROWS_WITH"; + case assertType::DT_CHECK_THROWS_WITH : return "CHECK_THROWS_WITH"; + case assertType::DT_REQUIRE_THROWS_WITH : return "REQUIRE_THROWS_WITH"; + + case assertType::DT_WARN_THROWS_WITH_AS : return "WARN_THROWS_WITH_AS"; + case assertType::DT_CHECK_THROWS_WITH_AS : return "CHECK_THROWS_WITH_AS"; + case assertType::DT_REQUIRE_THROWS_WITH_AS : return "REQUIRE_THROWS_WITH_AS"; + + case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW"; + case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW"; + case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW"; + + case assertType::DT_WARN_EQ : return "WARN_EQ"; + case assertType::DT_CHECK_EQ : return "CHECK_EQ"; + case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ"; + case assertType::DT_WARN_NE : return "WARN_NE"; + case assertType::DT_CHECK_NE : return "CHECK_NE"; + case assertType::DT_REQUIRE_NE : return "REQUIRE_NE"; + case assertType::DT_WARN_GT : return "WARN_GT"; + case assertType::DT_CHECK_GT : return "CHECK_GT"; + case assertType::DT_REQUIRE_GT : return "REQUIRE_GT"; + case assertType::DT_WARN_LT : return "WARN_LT"; + case assertType::DT_CHECK_LT : return "CHECK_LT"; + case assertType::DT_REQUIRE_LT : return "REQUIRE_LT"; + case assertType::DT_WARN_GE : return "WARN_GE"; + case assertType::DT_CHECK_GE : return "CHECK_GE"; + case assertType::DT_REQUIRE_GE : return "REQUIRE_GE"; + case assertType::DT_WARN_LE : return "WARN_LE"; + case assertType::DT_CHECK_LE : return "CHECK_LE"; + case assertType::DT_REQUIRE_LE : return "REQUIRE_LE"; + + case assertType::DT_WARN_UNARY : return "WARN_UNARY"; + case assertType::DT_CHECK_UNARY : return "CHECK_UNARY"; + case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY"; + case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE"; + case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE"; + case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE"; + } + DOCTEST_MSVC_SUPPRESS_WARNING_POP + return ""; +} +// clang-format on + +const char* failureString(assertType::Enum at) { + if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional + return "WARNING"; + if(at & assertType::is_check) //!OCLINT bitwise operator in conditional + return "ERROR"; + if(at & assertType::is_require) //!OCLINT bitwise operator in conditional + return "FATAL ERROR"; + return ""; +} + +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +// depending on the current options this will remove the path of filenames +const char* skipPathFromFilename(const char* file) { + if(getContextOptions()->no_path_in_filenames) { + auto back = std::strrchr(file, '\\'); + auto forward = std::strrchr(file, '/'); + if(back || forward) { + if(back > forward) + forward = back; + return forward + 1; + } + } + return file; +} +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +bool SubcaseSignature::operator<(const SubcaseSignature& other) const { + if(m_line != other.m_line) + return m_line < other.m_line; + if(std::strcmp(m_file, other.m_file) != 0) + return std::strcmp(m_file, other.m_file) < 0; + return m_name.compare(other.m_name) < 0; +} + +IContextScope::IContextScope() = default; +IContextScope::~IContextScope() = default; + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +String toString(char* in) { return toString(static_cast(in)); } +String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +String toString(bool in) { return in ? "true" : "false"; } +String toString(float in) { return fpToString(in, 5) + "f"; } +String toString(double in) { return fpToString(in, 10); } +String toString(double long in) { return fpToString(in, 15); } + +#define DOCTEST_TO_STRING_OVERLOAD(type, fmt) \ + String toString(type in) { \ + char buf[64]; \ + std::sprintf(buf, fmt, in); \ + return buf; \ + } + +DOCTEST_TO_STRING_OVERLOAD(char, "%d") +DOCTEST_TO_STRING_OVERLOAD(char signed, "%d") +DOCTEST_TO_STRING_OVERLOAD(char unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int short, "%d") +DOCTEST_TO_STRING_OVERLOAD(int short unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int, "%d") +DOCTEST_TO_STRING_OVERLOAD(unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int long, "%ld") +DOCTEST_TO_STRING_OVERLOAD(int long unsigned, "%lu") +DOCTEST_TO_STRING_OVERLOAD(int long long, "%lld") +DOCTEST_TO_STRING_OVERLOAD(int long long unsigned, "%llu") + +String toString(std::nullptr_t) { return "NULL"; } + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +String toString(const std::string& in) { return in.c_str(); } +#endif // VS 2019 + +Approx::Approx(double value) + : m_epsilon(static_cast(std::numeric_limits::epsilon()) * 100) + , m_scale(1.0) + , m_value(value) {} + +Approx Approx::operator()(double value) const { + Approx approx(value); + approx.epsilon(m_epsilon); + approx.scale(m_scale); + return approx; +} + +Approx& Approx::epsilon(double newEpsilon) { + m_epsilon = newEpsilon; + return *this; +} +Approx& Approx::scale(double newScale) { + m_scale = newScale; + return *this; +} + +bool operator==(double lhs, const Approx& rhs) { + // Thanks to Richard Harris for his help refining this formula + return std::fabs(lhs - rhs.m_value) < + rhs.m_epsilon * (rhs.m_scale + std::max(std::fabs(lhs), std::fabs(rhs.m_value))); +} +bool operator==(const Approx& lhs, double rhs) { return operator==(rhs, lhs); } +bool operator!=(double lhs, const Approx& rhs) { return !operator==(lhs, rhs); } +bool operator!=(const Approx& lhs, double rhs) { return !operator==(rhs, lhs); } +bool operator<=(double lhs, const Approx& rhs) { return lhs < rhs.m_value || lhs == rhs; } +bool operator<=(const Approx& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; } +bool operator>=(double lhs, const Approx& rhs) { return lhs > rhs.m_value || lhs == rhs; } +bool operator>=(const Approx& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; } +bool operator<(double lhs, const Approx& rhs) { return lhs < rhs.m_value && lhs != rhs; } +bool operator<(const Approx& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; } +bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs != rhs; } +bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } + +String toString(const Approx& in) { + return String("Approx( ") + doctest::toString(in.m_value) + " )"; +} +const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); } + +} // namespace doctest + +#ifdef DOCTEST_CONFIG_DISABLE +namespace doctest { +Context::Context(int, const char* const*) {} +Context::~Context() = default; +void Context::applyCommandLine(int, const char* const*) {} +void Context::addFilter(const char*, const char*) {} +void Context::clearFilters() {} +void Context::setOption(const char*, int) {} +void Context::setOption(const char*, const char*) {} +bool Context::shouldExit() { return false; } +void Context::setAsDefaultForAssertsOutOfTestCases() {} +void Context::setAssertHandler(detail::assert_handler) {} +int Context::run() { return 0; } + +IReporter::~IReporter() = default; + +int IReporter::get_num_active_contexts() { return 0; } +const IContextScope* const* IReporter::get_active_contexts() { return nullptr; } +int IReporter::get_num_stringified_contexts() { return 0; } +const String* IReporter::get_stringified_contexts() { return nullptr; } + +int registerReporter(const char*, int, IReporter*) { return 0; } + +} // namespace doctest +#else // DOCTEST_CONFIG_DISABLE + +#if !defined(DOCTEST_CONFIG_COLORS_NONE) +#if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI) +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_CONFIG_COLORS_WINDOWS +#else // linux +#define DOCTEST_CONFIG_COLORS_ANSI +#endif // platform +#endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI +#endif // DOCTEST_CONFIG_COLORS_NONE + +namespace doctest_detail_test_suite_ns { +// holds the current test suite +doctest::detail::TestSuite& getCurrentTestSuite() { + static doctest::detail::TestSuite data; + return data; +} +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +namespace { + // the int (priority) is part of the key for automatic sorting - sadly one can register a + // reporter with a duplicate name and a different priority but hopefully that won't happen often :| + typedef std::map, reporterCreatorFunc> reporterMap; + + reporterMap& getReporters() { + static reporterMap data; + return data; + } + reporterMap& getListeners() { + static reporterMap data; + return data; + } +} // namespace +namespace detail { +#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, ...) \ + for(auto& curr_rep : g_cs->reporters_currently_used) \ + curr_rep->function(__VA_ARGS__) + + bool checkIfShouldThrow(assertType::Enum at) { + if(at & assertType::is_require) //!OCLINT bitwise operator in conditional + return true; + + if((at & assertType::is_check) //!OCLINT bitwise operator in conditional + && getContextOptions()->abort_after > 0 && + (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >= + getContextOptions()->abort_after) + return true; + + return false; + } + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + [[noreturn]] void throwException() { + g_cs->shouldLogCurrentException = false; + throw TestFailureException(); + } // NOLINT(cert-err60-cpp) +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + void throwException() {} +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +} // namespace detail + +namespace { + using namespace detail; + // matching of a string against a wildcard mask (case sensitivity configurable) taken from + // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing + int wildcmp(const char* str, const char* wild, bool caseSensitive) { + const char* cp = nullptr; + const char* mp = nullptr; + + while((*str) && (*wild != '*')) { + if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) && + (*wild != '?')) { + return 0; + } + wild++; + str++; + } + + while(*str) { + if(*wild == '*') { + if(!*++wild) { + return 1; + } + mp = wild; + cp = str + 1; + } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) || + (*wild == '?')) { + wild++; + str++; + } else { + wild = mp; //!OCLINT parameter reassignment + str = cp++; //!OCLINT parameter reassignment + } + } + + while(*wild == '*') { + wild++; + } + return !*wild; + } + + //// C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html + //unsigned hashStr(unsigned const char* str) { + // unsigned long hash = 5381; + // char c; + // while((c = *str++)) + // hash = ((hash << 5) + hash) + c; // hash * 33 + c + // return hash; + //} + + // checks if the name matches any of the filters (and can be configured what to do when empty) + bool matchesAny(const char* name, const std::vector& filters, bool matchEmpty, + bool caseSensitive) { + if(filters.empty() && matchEmpty) + return true; + for(auto& curr : filters) + if(wildcmp(name, curr.c_str(), caseSensitive)) + return true; + return false; + } +} // namespace +namespace detail { + + Subcase::Subcase(const String& name, const char* file, int line) + : m_signature({name, file, line}) { + ContextState* s = g_cs; + + // check subcase filters + if(s->subcasesStack.size() < size_t(s->subcase_filter_levels)) { + if(!matchesAny(m_signature.m_name.c_str(), s->filters[6], true, s->case_sensitive)) + return; + if(matchesAny(m_signature.m_name.c_str(), s->filters[7], false, s->case_sensitive)) + return; + } + + // if a Subcase on the same level has already been entered + if(s->subcasesStack.size() < size_t(s->subcasesCurrentMaxLevel)) { + s->should_reenter = true; + return; + } + + // push the current signature to the stack so we can check if the + // current stack + the current new subcase have been traversed + s->subcasesStack.push_back(m_signature); + if(s->subcasesPassed.count(s->subcasesStack) != 0) { + // pop - revert to previous stack since we've already passed this + s->subcasesStack.pop_back(); + return; + } + + s->subcasesCurrentMaxLevel = s->subcasesStack.size(); + m_entered = true; + + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); + } + + Subcase::~Subcase() { + if(m_entered) { + // only mark the subcase stack as passed if no subcases have been skipped + if(g_cs->should_reenter == false) + g_cs->subcasesPassed.insert(g_cs->subcasesStack); + g_cs->subcasesStack.pop_back(); + +#if __cplusplus >= 201703L && defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411 + if(std::uncaught_exceptions() > 0 +#else + if(std::uncaught_exception() +#endif + && g_cs->shouldLogCurrentException) { + DOCTEST_ITERATE_THROUGH_REPORTERS( + test_case_exception, {"exception thrown in subcase - will translate later " + "when the whole test case has been exited (cannot " + "translate while there is an active exception)", + false}); + g_cs->shouldLogCurrentException = false; + } + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); + } + } + + Subcase::operator bool() const { return m_entered; } + + Result::Result(bool passed, const String& decomposition) + : m_passed(passed) + , m_decomp(decomposition) {} + + ExpressionDecomposer::ExpressionDecomposer(assertType::Enum at) + : m_at(at) {} + + TestSuite& TestSuite::operator*(const char* in) { + m_test_suite = in; + // clear state + m_description = nullptr; + m_skip = false; + m_may_fail = false; + m_should_fail = false; + m_expected_failures = 0; + m_timeout = 0; + return *this; + } + + TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, + const char* type, int template_id) { + m_file = file; + m_line = line; + m_name = nullptr; // will be later overridden in operator* + m_test_suite = test_suite.m_test_suite; + m_description = test_suite.m_description; + m_skip = test_suite.m_skip; + m_may_fail = test_suite.m_may_fail; + m_should_fail = test_suite.m_should_fail; + m_expected_failures = test_suite.m_expected_failures; + m_timeout = test_suite.m_timeout; + + m_test = test; + m_type = type; + m_template_id = template_id; + } + + TestCase::TestCase(const TestCase& other) + : TestCaseData() { + *this = other; + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function + DOCTEST_MSVC_SUPPRESS_WARNING(26437) // Do not slice + TestCase& TestCase::operator=(const TestCase& other) { + static_cast(*this) = static_cast(other); + + m_test = other.m_test; + m_type = other.m_type; + m_template_id = other.m_template_id; + m_full_name = other.m_full_name; + + if(m_template_id != -1) + m_name = m_full_name.c_str(); + return *this; + } + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + TestCase& TestCase::operator*(const char* in) { + m_name = in; + // make a new name with an appended type for templated test case + if(m_template_id != -1) { + m_full_name = String(m_name) + m_type; + // redirect the name to point to the newly constructed full name + m_name = m_full_name.c_str(); + } + return *this; + } + + bool TestCase::operator<(const TestCase& other) const { + if(m_line != other.m_line) + return m_line < other.m_line; + const int file_cmp = std::strcmp(m_file, other.m_file); + if(file_cmp != 0) + return file_cmp < 0; + return m_template_id < other.m_template_id; + } +} // namespace detail +namespace { + using namespace detail; + // for sorting tests by file/line + bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) { +#if DOCTEST_MSVC + // this is needed because MSVC gives different case for drive letters + // for __FILE__ when evaluated in a header and a source file + const int res = doctest::stricmp(lhs->m_file, rhs->m_file); +#else // MSVC + const int res = std::strcmp(lhs->m_file, rhs->m_file); +#endif // MSVC + if(res != 0) + return res < 0; + if(lhs->m_line != rhs->m_line) + return lhs->m_line < rhs->m_line; + return lhs->m_template_id < rhs->m_template_id; + } + + // for sorting tests by suite/file/line + bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) { + const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite); + if(res != 0) + return res < 0; + return fileOrderComparator(lhs, rhs); + } + + // for sorting tests by name/suite/file/line + bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) { + const int res = std::strcmp(lhs->m_name, rhs->m_name); + if(res != 0) + return res < 0; + return suiteOrderComparator(lhs, rhs); + } + + // all the registered tests + std::set& getRegisteredTests() { + static std::set data; + return data; + } + +#ifdef DOCTEST_CONFIG_COLORS_WINDOWS + HANDLE g_stdoutHandle; + WORD g_origFgAttrs; + WORD g_origBgAttrs; + bool g_attrsInitted = false; + + int colors_init() { + if(!g_attrsInitted) { + g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); + g_attrsInitted = true; + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo); + g_origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | + BACKGROUND_BLUE | BACKGROUND_INTENSITY); + g_origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | + FOREGROUND_BLUE | FOREGROUND_INTENSITY); + } + return 0; + } + + int dumy_init_console_colors = colors_init(); +#endif // DOCTEST_CONFIG_COLORS_WINDOWS + + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + void color_to_stream(std::ostream& s, Color::Enum code) { + ((void)s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS + ((void)code); // for DOCTEST_CONFIG_COLORS_NONE +#ifdef DOCTEST_CONFIG_COLORS_ANSI + if(g_no_colors || + (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false)) + return; + + auto col = ""; + // clang-format off + switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement + case Color::Red: col = "[0;31m"; break; + case Color::Green: col = "[0;32m"; break; + case Color::Blue: col = "[0;34m"; break; + case Color::Cyan: col = "[0;36m"; break; + case Color::Yellow: col = "[0;33m"; break; + case Color::Grey: col = "[1;30m"; break; + case Color::LightGrey: col = "[0;37m"; break; + case Color::BrightRed: col = "[1;31m"; break; + case Color::BrightGreen: col = "[1;32m"; break; + case Color::BrightWhite: col = "[1;37m"; break; + case Color::Bright: // invalid + case Color::None: + case Color::White: + default: col = "[0m"; + } + // clang-format on + s << "\033" << col; +#endif // DOCTEST_CONFIG_COLORS_ANSI + +#ifdef DOCTEST_CONFIG_COLORS_WINDOWS + if(g_no_colors || + (isatty(fileno(stdout)) == false && getContextOptions()->force_colors == false)) + return; + +#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(g_stdoutHandle, x | g_origBgAttrs) + + // clang-format off + switch (code) { + case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; + case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break; + case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break; + case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break; + case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break; + case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break; + case Color::Grey: DOCTEST_SET_ATTR(0); break; + case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break; + case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break; + case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break; + case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; + case Color::None: + case Color::Bright: // invalid + default: DOCTEST_SET_ATTR(g_origFgAttrs); + } + // clang-format on +#endif // DOCTEST_CONFIG_COLORS_WINDOWS + } + DOCTEST_CLANG_SUPPRESS_WARNING_POP + + std::vector& getExceptionTranslators() { + static std::vector data; + return data; + } + + String translateActiveException() { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + String res; + auto& translators = getExceptionTranslators(); + for(auto& curr : translators) + if(curr->translate(res)) + return res; + // clang-format off + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcatch-value") + try { + throw; + } catch(std::exception& ex) { + return ex.what(); + } catch(std::string& msg) { + return msg.c_str(); + } catch(const char* msg) { + return msg; + } catch(...) { + return "unknown exception"; + } + DOCTEST_GCC_SUPPRESS_WARNING_POP +// clang-format on +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + return ""; +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } +} // namespace + +namespace detail { + // used by the macros for registering tests + int regTest(const TestCase& tc) { + getRegisteredTests().insert(tc); + return 0; + } + + // sets the current test suite + int setTestSuite(const TestSuite& ts) { + doctest_detail_test_suite_ns::getCurrentTestSuite() = ts; + return 0; + } + +#ifdef DOCTEST_IS_DEBUGGER_ACTIVE + bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); } +#else // DOCTEST_IS_DEBUGGER_ACTIVE +#ifdef DOCTEST_PLATFORM_MAC + // The following function is taken directly from the following technical note: + // https://developer.apple.com/library/archive/qa/qa1361/_index.html + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive() { + int mib[4]; + kinfo_proc info; + size_t size; + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + info.kp_proc.p_flag = 0; + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + // Call sysctl. + size = sizeof(info); + if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) { + std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n"; + return false; + } + // We're being debugged if the P_TRACED flag is set. + return ((info.kp_proc.p_flag & P_TRACED) != 0); + } +#elif DOCTEST_MSVC || defined(__MINGW32__) + bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } +#else + bool isDebuggerActive() { return false; } +#endif // Platform +#endif // DOCTEST_IS_DEBUGGER_ACTIVE + + void registerExceptionTranslatorImpl(const IExceptionTranslator* et) { + if(std::find(getExceptionTranslators().begin(), getExceptionTranslators().end(), et) == + getExceptionTranslators().end()) + getExceptionTranslators().push_back(et); + } + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + void toStream(std::ostream* s, char* in) { *s << in; } + void toStream(std::ostream* s, const char* in) { *s << in; } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + void toStream(std::ostream* s, bool in) { *s << std::boolalpha << in << std::noboolalpha; } + void toStream(std::ostream* s, float in) { *s << in; } + void toStream(std::ostream* s, double in) { *s << in; } + void toStream(std::ostream* s, double long in) { *s << in; } + + void toStream(std::ostream* s, char in) { *s << in; } + void toStream(std::ostream* s, char signed in) { *s << in; } + void toStream(std::ostream* s, char unsigned in) { *s << in; } + void toStream(std::ostream* s, int short in) { *s << in; } + void toStream(std::ostream* s, int short unsigned in) { *s << in; } + void toStream(std::ostream* s, int in) { *s << in; } + void toStream(std::ostream* s, int unsigned in) { *s << in; } + void toStream(std::ostream* s, int long in) { *s << in; } + void toStream(std::ostream* s, int long unsigned in) { *s << in; } + void toStream(std::ostream* s, int long long in) { *s << in; } + void toStream(std::ostream* s, int long long unsigned in) { *s << in; } + + DOCTEST_THREAD_LOCAL std::vector g_infoContexts; // for logging with INFO() + + ContextScopeBase::ContextScopeBase() { + g_infoContexts.push_back(this); + } + + // destroy cannot be inlined into the destructor because that would mean calling stringify after + // ContextScope has been destroyed (base class destructors run after derived class destructors). + // Instead, ContextScope calls this method directly from its destructor. + void ContextScopeBase::destroy() { +#if __cplusplus >= 201703L && defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411 + if(std::uncaught_exceptions() > 0) { +#else + if(std::uncaught_exception()) { +#endif + std::ostringstream s; + this->stringify(&s); + g_cs->stringifiedContexts.push_back(s.str().c_str()); + } + g_infoContexts.pop_back(); + } + +} // namespace detail +namespace { + using namespace detail; + + std::ostream& file_line_to_stream(std::ostream& s, const char* file, int line, + const char* tail = "") { + const auto opt = getContextOptions(); + s << Color::LightGrey << skipPathFromFilename(file) << (opt->gnu_file_line ? ":" : "(") + << (opt->no_line_numbers ? 0 : line) // 0 or the real num depending on the option + << (opt->gnu_file_line ? ":" : "):") << tail; + return s; + } + +#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH) + struct FatalConditionHandler + { + void reset() {} + }; +#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + + void reportFatal(const std::string&); + +#ifdef DOCTEST_PLATFORM_WINDOWS + + struct SignalDefs + { + DWORD id; + const char* name; + }; + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + SignalDefs signalDefs[] = { + {EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal"}, + {EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow"}, + {EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal"}, + {EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error"}, + }; + + struct FatalConditionHandler + { + static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) { + for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { + reportFatal(signalDefs[i].name); + break; + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + FatalConditionHandler() { + isSet = true; + // 32k seems enough for doctest to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + // Register an unhandled exception filter + previousTop = SetUnhandledExceptionFilter(handleException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + } + + static void reset() { + if(isSet) { + // Unregister handler and restore the old guarantee + SetUnhandledExceptionFilter(previousTop); + SetThreadStackGuarantee(&guaranteeSize); + previousTop = nullptr; + isSet = false; + } + } + + ~FatalConditionHandler() { reset(); } + + private: + static bool isSet; + static ULONG guaranteeSize; + static LPTOP_LEVEL_EXCEPTION_FILTER previousTop; + }; + + bool FatalConditionHandler::isSet = false; + ULONG FatalConditionHandler::guaranteeSize = 0; + LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr; + +#else // DOCTEST_PLATFORM_WINDOWS + + struct SignalDefs + { + int id; + const char* name; + }; + SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"}, + {SIGILL, "SIGILL - Illegal instruction signal"}, + {SIGFPE, "SIGFPE - Floating point error signal"}, + {SIGSEGV, "SIGSEGV - Segmentation violation signal"}, + {SIGTERM, "SIGTERM - Termination request signal"}, + {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}}; + + struct FatalConditionHandler + { + static bool isSet; + static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)]; + static stack_t oldSigStack; + static char altStackMem[4 * SIGSTKSZ]; + + static void handleSignal(int sig) { + const char* name = ""; + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + SignalDefs& def = signalDefs[i]; + if(sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise(sig); + } + + FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = sizeof(altStackMem); + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = {}; + sa.sa_handler = handleSignal; // NOLINT + sa.sa_flags = SA_ONSTACK; + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + ~FatalConditionHandler() { reset(); } + static void reset() { + if(isSet) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + isSet = false; + } + } + }; + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + char FatalConditionHandler::altStackMem[] = {}; + +#endif // DOCTEST_PLATFORM_WINDOWS +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + +} // namespace + +namespace { + using namespace detail; + +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text) +#else + // TODO: integration with XCode and other IDEs +#define DOCTEST_OUTPUT_DEBUG_STRING(text) // NOLINT(clang-diagnostic-unused-macros) +#endif // Platform + + void addAssert(assertType::Enum at) { + if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional + g_cs->numAssertsCurrentTest_atomic++; + } + + void addFailedAssert(assertType::Enum at) { + if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional + g_cs->numAssertsFailedCurrentTest_atomic++; + } + +#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH) + void reportFatal(const std::string& message) { + g_cs->failure_flags |= TestCaseFailureReason::Crash; + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true}); + + while(g_cs->subcasesStack.size()) { + g_cs->subcasesStack.pop_back(); + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); + } + + g_cs->finalizeTestCaseData(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); + } +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH +} // namespace +namespace detail { + + ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const char* exception_string) { + m_test_case = g_cs->currentTest; + m_at = at; + m_file = file; + m_line = line; + m_expr = expr; + m_failed = true; + m_threw = false; + m_threw_as = false; + m_exception_type = exception_type; + m_exception_string = exception_string; +#if DOCTEST_MSVC + if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC + ++m_expr; +#endif // MSVC + } + + void ResultBuilder::setResult(const Result& res) { + m_decomp = res.m_decomp; + m_failed = !res.m_passed; + } + + void ResultBuilder::translateException() { + m_threw = true; + m_exception = translateActiveException(); + } + + bool ResultBuilder::log() { + if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + m_failed = !m_threw; + } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT + m_failed = !m_threw_as || (m_exception != m_exception_string); + } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + m_failed = !m_threw_as; + } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + m_failed = m_exception != m_exception_string; + } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + m_failed = m_threw; + } + + if(m_exception.size()) + m_exception = String("\"") + m_exception + "\""; + + if(is_running_in_test) { + addAssert(m_at); + DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this); + + if(m_failed) + addFailedAssert(m_at); + } else if(m_failed) { + failed_out_of_a_testing_context(*this); + } + + return m_failed && isDebuggerActive() && + !getContextOptions()->no_breaks; // break into debugger + } + + void ResultBuilder::react() const { + if(m_failed && checkIfShouldThrow(m_at)) + throwException(); + } + + void failed_out_of_a_testing_context(const AssertData& ad) { + if(g_cs->ah) + g_cs->ah(ad); + else + std::abort(); + } + + void decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, + Result result) { + bool failed = !result.m_passed; + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp); + DOCTEST_ASSERT_IN_TESTS(result.m_decomp); + } + + MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) { + m_stream = getTlsOss(); + m_file = file; + m_line = line; + m_severity = severity; + } + + IExceptionTranslator::IExceptionTranslator() = default; + IExceptionTranslator::~IExceptionTranslator() = default; + + bool MessageBuilder::log() { + m_string = getTlsOssResult(); + DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this); + + const bool isWarn = m_severity & assertType::is_warn; + + // warn is just a message in this context so we don't treat it as an assert + if(!isWarn) { + addAssert(m_severity); + addFailedAssert(m_severity); + } + + return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn; // break + } + + void MessageBuilder::react() { + if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional + throwException(); + } + + MessageBuilder::~MessageBuilder() = default; +} // namespace detail +namespace { + using namespace detail; + + template + [[noreturn]] void throw_exception(Ex const& e) { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + throw e; +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + std::cerr << "doctest will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; + std::terminate(); +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } + +#define DOCTEST_INTERNAL_ERROR(msg) \ + throw_exception(std::logic_error( \ + __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) + + // clang-format off + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ); + + ScopedElement( ScopedElement&& other ) noexcept; + ScopedElement& operator=( ScopedElement&& other ) noexcept; + + ~ScopedElement(); + + ScopedElement& writeText( std::string const& text, bool indent = true ); + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer = nullptr; + }; + + XmlWriter( std::ostream& os = std::cout ); + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name ); + + ScopedElement scopedElement( std::string const& name ); + + XmlWriter& endElement(); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, const char* attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + std::stringstream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ); + + //XmlWriter& writeComment( std::string const& text ); + + //void writeStylesheetRef( std::string const& url ); + + //XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + + private: + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + +using uchar = unsigned char; + +namespace { + + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; + } + if ((c & 0xF0) == 0xE0) { + return 3; + } + if ((c & 0xF8) == 0xF0) { + return 4; + } + DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + void hexEscapeChar(std::ostream& os, unsigned char c) { + std::ios_base::fmtflags f(os.flags()); + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast(c); + os.flags(f); + } + +} // anonymous namespace + + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void XmlEncode::encodeTo( std::ostream& os ) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: https://www.w3.org/TR/xml/#syntax) + + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + uchar c = m_str[idx]; + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: https://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + uchar nc = m_str[idx + n]; + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + ( value < 0x800 && encBytes > 2) || // removed "0x80 <= value &&" because redundant + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept + : m_writer( other.m_writer ){ + other.m_writer = nullptr; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + return *this; + } + + + XmlWriter::ScopedElement::~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { + m_writer->writeText( text, indent ); + return *this; + } + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + writeDeclaration(); + } + + XmlWriter::~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& XmlWriter::endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << ""; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) { + if( !name.empty() && attribute && attribute[0] != '\0' ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + //XmlWriter& XmlWriter::writeComment( std::string const& text ) { + // ensureTagClosed(); + // m_os << m_indent << ""; + // m_needsNewline = true; + // return *this; + //} + + //void XmlWriter::writeStylesheetRef( std::string const& url ) { + // m_os << "\n"; + //} + + //XmlWriter& XmlWriter::writeBlankLine() { + // ensureTagClosed(); + // m_os << '\n'; + // return *this; + //} + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + void XmlWriter::writeDeclaration() { + m_os << "\n"; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } + +// ================================================================================================= +// End of copy-pasted code from Catch +// ================================================================================================= + + // clang-format on + + struct XmlReporter : public IReporter + { + XmlWriter xml; + std::mutex mutex; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc = nullptr; + + XmlReporter(const ContextOptions& co) + : xml(*co.cout) + , opt(co) {} + + void log_contexts() { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + std::stringstream ss; + for(int i = 0; i < num_contexts; ++i) { + contexts[i]->stringify(&ss); + xml.scopedElement("Info").writeText(ss.str()); + ss.str(""); + } + } + } + + unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } + + void test_case_start_impl(const TestCaseData& in) { + bool open_ts_tag = false; + if(tc != nullptr) { // we have already opened a test suite + if(std::strcmp(tc->m_test_suite, in.m_test_suite) != 0) { + xml.endElement(); + open_ts_tag = true; + } + } + else { + open_ts_tag = true; // first test case ==> first test suite + } + + if(open_ts_tag) { + xml.startElement("TestSuite"); + xml.writeAttribute("name", in.m_test_suite); + } + + tc = ∈ + xml.startElement("TestCase") + .writeAttribute("name", in.m_name) + .writeAttribute("filename", skipPathFromFilename(in.m_file)) + .writeAttribute("line", line(in.m_line)) + .writeAttribute("description", in.m_description); + + if(Approx(in.m_timeout) != 0) + xml.writeAttribute("timeout", in.m_timeout); + if(in.m_may_fail) + xml.writeAttribute("may_fail", true); + if(in.m_should_fail) + xml.writeAttribute("should_fail", true); + } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData& in) override { + test_run_start(); + if(opt.list_reporters) { + for(auto& curr : getListeners()) + xml.scopedElement("Listener") + .writeAttribute("priority", curr.first.first) + .writeAttribute("name", curr.first.second); + for(auto& curr : getReporters()) + xml.scopedElement("Reporter") + .writeAttribute("priority", curr.first.first) + .writeAttribute("name", curr.first.second); + } else if(opt.count || opt.list_test_cases) { + for(unsigned i = 0; i < in.num_data; ++i) { + xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name) + .writeAttribute("testsuite", in.data[i]->m_test_suite) + .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file)) + .writeAttribute("line", line(in.data[i]->m_line)); + } + xml.scopedElement("OverallResultsTestCases") + .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); + } else if(opt.list_test_suites) { + for(unsigned i = 0; i < in.num_data; ++i) + xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]->m_test_suite); + xml.scopedElement("OverallResultsTestCases") + .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); + xml.scopedElement("OverallResultsTestSuites") + .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters); + } + xml.endElement(); + } + + void test_run_start() override { + // remove .exe extension - mainly to have the same output on UNIX and Windows + std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); +#ifdef DOCTEST_PLATFORM_WINDOWS + if(binary_name.rfind(".exe") != std::string::npos) + binary_name = binary_name.substr(0, binary_name.length() - 4); +#endif // DOCTEST_PLATFORM_WINDOWS + + xml.startElement("doctest").writeAttribute("binary", binary_name); + if(opt.no_version == false) + xml.writeAttribute("version", DOCTEST_VERSION_STR); + + // only the consequential ones (TODO: filters) + xml.scopedElement("Options") + .writeAttribute("order_by", opt.order_by.c_str()) + .writeAttribute("rand_seed", opt.rand_seed) + .writeAttribute("first", opt.first) + .writeAttribute("last", opt.last) + .writeAttribute("abort_after", opt.abort_after) + .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels) + .writeAttribute("case_sensitive", opt.case_sensitive) + .writeAttribute("no_throw", opt.no_throw) + .writeAttribute("no_skip", opt.no_skip); + } + + void test_run_end(const TestRunStats& p) override { + if(tc) // the TestSuite tag - only if there has been at least 1 test case + xml.endElement(); + + xml.scopedElement("OverallResultsAsserts") + .writeAttribute("successes", p.numAsserts - p.numAssertsFailed) + .writeAttribute("failures", p.numAssertsFailed); + + xml.startElement("OverallResultsTestCases") + .writeAttribute("successes", + p.numTestCasesPassingFilters - p.numTestCasesFailed) + .writeAttribute("failures", p.numTestCasesFailed); + if(opt.no_skipped_summary == false) + xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters); + xml.endElement(); + + xml.endElement(); + } + + void test_case_start(const TestCaseData& in) override { + test_case_start_impl(in); + xml.ensureTagClosed(); + } + + void test_case_reenter(const TestCaseData&) override {} + + void test_case_end(const CurrentTestCaseStats& st) override { + xml.startElement("OverallResultsAsserts") + .writeAttribute("successes", + st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest) + .writeAttribute("failures", st.numAssertsFailedCurrentTest); + if(opt.duration) + xml.writeAttribute("duration", st.seconds); + if(tc->m_expected_failures) + xml.writeAttribute("expected_failures", tc->m_expected_failures); + xml.endElement(); + + xml.endElement(); + } + + void test_case_exception(const TestCaseException& e) override { + std::lock_guard lock(mutex); + + xml.scopedElement("Exception") + .writeAttribute("crash", e.is_crash) + .writeText(e.error_string.c_str()); + } + + void subcase_start(const SubcaseSignature& in) override { + std::lock_guard lock(mutex); + + xml.startElement("SubCase") + .writeAttribute("name", in.m_name) + .writeAttribute("filename", skipPathFromFilename(in.m_file)) + .writeAttribute("line", line(in.m_line)); + xml.ensureTagClosed(); + } + + void subcase_end() override { xml.endElement(); } + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed && !opt.success) + return; + + std::lock_guard lock(mutex); + + xml.startElement("Expression") + .writeAttribute("success", !rb.m_failed) + .writeAttribute("type", assertString(rb.m_at)) + .writeAttribute("filename", skipPathFromFilename(rb.m_file)) + .writeAttribute("line", line(rb.m_line)); + + xml.scopedElement("Original").writeText(rb.m_expr); + + if(rb.m_threw) + xml.scopedElement("Exception").writeText(rb.m_exception.c_str()); + + if(rb.m_at & assertType::is_throws_as) + xml.scopedElement("ExpectedException").writeText(rb.m_exception_type); + if(rb.m_at & assertType::is_throws_with) + xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string); + if((rb.m_at & assertType::is_normal) && !rb.m_threw) + xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str()); + + log_contexts(); + + xml.endElement(); + } + + void log_message(const MessageData& mb) override { + std::lock_guard lock(mutex); + + xml.startElement("Message") + .writeAttribute("type", failureString(mb.m_severity)) + .writeAttribute("filename", skipPathFromFilename(mb.m_file)) + .writeAttribute("line", line(mb.m_line)); + + xml.scopedElement("Text").writeText(mb.m_string.c_str()); + + log_contexts(); + + xml.endElement(); + } + + void test_case_skipped(const TestCaseData& in) override { + if(opt.no_skipped_summary == false) { + test_case_start_impl(in); + xml.writeAttribute("skipped", "true"); + xml.endElement(); + } + } + }; + + DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter); + + struct Whitespace + { + int nrSpaces; + explicit Whitespace(int nr) + : nrSpaces(nr) {} + }; + + std::ostream& operator<<(std::ostream& out, const Whitespace& ws) { + if(ws.nrSpaces != 0) + out << std::setw(ws.nrSpaces) << ' '; + return out; + } + + struct ConsoleReporter : public IReporter + { + std::ostream& s; + bool hasLoggedCurrentTestStart; + std::vector subcasesStack; + std::mutex mutex; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc; + + ConsoleReporter(const ContextOptions& co) + : s(*co.cout) + , opt(co) {} + + ConsoleReporter(const ContextOptions& co, std::ostream& ostr) + : s(ostr) + , opt(co) {} + + // ========================================================================================= + // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE + // ========================================================================================= + + void separator_to_stream() { + s << Color::Yellow + << "===============================================================================" + "\n"; + } + + const char* getSuccessOrFailString(bool success, assertType::Enum at, + const char* success_str) { + if(success) + return success_str; + return failureString(at); + } + + Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) { + return success ? Color::BrightGreen : + (at & assertType::is_warn) ? Color::Yellow : Color::Red; + } + + void successOrFailColoredStringToStream(bool success, assertType::Enum at, + const char* success_str = "SUCCESS") { + s << getSuccessOrFailColor(success, at) + << getSuccessOrFailString(success, at, success_str) << ": "; + } + + void log_contexts() { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + + s << Color::None << " logged: "; + for(int i = 0; i < num_contexts; ++i) { + s << (i == 0 ? "" : " "); + contexts[i]->stringify(&s); + s << "\n"; + } + } + + s << "\n"; + } + + void logTestStart() { + if(hasLoggedCurrentTestStart) + return; + + separator_to_stream(); + file_line_to_stream(s, tc->m_file, tc->m_line, "\n"); + if(tc->m_description) + s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n"; + if(tc->m_test_suite && tc->m_test_suite[0] != '\0') + s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n"; + if(strncmp(tc->m_name, " Scenario:", 11) != 0) + s << Color::None << "TEST CASE: "; + s << Color::None << tc->m_name << "\n"; + + for(auto& curr : subcasesStack) + if(curr.m_name[0] != '\0') + s << " " << curr.m_name << "\n"; + + s << "\n"; + + hasLoggedCurrentTestStart = true; + } + + void printVersion() { + if(opt.no_version == false) + s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \"" + << DOCTEST_VERSION_STR << "\"\n"; + } + + void printIntro() { + printVersion(); + s << Color::Cyan << "[doctest] " << Color::None + << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; + } + + void printHelp() { + int sizePrefixDisplay = static_cast(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY)); + printVersion(); + // clang-format off + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n"; + s << Color::Cyan << "[doctest] " << Color::None; + s << "filter values: \"str1,str2,str3\" (comma separated strings)\n"; + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "filters use wildcards for matching strings\n"; + s << Color::Cyan << "[doctest] " << Color::None; + s << "something passes a filter if any of the strings in a filter matches\n"; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n"; +#endif + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "Query flags - the program quits after them. Available:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h " + << Whitespace(sizePrefixDisplay*0) << "prints this message\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version " + << Whitespace(sizePrefixDisplay*1) << "prints the version\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count " + << Whitespace(sizePrefixDisplay*1) << "prints the number of matching tests\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases " + << Whitespace(sizePrefixDisplay*1) << "lists all matching tests by name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites " + << Whitespace(sizePrefixDisplay*1) << "lists all matching test suites\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters " + << Whitespace(sizePrefixDisplay*1) << "lists all registered reporters\n\n"; + // ================================================================================== << 79 + s << Color::Cyan << "[doctest] " << Color::None; + s << "The available / options/filters are:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their file\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their file\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their test suite\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their test suite\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase= " + << Whitespace(sizePrefixDisplay*1) << "filters subcases by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters= " + << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out= " + << Whitespace(sizePrefixDisplay*1) << "output filename\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by= " + << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n"; + s << Whitespace(sizePrefixDisplay*3) << " - by [file/suite/name/rand]\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed= " + << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first= " + << Whitespace(sizePrefixDisplay*1) << "the first test passing the filters to\n"; + s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last= " + << Whitespace(sizePrefixDisplay*1) << "the last test passing the filters to\n"; + s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after= " + << Whitespace(sizePrefixDisplay*1) << "stop after failed assertions\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels= " + << Whitespace(sizePrefixDisplay*1) << "apply filters for the first levels\n"; + s << Color::Cyan << "\n[doctest] " << Color::None; + s << "Bool options - can be used like flags and true is assumed. Available:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success= " + << Whitespace(sizePrefixDisplay*1) << "include successful assertions in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive= " + << Whitespace(sizePrefixDisplay*1) << "filters being treated as case sensitive\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit= " + << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration= " + << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw= " + << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode= " + << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run= " + << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version= " + << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors= " + << Whitespace(sizePrefixDisplay*1) << "disables colors in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors= " + << Whitespace(sizePrefixDisplay*1) << "use colors even when not in a tty\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks= " + << Whitespace(sizePrefixDisplay*1) << "disables breakpoints in debuggers\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip= " + << Whitespace(sizePrefixDisplay*1) << "don't skip test cases marked as skip\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line= " + << Whitespace(sizePrefixDisplay*1) << ":n: vs (n): for line numbers in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames= " + << Whitespace(sizePrefixDisplay*1) << "only filenames and no paths in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers= " + << Whitespace(sizePrefixDisplay*1) << "0 instead of real line numbers in output\n"; + // ================================================================================== << 79 + // clang-format on + + s << Color::Cyan << "\n[doctest] " << Color::None; + s << "for more information visit the project documentation\n\n"; + } + + void printRegisteredReporters() { + printVersion(); + auto printReporters = [this] (const reporterMap& reporters, const char* type) { + if(reporters.size()) { + s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n"; + for(auto& curr : reporters) + s << "priority: " << std::setw(5) << curr.first.first + << " name: " << curr.first.second << "\n"; + } + }; + printReporters(getListeners(), "listeners"); + printReporters(getReporters(), "reporters"); + } + + void list_query_results() { + separator_to_stream(); + if(opt.count || opt.list_test_cases) { + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + } else if(opt.list_test_suites) { + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "test suites with unskipped test cases passing the current filters: " + << g_cs->numTestSuitesPassingFilters << "\n"; + } + } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData& in) override { + if(opt.version) { + printVersion(); + } else if(opt.help) { + printHelp(); + } else if(opt.list_reporters) { + printRegisteredReporters(); + } else if(opt.count || opt.list_test_cases) { + if(opt.list_test_cases) { + s << Color::Cyan << "[doctest] " << Color::None + << "listing all test case names\n"; + separator_to_stream(); + } + + for(unsigned i = 0; i < in.num_data; ++i) + s << Color::None << in.data[i]->m_name << "\n"; + + separator_to_stream(); + + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + + } else if(opt.list_test_suites) { + s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n"; + separator_to_stream(); + + for(unsigned i = 0; i < in.num_data; ++i) + s << Color::None << in.data[i]->m_test_suite << "\n"; + + separator_to_stream(); + + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "test suites with unskipped test cases passing the current filters: " + << g_cs->numTestSuitesPassingFilters << "\n"; + } + } + + void test_run_start() override { printIntro(); } + + void test_run_end(const TestRunStats& p) override { + separator_to_stream(); + s << std::dec; + + const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0; + s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(6) + << p.numTestCasesPassingFilters << " | " + << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None : + Color::Green) + << std::setw(6) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" + << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None) + << std::setw(6) << p.numTestCasesFailed << " failed" << Color::None << " | "; + if(opt.no_skipped_summary == false) { + const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters; + s << (numSkipped == 0 ? Color::None : Color::Yellow) << std::setw(6) << numSkipped + << " skipped" << Color::None; + } + s << "\n"; + s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(6) + << p.numAsserts << " | " + << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green) + << std::setw(6) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None + << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(6) + << p.numAssertsFailed << " failed" << Color::None << " |\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green) + << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl; + } + + void test_case_start(const TestCaseData& in) override { + hasLoggedCurrentTestStart = false; + tc = ∈ + } + + void test_case_reenter(const TestCaseData&) override {} + + void test_case_end(const CurrentTestCaseStats& st) override { + // log the preamble of the test case only if there is something + // else to print - something other than that an assert has failed + if(opt.duration || + (st.failure_flags && st.failure_flags != TestCaseFailureReason::AssertFailure)) + logTestStart(); + + if(opt.duration) + s << Color::None << std::setprecision(6) << std::fixed << st.seconds + << " s: " << tc->m_name << "\n"; + + if(st.failure_flags & TestCaseFailureReason::Timeout) + s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6) + << std::fixed << tc->m_timeout << "!\n"; + + if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) { + s << Color::Red << "Should have failed but didn't! Marking it as failed!\n"; + } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) { + s << Color::Yellow << "Failed as expected so marking it as not failed\n"; + } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) { + s << Color::Yellow << "Allowed to fail so marking it as not failed\n"; + } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) { + s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures + << " times so marking it as failed!\n"; + } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) { + s << Color::Yellow << "Failed exactly " << tc->m_expected_failures + << " times as expected so marking it as not failed!\n"; + } + if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) { + s << Color::Red << "Aborting - too many failed asserts!\n"; + } + s << Color::None; // lgtm [cpp/useless-expression] + } + + void test_case_exception(const TestCaseException& e) override { + logTestStart(); + + file_line_to_stream(s, tc->m_file, tc->m_line, " "); + successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require : + assertType::is_check); + s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ") + << Color::Cyan << e.error_string << "\n"; + + int num_stringified_contexts = get_num_stringified_contexts(); + if(num_stringified_contexts) { + auto stringified_contexts = get_stringified_contexts(); + s << Color::None << " logged: "; + for(int i = num_stringified_contexts; i > 0; --i) { + s << (i == num_stringified_contexts ? "" : " ") + << stringified_contexts[i - 1] << "\n"; + } + } + s << "\n" << Color::None; + } + + void subcase_start(const SubcaseSignature& subc) override { + std::lock_guard lock(mutex); + subcasesStack.push_back(subc); + hasLoggedCurrentTestStart = false; + } + + void subcase_end() override { + std::lock_guard lock(mutex); + subcasesStack.pop_back(); + hasLoggedCurrentTestStart = false; + } + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed && !opt.success) + return; + + std::lock_guard lock(mutex); + + logTestStart(); + + file_line_to_stream(s, rb.m_file, rb.m_line, " "); + successOrFailColoredStringToStream(!rb.m_failed, rb.m_at); + if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) == + 0) //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) " + << Color::None; + + if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n"; + } else if((rb.m_at & assertType::is_throws_as) && + (rb.m_at & assertType::is_throws_with)) { //!OCLINT + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None; + if(rb.m_threw) { + if(!rb.m_failed) { + s << "threw as expected!\n"; + } else { + s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n"; + } + } else { + s << "did NOT throw at all!\n"; + } + } else if(rb.m_at & + assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", " + << rb.m_exception_type << " ) " << Color::None + << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & + assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string << "\" ) " << Color::None + << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan + << rb.m_exception << "\n"; + } else { + s << (rb.m_threw ? "THREW exception: " : + (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")); + if(rb.m_threw) + s << rb.m_exception << "\n"; + else + s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n"; + } + + log_contexts(); + } + + void log_message(const MessageData& mb) override { + std::lock_guard lock(mutex); + + logTestStart(); + + file_line_to_stream(s, mb.m_file, mb.m_line, " "); + s << getSuccessOrFailColor(false, mb.m_severity) + << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity, + "MESSAGE") << ": "; + s << Color::None << mb.m_string << "\n"; + log_contexts(); + } + + void test_case_skipped(const TestCaseData&) override {} + }; + + DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter); + +#ifdef DOCTEST_PLATFORM_WINDOWS + struct DebugOutputWindowReporter : public ConsoleReporter + { + DOCTEST_THREAD_LOCAL static std::ostringstream oss; + + DebugOutputWindowReporter(const ContextOptions& co) + : ConsoleReporter(co, oss) {} + +#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg) \ + void func(type arg) override { \ + bool with_col = g_no_colors; \ + g_no_colors = false; \ + ConsoleReporter::func(arg); \ + DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \ + oss.str(""); \ + g_no_colors = with_col; \ + } + + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_reenter, const TestCaseData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in) + }; + + DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss; +#endif // DOCTEST_PLATFORM_WINDOWS + + // the implementation of parseOption() + bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String* value) { + // going from the end to the beginning and stopping on the first occurrence from the end + for(int i = argc; i > 0; --i) { + auto index = i - 1; + auto temp = std::strstr(argv[index], pattern); + if(temp && (value || strlen(temp) == strlen(pattern))) { //!OCLINT prefer early exits and continue + // eliminate matches in which the chars before the option are not '-' + bool noBadCharsFound = true; + auto curr = argv[index]; + while(curr != temp) { + if(*curr++ != '-') { + noBadCharsFound = false; + break; + } + } + if(noBadCharsFound && argv[index][0] == '-') { + if(value) { + // parsing the value of an option + temp += strlen(pattern); + const unsigned len = strlen(temp); + if(len) { + *value = temp; + return true; + } + } else { + // just a flag - no value + return true; + } + } + } + } + return false; + } + + // parses an option and returns the string after the '=' character + bool parseOption(int argc, const char* const* argv, const char* pattern, String* value = nullptr, + const String& defaultVal = String()) { + if(value) + *value = defaultVal; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + // offset (normally 3 for "dt-") to skip prefix + if(parseOptionImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX), value)) + return true; +#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + return parseOptionImpl(argc, argv, pattern, value); + } + + // locates a flag on the command line + bool parseFlag(int argc, const char* const* argv, const char* pattern) { + return parseOption(argc, argv, pattern); + } + + // parses a comma separated list of words after a pattern in one of the arguments in argv + bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern, + std::vector& res) { + String filtersString; + if(parseOption(argc, argv, pattern, &filtersString)) { + // tokenize with "," as a separator + // cppcheck-suppress strtokCalled + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + auto pch = std::strtok(filtersString.c_str(), ","); // modifies the string + while(pch != nullptr) { + if(strlen(pch)) + res.push_back(pch); + // uses the strtok() internal state to go to the next token + // cppcheck-suppress strtokCalled + pch = std::strtok(nullptr, ","); + } + DOCTEST_CLANG_SUPPRESS_WARNING_POP + return true; + } + return false; + } + + enum optionType + { + option_bool, + option_int + }; + + // parses an int/bool option from the command line + bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type, + int& res) { + String parsedValue; + if(!parseOption(argc, argv, pattern, &parsedValue)) + return false; + + if(type == 0) { + // boolean + const char positive[][5] = {"1", "true", "on", "yes"}; // 5 - strlen("true") + 1 + const char negative[][6] = {"0", "false", "off", "no"}; // 6 - strlen("false") + 1 + + // if the value matches any of the positive/negative possibilities + for(unsigned i = 0; i < 4; i++) { + if(parsedValue.compare(positive[i], true) == 0) { + res = 1; //!OCLINT parameter reassignment + return true; + } + if(parsedValue.compare(negative[i], true) == 0) { + res = 0; //!OCLINT parameter reassignment + return true; + } + } + } else { + // integer + // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse... + int theInt = std::atoi(parsedValue.c_str()); // NOLINT + if(theInt != 0) { + res = theInt; //!OCLINT parameter reassignment + return true; + } + } + return false; + } +} // namespace + +Context::Context(int argc, const char* const* argv) + : p(new detail::ContextState) { + parseArgs(argc, argv, true); + if(argc) + p->binary_name = argv[0]; +} + +Context::~Context() { + if(g_cs == p) + g_cs = nullptr; + delete p; +} + +void Context::applyCommandLine(int argc, const char* const* argv) { + parseArgs(argc, argv); + if(argc) + p->binary_name = argv[0]; +} + +// parses args +void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { + using namespace detail; + + // clang-format off + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file=", p->filters[0]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sf=", p->filters[0]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file-exclude=",p->filters[1]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sfe=", p->filters[1]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite=", p->filters[2]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ts=", p->filters[2]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite-exclude=", p->filters[3]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tse=", p->filters[3]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case=", p->filters[4]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tc=", p->filters[4]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case-exclude=", p->filters[5]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tce=", p->filters[5]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase=", p->filters[6]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sc=", p->filters[6]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase-exclude=", p->filters[7]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=", p->filters[7]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=", p->filters[8]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=", p->filters[8]); + // clang-format on + + int intRes = 0; + String strRes; + +#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \ + if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) || \ + parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes)) \ + p->var = !!intRes; \ + else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) || \ + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname)) \ + p->var = true; \ + else if(withDefaults) \ + p->var = default + +#define DOCTEST_PARSE_INT_OPTION(name, sname, var, default) \ + if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_int, intRes) || \ + parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_int, intRes)) \ + p->var = intRes; \ + else if(withDefaults) \ + p->var = default + +#define DOCTEST_PARSE_STR_OPTION(name, sname, var, default) \ + if(parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", &strRes, default) || \ + parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", &strRes, default) || \ + withDefaults) \ + p->var = strRes + + // clang-format off + DOCTEST_PARSE_STR_OPTION("out", "o", out, ""); + DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file"); + DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0); + + DOCTEST_PARSE_INT_OPTION("first", "f", first, 0); + DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX); + + DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0); + DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX); + + DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-breaks", "nb", no_breaks, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skip", "ns", no_skip, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC)); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false); + // clang-format on + + if(withDefaults) { + p->help = false; + p->version = false; + p->count = false; + p->list_test_cases = false; + p->list_test_suites = false; + p->list_reporters = false; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "help") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "h") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "?")) { + p->help = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "version") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "v")) { + p->version = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "count") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "c")) { + p->count = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-cases") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ltc")) { + p->list_test_cases = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-suites") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lts")) { + p->list_test_suites = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-reporters") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lr")) { + p->list_reporters = true; + p->exit = true; + } +} + +// allows the user to add procedurally to the filters from the command line +void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); } + +// allows the user to clear all filters from the command line +void Context::clearFilters() { + for(auto& curr : p->filters) + curr.clear(); +} + +// allows the user to override procedurally the int/bool options from the command line +void Context::setOption(const char* option, int value) { + setOption(option, toString(value).c_str()); +} + +// allows the user to override procedurally the string options from the command line +void Context::setOption(const char* option, const char* value) { + auto argv = String("-") + option + "=" + value; + auto lvalue = argv.c_str(); + parseArgs(1, &lvalue); +} + +// users should query this in their main() and exit the program if true +bool Context::shouldExit() { return p->exit; } + +void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; } + +void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; } + +// the main function that does all the filtering and test running +int Context::run() { + using namespace detail; + + // save the old context state in case such was setup - for using asserts out of a testing context + auto old_cs = g_cs; + // this is the current contest + g_cs = p; + is_running_in_test = true; + + g_no_colors = p->no_colors; + p->resetRunData(); + + // stdout by default + p->cout = &std::cout; + p->cerr = &std::cerr; + + // or to a file if specified + std::fstream fstr; + if(p->out.size()) { + fstr.open(p->out.c_str(), std::fstream::out); + p->cout = &fstr; + } + + auto cleanup_and_return = [&]() { + if(fstr.is_open()) + fstr.close(); + + // restore context + g_cs = old_cs; + is_running_in_test = false; + + // we have to free the reporters which were allocated when the run started + for(auto& curr : p->reporters_currently_used) + delete curr; + p->reporters_currently_used.clear(); + + if(p->numTestCasesFailed && !p->no_exitcode) + return EXIT_FAILURE; + return EXIT_SUCCESS; + }; + + // setup default reporter if none is given through the command line + if(p->filters[8].empty()) + p->filters[8].push_back("console"); + + // check to see if any of the registered reporters has been selected + for(auto& curr : getReporters()) { + if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive)) + p->reporters_currently_used.push_back(curr.second(*g_cs)); + } + + // TODO: check if there is nothing in reporters_currently_used + + // prepend all listeners + for(auto& curr : getListeners()) + p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs)); + +#ifdef DOCTEST_PLATFORM_WINDOWS + if(isDebuggerActive()) + p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs)); +#endif // DOCTEST_PLATFORM_WINDOWS + + // handle version, help and no_run + if(p->no_run || p->version || p->help || p->list_reporters) { + DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData()); + + return cleanup_and_return(); + } + + std::vector testArray; + for(auto& curr : getRegisteredTests()) + testArray.push_back(&curr); + p->numTestCases = testArray.size(); + + // sort the collected records + if(!testArray.empty()) { + if(p->order_by.compare("file", true) == 0) { + std::sort(testArray.begin(), testArray.end(), fileOrderComparator); + } else if(p->order_by.compare("suite", true) == 0) { + std::sort(testArray.begin(), testArray.end(), suiteOrderComparator); + } else if(p->order_by.compare("name", true) == 0) { + std::sort(testArray.begin(), testArray.end(), nameOrderComparator); + } else if(p->order_by.compare("rand", true) == 0) { + std::srand(p->rand_seed); + + // random_shuffle implementation + const auto first = &testArray[0]; + for(size_t i = testArray.size() - 1; i > 0; --i) { + int idxToSwap = std::rand() % (i + 1); // NOLINT + + const auto temp = first[i]; + + first[i] = first[idxToSwap]; + first[idxToSwap] = temp; + } + } + } + + std::set testSuitesPassingFilt; + + bool query_mode = p->count || p->list_test_cases || p->list_test_suites; + std::vector queryResults; + + if(!query_mode) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY); + + // invoke the registered functions if they match the filter criteria (or just count them) + for(auto& curr : testArray) { + const auto& tc = *curr; + + bool skip_me = false; + if(tc.m_skip && !p->no_skip) + skip_me = true; + + if(!matchesAny(tc.m_file, p->filters[0], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_file, p->filters[1], false, p->case_sensitive)) + skip_me = true; + if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive)) + skip_me = true; + if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive)) + skip_me = true; + + if(!skip_me) + p->numTestCasesPassingFilters++; + + // skip the test if it is not in the execution range + if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) || + (p->first > p->numTestCasesPassingFilters)) + skip_me = true; + + if(skip_me) { + if(!query_mode) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc); + continue; + } + + // do not execute the test if we are to only count the number of filter passing tests + if(p->count) + continue; + + // print the name of the test and don't execute it + if(p->list_test_cases) { + queryResults.push_back(&tc); + continue; + } + + // print the name of the test suite if not done already and don't execute it + if(p->list_test_suites) { + if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') { + queryResults.push_back(&tc); + testSuitesPassingFilt.insert(tc.m_test_suite); + p->numTestSuitesPassingFilters++; + } + continue; + } + + // execute the test if it passes all the filtering + { + p->currentTest = &tc; + + p->failure_flags = TestCaseFailureReason::None; + p->seconds = 0; + + // reset atomic counters + p->numAssertsFailedCurrentTest_atomic = 0; + p->numAssertsCurrentTest_atomic = 0; + + p->subcasesPassed.clear(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc); + + p->timer.start(); + + bool run_test = true; + + do { + // reset some of the fields for subcases (except for the set of fully passed ones) + p->should_reenter = false; + p->subcasesCurrentMaxLevel = 0; + p->subcasesStack.clear(); + + p->shouldLogCurrentException = true; + + // reset stuff for logging with INFO() + p->stringifiedContexts.clear(); + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + try { +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + FatalConditionHandler fatalConditionHandler; // Handle signals + // execute the test + tc.m_test(); + fatalConditionHandler.reset(); +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + } catch(const TestFailureException&) { + p->failure_flags |= TestCaseFailureReason::AssertFailure; + } catch(...) { + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, + {translateActiveException(), false}); + p->failure_flags |= TestCaseFailureReason::Exception; + } +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + + // exit this loop if enough assertions have failed - even if there are more subcases + if(p->abort_after > 0 && + p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) { + run_test = false; + p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts; + } + + if(p->should_reenter && run_test) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc); + if(!p->should_reenter) + run_test = false; + } while(run_test); + + p->finalizeTestCaseData(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + + p->currentTest = nullptr; + + // stop executing tests if enough assertions have failed + if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after) + break; + } + } + + if(!query_mode) { + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); + } else { + QueryData qdata; + qdata.run_stats = g_cs; + qdata.data = queryResults.data(); + qdata.num_data = unsigned(queryResults.size()); + DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata); + } + + // see these issues on the reasoning for this: + // - https://github.com/onqtam/doctest/issues/143#issuecomment-414418903 + // - https://github.com/onqtam/doctest/issues/126 + auto DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS = []() DOCTEST_NOINLINE + { std::cout << std::string(); }; + DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS(); + + return cleanup_and_return(); +} + +IReporter::~IReporter() = default; + +int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); } +const IContextScope* const* IReporter::get_active_contexts() { + return get_num_active_contexts() ? &detail::g_infoContexts[0] : nullptr; +} + +int IReporter::get_num_stringified_contexts() { return detail::g_cs->stringifiedContexts.size(); } +const String* IReporter::get_stringified_contexts() { + return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr; +} + +namespace detail { + void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) { + if(isReporter) + getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); + else + getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); + } +} // namespace detail + +} // namespace doctest + +#endif // DOCTEST_CONFIG_DISABLE + +#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182 +int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); } +DOCTEST_MSVC_SUPPRESS_WARNING_POP +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_LIBRARY_IMPLEMENTATION +#endif // DOCTEST_CONFIG_IMPLEMENT diff --git a/Firmware/doctest/parts/doctest.cpp b/Firmware/doctest/parts/doctest.cpp new file mode 100644 index 00000000..a423d0a7 --- /dev/null +++ b/Firmware/doctest/parts/doctest.cpp @@ -0,0 +1,3344 @@ +#if defined(DOCTEST_CONFIG_IMPLEMENT) || !defined(DOCTEST_SINGLE_HEADER) + +#ifndef DOCTEST_SINGLE_HEADER +#include "doctest_fwd.h" +#endif // DOCTEST_SINGLE_HEADER + +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-macros") + +#ifndef DOCTEST_LIBRARY_IMPLEMENTATION +#define DOCTEST_LIBRARY_IMPLEMENTATION + +DOCTEST_CLANG_SUPPRESS_WARNING_POP + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wshorten-64-to-32") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-variable-declarations") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wcovered-switch-default") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-noreturn") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdisabled-macro-expansion") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-field-initializers") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-braces") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-enum") +DOCTEST_GCC_SUPPRESS_WARNING("-Wswitch-default") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") +DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmultiple-inheritance") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsuggest-attribute") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration +DOCTEST_MSVC_SUPPRESS_WARNING(4267) // 'var' : conversion from 'x' to 'y', possible loss of data +DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression +DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated +DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant +DOCTEST_MSVC_SUPPRESS_WARNING(4530) // C++ exception handler used, but unwind semantics not enabled +DOCTEST_MSVC_SUPPRESS_WARNING(4577) // 'noexcept' used with no exception handling mode specified +DOCTEST_MSVC_SUPPRESS_WARNING(4774) // format string expected in argument is not a string literal +DOCTEST_MSVC_SUPPRESS_WARNING(4365) // conversion from 'int' to 'unsigned', signed/unsigned mismatch +DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding in structs +DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe +DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C +DOCTEST_MSVC_SUPPRESS_WARNING(5045) // Spectre mitigation stuff +DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning) +// static analysis +DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' +DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable +DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... +DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtor... +DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN + +// required includes - will go only in one translation unit! +#include +#include +#include +// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/onqtam/doctest/pull/37 +#ifdef __BORLANDC__ +#include +#endif // __BORLANDC__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef DOCTEST_CONFIG_POSIX_SIGNALS +#include +#endif // DOCTEST_CONFIG_POSIX_SIGNALS +#include +#include +#include + +#ifdef DOCTEST_PLATFORM_MAC +#include +#include +#include +#endif // DOCTEST_PLATFORM_MAC + +#ifdef DOCTEST_PLATFORM_WINDOWS + +// defines for a leaner windows.h +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// not sure what AfxWin.h is for - here I do what Catch does +#ifdef __AFXDLL +#include +#else +#include +#endif +#include + +#else // DOCTEST_PLATFORM_WINDOWS + +#include +#include + +#endif // DOCTEST_PLATFORM_WINDOWS + +DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END + +// counts the number of elements in a C array +#define DOCTEST_COUNTOF(x) (sizeof(x) / sizeof(x[0])) + +#ifdef DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled +#else // DOCTEST_CONFIG_DISABLE +#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled +#endif // DOCTEST_CONFIG_DISABLE + +#ifndef DOCTEST_CONFIG_OPTIONS_PREFIX +#define DOCTEST_CONFIG_OPTIONS_PREFIX "dt-" +#endif + +#ifndef DOCTEST_THREAD_LOCAL +#define DOCTEST_THREAD_LOCAL thread_local +#endif + +#ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS +#define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX +#else +#define DOCTEST_OPTIONS_PREFIX_DISPLAY "" +#endif + +namespace doctest { + +bool is_running_in_test = false; + +namespace { + using namespace detail; + // case insensitive strcmp + int stricmp(const char* a, const char* b) { + for(;; a++, b++) { + const int d = tolower(*a) - tolower(*b); + if(d != 0 || !*a) + return d; + } + } + + template + String fpToString(T value, int precision) { + std::ostringstream oss; + oss << std::setprecision(precision) << std::fixed << value; + std::string d = oss.str(); + size_t i = d.find_last_not_of('0'); + if(i != std::string::npos && i != d.size() - 1) { + if(d[i] == '.') + i++; + d = d.substr(0, i + 1); + } + return d.c_str(); + } + + struct Endianness + { + enum Arch + { + Big, + Little + }; + + static Arch which() { + int x = 1; + // casting any data pointer to char* is allowed + auto ptr = reinterpret_cast(&x); + if(*ptr) + return Little; + return Big; + } + }; +} // namespace + +namespace detail { + void my_memcpy(void* dest, const void* src, unsigned num) { memcpy(dest, src, num); } + + String rawMemoryToString(const void* object, unsigned size) { + // Reverse order for little endian architectures + int i = 0, end = static_cast(size), inc = 1; + if(Endianness::which() == Endianness::Little) { + i = end - 1; + end = inc = -1; + } + + unsigned const char* bytes = static_cast(object); + std::ostringstream oss; + oss << "0x" << std::setfill('0') << std::hex; + for(; i != end; i += inc) + oss << std::setw(2) << static_cast(bytes[i]); + return oss.str().c_str(); + } + + DOCTEST_THREAD_LOCAL std::ostringstream g_oss; // NOLINT(cert-err58-cpp) + + std::ostream* getTlsOss() { + g_oss.clear(); // there shouldn't be anything worth clearing in the flags + g_oss.str(""); // the slow way of resetting a string stream + //g_oss.seekp(0); // optimal reset - as seen here: https://stackoverflow.com/a/624291/3162383 + return &g_oss; + } + + String getTlsOssResult() { + //g_oss << std::ends; // needed - as shown here: https://stackoverflow.com/a/624291/3162383 + return g_oss.str().c_str(); + } + +#ifndef DOCTEST_CONFIG_DISABLE + +namespace timer_large_integer +{ + +#if defined(DOCTEST_PLATFORM_WINDOWS) + typedef ULONGLONG type; +#else // DOCTEST_PLATFORM_WINDOWS + using namespace std; + typedef uint64_t type; +#endif // DOCTEST_PLATFORM_WINDOWS +} + +typedef timer_large_integer::type ticks_t; + +#ifdef DOCTEST_CONFIG_GETCURRENTTICKS + ticks_t getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); } +#elif defined(DOCTEST_PLATFORM_WINDOWS) + ticks_t getCurrentTicks() { + static LARGE_INTEGER hz = {0}, hzo = {0}; + if(!hz.QuadPart) { + QueryPerformanceFrequency(&hz); + QueryPerformanceCounter(&hzo); + } + LARGE_INTEGER t; + QueryPerformanceCounter(&t); + return ((t.QuadPart - hzo.QuadPart) * LONGLONG(1000000)) / hz.QuadPart; + } +#else // DOCTEST_PLATFORM_WINDOWS + ticks_t getCurrentTicks() { + timeval t; + gettimeofday(&t, nullptr); + return static_cast(t.tv_sec) * 1000000 + static_cast(t.tv_usec); + } +#endif // DOCTEST_PLATFORM_WINDOWS + + struct Timer + { + void start() { m_ticks = getCurrentTicks(); } + unsigned int getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + //unsigned int getElapsedMilliseconds() const { + // return static_cast(getElapsedMicroseconds() / 1000); + //} + double getElapsedSeconds() const { return (getCurrentTicks() - m_ticks) / 1000000.0; } + + private: + ticks_t m_ticks = 0; + }; + + // this holds both parameters from the command line and runtime data for tests + struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats + { + std::atomic numAssertsCurrentTest_atomic; + std::atomic numAssertsFailedCurrentTest_atomic; + + std::vector> filters = decltype(filters)(9); // 9 different filters + + std::vector reporters_currently_used; + + const TestCase* currentTest = nullptr; + + assert_handler ah = nullptr; + + Timer timer; + + std::vector stringifiedContexts; // logging from INFO() due to an exception + + // stuff for subcases + std::vector subcasesStack; + std::set subcasesPassed; + int subcasesCurrentMaxLevel; + bool should_reenter; + std::atomic shouldLogCurrentException; + + void resetRunData() { + numTestCases = 0; + numTestCasesPassingFilters = 0; + numTestSuitesPassingFilters = 0; + numTestCasesFailed = 0; + numAsserts = 0; + numAssertsFailed = 0; + numAssertsCurrentTest = 0; + numAssertsFailedCurrentTest = 0; + } + + void finalizeTestCaseData() { + seconds = timer.getElapsedSeconds(); + + // update the non-atomic counters + numAsserts += numAssertsCurrentTest_atomic; + numAssertsFailed += numAssertsFailedCurrentTest_atomic; + numAssertsCurrentTest = numAssertsCurrentTest_atomic; + numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic; + + if(numAssertsFailedCurrentTest) + failure_flags |= TestCaseFailureReason::AssertFailure; + + if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 && + Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout) + failure_flags |= TestCaseFailureReason::Timeout; + + if(currentTest->m_should_fail) { + if(failure_flags) { + failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid; + } else { + failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt; + } + } else if(failure_flags && currentTest->m_may_fail) { + failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid; + } else if(currentTest->m_expected_failures > 0) { + if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) { + failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes; + } else { + failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes; + } + } + + bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) || + (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) || + (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags); + + // if any subcase has failed - the whole test case has failed + if(failure_flags && !ok_to_fail) + numTestCasesFailed++; + } + }; + + ContextState* g_cs = nullptr; + + // used to avoid locks for the debug output + // TODO: figure out if this is indeed necessary/correct - seems like either there still + // could be a race or that there wouldn't be a race even if using the context directly + DOCTEST_THREAD_LOCAL bool g_no_colors; + +#endif // DOCTEST_CONFIG_DISABLE +} // namespace detail + +void String::setOnHeap() { *reinterpret_cast(&buf[last]) = 128; } +void String::setLast(unsigned in) { buf[last] = char(in); } + +void String::copy(const String& other) { + using namespace std; + if(other.isOnStack()) { + memcpy(buf, other.buf, len); + } else { + setOnHeap(); + data.size = other.data.size; + data.capacity = data.size + 1; + data.ptr = new char[data.capacity]; + memcpy(data.ptr, other.data.ptr, data.size + 1); + } +} + +String::String() { + buf[0] = '\0'; + setLast(); +} + +String::~String() { + if(!isOnStack()) + delete[] data.ptr; +} + +String::String(const char* in) + : String(in, strlen(in)) {} + +String::String(const char* in, unsigned in_size) { + using namespace std; + if(in_size <= last) { + memcpy(buf, in, in_size + 1); + setLast(last - in_size); + } else { + setOnHeap(); + data.size = in_size; + data.capacity = data.size + 1; + data.ptr = new char[data.capacity]; + memcpy(data.ptr, in, in_size + 1); + } +} + +String::String(const String& other) { copy(other); } + +String& String::operator=(const String& other) { + if(this != &other) { + if(!isOnStack()) + delete[] data.ptr; + + copy(other); + } + + return *this; +} + +String& String::operator+=(const String& other) { + const unsigned my_old_size = size(); + const unsigned other_size = other.size(); + const unsigned total_size = my_old_size + other_size; + using namespace std; + if(isOnStack()) { + if(total_size < len) { + // append to the current stack space + memcpy(buf + my_old_size, other.c_str(), other_size + 1); + setLast(last - total_size); + } else { + // alloc new chunk + char* temp = new char[total_size + 1]; + // copy current data to new location before writing in the union + memcpy(temp, buf, my_old_size); // skip the +1 ('\0') for speed + // update data in union + setOnHeap(); + data.size = total_size; + data.capacity = data.size + 1; + data.ptr = temp; + // transfer the rest of the data + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } + } else { + if(data.capacity > total_size) { + // append to the current heap block + data.size = total_size; + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } else { + // resize + data.capacity *= 2; + if(data.capacity <= total_size) + data.capacity = total_size + 1; + // alloc new chunk + char* temp = new char[data.capacity]; + // copy current data to new location before releasing it + memcpy(temp, data.ptr, my_old_size); // skip the +1 ('\0') for speed + // release old chunk + delete[] data.ptr; + // update the rest of the union members + data.size = total_size; + data.ptr = temp; + // transfer the rest of the data + memcpy(data.ptr + my_old_size, other.c_str(), other_size + 1); + } + } + + return *this; +} + +String String::operator+(const String& other) const { return String(*this) += other; } + +String::String(String&& other) { + using namespace std; + memcpy(buf, other.buf, len); + other.buf[0] = '\0'; + other.setLast(); +} + +String& String::operator=(String&& other) { + using namespace std; + if(this != &other) { + if(!isOnStack()) + delete[] data.ptr; + memcpy(buf, other.buf, len); + other.buf[0] = '\0'; + other.setLast(); + } + return *this; +} + +char String::operator[](unsigned i) const { + return const_cast(this)->operator[](i); // NOLINT +} + +char& String::operator[](unsigned i) { + if(isOnStack()) + return reinterpret_cast(buf)[i]; + return data.ptr[i]; +} + +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmaybe-uninitialized") +unsigned String::size() const { + if(isOnStack()) + return last - (unsigned(buf[last]) & 31); // using "last" would work only if "len" is 32 + return data.size; +} +DOCTEST_GCC_SUPPRESS_WARNING_POP + +unsigned String::capacity() const { + if(isOnStack()) + return len; + return data.capacity; +} + +int String::compare(const char* other, bool no_case) const { + if(no_case) + return doctest::stricmp(c_str(), other); + return std::strcmp(c_str(), other); +} + +int String::compare(const String& other, bool no_case) const { + return compare(other.c_str(), no_case); +} + +// clang-format off +bool operator==(const String& lhs, const String& rhs) { return lhs.compare(rhs) == 0; } +bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } +bool operator< (const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; } +bool operator> (const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; } +bool operator<=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) < 0 : true; } +bool operator>=(const String& lhs, const String& rhs) { return (lhs != rhs) ? lhs.compare(rhs) > 0 : true; } +// clang-format on + +std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); } + +namespace { + void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;) +} // namespace + +namespace Color { + std::ostream& operator<<(std::ostream& s, Color::Enum code) { + color_to_stream(s, code); + return s; + } +} // namespace Color + +// clang-format off +const char* assertString(assertType::Enum at) { + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4062) // enum 'x' in switch of enum 'y' is not handled + switch(at) { //!OCLINT missing default in switch statements + case assertType::DT_WARN : return "WARN"; + case assertType::DT_CHECK : return "CHECK"; + case assertType::DT_REQUIRE : return "REQUIRE"; + + case assertType::DT_WARN_FALSE : return "WARN_FALSE"; + case assertType::DT_CHECK_FALSE : return "CHECK_FALSE"; + case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE"; + + case assertType::DT_WARN_THROWS : return "WARN_THROWS"; + case assertType::DT_CHECK_THROWS : return "CHECK_THROWS"; + case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS"; + + case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS"; + case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS"; + case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS"; + + case assertType::DT_WARN_THROWS_WITH : return "WARN_THROWS_WITH"; + case assertType::DT_CHECK_THROWS_WITH : return "CHECK_THROWS_WITH"; + case assertType::DT_REQUIRE_THROWS_WITH : return "REQUIRE_THROWS_WITH"; + + case assertType::DT_WARN_THROWS_WITH_AS : return "WARN_THROWS_WITH_AS"; + case assertType::DT_CHECK_THROWS_WITH_AS : return "CHECK_THROWS_WITH_AS"; + case assertType::DT_REQUIRE_THROWS_WITH_AS : return "REQUIRE_THROWS_WITH_AS"; + + case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW"; + case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW"; + case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW"; + + case assertType::DT_WARN_EQ : return "WARN_EQ"; + case assertType::DT_CHECK_EQ : return "CHECK_EQ"; + case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ"; + case assertType::DT_WARN_NE : return "WARN_NE"; + case assertType::DT_CHECK_NE : return "CHECK_NE"; + case assertType::DT_REQUIRE_NE : return "REQUIRE_NE"; + case assertType::DT_WARN_GT : return "WARN_GT"; + case assertType::DT_CHECK_GT : return "CHECK_GT"; + case assertType::DT_REQUIRE_GT : return "REQUIRE_GT"; + case assertType::DT_WARN_LT : return "WARN_LT"; + case assertType::DT_CHECK_LT : return "CHECK_LT"; + case assertType::DT_REQUIRE_LT : return "REQUIRE_LT"; + case assertType::DT_WARN_GE : return "WARN_GE"; + case assertType::DT_CHECK_GE : return "CHECK_GE"; + case assertType::DT_REQUIRE_GE : return "REQUIRE_GE"; + case assertType::DT_WARN_LE : return "WARN_LE"; + case assertType::DT_CHECK_LE : return "CHECK_LE"; + case assertType::DT_REQUIRE_LE : return "REQUIRE_LE"; + + case assertType::DT_WARN_UNARY : return "WARN_UNARY"; + case assertType::DT_CHECK_UNARY : return "CHECK_UNARY"; + case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY"; + case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE"; + case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE"; + case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE"; + } + DOCTEST_MSVC_SUPPRESS_WARNING_POP + return ""; +} +// clang-format on + +const char* failureString(assertType::Enum at) { + if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional + return "WARNING"; + if(at & assertType::is_check) //!OCLINT bitwise operator in conditional + return "ERROR"; + if(at & assertType::is_require) //!OCLINT bitwise operator in conditional + return "FATAL ERROR"; + return ""; +} + +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") +// depending on the current options this will remove the path of filenames +const char* skipPathFromFilename(const char* file) { + if(getContextOptions()->no_path_in_filenames) { + auto back = std::strrchr(file, '\\'); + auto forward = std::strrchr(file, '/'); + if(back || forward) { + if(back > forward) + forward = back; + return forward + 1; + } + } + return file; +} +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +bool SubcaseSignature::operator<(const SubcaseSignature& other) const { + if(m_line != other.m_line) + return m_line < other.m_line; + if(std::strcmp(m_file, other.m_file) != 0) + return std::strcmp(m_file, other.m_file) < 0; + return m_name.compare(other.m_name) < 0; +} + +IContextScope::IContextScope() = default; +IContextScope::~IContextScope() = default; + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +String toString(char* in) { return toString(static_cast(in)); } +String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +String toString(bool in) { return in ? "true" : "false"; } +String toString(float in) { return fpToString(in, 5) + "f"; } +String toString(double in) { return fpToString(in, 10); } +String toString(double long in) { return fpToString(in, 15); } + +#define DOCTEST_TO_STRING_OVERLOAD(type, fmt) \ + String toString(type in) { \ + char buf[64]; \ + std::sprintf(buf, fmt, in); \ + return buf; \ + } + +DOCTEST_TO_STRING_OVERLOAD(char, "%d") +DOCTEST_TO_STRING_OVERLOAD(char signed, "%d") +DOCTEST_TO_STRING_OVERLOAD(char unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int short, "%d") +DOCTEST_TO_STRING_OVERLOAD(int short unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int, "%d") +DOCTEST_TO_STRING_OVERLOAD(unsigned, "%u") +DOCTEST_TO_STRING_OVERLOAD(int long, "%ld") +DOCTEST_TO_STRING_OVERLOAD(int long unsigned, "%lu") +DOCTEST_TO_STRING_OVERLOAD(int long long, "%lld") +DOCTEST_TO_STRING_OVERLOAD(int long long unsigned, "%llu") + +String toString(std::nullptr_t) { return "NULL"; } + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +String toString(const std::string& in) { return in.c_str(); } +#endif // VS 2019 + +Approx::Approx(double value) + : m_epsilon(static_cast(std::numeric_limits::epsilon()) * 100) + , m_scale(1.0) + , m_value(value) {} + +Approx Approx::operator()(double value) const { + Approx approx(value); + approx.epsilon(m_epsilon); + approx.scale(m_scale); + return approx; +} + +Approx& Approx::epsilon(double newEpsilon) { + m_epsilon = newEpsilon; + return *this; +} +Approx& Approx::scale(double newScale) { + m_scale = newScale; + return *this; +} + +bool operator==(double lhs, const Approx& rhs) { + // Thanks to Richard Harris for his help refining this formula + return std::fabs(lhs - rhs.m_value) < + rhs.m_epsilon * (rhs.m_scale + std::max(std::fabs(lhs), std::fabs(rhs.m_value))); +} +bool operator==(const Approx& lhs, double rhs) { return operator==(rhs, lhs); } +bool operator!=(double lhs, const Approx& rhs) { return !operator==(lhs, rhs); } +bool operator!=(const Approx& lhs, double rhs) { return !operator==(rhs, lhs); } +bool operator<=(double lhs, const Approx& rhs) { return lhs < rhs.m_value || lhs == rhs; } +bool operator<=(const Approx& lhs, double rhs) { return lhs.m_value < rhs || lhs == rhs; } +bool operator>=(double lhs, const Approx& rhs) { return lhs > rhs.m_value || lhs == rhs; } +bool operator>=(const Approx& lhs, double rhs) { return lhs.m_value > rhs || lhs == rhs; } +bool operator<(double lhs, const Approx& rhs) { return lhs < rhs.m_value && lhs != rhs; } +bool operator<(const Approx& lhs, double rhs) { return lhs.m_value < rhs && lhs != rhs; } +bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs != rhs; } +bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } + +String toString(const Approx& in) { + return String("Approx( ") + doctest::toString(in.m_value) + " )"; +} +const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); } + +} // namespace doctest + +#ifdef DOCTEST_CONFIG_DISABLE +namespace doctest { +Context::Context(int, const char* const*) {} +Context::~Context() = default; +void Context::applyCommandLine(int, const char* const*) {} +void Context::addFilter(const char*, const char*) {} +void Context::clearFilters() {} +void Context::setOption(const char*, int) {} +void Context::setOption(const char*, const char*) {} +bool Context::shouldExit() { return false; } +void Context::setAsDefaultForAssertsOutOfTestCases() {} +void Context::setAssertHandler(detail::assert_handler) {} +int Context::run() { return 0; } + +IReporter::~IReporter() = default; + +int IReporter::get_num_active_contexts() { return 0; } +const IContextScope* const* IReporter::get_active_contexts() { return nullptr; } +int IReporter::get_num_stringified_contexts() { return 0; } +const String* IReporter::get_stringified_contexts() { return nullptr; } + +int registerReporter(const char*, int, IReporter*) { return 0; } + +} // namespace doctest +#else // DOCTEST_CONFIG_DISABLE + +#if !defined(DOCTEST_CONFIG_COLORS_NONE) +#if !defined(DOCTEST_CONFIG_COLORS_WINDOWS) && !defined(DOCTEST_CONFIG_COLORS_ANSI) +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_CONFIG_COLORS_WINDOWS +#else // linux +#define DOCTEST_CONFIG_COLORS_ANSI +#endif // platform +#endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI +#endif // DOCTEST_CONFIG_COLORS_NONE + +namespace doctest_detail_test_suite_ns { +// holds the current test suite +doctest::detail::TestSuite& getCurrentTestSuite() { + static doctest::detail::TestSuite data; + return data; +} +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +namespace { + // the int (priority) is part of the key for automatic sorting - sadly one can register a + // reporter with a duplicate name and a different priority but hopefully that won't happen often :| + typedef std::map, reporterCreatorFunc> reporterMap; + + reporterMap& getReporters() { + static reporterMap data; + return data; + } + reporterMap& getListeners() { + static reporterMap data; + return data; + } +} // namespace +namespace detail { +#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, ...) \ + for(auto& curr_rep : g_cs->reporters_currently_used) \ + curr_rep->function(__VA_ARGS__) + + bool checkIfShouldThrow(assertType::Enum at) { + if(at & assertType::is_require) //!OCLINT bitwise operator in conditional + return true; + + if((at & assertType::is_check) //!OCLINT bitwise operator in conditional + && getContextOptions()->abort_after > 0 && + (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >= + getContextOptions()->abort_after) + return true; + + return false; + } + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + [[noreturn]] void throwException() { + g_cs->shouldLogCurrentException = false; + throw TestFailureException(); + } // NOLINT(cert-err60-cpp) +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + void throwException() {} +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +} // namespace detail + +namespace { + using namespace detail; + // matching of a string against a wildcard mask (case sensitivity configurable) taken from + // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing + int wildcmp(const char* str, const char* wild, bool caseSensitive) { + const char* cp = nullptr; + const char* mp = nullptr; + + while((*str) && (*wild != '*')) { + if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) && + (*wild != '?')) { + return 0; + } + wild++; + str++; + } + + while(*str) { + if(*wild == '*') { + if(!*++wild) { + return 1; + } + mp = wild; + cp = str + 1; + } else if((caseSensitive ? (*wild == *str) : (tolower(*wild) == tolower(*str))) || + (*wild == '?')) { + wild++; + str++; + } else { + wild = mp; //!OCLINT parameter reassignment + str = cp++; //!OCLINT parameter reassignment + } + } + + while(*wild == '*') { + wild++; + } + return !*wild; + } + + //// C string hash function (djb2) - taken from http://www.cse.yorku.ca/~oz/hash.html + //unsigned hashStr(unsigned const char* str) { + // unsigned long hash = 5381; + // char c; + // while((c = *str++)) + // hash = ((hash << 5) + hash) + c; // hash * 33 + c + // return hash; + //} + + // checks if the name matches any of the filters (and can be configured what to do when empty) + bool matchesAny(const char* name, const std::vector& filters, bool matchEmpty, + bool caseSensitive) { + if(filters.empty() && matchEmpty) + return true; + for(auto& curr : filters) + if(wildcmp(name, curr.c_str(), caseSensitive)) + return true; + return false; + } +} // namespace +namespace detail { + + Subcase::Subcase(const String& name, const char* file, int line) + : m_signature({name, file, line}) { + ContextState* s = g_cs; + + // check subcase filters + if(s->subcasesStack.size() < size_t(s->subcase_filter_levels)) { + if(!matchesAny(m_signature.m_name.c_str(), s->filters[6], true, s->case_sensitive)) + return; + if(matchesAny(m_signature.m_name.c_str(), s->filters[7], false, s->case_sensitive)) + return; + } + + // if a Subcase on the same level has already been entered + if(s->subcasesStack.size() < size_t(s->subcasesCurrentMaxLevel)) { + s->should_reenter = true; + return; + } + + // push the current signature to the stack so we can check if the + // current stack + the current new subcase have been traversed + s->subcasesStack.push_back(m_signature); + if(s->subcasesPassed.count(s->subcasesStack) != 0) { + // pop - revert to previous stack since we've already passed this + s->subcasesStack.pop_back(); + return; + } + + s->subcasesCurrentMaxLevel = s->subcasesStack.size(); + m_entered = true; + + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature); + } + + Subcase::~Subcase() { + if(m_entered) { + // only mark the subcase stack as passed if no subcases have been skipped + if(g_cs->should_reenter == false) + g_cs->subcasesPassed.insert(g_cs->subcasesStack); + g_cs->subcasesStack.pop_back(); + +#if __cplusplus >= 201703L && defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411 + if(std::uncaught_exceptions() > 0 +#else + if(std::uncaught_exception() +#endif + && g_cs->shouldLogCurrentException) { + DOCTEST_ITERATE_THROUGH_REPORTERS( + test_case_exception, {"exception thrown in subcase - will translate later " + "when the whole test case has been exited (cannot " + "translate while there is an active exception)", + false}); + g_cs->shouldLogCurrentException = false; + } + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); + } + } + + Subcase::operator bool() const { return m_entered; } + + Result::Result(bool passed, const String& decomposition) + : m_passed(passed) + , m_decomp(decomposition) {} + + ExpressionDecomposer::ExpressionDecomposer(assertType::Enum at) + : m_at(at) {} + + TestSuite& TestSuite::operator*(const char* in) { + m_test_suite = in; + // clear state + m_description = nullptr; + m_skip = false; + m_may_fail = false; + m_should_fail = false; + m_expected_failures = 0; + m_timeout = 0; + return *this; + } + + TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, + const char* type, int template_id) { + m_file = file; + m_line = line; + m_name = nullptr; // will be later overridden in operator* + m_test_suite = test_suite.m_test_suite; + m_description = test_suite.m_description; + m_skip = test_suite.m_skip; + m_may_fail = test_suite.m_may_fail; + m_should_fail = test_suite.m_should_fail; + m_expected_failures = test_suite.m_expected_failures; + m_timeout = test_suite.m_timeout; + + m_test = test; + m_type = type; + m_template_id = template_id; + } + + TestCase::TestCase(const TestCase& other) + : TestCaseData() { + *this = other; + } + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function + DOCTEST_MSVC_SUPPRESS_WARNING(26437) // Do not slice + TestCase& TestCase::operator=(const TestCase& other) { + static_cast(*this) = static_cast(other); + + m_test = other.m_test; + m_type = other.m_type; + m_template_id = other.m_template_id; + m_full_name = other.m_full_name; + + if(m_template_id != -1) + m_name = m_full_name.c_str(); + return *this; + } + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + TestCase& TestCase::operator*(const char* in) { + m_name = in; + // make a new name with an appended type for templated test case + if(m_template_id != -1) { + m_full_name = String(m_name) + m_type; + // redirect the name to point to the newly constructed full name + m_name = m_full_name.c_str(); + } + return *this; + } + + bool TestCase::operator<(const TestCase& other) const { + if(m_line != other.m_line) + return m_line < other.m_line; + const int file_cmp = std::strcmp(m_file, other.m_file); + if(file_cmp != 0) + return file_cmp < 0; + return m_template_id < other.m_template_id; + } +} // namespace detail +namespace { + using namespace detail; + // for sorting tests by file/line + bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) { +#if DOCTEST_MSVC + // this is needed because MSVC gives different case for drive letters + // for __FILE__ when evaluated in a header and a source file + const int res = doctest::stricmp(lhs->m_file, rhs->m_file); +#else // MSVC + const int res = std::strcmp(lhs->m_file, rhs->m_file); +#endif // MSVC + if(res != 0) + return res < 0; + if(lhs->m_line != rhs->m_line) + return lhs->m_line < rhs->m_line; + return lhs->m_template_id < rhs->m_template_id; + } + + // for sorting tests by suite/file/line + bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) { + const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite); + if(res != 0) + return res < 0; + return fileOrderComparator(lhs, rhs); + } + + // for sorting tests by name/suite/file/line + bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) { + const int res = std::strcmp(lhs->m_name, rhs->m_name); + if(res != 0) + return res < 0; + return suiteOrderComparator(lhs, rhs); + } + + // all the registered tests + std::set& getRegisteredTests() { + static std::set data; + return data; + } + +#ifdef DOCTEST_CONFIG_COLORS_WINDOWS + HANDLE g_stdoutHandle; + WORD g_origFgAttrs; + WORD g_origBgAttrs; + bool g_attrsInitted = false; + + int colors_init() { + if(!g_attrsInitted) { + g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); + g_attrsInitted = true; + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo); + g_origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED | + BACKGROUND_BLUE | BACKGROUND_INTENSITY); + g_origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED | + FOREGROUND_BLUE | FOREGROUND_INTENSITY); + } + return 0; + } + + int dumy_init_console_colors = colors_init(); +#endif // DOCTEST_CONFIG_COLORS_WINDOWS + + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + void color_to_stream(std::ostream& s, Color::Enum code) { + ((void)s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS + ((void)code); // for DOCTEST_CONFIG_COLORS_NONE +#ifdef DOCTEST_CONFIG_COLORS_ANSI + if(g_no_colors || + (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false)) + return; + + auto col = ""; + // clang-format off + switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement + case Color::Red: col = "[0;31m"; break; + case Color::Green: col = "[0;32m"; break; + case Color::Blue: col = "[0;34m"; break; + case Color::Cyan: col = "[0;36m"; break; + case Color::Yellow: col = "[0;33m"; break; + case Color::Grey: col = "[1;30m"; break; + case Color::LightGrey: col = "[0;37m"; break; + case Color::BrightRed: col = "[1;31m"; break; + case Color::BrightGreen: col = "[1;32m"; break; + case Color::BrightWhite: col = "[1;37m"; break; + case Color::Bright: // invalid + case Color::None: + case Color::White: + default: col = "[0m"; + } + // clang-format on + s << "\033" << col; +#endif // DOCTEST_CONFIG_COLORS_ANSI + +#ifdef DOCTEST_CONFIG_COLORS_WINDOWS + if(g_no_colors || + (isatty(fileno(stdout)) == false && getContextOptions()->force_colors == false)) + return; + +#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(g_stdoutHandle, x | g_origBgAttrs) + + // clang-format off + switch (code) { + case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; + case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break; + case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break; + case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break; + case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break; + case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break; + case Color::Grey: DOCTEST_SET_ATTR(0); break; + case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break; + case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break; + case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break; + case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break; + case Color::None: + case Color::Bright: // invalid + default: DOCTEST_SET_ATTR(g_origFgAttrs); + } + // clang-format on +#endif // DOCTEST_CONFIG_COLORS_WINDOWS + } + DOCTEST_CLANG_SUPPRESS_WARNING_POP + + std::vector& getExceptionTranslators() { + static std::vector data; + return data; + } + + String translateActiveException() { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + String res; + auto& translators = getExceptionTranslators(); + for(auto& curr : translators) + if(curr->translate(res)) + return res; + // clang-format off + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcatch-value") + try { + throw; + } catch(std::exception& ex) { + return ex.what(); + } catch(std::string& msg) { + return msg.c_str(); + } catch(const char* msg) { + return msg; + } catch(...) { + return "unknown exception"; + } + DOCTEST_GCC_SUPPRESS_WARNING_POP +// clang-format on +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + return ""; +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } +} // namespace + +namespace detail { + // used by the macros for registering tests + int regTest(const TestCase& tc) { + getRegisteredTests().insert(tc); + return 0; + } + + // sets the current test suite + int setTestSuite(const TestSuite& ts) { + doctest_detail_test_suite_ns::getCurrentTestSuite() = ts; + return 0; + } + +#ifdef DOCTEST_IS_DEBUGGER_ACTIVE + bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); } +#else // DOCTEST_IS_DEBUGGER_ACTIVE +#ifdef DOCTEST_PLATFORM_MAC + // The following function is taken directly from the following technical note: + // https://developer.apple.com/library/archive/qa/qa1361/_index.html + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive() { + int mib[4]; + kinfo_proc info; + size_t size; + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + info.kp_proc.p_flag = 0; + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + // Call sysctl. + size = sizeof(info); + if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) { + std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n"; + return false; + } + // We're being debugged if the P_TRACED flag is set. + return ((info.kp_proc.p_flag & P_TRACED) != 0); + } +#elif DOCTEST_MSVC || defined(__MINGW32__) + bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } +#else + bool isDebuggerActive() { return false; } +#endif // Platform +#endif // DOCTEST_IS_DEBUGGER_ACTIVE + + void registerExceptionTranslatorImpl(const IExceptionTranslator* et) { + if(std::find(getExceptionTranslators().begin(), getExceptionTranslators().end(), et) == + getExceptionTranslators().end()) + getExceptionTranslators().push_back(et); + } + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + void toStream(std::ostream* s, char* in) { *s << in; } + void toStream(std::ostream* s, const char* in) { *s << in; } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + void toStream(std::ostream* s, bool in) { *s << std::boolalpha << in << std::noboolalpha; } + void toStream(std::ostream* s, float in) { *s << in; } + void toStream(std::ostream* s, double in) { *s << in; } + void toStream(std::ostream* s, double long in) { *s << in; } + + void toStream(std::ostream* s, char in) { *s << in; } + void toStream(std::ostream* s, char signed in) { *s << in; } + void toStream(std::ostream* s, char unsigned in) { *s << in; } + void toStream(std::ostream* s, int short in) { *s << in; } + void toStream(std::ostream* s, int short unsigned in) { *s << in; } + void toStream(std::ostream* s, int in) { *s << in; } + void toStream(std::ostream* s, int unsigned in) { *s << in; } + void toStream(std::ostream* s, int long in) { *s << in; } + void toStream(std::ostream* s, int long unsigned in) { *s << in; } + void toStream(std::ostream* s, int long long in) { *s << in; } + void toStream(std::ostream* s, int long long unsigned in) { *s << in; } + + DOCTEST_THREAD_LOCAL std::vector g_infoContexts; // for logging with INFO() + + ContextScopeBase::ContextScopeBase() { + g_infoContexts.push_back(this); + } + + // destroy cannot be inlined into the destructor because that would mean calling stringify after + // ContextScope has been destroyed (base class destructors run after derived class destructors). + // Instead, ContextScope calls this method directly from its destructor. + void ContextScopeBase::destroy() { +#if __cplusplus >= 201703L && defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411 + if(std::uncaught_exceptions() > 0) { +#else + if(std::uncaught_exception()) { +#endif + std::ostringstream s; + this->stringify(&s); + g_cs->stringifiedContexts.push_back(s.str().c_str()); + } + g_infoContexts.pop_back(); + } + +} // namespace detail +namespace { + using namespace detail; + + std::ostream& file_line_to_stream(std::ostream& s, const char* file, int line, + const char* tail = "") { + const auto opt = getContextOptions(); + s << Color::LightGrey << skipPathFromFilename(file) << (opt->gnu_file_line ? ":" : "(") + << (opt->no_line_numbers ? 0 : line) // 0 or the real num depending on the option + << (opt->gnu_file_line ? ":" : "):") << tail; + return s; + } + +#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH) + struct FatalConditionHandler + { + void reset() {} + }; +#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + + void reportFatal(const std::string&); + +#ifdef DOCTEST_PLATFORM_WINDOWS + + struct SignalDefs + { + DWORD id; + const char* name; + }; + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + SignalDefs signalDefs[] = { + {EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal"}, + {EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow"}, + {EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal"}, + {EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error"}, + }; + + struct FatalConditionHandler + { + static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) { + for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { + reportFatal(signalDefs[i].name); + break; + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + FatalConditionHandler() { + isSet = true; + // 32k seems enough for doctest to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + // Register an unhandled exception filter + previousTop = SetUnhandledExceptionFilter(handleException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + } + + static void reset() { + if(isSet) { + // Unregister handler and restore the old guarantee + SetUnhandledExceptionFilter(previousTop); + SetThreadStackGuarantee(&guaranteeSize); + previousTop = nullptr; + isSet = false; + } + } + + ~FatalConditionHandler() { reset(); } + + private: + static bool isSet; + static ULONG guaranteeSize; + static LPTOP_LEVEL_EXCEPTION_FILTER previousTop; + }; + + bool FatalConditionHandler::isSet = false; + ULONG FatalConditionHandler::guaranteeSize = 0; + LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr; + +#else // DOCTEST_PLATFORM_WINDOWS + + struct SignalDefs + { + int id; + const char* name; + }; + SignalDefs signalDefs[] = {{SIGINT, "SIGINT - Terminal interrupt signal"}, + {SIGILL, "SIGILL - Illegal instruction signal"}, + {SIGFPE, "SIGFPE - Floating point error signal"}, + {SIGSEGV, "SIGSEGV - Segmentation violation signal"}, + {SIGTERM, "SIGTERM - Termination request signal"}, + {SIGABRT, "SIGABRT - Abort (abnormal termination) signal"}}; + + struct FatalConditionHandler + { + static bool isSet; + static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)]; + static stack_t oldSigStack; + static char altStackMem[4 * SIGSTKSZ]; + + static void handleSignal(int sig) { + const char* name = ""; + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + SignalDefs& def = signalDefs[i]; + if(sig == def.id) { + name = def.name; + break; + } + } + reset(); + reportFatal(name); + raise(sig); + } + + FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = sizeof(altStackMem); + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = {}; + sa.sa_handler = handleSignal; // NOLINT + sa.sa_flags = SA_ONSTACK; + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + + ~FatalConditionHandler() { reset(); } + static void reset() { + if(isSet) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for(std::size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + isSet = false; + } + } + }; + + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + char FatalConditionHandler::altStackMem[] = {}; + +#endif // DOCTEST_PLATFORM_WINDOWS +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH + +} // namespace + +namespace { + using namespace detail; + +#ifdef DOCTEST_PLATFORM_WINDOWS +#define DOCTEST_OUTPUT_DEBUG_STRING(text) ::OutputDebugStringA(text) +#else + // TODO: integration with XCode and other IDEs +#define DOCTEST_OUTPUT_DEBUG_STRING(text) // NOLINT(clang-diagnostic-unused-macros) +#endif // Platform + + void addAssert(assertType::Enum at) { + if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional + g_cs->numAssertsCurrentTest_atomic++; + } + + void addFailedAssert(assertType::Enum at) { + if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional + g_cs->numAssertsFailedCurrentTest_atomic++; + } + +#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH) + void reportFatal(const std::string& message) { + g_cs->failure_flags |= TestCaseFailureReason::Crash; + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true}); + + while(g_cs->subcasesStack.size()) { + g_cs->subcasesStack.pop_back(); + DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY); + } + + g_cs->finalizeTestCaseData(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); + } +#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH +} // namespace +namespace detail { + + ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type, const char* exception_string) { + m_test_case = g_cs->currentTest; + m_at = at; + m_file = file; + m_line = line; + m_expr = expr; + m_failed = true; + m_threw = false; + m_threw_as = false; + m_exception_type = exception_type; + m_exception_string = exception_string; +#if DOCTEST_MSVC + if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC + ++m_expr; +#endif // MSVC + } + + void ResultBuilder::setResult(const Result& res) { + m_decomp = res.m_decomp; + m_failed = !res.m_passed; + } + + void ResultBuilder::translateException() { + m_threw = true; + m_exception = translateActiveException(); + } + + bool ResultBuilder::log() { + if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + m_failed = !m_threw; + } else if((m_at & assertType::is_throws_as) && (m_at & assertType::is_throws_with)) { //!OCLINT + m_failed = !m_threw_as || (m_exception != m_exception_string); + } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + m_failed = !m_threw_as; + } else if(m_at & assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + m_failed = m_exception != m_exception_string; + } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + m_failed = m_threw; + } + + if(m_exception.size()) + m_exception = String("\"") + m_exception + "\""; + + if(is_running_in_test) { + addAssert(m_at); + DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this); + + if(m_failed) + addFailedAssert(m_at); + } else if(m_failed) { + failed_out_of_a_testing_context(*this); + } + + return m_failed && isDebuggerActive() && + !getContextOptions()->no_breaks; // break into debugger + } + + void ResultBuilder::react() const { + if(m_failed && checkIfShouldThrow(m_at)) + throwException(); + } + + void failed_out_of_a_testing_context(const AssertData& ad) { + if(g_cs->ah) + g_cs->ah(ad); + else + std::abort(); + } + + void decomp_assert(assertType::Enum at, const char* file, int line, const char* expr, + Result result) { + bool failed = !result.m_passed; + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp); + DOCTEST_ASSERT_IN_TESTS(result.m_decomp); + } + + MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) { + m_stream = getTlsOss(); + m_file = file; + m_line = line; + m_severity = severity; + } + + IExceptionTranslator::IExceptionTranslator() = default; + IExceptionTranslator::~IExceptionTranslator() = default; + + bool MessageBuilder::log() { + m_string = getTlsOssResult(); + DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this); + + const bool isWarn = m_severity & assertType::is_warn; + + // warn is just a message in this context so we don't treat it as an assert + if(!isWarn) { + addAssert(m_severity); + addFailedAssert(m_severity); + } + + return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn; // break + } + + void MessageBuilder::react() { + if(m_severity & assertType::is_require) //!OCLINT bitwise operator in conditional + throwException(); + } + + MessageBuilder::~MessageBuilder() = default; +} // namespace detail +namespace { + using namespace detail; + + template + [[noreturn]] void throw_exception(Ex const& e) { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + throw e; +#else // DOCTEST_CONFIG_NO_EXCEPTIONS + std::cerr << "doctest will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; + std::terminate(); +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + } + +#define DOCTEST_INTERNAL_ERROR(msg) \ + throw_exception(std::logic_error( \ + __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) + + // clang-format off + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ); + + ScopedElement( ScopedElement&& other ) noexcept; + ScopedElement& operator=( ScopedElement&& other ) noexcept; + + ~ScopedElement(); + + ScopedElement& writeText( std::string const& text, bool indent = true ); + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer = nullptr; + }; + + XmlWriter( std::ostream& os = std::cout ); + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name ); + + ScopedElement scopedElement( std::string const& name ); + + XmlWriter& endElement(); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, const char* attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + std::stringstream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ); + + //XmlWriter& writeComment( std::string const& text ); + + //void writeStylesheetRef( std::string const& url ); + + //XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + + private: + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +// ================================================================================================= +// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp +// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched. +// ================================================================================================= + +using uchar = unsigned char; + +namespace { + + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; + } + if ((c & 0xF0) == 0xE0) { + return 3; + } + if ((c & 0xF8) == 0xF0) { + return 4; + } + DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + void hexEscapeChar(std::ostream& os, unsigned char c) { + std::ios_base::fmtflags f(os.flags()); + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast(c); + os.flags(f); + } + +} // anonymous namespace + + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void XmlEncode::encodeTo( std::ostream& os ) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: https://www.w3.org/TR/xml/#syntax) + + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + uchar c = m_str[idx]; + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: https://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + uchar nc = m_str[idx + n]; + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + ( value < 0x800 && encBytes > 2) || // removed "0x80 <= value &&" because redundant + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept + : m_writer( other.m_writer ){ + other.m_writer = nullptr; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + return *this; + } + + + XmlWriter::ScopedElement::~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { + m_writer->writeText( text, indent ); + return *this; + } + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + writeDeclaration(); + } + + XmlWriter::~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& XmlWriter::endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << ""; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) { + if( !name.empty() && attribute && attribute[0] != '\0' ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + //XmlWriter& XmlWriter::writeComment( std::string const& text ) { + // ensureTagClosed(); + // m_os << m_indent << ""; + // m_needsNewline = true; + // return *this; + //} + + //void XmlWriter::writeStylesheetRef( std::string const& url ) { + // m_os << "\n"; + //} + + //XmlWriter& XmlWriter::writeBlankLine() { + // ensureTagClosed(); + // m_os << '\n'; + // return *this; + //} + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + void XmlWriter::writeDeclaration() { + m_os << "\n"; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } + +// ================================================================================================= +// End of copy-pasted code from Catch +// ================================================================================================= + + // clang-format on + + struct XmlReporter : public IReporter + { + XmlWriter xml; + std::mutex mutex; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc = nullptr; + + XmlReporter(const ContextOptions& co) + : xml(*co.cout) + , opt(co) {} + + void log_contexts() { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + std::stringstream ss; + for(int i = 0; i < num_contexts; ++i) { + contexts[i]->stringify(&ss); + xml.scopedElement("Info").writeText(ss.str()); + ss.str(""); + } + } + } + + unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } + + void test_case_start_impl(const TestCaseData& in) { + bool open_ts_tag = false; + if(tc != nullptr) { // we have already opened a test suite + if(std::strcmp(tc->m_test_suite, in.m_test_suite) != 0) { + xml.endElement(); + open_ts_tag = true; + } + } + else { + open_ts_tag = true; // first test case ==> first test suite + } + + if(open_ts_tag) { + xml.startElement("TestSuite"); + xml.writeAttribute("name", in.m_test_suite); + } + + tc = ∈ + xml.startElement("TestCase") + .writeAttribute("name", in.m_name) + .writeAttribute("filename", skipPathFromFilename(in.m_file)) + .writeAttribute("line", line(in.m_line)) + .writeAttribute("description", in.m_description); + + if(Approx(in.m_timeout) != 0) + xml.writeAttribute("timeout", in.m_timeout); + if(in.m_may_fail) + xml.writeAttribute("may_fail", true); + if(in.m_should_fail) + xml.writeAttribute("should_fail", true); + } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData& in) override { + test_run_start(); + if(opt.list_reporters) { + for(auto& curr : getListeners()) + xml.scopedElement("Listener") + .writeAttribute("priority", curr.first.first) + .writeAttribute("name", curr.first.second); + for(auto& curr : getReporters()) + xml.scopedElement("Reporter") + .writeAttribute("priority", curr.first.first) + .writeAttribute("name", curr.first.second); + } else if(opt.count || opt.list_test_cases) { + for(unsigned i = 0; i < in.num_data; ++i) { + xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name) + .writeAttribute("testsuite", in.data[i]->m_test_suite) + .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file)) + .writeAttribute("line", line(in.data[i]->m_line)); + } + xml.scopedElement("OverallResultsTestCases") + .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); + } else if(opt.list_test_suites) { + for(unsigned i = 0; i < in.num_data; ++i) + xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]->m_test_suite); + xml.scopedElement("OverallResultsTestCases") + .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters); + xml.scopedElement("OverallResultsTestSuites") + .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters); + } + xml.endElement(); + } + + void test_run_start() override { + // remove .exe extension - mainly to have the same output on UNIX and Windows + std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); +#ifdef DOCTEST_PLATFORM_WINDOWS + if(binary_name.rfind(".exe") != std::string::npos) + binary_name = binary_name.substr(0, binary_name.length() - 4); +#endif // DOCTEST_PLATFORM_WINDOWS + + xml.startElement("doctest").writeAttribute("binary", binary_name); + if(opt.no_version == false) + xml.writeAttribute("version", DOCTEST_VERSION_STR); + + // only the consequential ones (TODO: filters) + xml.scopedElement("Options") + .writeAttribute("order_by", opt.order_by.c_str()) + .writeAttribute("rand_seed", opt.rand_seed) + .writeAttribute("first", opt.first) + .writeAttribute("last", opt.last) + .writeAttribute("abort_after", opt.abort_after) + .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels) + .writeAttribute("case_sensitive", opt.case_sensitive) + .writeAttribute("no_throw", opt.no_throw) + .writeAttribute("no_skip", opt.no_skip); + } + + void test_run_end(const TestRunStats& p) override { + if(tc) // the TestSuite tag - only if there has been at least 1 test case + xml.endElement(); + + xml.scopedElement("OverallResultsAsserts") + .writeAttribute("successes", p.numAsserts - p.numAssertsFailed) + .writeAttribute("failures", p.numAssertsFailed); + + xml.startElement("OverallResultsTestCases") + .writeAttribute("successes", + p.numTestCasesPassingFilters - p.numTestCasesFailed) + .writeAttribute("failures", p.numTestCasesFailed); + if(opt.no_skipped_summary == false) + xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters); + xml.endElement(); + + xml.endElement(); + } + + void test_case_start(const TestCaseData& in) override { + test_case_start_impl(in); + xml.ensureTagClosed(); + } + + void test_case_reenter(const TestCaseData&) override {} + + void test_case_end(const CurrentTestCaseStats& st) override { + xml.startElement("OverallResultsAsserts") + .writeAttribute("successes", + st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest) + .writeAttribute("failures", st.numAssertsFailedCurrentTest); + if(opt.duration) + xml.writeAttribute("duration", st.seconds); + if(tc->m_expected_failures) + xml.writeAttribute("expected_failures", tc->m_expected_failures); + xml.endElement(); + + xml.endElement(); + } + + void test_case_exception(const TestCaseException& e) override { + std::lock_guard lock(mutex); + + xml.scopedElement("Exception") + .writeAttribute("crash", e.is_crash) + .writeText(e.error_string.c_str()); + } + + void subcase_start(const SubcaseSignature& in) override { + std::lock_guard lock(mutex); + + xml.startElement("SubCase") + .writeAttribute("name", in.m_name) + .writeAttribute("filename", skipPathFromFilename(in.m_file)) + .writeAttribute("line", line(in.m_line)); + xml.ensureTagClosed(); + } + + void subcase_end() override { xml.endElement(); } + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed && !opt.success) + return; + + std::lock_guard lock(mutex); + + xml.startElement("Expression") + .writeAttribute("success", !rb.m_failed) + .writeAttribute("type", assertString(rb.m_at)) + .writeAttribute("filename", skipPathFromFilename(rb.m_file)) + .writeAttribute("line", line(rb.m_line)); + + xml.scopedElement("Original").writeText(rb.m_expr); + + if(rb.m_threw) + xml.scopedElement("Exception").writeText(rb.m_exception.c_str()); + + if(rb.m_at & assertType::is_throws_as) + xml.scopedElement("ExpectedException").writeText(rb.m_exception_type); + if(rb.m_at & assertType::is_throws_with) + xml.scopedElement("ExpectedExceptionString").writeText(rb.m_exception_string); + if((rb.m_at & assertType::is_normal) && !rb.m_threw) + xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str()); + + log_contexts(); + + xml.endElement(); + } + + void log_message(const MessageData& mb) override { + std::lock_guard lock(mutex); + + xml.startElement("Message") + .writeAttribute("type", failureString(mb.m_severity)) + .writeAttribute("filename", skipPathFromFilename(mb.m_file)) + .writeAttribute("line", line(mb.m_line)); + + xml.scopedElement("Text").writeText(mb.m_string.c_str()); + + log_contexts(); + + xml.endElement(); + } + + void test_case_skipped(const TestCaseData& in) override { + if(opt.no_skipped_summary == false) { + test_case_start_impl(in); + xml.writeAttribute("skipped", "true"); + xml.endElement(); + } + } + }; + + DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter); + + struct Whitespace + { + int nrSpaces; + explicit Whitespace(int nr) + : nrSpaces(nr) {} + }; + + std::ostream& operator<<(std::ostream& out, const Whitespace& ws) { + if(ws.nrSpaces != 0) + out << std::setw(ws.nrSpaces) << ' '; + return out; + } + + struct ConsoleReporter : public IReporter + { + std::ostream& s; + bool hasLoggedCurrentTestStart; + std::vector subcasesStack; + std::mutex mutex; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc; + + ConsoleReporter(const ContextOptions& co) + : s(*co.cout) + , opt(co) {} + + ConsoleReporter(const ContextOptions& co, std::ostream& ostr) + : s(ostr) + , opt(co) {} + + // ========================================================================================= + // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE + // ========================================================================================= + + void separator_to_stream() { + s << Color::Yellow + << "===============================================================================" + "\n"; + } + + const char* getSuccessOrFailString(bool success, assertType::Enum at, + const char* success_str) { + if(success) + return success_str; + return failureString(at); + } + + Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) { + return success ? Color::BrightGreen : + (at & assertType::is_warn) ? Color::Yellow : Color::Red; + } + + void successOrFailColoredStringToStream(bool success, assertType::Enum at, + const char* success_str = "SUCCESS") { + s << getSuccessOrFailColor(success, at) + << getSuccessOrFailString(success, at, success_str) << ": "; + } + + void log_contexts() { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + + s << Color::None << " logged: "; + for(int i = 0; i < num_contexts; ++i) { + s << (i == 0 ? "" : " "); + contexts[i]->stringify(&s); + s << "\n"; + } + } + + s << "\n"; + } + + void logTestStart() { + if(hasLoggedCurrentTestStart) + return; + + separator_to_stream(); + file_line_to_stream(s, tc->m_file, tc->m_line, "\n"); + if(tc->m_description) + s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n"; + if(tc->m_test_suite && tc->m_test_suite[0] != '\0') + s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n"; + if(strncmp(tc->m_name, " Scenario:", 11) != 0) + s << Color::None << "TEST CASE: "; + s << Color::None << tc->m_name << "\n"; + + for(auto& curr : subcasesStack) + if(curr.m_name[0] != '\0') + s << " " << curr.m_name << "\n"; + + s << "\n"; + + hasLoggedCurrentTestStart = true; + } + + void printVersion() { + if(opt.no_version == false) + s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \"" + << DOCTEST_VERSION_STR << "\"\n"; + } + + void printIntro() { + printVersion(); + s << Color::Cyan << "[doctest] " << Color::None + << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n"; + } + + void printHelp() { + int sizePrefixDisplay = static_cast(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY)); + printVersion(); + // clang-format off + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n"; + s << Color::Cyan << "[doctest] " << Color::None; + s << "filter values: \"str1,str2,str3\" (comma separated strings)\n"; + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "filters use wildcards for matching strings\n"; + s << Color::Cyan << "[doctest] " << Color::None; + s << "something passes a filter if any of the strings in a filter matches\n"; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n"; +#endif + s << Color::Cyan << "[doctest]\n" << Color::None; + s << Color::Cyan << "[doctest] " << Color::None; + s << "Query flags - the program quits after them. Available:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h " + << Whitespace(sizePrefixDisplay*0) << "prints this message\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version " + << Whitespace(sizePrefixDisplay*1) << "prints the version\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count " + << Whitespace(sizePrefixDisplay*1) << "prints the number of matching tests\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases " + << Whitespace(sizePrefixDisplay*1) << "lists all matching tests by name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites " + << Whitespace(sizePrefixDisplay*1) << "lists all matching test suites\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters " + << Whitespace(sizePrefixDisplay*1) << "lists all registered reporters\n\n"; + // ================================================================================== << 79 + s << Color::Cyan << "[doctest] " << Color::None; + s << "The available / options/filters are:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their file\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their file\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite= " + << Whitespace(sizePrefixDisplay*1) << "filters tests by their test suite\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT tests by their test suite\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase= " + << Whitespace(sizePrefixDisplay*1) << "filters subcases by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude= " + << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters= " + << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out= " + << Whitespace(sizePrefixDisplay*1) << "output filename\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by= " + << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n"; + s << Whitespace(sizePrefixDisplay*3) << " - by [file/suite/name/rand]\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed= " + << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first= " + << Whitespace(sizePrefixDisplay*1) << "the first test passing the filters to\n"; + s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last= " + << Whitespace(sizePrefixDisplay*1) << "the last test passing the filters to\n"; + s << Whitespace(sizePrefixDisplay*3) << " execute - for range-based execution\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after= " + << Whitespace(sizePrefixDisplay*1) << "stop after failed assertions\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels= " + << Whitespace(sizePrefixDisplay*1) << "apply filters for the first levels\n"; + s << Color::Cyan << "\n[doctest] " << Color::None; + s << "Bool options - can be used like flags and true is assumed. Available:\n\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success= " + << Whitespace(sizePrefixDisplay*1) << "include successful assertions in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive= " + << Whitespace(sizePrefixDisplay*1) << "filters being treated as case sensitive\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit= " + << Whitespace(sizePrefixDisplay*1) << "exits after the tests finish\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration= " + << Whitespace(sizePrefixDisplay*1) << "prints the time duration of each test\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw= " + << Whitespace(sizePrefixDisplay*1) << "skips exceptions-related assert checks\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode= " + << Whitespace(sizePrefixDisplay*1) << "returns (or exits) always with success\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run= " + << Whitespace(sizePrefixDisplay*1) << "skips all runtime doctest operations\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version= " + << Whitespace(sizePrefixDisplay*1) << "omit the framework version in the output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors= " + << Whitespace(sizePrefixDisplay*1) << "disables colors in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors= " + << Whitespace(sizePrefixDisplay*1) << "use colors even when not in a tty\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks= " + << Whitespace(sizePrefixDisplay*1) << "disables breakpoints in debuggers\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip= " + << Whitespace(sizePrefixDisplay*1) << "don't skip test cases marked as skip\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line= " + << Whitespace(sizePrefixDisplay*1) << ":n: vs (n): for line numbers in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames= " + << Whitespace(sizePrefixDisplay*1) << "only filenames and no paths in output\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers= " + << Whitespace(sizePrefixDisplay*1) << "0 instead of real line numbers in output\n"; + // ================================================================================== << 79 + // clang-format on + + s << Color::Cyan << "\n[doctest] " << Color::None; + s << "for more information visit the project documentation\n\n"; + } + + void printRegisteredReporters() { + printVersion(); + auto printReporters = [this] (const reporterMap& reporters, const char* type) { + if(reporters.size()) { + s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n"; + for(auto& curr : reporters) + s << "priority: " << std::setw(5) << curr.first.first + << " name: " << curr.first.second << "\n"; + } + }; + printReporters(getListeners(), "listeners"); + printReporters(getReporters(), "reporters"); + } + + void list_query_results() { + separator_to_stream(); + if(opt.count || opt.list_test_cases) { + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + } else if(opt.list_test_suites) { + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "test suites with unskipped test cases passing the current filters: " + << g_cs->numTestSuitesPassingFilters << "\n"; + } + } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData& in) override { + if(opt.version) { + printVersion(); + } else if(opt.help) { + printHelp(); + } else if(opt.list_reporters) { + printRegisteredReporters(); + } else if(opt.count || opt.list_test_cases) { + if(opt.list_test_cases) { + s << Color::Cyan << "[doctest] " << Color::None + << "listing all test case names\n"; + separator_to_stream(); + } + + for(unsigned i = 0; i < in.num_data; ++i) + s << Color::None << in.data[i]->m_name << "\n"; + + separator_to_stream(); + + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + + } else if(opt.list_test_suites) { + s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n"; + separator_to_stream(); + + for(unsigned i = 0; i < in.num_data; ++i) + s << Color::None << in.data[i]->m_test_suite << "\n"; + + separator_to_stream(); + + s << Color::Cyan << "[doctest] " << Color::None + << "unskipped test cases passing the current filters: " + << g_cs->numTestCasesPassingFilters << "\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "test suites with unskipped test cases passing the current filters: " + << g_cs->numTestSuitesPassingFilters << "\n"; + } + } + + void test_run_start() override { printIntro(); } + + void test_run_end(const TestRunStats& p) override { + separator_to_stream(); + s << std::dec; + + const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0; + s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(6) + << p.numTestCasesPassingFilters << " | " + << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None : + Color::Green) + << std::setw(6) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" + << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None) + << std::setw(6) << p.numTestCasesFailed << " failed" << Color::None << " | "; + if(opt.no_skipped_summary == false) { + const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters; + s << (numSkipped == 0 ? Color::None : Color::Yellow) << std::setw(6) << numSkipped + << " skipped" << Color::None; + } + s << "\n"; + s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(6) + << p.numAsserts << " | " + << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green) + << std::setw(6) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None + << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(6) + << p.numAssertsFailed << " failed" << Color::None << " |\n"; + s << Color::Cyan << "[doctest] " << Color::None + << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green) + << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl; + } + + void test_case_start(const TestCaseData& in) override { + hasLoggedCurrentTestStart = false; + tc = ∈ + } + + void test_case_reenter(const TestCaseData&) override {} + + void test_case_end(const CurrentTestCaseStats& st) override { + // log the preamble of the test case only if there is something + // else to print - something other than that an assert has failed + if(opt.duration || + (st.failure_flags && st.failure_flags != TestCaseFailureReason::AssertFailure)) + logTestStart(); + + if(opt.duration) + s << Color::None << std::setprecision(6) << std::fixed << st.seconds + << " s: " << tc->m_name << "\n"; + + if(st.failure_flags & TestCaseFailureReason::Timeout) + s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6) + << std::fixed << tc->m_timeout << "!\n"; + + if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) { + s << Color::Red << "Should have failed but didn't! Marking it as failed!\n"; + } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) { + s << Color::Yellow << "Failed as expected so marking it as not failed\n"; + } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) { + s << Color::Yellow << "Allowed to fail so marking it as not failed\n"; + } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) { + s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures + << " times so marking it as failed!\n"; + } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) { + s << Color::Yellow << "Failed exactly " << tc->m_expected_failures + << " times as expected so marking it as not failed!\n"; + } + if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) { + s << Color::Red << "Aborting - too many failed asserts!\n"; + } + s << Color::None; // lgtm [cpp/useless-expression] + } + + void test_case_exception(const TestCaseException& e) override { + logTestStart(); + + file_line_to_stream(s, tc->m_file, tc->m_line, " "); + successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require : + assertType::is_check); + s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ") + << Color::Cyan << e.error_string << "\n"; + + int num_stringified_contexts = get_num_stringified_contexts(); + if(num_stringified_contexts) { + auto stringified_contexts = get_stringified_contexts(); + s << Color::None << " logged: "; + for(int i = num_stringified_contexts; i > 0; --i) { + s << (i == num_stringified_contexts ? "" : " ") + << stringified_contexts[i - 1] << "\n"; + } + } + s << "\n" << Color::None; + } + + void subcase_start(const SubcaseSignature& subc) override { + std::lock_guard lock(mutex); + subcasesStack.push_back(subc); + hasLoggedCurrentTestStart = false; + } + + void subcase_end() override { + std::lock_guard lock(mutex); + subcasesStack.pop_back(); + hasLoggedCurrentTestStart = false; + } + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed && !opt.success) + return; + + std::lock_guard lock(mutex); + + logTestStart(); + + file_line_to_stream(s, rb.m_file, rb.m_line, " "); + successOrFailColoredStringToStream(!rb.m_failed, rb.m_at); + if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) == + 0) //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) " + << Color::None; + + if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n"; + } else if((rb.m_at & assertType::is_throws_as) && + (rb.m_at & assertType::is_throws_with)) { //!OCLINT + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None; + if(rb.m_threw) { + if(!rb.m_failed) { + s << "threw as expected!\n"; + } else { + s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n"; + } + } else { + s << "did NOT throw at all!\n"; + } + } else if(rb.m_at & + assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", " + << rb.m_exception_type << " ) " << Color::None + << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & + assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string << "\" ) " << Color::None + << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan + << rb.m_exception << "\n"; + } else { + s << (rb.m_threw ? "THREW exception: " : + (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")); + if(rb.m_threw) + s << rb.m_exception << "\n"; + else + s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n"; + } + + log_contexts(); + } + + void log_message(const MessageData& mb) override { + std::lock_guard lock(mutex); + + logTestStart(); + + file_line_to_stream(s, mb.m_file, mb.m_line, " "); + s << getSuccessOrFailColor(false, mb.m_severity) + << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity, + "MESSAGE") << ": "; + s << Color::None << mb.m_string << "\n"; + log_contexts(); + } + + void test_case_skipped(const TestCaseData&) override {} + }; + + DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter); + +#ifdef DOCTEST_PLATFORM_WINDOWS + struct DebugOutputWindowReporter : public ConsoleReporter + { + DOCTEST_THREAD_LOCAL static std::ostringstream oss; + + DebugOutputWindowReporter(const ContextOptions& co) + : ConsoleReporter(co, oss) {} + +#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg) \ + void func(type arg) override { \ + bool with_col = g_no_colors; \ + g_no_colors = false; \ + ConsoleReporter::func(arg); \ + DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \ + oss.str(""); \ + g_no_colors = with_col; \ + } + + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_reenter, const TestCaseData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in) + DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in) + }; + + DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss; +#endif // DOCTEST_PLATFORM_WINDOWS + + // the implementation of parseOption() + bool parseOptionImpl(int argc, const char* const* argv, const char* pattern, String* value) { + // going from the end to the beginning and stopping on the first occurrence from the end + for(int i = argc; i > 0; --i) { + auto index = i - 1; + auto temp = std::strstr(argv[index], pattern); + if(temp && (value || strlen(temp) == strlen(pattern))) { //!OCLINT prefer early exits and continue + // eliminate matches in which the chars before the option are not '-' + bool noBadCharsFound = true; + auto curr = argv[index]; + while(curr != temp) { + if(*curr++ != '-') { + noBadCharsFound = false; + break; + } + } + if(noBadCharsFound && argv[index][0] == '-') { + if(value) { + // parsing the value of an option + temp += strlen(pattern); + const unsigned len = strlen(temp); + if(len) { + *value = temp; + return true; + } + } else { + // just a flag - no value + return true; + } + } + } + } + return false; + } + + // parses an option and returns the string after the '=' character + bool parseOption(int argc, const char* const* argv, const char* pattern, String* value = nullptr, + const String& defaultVal = String()) { + if(value) + *value = defaultVal; +#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + // offset (normally 3 for "dt-") to skip prefix + if(parseOptionImpl(argc, argv, pattern + strlen(DOCTEST_CONFIG_OPTIONS_PREFIX), value)) + return true; +#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS + return parseOptionImpl(argc, argv, pattern, value); + } + + // locates a flag on the command line + bool parseFlag(int argc, const char* const* argv, const char* pattern) { + return parseOption(argc, argv, pattern); + } + + // parses a comma separated list of words after a pattern in one of the arguments in argv + bool parseCommaSepArgs(int argc, const char* const* argv, const char* pattern, + std::vector& res) { + String filtersString; + if(parseOption(argc, argv, pattern, &filtersString)) { + // tokenize with "," as a separator + // cppcheck-suppress strtokCalled + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") + auto pch = std::strtok(filtersString.c_str(), ","); // modifies the string + while(pch != nullptr) { + if(strlen(pch)) + res.push_back(pch); + // uses the strtok() internal state to go to the next token + // cppcheck-suppress strtokCalled + pch = std::strtok(nullptr, ","); + } + DOCTEST_CLANG_SUPPRESS_WARNING_POP + return true; + } + return false; + } + + enum optionType + { + option_bool, + option_int + }; + + // parses an int/bool option from the command line + bool parseIntOption(int argc, const char* const* argv, const char* pattern, optionType type, + int& res) { + String parsedValue; + if(!parseOption(argc, argv, pattern, &parsedValue)) + return false; + + if(type == 0) { + // boolean + const char positive[][5] = {"1", "true", "on", "yes"}; // 5 - strlen("true") + 1 + const char negative[][6] = {"0", "false", "off", "no"}; // 6 - strlen("false") + 1 + + // if the value matches any of the positive/negative possibilities + for(unsigned i = 0; i < 4; i++) { + if(parsedValue.compare(positive[i], true) == 0) { + res = 1; //!OCLINT parameter reassignment + return true; + } + if(parsedValue.compare(negative[i], true) == 0) { + res = 0; //!OCLINT parameter reassignment + return true; + } + } + } else { + // integer + // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse... + int theInt = std::atoi(parsedValue.c_str()); // NOLINT + if(theInt != 0) { + res = theInt; //!OCLINT parameter reassignment + return true; + } + } + return false; + } +} // namespace + +Context::Context(int argc, const char* const* argv) + : p(new detail::ContextState) { + parseArgs(argc, argv, true); + if(argc) + p->binary_name = argv[0]; +} + +Context::~Context() { + if(g_cs == p) + g_cs = nullptr; + delete p; +} + +void Context::applyCommandLine(int argc, const char* const* argv) { + parseArgs(argc, argv); + if(argc) + p->binary_name = argv[0]; +} + +// parses args +void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { + using namespace detail; + + // clang-format off + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file=", p->filters[0]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sf=", p->filters[0]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "source-file-exclude=",p->filters[1]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sfe=", p->filters[1]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite=", p->filters[2]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ts=", p->filters[2]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-suite-exclude=", p->filters[3]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tse=", p->filters[3]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case=", p->filters[4]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tc=", p->filters[4]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-case-exclude=", p->filters[5]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tce=", p->filters[5]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase=", p->filters[6]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sc=", p->filters[6]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "subcase-exclude=", p->filters[7]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=", p->filters[7]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=", p->filters[8]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=", p->filters[8]); + // clang-format on + + int intRes = 0; + String strRes; + +#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \ + if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) || \ + parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes)) \ + p->var = !!intRes; \ + else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) || \ + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname)) \ + p->var = true; \ + else if(withDefaults) \ + p->var = default + +#define DOCTEST_PARSE_INT_OPTION(name, sname, var, default) \ + if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_int, intRes) || \ + parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_int, intRes)) \ + p->var = intRes; \ + else if(withDefaults) \ + p->var = default + +#define DOCTEST_PARSE_STR_OPTION(name, sname, var, default) \ + if(parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", &strRes, default) || \ + parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", &strRes, default) || \ + withDefaults) \ + p->var = strRes + + // clang-format off + DOCTEST_PARSE_STR_OPTION("out", "o", out, ""); + DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file"); + DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0); + + DOCTEST_PARSE_INT_OPTION("first", "f", first, 0); + DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX); + + DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0); + DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX); + + DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("exit", "e", exit, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("duration", "d", duration, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-throw", "nt", no_throw, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-exitcode", "ne", no_exitcode, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-run", "nr", no_run, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-version", "nv", no_version, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-colors", "nc", no_colors, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("force-colors", "fc", force_colors, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-breaks", "nb", no_breaks, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skip", "ns", no_skip, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC)); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false); + // clang-format on + + if(withDefaults) { + p->help = false; + p->version = false; + p->count = false; + p->list_test_cases = false; + p->list_test_suites = false; + p->list_reporters = false; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "help") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "h") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "?")) { + p->help = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "version") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "v")) { + p->version = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "count") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "c")) { + p->count = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-cases") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "ltc")) { + p->list_test_cases = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-test-suites") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lts")) { + p->list_test_suites = true; + p->exit = true; + } + if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "list-reporters") || + parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "lr")) { + p->list_reporters = true; + p->exit = true; + } +} + +// allows the user to add procedurally to the filters from the command line +void Context::addFilter(const char* filter, const char* value) { setOption(filter, value); } + +// allows the user to clear all filters from the command line +void Context::clearFilters() { + for(auto& curr : p->filters) + curr.clear(); +} + +// allows the user to override procedurally the int/bool options from the command line +void Context::setOption(const char* option, int value) { + setOption(option, toString(value).c_str()); +} + +// allows the user to override procedurally the string options from the command line +void Context::setOption(const char* option, const char* value) { + auto argv = String("-") + option + "=" + value; + auto lvalue = argv.c_str(); + parseArgs(1, &lvalue); +} + +// users should query this in their main() and exit the program if true +bool Context::shouldExit() { return p->exit; } + +void Context::setAsDefaultForAssertsOutOfTestCases() { g_cs = p; } + +void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; } + +// the main function that does all the filtering and test running +int Context::run() { + using namespace detail; + + // save the old context state in case such was setup - for using asserts out of a testing context + auto old_cs = g_cs; + // this is the current contest + g_cs = p; + is_running_in_test = true; + + g_no_colors = p->no_colors; + p->resetRunData(); + + // stdout by default + p->cout = &std::cout; + p->cerr = &std::cerr; + + // or to a file if specified + std::fstream fstr; + if(p->out.size()) { + fstr.open(p->out.c_str(), std::fstream::out); + p->cout = &fstr; + } + + auto cleanup_and_return = [&]() { + if(fstr.is_open()) + fstr.close(); + + // restore context + g_cs = old_cs; + is_running_in_test = false; + + // we have to free the reporters which were allocated when the run started + for(auto& curr : p->reporters_currently_used) + delete curr; + p->reporters_currently_used.clear(); + + if(p->numTestCasesFailed && !p->no_exitcode) + return EXIT_FAILURE; + return EXIT_SUCCESS; + }; + + // setup default reporter if none is given through the command line + if(p->filters[8].empty()) + p->filters[8].push_back("console"); + + // check to see if any of the registered reporters has been selected + for(auto& curr : getReporters()) { + if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive)) + p->reporters_currently_used.push_back(curr.second(*g_cs)); + } + + // TODO: check if there is nothing in reporters_currently_used + + // prepend all listeners + for(auto& curr : getListeners()) + p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs)); + +#ifdef DOCTEST_PLATFORM_WINDOWS + if(isDebuggerActive()) + p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs)); +#endif // DOCTEST_PLATFORM_WINDOWS + + // handle version, help and no_run + if(p->no_run || p->version || p->help || p->list_reporters) { + DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData()); + + return cleanup_and_return(); + } + + std::vector testArray; + for(auto& curr : getRegisteredTests()) + testArray.push_back(&curr); + p->numTestCases = testArray.size(); + + // sort the collected records + if(!testArray.empty()) { + if(p->order_by.compare("file", true) == 0) { + std::sort(testArray.begin(), testArray.end(), fileOrderComparator); + } else if(p->order_by.compare("suite", true) == 0) { + std::sort(testArray.begin(), testArray.end(), suiteOrderComparator); + } else if(p->order_by.compare("name", true) == 0) { + std::sort(testArray.begin(), testArray.end(), nameOrderComparator); + } else if(p->order_by.compare("rand", true) == 0) { + std::srand(p->rand_seed); + + // random_shuffle implementation + const auto first = &testArray[0]; + for(size_t i = testArray.size() - 1; i > 0; --i) { + int idxToSwap = std::rand() % (i + 1); // NOLINT + + const auto temp = first[i]; + + first[i] = first[idxToSwap]; + first[idxToSwap] = temp; + } + } + } + + std::set testSuitesPassingFilt; + + bool query_mode = p->count || p->list_test_cases || p->list_test_suites; + std::vector queryResults; + + if(!query_mode) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY); + + // invoke the registered functions if they match the filter criteria (or just count them) + for(auto& curr : testArray) { + const auto& tc = *curr; + + bool skip_me = false; + if(tc.m_skip && !p->no_skip) + skip_me = true; + + if(!matchesAny(tc.m_file, p->filters[0], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_file, p->filters[1], false, p->case_sensitive)) + skip_me = true; + if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive)) + skip_me = true; + if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive)) + skip_me = true; + if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive)) + skip_me = true; + + if(!skip_me) + p->numTestCasesPassingFilters++; + + // skip the test if it is not in the execution range + if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) || + (p->first > p->numTestCasesPassingFilters)) + skip_me = true; + + if(skip_me) { + if(!query_mode) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc); + continue; + } + + // do not execute the test if we are to only count the number of filter passing tests + if(p->count) + continue; + + // print the name of the test and don't execute it + if(p->list_test_cases) { + queryResults.push_back(&tc); + continue; + } + + // print the name of the test suite if not done already and don't execute it + if(p->list_test_suites) { + if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') { + queryResults.push_back(&tc); + testSuitesPassingFilt.insert(tc.m_test_suite); + p->numTestSuitesPassingFilters++; + } + continue; + } + + // execute the test if it passes all the filtering + { + p->currentTest = &tc; + + p->failure_flags = TestCaseFailureReason::None; + p->seconds = 0; + + // reset atomic counters + p->numAssertsFailedCurrentTest_atomic = 0; + p->numAssertsCurrentTest_atomic = 0; + + p->subcasesPassed.clear(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc); + + p->timer.start(); + + bool run_test = true; + + do { + // reset some of the fields for subcases (except for the set of fully passed ones) + p->should_reenter = false; + p->subcasesCurrentMaxLevel = 0; + p->subcasesStack.clear(); + + p->shouldLogCurrentException = true; + + // reset stuff for logging with INFO() + p->stringifiedContexts.clear(); + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + try { +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + FatalConditionHandler fatalConditionHandler; // Handle signals + // execute the test + tc.m_test(); + fatalConditionHandler.reset(); +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + } catch(const TestFailureException&) { + p->failure_flags |= TestCaseFailureReason::AssertFailure; + } catch(...) { + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, + {translateActiveException(), false}); + p->failure_flags |= TestCaseFailureReason::Exception; + } +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + + // exit this loop if enough assertions have failed - even if there are more subcases + if(p->abort_after > 0 && + p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) { + run_test = false; + p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts; + } + + if(p->should_reenter && run_test) + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_reenter, tc); + if(!p->should_reenter) + run_test = false; + } while(run_test); + + p->finalizeTestCaseData(); + + DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs); + + p->currentTest = nullptr; + + // stop executing tests if enough assertions have failed + if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after) + break; + } + } + + if(!query_mode) { + DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs); + } else { + QueryData qdata; + qdata.run_stats = g_cs; + qdata.data = queryResults.data(); + qdata.num_data = unsigned(queryResults.size()); + DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata); + } + + // see these issues on the reasoning for this: + // - https://github.com/onqtam/doctest/issues/143#issuecomment-414418903 + // - https://github.com/onqtam/doctest/issues/126 + auto DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS = []() DOCTEST_NOINLINE + { std::cout << std::string(); }; + DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS(); + + return cleanup_and_return(); +} + +IReporter::~IReporter() = default; + +int IReporter::get_num_active_contexts() { return detail::g_infoContexts.size(); } +const IContextScope* const* IReporter::get_active_contexts() { + return get_num_active_contexts() ? &detail::g_infoContexts[0] : nullptr; +} + +int IReporter::get_num_stringified_contexts() { return detail::g_cs->stringifiedContexts.size(); } +const String* IReporter::get_stringified_contexts() { + return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr; +} + +namespace detail { + void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c, bool isReporter) { + if(isReporter) + getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); + else + getListeners().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c)); + } +} // namespace detail + +} // namespace doctest + +#endif // DOCTEST_CONFIG_DISABLE + +#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182 +int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); } +DOCTEST_MSVC_SUPPRESS_WARNING_POP +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_LIBRARY_IMPLEMENTATION +#endif // DOCTEST_CONFIG_IMPLEMENT diff --git a/Firmware/doctest/parts/doctest_fwd.h b/Firmware/doctest/parts/doctest_fwd.h new file mode 100644 index 00000000..031f2cd1 --- /dev/null +++ b/Firmware/doctest/parts/doctest_fwd.h @@ -0,0 +1,2604 @@ +// +// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD +// +// Copyright (c) 2016-2019 Viktor Kirilov +// +// Distributed under the MIT Software License +// See accompanying file LICENSE.txt or copy at +// https://opensource.org/licenses/MIT +// +// The documentation can be found at the library's page: +// https://github.com/onqtam/doctest/blob/master/doc/markdown/readme.md +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= +// +// The library is heavily influenced by Catch - https://github.com/catchorg/Catch2 +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt +// +// The concept of subcases (sections in Catch) and expression decomposition are from there. +// Some parts of the code are taken directly: +// - stringification - the detection of "ostream& operator<<(ostream&, const T&)" and StringMaker<> +// - the Approx() helper class for floating point comparison +// - colors in the console +// - breaking into a debugger +// - signal / SEH handling +// - timer +// - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste) +// +// The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest +// which uses the Boost Software License - Version 1.0 +// see here - https://github.com/martinmoene/lest/blob/master/LICENSE.txt +// +// ================================================================================================= +// ================================================================================================= +// ================================================================================================= + +#ifndef DOCTEST_LIBRARY_INCLUDED +#define DOCTEST_LIBRARY_INCLUDED + +// ================================================================================================= +// == VERSION ====================================================================================== +// ================================================================================================= + +#define DOCTEST_VERSION_MAJOR 2 +#define DOCTEST_VERSION_MINOR 3 +#define DOCTEST_VERSION_PATCH 7 +#define DOCTEST_VERSION_STR "2.3.7" + +#define DOCTEST_VERSION \ + (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) + +// ================================================================================================= +// == COMPILER VERSION ============================================================================= +// ================================================================================================= + +// ideas for the version stuff are taken from here: https://github.com/cxxstuff/cxx_detect + +#define DOCTEST_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR)*10000000 + (MINOR)*100000 + (PATCH)) + +// GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl... +#if defined(_MSC_VER) && defined(_MSC_FULL_VER) +#if _MSC_VER == _MSC_FULL_VER / 10000 +#define DOCTEST_MSVC DOCTEST_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000) +#else // MSVC +#define DOCTEST_MSVC \ + DOCTEST_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000) +#endif // MSVC +#endif // MSVC +#if defined(__clang__) && defined(__clang_minor__) +#define DOCTEST_CLANG DOCTEST_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__) +#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && \ + !defined(__INTEL_COMPILER) +#define DOCTEST_GCC DOCTEST_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#endif // GCC + +#ifndef DOCTEST_MSVC +#define DOCTEST_MSVC 0 +#endif // DOCTEST_MSVC +#ifndef DOCTEST_CLANG +#define DOCTEST_CLANG 0 +#endif // DOCTEST_CLANG +#ifndef DOCTEST_GCC +#define DOCTEST_GCC 0 +#endif // DOCTEST_GCC + +// ================================================================================================= +// == COMPILER WARNINGS HELPERS ==================================================================== +// ================================================================================================= + +#if DOCTEST_CLANG +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push") +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(clang diagnostic ignored w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop") +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH DOCTEST_CLANG_SUPPRESS_WARNING(w) +#else // DOCTEST_CLANG +#define DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +#define DOCTEST_CLANG_SUPPRESS_WARNING(w) +#define DOCTEST_CLANG_SUPPRESS_WARNING_POP +#define DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_CLANG + +#if DOCTEST_GCC +#define DOCTEST_PRAGMA_TO_STR(x) _Pragma(#x) +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push") +#define DOCTEST_GCC_SUPPRESS_WARNING(w) DOCTEST_PRAGMA_TO_STR(GCC diagnostic ignored w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop") +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING(w) +#else // DOCTEST_GCC +#define DOCTEST_GCC_SUPPRESS_WARNING_PUSH +#define DOCTEST_GCC_SUPPRESS_WARNING(w) +#define DOCTEST_GCC_SUPPRESS_WARNING_POP +#define DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_GCC + +#if DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push)) +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop)) +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH DOCTEST_MSVC_SUPPRESS_WARNING(w) +#else // DOCTEST_MSVC +#define DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +#define DOCTEST_MSVC_SUPPRESS_WARNING(w) +#define DOCTEST_MSVC_SUPPRESS_WARNING_POP +#define DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) +#endif // DOCTEST_MSVC + +// ================================================================================================= +// == COMPILER WARNINGS ============================================================================ +// ================================================================================================= + +DOCTEST_CLANG_SUPPRESS_WARNING_PUSH +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wdeprecated") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-local-typedef") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") + +DOCTEST_GCC_SUPPRESS_WARNING_PUSH +DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Wpragmas") +DOCTEST_GCC_SUPPRESS_WARNING("-Weffc++") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow") +DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") +DOCTEST_GCC_SUPPRESS_WARNING("-Wctor-dtor-privacy") +DOCTEST_GCC_SUPPRESS_WARNING("-Wmissing-declarations") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnon-virtual-dtor") +DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs") +DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast") +DOCTEST_GCC_SUPPRESS_WARNING("-Wnoexcept") +DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-promo") + +DOCTEST_MSVC_SUPPRESS_WARNING_PUSH +DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4619) // invalid compiler warning +DOCTEST_MSVC_SUPPRESS_WARNING(4996) // The compiler encountered a deprecated declaration +DOCTEST_MSVC_SUPPRESS_WARNING(4706) // assignment within conditional expression +DOCTEST_MSVC_SUPPRESS_WARNING(4512) // 'class' : assignment operator could not be generated +DOCTEST_MSVC_SUPPRESS_WARNING(4127) // conditional expression is constant +DOCTEST_MSVC_SUPPRESS_WARNING(4820) // padding +DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted +DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe +// static analysis +DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept' +DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable +DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ... +DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtr... +DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' + +// 4548 - expression before comma has no effect; expected expression with side - effect +// 4265 - class has virtual functions, but destructor is not virtual +// 4986 - exception specification does not match previous declaration +// 4350 - behavior change: 'member1' called instead of 'member2' +// 4668 - 'x' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' +// 4365 - conversion from 'int' to 'unsigned long', signed/unsigned mismatch +// 4774 - format string expected in argument 'x' is not a string literal +// 4820 - padding in structs + +// only 4 should be disabled globally: +// - 4514 # unreferenced inline function has been removed +// - 4571 # SEH related +// - 4710 # function not inlined +// - 4711 # function 'x' selected for automatic inline expansion + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \ + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH \ + DOCTEST_MSVC_SUPPRESS_WARNING(4548) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4265) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4986) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4350) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4668) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4365) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4774) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4820) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4625) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4626) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5027) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5026) \ + DOCTEST_MSVC_SUPPRESS_WARNING(4623) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5039) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5045) \ + DOCTEST_MSVC_SUPPRESS_WARNING(5105) + +#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP + +// ================================================================================================= +// == FEATURE DETECTION ============================================================================ +// ================================================================================================= + +// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support +// MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx +// GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html +// MSVC version table: +// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering +// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019) +// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017) +// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) +// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) +// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) +// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) +// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) +// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) + +#if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH) +#define DOCTEST_CONFIG_WINDOWS_SEH +#endif // MSVC +#if defined(DOCTEST_CONFIG_NO_WINDOWS_SEH) && defined(DOCTEST_CONFIG_WINDOWS_SEH) +#undef DOCTEST_CONFIG_WINDOWS_SEH +#endif // DOCTEST_CONFIG_NO_WINDOWS_SEH + +#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \ + !defined(__EMSCRIPTEN__) +#define DOCTEST_CONFIG_POSIX_SIGNALS +#endif // _WIN32 +#if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS) +#undef DOCTEST_CONFIG_POSIX_SIGNALS +#endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // no exceptions +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS +#define DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#if defined(DOCTEST_CONFIG_NO_EXCEPTIONS) && !defined(DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS) +#define DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS && !DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +#if defined(DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) && !defined(DOCTEST_CONFIG_IMPLEMENT) +#define DOCTEST_CONFIG_IMPLEMENT +#endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN + +#if defined(_WIN32) || defined(__CYGWIN__) +#if DOCTEST_MSVC +#define DOCTEST_SYMBOL_EXPORT __declspec(dllexport) +#define DOCTEST_SYMBOL_IMPORT __declspec(dllimport) +#else // MSVC +#define DOCTEST_SYMBOL_EXPORT __attribute__((dllexport)) +#define DOCTEST_SYMBOL_IMPORT __attribute__((dllimport)) +#endif // MSVC +#else // _WIN32 +#define DOCTEST_SYMBOL_EXPORT __attribute__((visibility("default"))) +#define DOCTEST_SYMBOL_IMPORT +#endif // _WIN32 + +#ifdef DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#ifdef DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_EXPORT +#else // DOCTEST_CONFIG_IMPLEMENT +#define DOCTEST_INTERFACE DOCTEST_SYMBOL_IMPORT +#endif // DOCTEST_CONFIG_IMPLEMENT +#else // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL +#define DOCTEST_INTERFACE +#endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL + +#define DOCTEST_EMPTY + +#if DOCTEST_MSVC +#define DOCTEST_NOINLINE __declspec(noinline) +#define DOCTEST_UNUSED +#define DOCTEST_ALIGNMENT(x) +#else // MSVC +#define DOCTEST_NOINLINE __attribute__((noinline)) +#define DOCTEST_UNUSED __attribute__((unused)) +#define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x))) +#endif // MSVC + +// ================================================================================================= +// == FEATURE DETECTION END ======================================================================== +// ================================================================================================= + +// internal macros for string concatenation and anonymous variable name generation +#define DOCTEST_CAT_IMPL(s1, s2) s1##s2 +#define DOCTEST_CAT(s1, s2) DOCTEST_CAT_IMPL(s1, s2) +#ifdef __COUNTER__ // not standard and may be missing for some compilers +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __COUNTER__) +#else // __COUNTER__ +#define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__) +#endif // __COUNTER__ + +#define DOCTEST_TOSTR(x) #x + +#ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x& +#else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE +#define DOCTEST_REF_WRAP(x) x +#endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE + +// not using __APPLE__ because... this is how Catch does it +#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED +#define DOCTEST_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define DOCTEST_PLATFORM_IPHONE +#elif defined(_WIN32) +#define DOCTEST_PLATFORM_WINDOWS +#else // DOCTEST_PLATFORM +#define DOCTEST_PLATFORM_LINUX +#endif // DOCTEST_PLATFORM + +#define DOCTEST_GLOBAL_NO_WARNINGS(var) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ + DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-variable") \ + static int var DOCTEST_UNUSED // NOLINT(fuchsia-statically-constructed-objects,cert-err58-cpp) +#define DOCTEST_GLOBAL_NO_WARNINGS_END() DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#ifndef DOCTEST_BREAK_INTO_DEBUGGER +// should probably take a look at https://github.com/scottt/debugbreak +#ifdef DOCTEST_PLATFORM_MAC +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) +#elif DOCTEST_MSVC +#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() +#elif defined(__MINGW32__) +DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wredundant-decls") +extern "C" __declspec(dllimport) void __stdcall DebugBreak(); +DOCTEST_GCC_SUPPRESS_WARNING_POP +#define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak() +#else // linux +#define DOCTEST_BREAK_INTO_DEBUGGER() ((void)0) +#endif // linux +#endif // DOCTEST_BREAK_INTO_DEBUGGER + +// this is kept here for backwards compatibility since the config option was changed +#ifdef DOCTEST_CONFIG_USE_IOSFWD +#define DOCTEST_CONFIG_USE_STD_HEADERS +#endif // DOCTEST_CONFIG_USE_IOSFWD + +#ifdef DOCTEST_CONFIG_USE_STD_HEADERS +#include +#include +#include +#else // DOCTEST_CONFIG_USE_STD_HEADERS + +#if DOCTEST_CLANG +// to detect if libc++ is being used with clang (the _LIBCPP_VERSION identifier) +#include +#endif // clang + +#ifdef _LIBCPP_VERSION +#define DOCTEST_STD_NAMESPACE_BEGIN _LIBCPP_BEGIN_NAMESPACE_STD +#define DOCTEST_STD_NAMESPACE_END _LIBCPP_END_NAMESPACE_STD +#else // _LIBCPP_VERSION +#define DOCTEST_STD_NAMESPACE_BEGIN namespace std { +#define DOCTEST_STD_NAMESPACE_END } +#endif // _LIBCPP_VERSION + +// Forward declaring 'X' in namespace std is not permitted by the C++ Standard. +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643) + +DOCTEST_STD_NAMESPACE_BEGIN // NOLINT (cert-dcl58-cpp) +typedef decltype(nullptr) nullptr_t; +template +struct char_traits; +template <> +struct char_traits; +template +class basic_ostream; +typedef basic_ostream> ostream; +template +class tuple; +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +template +class allocator; +template +class basic_string; +using string = basic_string, allocator>; +#endif // VS 2019 +DOCTEST_STD_NAMESPACE_END + +DOCTEST_MSVC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_USE_STD_HEADERS + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#include +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + +namespace doctest { + +DOCTEST_INTERFACE extern bool is_running_in_test; + +// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length +// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for: +// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128) +// - if small - capacity left before going on the heap - using the lowest 5 bits +// - if small - 2 bits are left unused - the second and third highest ones +// - if small - acts as a null terminator if strlen() is 23 (24 including the null terminator) +// and the "is small" bit remains "0" ("as well as the capacity left") so its OK +// Idea taken from this lecture about the string implementation of facebook/folly - fbstring +// https://www.youtube.com/watch?v=kPR8h4-qZdk +// TODO: +// - optimizations - like not deleting memory unnecessarily in operator= and etc. +// - resize/reserve/clear +// - substr +// - replace +// - back/front +// - iterator stuff +// - find & friends +// - push_back/pop_back +// - assign/insert/erase +// - relational operators as free functions - taking const char* as one of the params +class DOCTEST_INTERFACE String +{ + static const unsigned len = 24; //!OCLINT avoid private static members + static const unsigned last = len - 1; //!OCLINT avoid private static members + + struct view // len should be more than sizeof(view) - because of the final byte for flags + { + char* ptr; + unsigned size; + unsigned capacity; + }; + + union + { + char buf[len]; + view data; + }; + + bool isOnStack() const { return (buf[last] & 128) == 0; } + void setOnHeap(); + void setLast(unsigned in = last); + + void copy(const String& other); + +public: + String(); + ~String(); + + // cppcheck-suppress noExplicitConstructor + String(const char* in); + String(const char* in, unsigned in_size); + + String(const String& other); + String& operator=(const String& other); + + String& operator+=(const String& other); + String operator+(const String& other) const; + + String(String&& other); + String& operator=(String&& other); + + char operator[](unsigned i) const; + char& operator[](unsigned i); + + // the only functions I'm willing to leave in the interface - available for inlining + const char* c_str() const { return const_cast(this)->c_str(); } // NOLINT + char* c_str() { + if(isOnStack()) + return reinterpret_cast(buf); + return data.ptr; + } + + unsigned size() const; + unsigned capacity() const; + + int compare(const char* other, bool no_case = false) const; + int compare(const String& other, bool no_case = false) const; +}; + +DOCTEST_INTERFACE bool operator==(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator!=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator<=(const String& lhs, const String& rhs); +DOCTEST_INTERFACE bool operator>=(const String& lhs, const String& rhs); + +DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in); + +namespace Color { + enum Enum + { + None = 0, + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White + }; + + DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code); +} // namespace Color + +namespace assertType { + enum Enum + { + // macro traits + + is_warn = 1, + is_check = 2 * is_warn, + is_require = 2 * is_check, + + is_normal = 2 * is_require, + is_throws = 2 * is_normal, + is_throws_as = 2 * is_throws, + is_throws_with = 2 * is_throws_as, + is_nothrow = 2 * is_throws_with, + + is_false = 2 * is_nothrow, + is_unary = 2 * is_false, // not checked anywhere - used just to distinguish the types + + is_eq = 2 * is_unary, + is_ne = 2 * is_eq, + + is_lt = 2 * is_ne, + is_gt = 2 * is_lt, + + is_ge = 2 * is_gt, + is_le = 2 * is_ge, + + // macro types + + DT_WARN = is_normal | is_warn, + DT_CHECK = is_normal | is_check, + DT_REQUIRE = is_normal | is_require, + + DT_WARN_FALSE = is_normal | is_false | is_warn, + DT_CHECK_FALSE = is_normal | is_false | is_check, + DT_REQUIRE_FALSE = is_normal | is_false | is_require, + + DT_WARN_THROWS = is_throws | is_warn, + DT_CHECK_THROWS = is_throws | is_check, + DT_REQUIRE_THROWS = is_throws | is_require, + + DT_WARN_THROWS_AS = is_throws_as | is_warn, + DT_CHECK_THROWS_AS = is_throws_as | is_check, + DT_REQUIRE_THROWS_AS = is_throws_as | is_require, + + DT_WARN_THROWS_WITH = is_throws_with | is_warn, + DT_CHECK_THROWS_WITH = is_throws_with | is_check, + DT_REQUIRE_THROWS_WITH = is_throws_with | is_require, + + DT_WARN_THROWS_WITH_AS = is_throws_with | is_throws_as | is_warn, + DT_CHECK_THROWS_WITH_AS = is_throws_with | is_throws_as | is_check, + DT_REQUIRE_THROWS_WITH_AS = is_throws_with | is_throws_as | is_require, + + DT_WARN_NOTHROW = is_nothrow | is_warn, + DT_CHECK_NOTHROW = is_nothrow | is_check, + DT_REQUIRE_NOTHROW = is_nothrow | is_require, + + DT_WARN_EQ = is_normal | is_eq | is_warn, + DT_CHECK_EQ = is_normal | is_eq | is_check, + DT_REQUIRE_EQ = is_normal | is_eq | is_require, + + DT_WARN_NE = is_normal | is_ne | is_warn, + DT_CHECK_NE = is_normal | is_ne | is_check, + DT_REQUIRE_NE = is_normal | is_ne | is_require, + + DT_WARN_GT = is_normal | is_gt | is_warn, + DT_CHECK_GT = is_normal | is_gt | is_check, + DT_REQUIRE_GT = is_normal | is_gt | is_require, + + DT_WARN_LT = is_normal | is_lt | is_warn, + DT_CHECK_LT = is_normal | is_lt | is_check, + DT_REQUIRE_LT = is_normal | is_lt | is_require, + + DT_WARN_GE = is_normal | is_ge | is_warn, + DT_CHECK_GE = is_normal | is_ge | is_check, + DT_REQUIRE_GE = is_normal | is_ge | is_require, + + DT_WARN_LE = is_normal | is_le | is_warn, + DT_CHECK_LE = is_normal | is_le | is_check, + DT_REQUIRE_LE = is_normal | is_le | is_require, + + DT_WARN_UNARY = is_normal | is_unary | is_warn, + DT_CHECK_UNARY = is_normal | is_unary | is_check, + DT_REQUIRE_UNARY = is_normal | is_unary | is_require, + + DT_WARN_UNARY_FALSE = is_normal | is_false | is_unary | is_warn, + DT_CHECK_UNARY_FALSE = is_normal | is_false | is_unary | is_check, + DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require, + }; +} // namespace assertType + +DOCTEST_INTERFACE const char* assertString(assertType::Enum at); +DOCTEST_INTERFACE const char* failureString(assertType::Enum at); +DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file); + +struct DOCTEST_INTERFACE TestCaseData +{ + const char* m_file; // the file in which the test was registered + unsigned m_line; // the line where the test was registered + const char* m_name; // name of the test case + const char* m_test_suite; // the test suite in which the test was added + const char* m_description; + bool m_skip; + bool m_may_fail; + bool m_should_fail; + int m_expected_failures; + double m_timeout; +}; + +struct DOCTEST_INTERFACE AssertData +{ + // common - for all asserts + const TestCaseData* m_test_case; + assertType::Enum m_at; + const char* m_file; + int m_line; + const char* m_expr; + bool m_failed; + + // exception-related - for all asserts + bool m_threw; + String m_exception; + + // for normal asserts + String m_decomp; + + // for specific exception-related asserts + bool m_threw_as; + const char* m_exception_type; + const char* m_exception_string; +}; + +struct DOCTEST_INTERFACE MessageData +{ + String m_string; + const char* m_file; + int m_line; + assertType::Enum m_severity; +}; + +struct DOCTEST_INTERFACE SubcaseSignature +{ + String m_name; + const char* m_file; + int m_line; + + bool operator<(const SubcaseSignature& other) const; +}; + +struct DOCTEST_INTERFACE IContextScope +{ + IContextScope(); + virtual ~IContextScope(); + virtual void stringify(std::ostream*) const = 0; +}; + +struct ContextOptions //!OCLINT too many fields +{ + std::ostream* cout; // stdout stream - std::cout by default + std::ostream* cerr; // stderr stream - std::cerr by default + String binary_name; // the test binary name + + // == parameters from the command line + String out; // output filename + String order_by; // how tests should be ordered + unsigned rand_seed; // the seed for rand ordering + + unsigned first; // the first (matching) test to be executed + unsigned last; // the last (matching) test to be executed + + int abort_after; // stop tests after this many failed assertions + int subcase_filter_levels; // apply the subcase filters for the first N levels + + bool success; // include successful assertions in output + bool case_sensitive; // if filtering should be case sensitive + bool exit; // if the program should be exited after the tests are ran/whatever + bool duration; // print the time duration of each test case + bool no_throw; // to skip exceptions-related assertion macros + bool no_exitcode; // if the framework should return 0 as the exitcode + bool no_run; // to not run the tests at all (can be done with an "*" exclude) + bool no_version; // to not print the version of the framework + bool no_colors; // if output to the console should be colorized + bool force_colors; // forces the use of colors even when a tty cannot be detected + bool no_breaks; // to not break into the debugger + bool no_skip; // don't skip test cases which are marked to be skipped + bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x): + bool no_path_in_filenames; // if the path to files should be removed from the output + bool no_line_numbers; // if source code line numbers should be omitted from the output + bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!! + + bool help; // to print the help + bool version; // to print the version + bool count; // if only the count of matching tests is to be retrieved + bool list_test_cases; // to list all tests matching the filters + bool list_test_suites; // to list all suites matching the filters + bool list_reporters; // lists all registered reporters +}; + +namespace detail { +#if defined(DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || defined(DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS) + template + struct enable_if + {}; + + template + struct enable_if + { typedef TYPE type; }; +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format off + template struct remove_reference { typedef T type; }; + template struct remove_reference { typedef T type; }; + template struct remove_reference { typedef T type; }; + + template struct remove_const { typedef T type; }; + template struct remove_const { typedef T type; }; + // clang-format on + + template + struct deferred_false + // cppcheck-suppress unusedStructMember + { static const bool value = false; }; + + namespace has_insertion_operator_impl { + typedef char no; + typedef char yes[2]; + + struct any_t + { + template + // cppcheck-suppress noExplicitConstructor + any_t(const DOCTEST_REF_WRAP(T)); + }; + + yes& testStreamable(std::ostream&); + no testStreamable(no); + + no operator<<(const std::ostream&, const any_t&); + + template + struct has_insertion_operator + { + static std::ostream& s; + static const DOCTEST_REF_WRAP(T) t; + static const bool value = sizeof(decltype(testStreamable(s << t))) == sizeof(yes); + }; + } // namespace has_insertion_operator_impl + + template + struct has_insertion_operator : has_insertion_operator_impl::has_insertion_operator + {}; + + DOCTEST_INTERFACE void my_memcpy(void* dest, const void* src, unsigned num); + + DOCTEST_INTERFACE std::ostream* getTlsOss(); // returns a thread-local ostringstream + DOCTEST_INTERFACE String getTlsOssResult(); + + template + struct StringMakerBase + { + template + static String convert(const DOCTEST_REF_WRAP(T)) { + return "{?}"; + } + }; + + template <> + struct StringMakerBase + { + template + static String convert(const DOCTEST_REF_WRAP(T) in) { + *getTlsOss() << in; + return getTlsOssResult(); + } + }; + + DOCTEST_INTERFACE String rawMemoryToString(const void* object, unsigned size); + + template + String rawMemoryToString(const DOCTEST_REF_WRAP(T) object) { + return rawMemoryToString(&object, sizeof(object)); + } + + template + const char* type_to_string() { + return "<>"; + } +} // namespace detail + +template +struct StringMaker : public detail::StringMakerBase::value> +{}; + +template +struct StringMaker +{ + template + static String convert(U* p) { + if(p) + return detail::rawMemoryToString(p); + return "NULL"; + } +}; + +template +struct StringMaker +{ + static String convert(R C::*p) { + if(p) + return detail::rawMemoryToString(p); + return "NULL"; + } +}; + +template +String toString(const DOCTEST_REF_WRAP(T) value) { + return StringMaker::convert(value); +} + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +DOCTEST_INTERFACE String toString(char* in); +DOCTEST_INTERFACE String toString(const char* in); +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +DOCTEST_INTERFACE String toString(bool in); +DOCTEST_INTERFACE String toString(float in); +DOCTEST_INTERFACE String toString(double in); +DOCTEST_INTERFACE String toString(double long in); + +DOCTEST_INTERFACE String toString(char in); +DOCTEST_INTERFACE String toString(char signed in); +DOCTEST_INTERFACE String toString(char unsigned in); +DOCTEST_INTERFACE String toString(int short in); +DOCTEST_INTERFACE String toString(int short unsigned in); +DOCTEST_INTERFACE String toString(int in); +DOCTEST_INTERFACE String toString(int unsigned in); +DOCTEST_INTERFACE String toString(int long in); +DOCTEST_INTERFACE String toString(int long unsigned in); +DOCTEST_INTERFACE String toString(int long long in); +DOCTEST_INTERFACE String toString(int long long unsigned in); +DOCTEST_INTERFACE String toString(std::nullptr_t in); + +#if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) +// see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 +DOCTEST_INTERFACE String toString(const std::string& in); +#endif // VS 2019 + +class DOCTEST_INTERFACE Approx +{ +public: + explicit Approx(double value); + + Approx operator()(double value) const; + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + explicit Approx(const T& value, + typename detail::enable_if::value>::type* = + static_cast(nullptr)) { + *this = Approx(static_cast(value)); + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + Approx& epsilon(double newEpsilon); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + typename detail::enable_if::value, Approx&>::type epsilon( + const T& newEpsilon) { + m_epsilon = static_cast(newEpsilon); + return *this; + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + Approx& scale(double newScale); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template + typename detail::enable_if::value, Approx&>::type scale( + const T& newScale) { + m_scale = static_cast(newScale); + return *this; + } +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format off + DOCTEST_INTERFACE friend bool operator==(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator==(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator!=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator!=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator<=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator<=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator>=(double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator>=(const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator< (double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator< (const Approx & lhs, double rhs); + DOCTEST_INTERFACE friend bool operator> (double lhs, const Approx & rhs); + DOCTEST_INTERFACE friend bool operator> (const Approx & lhs, double rhs); + + DOCTEST_INTERFACE friend String toString(const Approx& in); + +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#define DOCTEST_APPROX_PREFIX \ + template friend typename detail::enable_if::value, bool>::type + + DOCTEST_APPROX_PREFIX operator==(const T& lhs, const Approx& rhs) { return operator==(double(lhs), rhs); } + DOCTEST_APPROX_PREFIX operator==(const Approx& lhs, const T& rhs) { return operator==(rhs, lhs); } + DOCTEST_APPROX_PREFIX operator!=(const T& lhs, const Approx& rhs) { return !operator==(lhs, rhs); } + DOCTEST_APPROX_PREFIX operator!=(const Approx& lhs, const T& rhs) { return !operator==(rhs, lhs); } + DOCTEST_APPROX_PREFIX operator<=(const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator<=(const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator>=(const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) || lhs == rhs; } + DOCTEST_APPROX_PREFIX operator< (const T& lhs, const Approx& rhs) { return double(lhs) < rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator< (const Approx& lhs, const T& rhs) { return lhs.m_value < double(rhs) && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const T& lhs, const Approx& rhs) { return double(lhs) > rhs.m_value && lhs != rhs; } + DOCTEST_APPROX_PREFIX operator> (const Approx& lhs, const T& rhs) { return lhs.m_value > double(rhs) && lhs != rhs; } +#undef DOCTEST_APPROX_PREFIX +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + + // clang-format on + +private: + double m_epsilon; + double m_scale; + double m_value; +}; + +DOCTEST_INTERFACE String toString(const Approx& in); + +DOCTEST_INTERFACE const ContextOptions* getContextOptions(); + +#if !defined(DOCTEST_CONFIG_DISABLE) + +namespace detail { + // clang-format off +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + template struct decay_array { typedef T type; }; + template struct decay_array { typedef T* type; }; + template struct decay_array { typedef T* type; }; + + template struct not_char_pointer { enum { value = 1 }; }; + template<> struct not_char_pointer { enum { value = 0 }; }; + template<> struct not_char_pointer { enum { value = 0 }; }; + + template struct can_use_op : public not_char_pointer::type> {}; +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + // clang-format on + + struct DOCTEST_INTERFACE TestFailureException + { + }; + + DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at); + +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + [[noreturn]] +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + DOCTEST_INTERFACE void throwException(); + + struct DOCTEST_INTERFACE Subcase + { + SubcaseSignature m_signature; + bool m_entered = false; + + Subcase(const String& name, const char* file, int line); + ~Subcase(); + + operator bool() const; + }; + + template + String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, + const DOCTEST_REF_WRAP(R) rhs) { + return toString(lhs) + op + toString(rhs); + } + +#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \ + template \ + DOCTEST_NOINLINE Result operator op(const DOCTEST_REF_WRAP(R) rhs) { \ + bool res = op_macro(lhs, rhs); \ + if(m_at & assertType::is_false) \ + res = !res; \ + if(!res || doctest::getContextOptions()->success) \ + return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \ + return Result(res); \ + } + + // more checks could be added - like in Catch: + // https://github.com/catchorg/Catch2/pull/1480/files + // https://github.com/catchorg/Catch2/pull/1481/files +#define DOCTEST_FORBIT_EXPRESSION(rt, op) \ + template \ + rt& operator op(const R&) { \ + static_assert(deferred_false::value, \ + "Expression Too Complex Please Rewrite As Binary Comparison!"); \ + return *this; \ + } + + struct DOCTEST_INTERFACE Result + { + bool m_passed; + String m_decomp; + + Result(bool passed, const String& decomposition = String()); + + // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence + DOCTEST_FORBIT_EXPRESSION(Result, &) + DOCTEST_FORBIT_EXPRESSION(Result, ^) + DOCTEST_FORBIT_EXPRESSION(Result, |) + DOCTEST_FORBIT_EXPRESSION(Result, &&) + DOCTEST_FORBIT_EXPRESSION(Result, ||) + DOCTEST_FORBIT_EXPRESSION(Result, ==) + DOCTEST_FORBIT_EXPRESSION(Result, !=) + DOCTEST_FORBIT_EXPRESSION(Result, <) + DOCTEST_FORBIT_EXPRESSION(Result, >) + DOCTEST_FORBIT_EXPRESSION(Result, <=) + DOCTEST_FORBIT_EXPRESSION(Result, >=) + DOCTEST_FORBIT_EXPRESSION(Result, =) + DOCTEST_FORBIT_EXPRESSION(Result, +=) + DOCTEST_FORBIT_EXPRESSION(Result, -=) + DOCTEST_FORBIT_EXPRESSION(Result, *=) + DOCTEST_FORBIT_EXPRESSION(Result, /=) + DOCTEST_FORBIT_EXPRESSION(Result, %=) + DOCTEST_FORBIT_EXPRESSION(Result, <<=) + DOCTEST_FORBIT_EXPRESSION(Result, >>=) + DOCTEST_FORBIT_EXPRESSION(Result, &=) + DOCTEST_FORBIT_EXPRESSION(Result, ^=) + DOCTEST_FORBIT_EXPRESSION(Result, |=) + }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + DOCTEST_CLANG_SUPPRESS_WARNING_PUSH + DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-conversion") + DOCTEST_CLANG_SUPPRESS_WARNING("-Wsign-compare") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wdouble-promotion") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wconversion") + //DOCTEST_CLANG_SUPPRESS_WARNING("-Wfloat-equal") + + DOCTEST_GCC_SUPPRESS_WARNING_PUSH + DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-conversion") + DOCTEST_GCC_SUPPRESS_WARNING("-Wsign-compare") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wdouble-promotion") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wconversion") + //DOCTEST_GCC_SUPPRESS_WARNING("-Wfloat-equal") + + DOCTEST_MSVC_SUPPRESS_WARNING_PUSH + // https://stackoverflow.com/questions/39479163 what's the difference between 4018 and 4389 + DOCTEST_MSVC_SUPPRESS_WARNING(4388) // signed/unsigned mismatch + DOCTEST_MSVC_SUPPRESS_WARNING(4389) // 'operator' : signed/unsigned mismatch + DOCTEST_MSVC_SUPPRESS_WARNING(4018) // 'expression' : signed/unsigned mismatch + //DOCTEST_MSVC_SUPPRESS_WARNING(4805) // 'operation' : unsafe mix of type 'type' and type 'type' in operation + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + // clang-format off +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE bool +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_COMPARISON_RETURN_TYPE typename enable_if::value || can_use_op::value, bool>::type + inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } + inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } + inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); } + inline bool gt(const char* lhs, const char* rhs) { return String(lhs) > String(rhs); } + inline bool le(const char* lhs, const char* rhs) { return String(lhs) <= String(rhs); } + inline bool ge(const char* lhs, const char* rhs) { return String(lhs) >= String(rhs); } +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + // clang-format on + +#define DOCTEST_RELATIONAL_OP(name, op) \ + template \ + DOCTEST_COMPARISON_RETURN_TYPE name(const DOCTEST_REF_WRAP(L) lhs, \ + const DOCTEST_REF_WRAP(R) rhs) { \ + return lhs op rhs; \ + } + + DOCTEST_RELATIONAL_OP(eq, ==) + DOCTEST_RELATIONAL_OP(ne, !=) + DOCTEST_RELATIONAL_OP(lt, <) + DOCTEST_RELATIONAL_OP(gt, >) + DOCTEST_RELATIONAL_OP(le, <=) + DOCTEST_RELATIONAL_OP(ge, >=) + +#ifndef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) l == r +#define DOCTEST_CMP_NE(l, r) l != r +#define DOCTEST_CMP_GT(l, r) l > r +#define DOCTEST_CMP_LT(l, r) l < r +#define DOCTEST_CMP_GE(l, r) l >= r +#define DOCTEST_CMP_LE(l, r) l <= r +#else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING +#define DOCTEST_CMP_EQ(l, r) eq(l, r) +#define DOCTEST_CMP_NE(l, r) ne(l, r) +#define DOCTEST_CMP_GT(l, r) gt(l, r) +#define DOCTEST_CMP_LT(l, r) lt(l, r) +#define DOCTEST_CMP_GE(l, r) ge(l, r) +#define DOCTEST_CMP_LE(l, r) le(l, r) +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + + template + // cppcheck-suppress copyCtorAndEqOperator + struct Expression_lhs + { + L lhs; + assertType::Enum m_at; + + explicit Expression_lhs(L in, assertType::Enum at) + : lhs(in) + , m_at(at) {} + + DOCTEST_NOINLINE operator Result() { + bool res = !!lhs; + if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional + res = !res; + + if(!res || getContextOptions()->success) + return Result(res, toString(lhs)); + return Result(res); + } + + // clang-format off + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>, " > ", DOCTEST_CMP_GT) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<, " < ", DOCTEST_CMP_LT) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(>=, " >= ", DOCTEST_CMP_GE) //!OCLINT bitwise operator in conditional + DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(<=, " <= ", DOCTEST_CMP_LE) //!OCLINT bitwise operator in conditional + // clang-format on + + // forbidding some expressions based on this table: https://en.cppreference.com/w/cpp/language/operator_precedence + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &&) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ||) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, =) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, +=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, -=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, *=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, /=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, %=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, &=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, ^=) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, |=) + // these 2 are unfortunate because they should be allowed - they have higher precedence over the comparisons, but the + // ExpressionDecomposer class uses the left shift operator to capture the left operand of the binary expression... + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, <<) + DOCTEST_FORBIT_EXPRESSION(Expression_lhs, >>) + }; + +#ifndef DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + DOCTEST_CLANG_SUPPRESS_WARNING_POP + DOCTEST_MSVC_SUPPRESS_WARNING_POP + DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION + + struct DOCTEST_INTERFACE ExpressionDecomposer + { + assertType::Enum m_at; + + ExpressionDecomposer(assertType::Enum at); + + // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table) + // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now... + // https://github.com/catchorg/Catch2/issues/870 + // https://github.com/catchorg/Catch2/issues/565 + template + Expression_lhs operator<<(const DOCTEST_REF_WRAP(L) operand) { + return Expression_lhs(operand, m_at); + } + }; + + struct DOCTEST_INTERFACE TestSuite + { + const char* m_test_suite; + const char* m_description; + bool m_skip; + bool m_may_fail; + bool m_should_fail; + int m_expected_failures; + double m_timeout; + + TestSuite& operator*(const char* in); + + template + TestSuite& operator*(const T& in) { + in.fill(*this); + return *this; + } + }; + + typedef void (*funcType)(); + + struct DOCTEST_INTERFACE TestCase : public TestCaseData + { + funcType m_test; // a function pointer to the test case + + const char* m_type; // for templated test cases - gets appended to the real name + int m_template_id; // an ID used to distinguish between the different versions of a templated test case + String m_full_name; // contains the name (only for templated test cases!) + the template type + + TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite, + const char* type = "", int template_id = -1); + + TestCase(const TestCase& other); + + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function + TestCase& operator=(const TestCase& other); + DOCTEST_MSVC_SUPPRESS_WARNING_POP + + TestCase& operator*(const char* in); + + template + TestCase& operator*(const T& in) { + in.fill(*this); + return *this; + } + + bool operator<(const TestCase& other) const; + }; + + // forward declarations of functions used by the macros + DOCTEST_INTERFACE int regTest(const TestCase& tc); + DOCTEST_INTERFACE int setTestSuite(const TestSuite& ts); + DOCTEST_INTERFACE bool isDebuggerActive(); + + template + int instantiationHelper(const T&) { return 0; } + + namespace binaryAssertComparison { + enum Enum + { + eq = 0, + ne, + gt, + lt, + ge, + le + }; + } // namespace binaryAssertComparison + + // clang-format off + template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L), const DOCTEST_REF_WRAP(R) ) const { return false; } }; + +#define DOCTEST_BINARY_RELATIONAL_OP(n, op) \ + template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } }; + // clang-format on + + DOCTEST_BINARY_RELATIONAL_OP(0, eq) + DOCTEST_BINARY_RELATIONAL_OP(1, ne) + DOCTEST_BINARY_RELATIONAL_OP(2, gt) + DOCTEST_BINARY_RELATIONAL_OP(3, lt) + DOCTEST_BINARY_RELATIONAL_OP(4, ge) + DOCTEST_BINARY_RELATIONAL_OP(5, le) + + struct DOCTEST_INTERFACE ResultBuilder : public AssertData + { + ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr, + const char* exception_type = "", const char* exception_string = ""); + + void setResult(const Result& res); + + template + DOCTEST_NOINLINE void binary_assert(const DOCTEST_REF_WRAP(L) lhs, + const DOCTEST_REF_WRAP(R) rhs) { + m_failed = !RelationalComparator()(lhs, rhs); + if(m_failed || getContextOptions()->success) + m_decomp = stringifyBinaryExpr(lhs, ", ", rhs); + } + + template + DOCTEST_NOINLINE void unary_assert(const DOCTEST_REF_WRAP(L) val) { + m_failed = !val; + + if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional + m_failed = !m_failed; + + if(m_failed || getContextOptions()->success) + m_decomp = toString(val); + } + + void translateException(); + + bool log(); + void react() const; + }; + + namespace assertAction { + enum Enum + { + nothing = 0, + dbgbreak = 1, + shouldthrow = 2 + }; + } // namespace assertAction + + DOCTEST_INTERFACE void failed_out_of_a_testing_context(const AssertData& ad); + + DOCTEST_INTERFACE void decomp_assert(assertType::Enum at, const char* file, int line, + const char* expr, Result result); + +#define DOCTEST_ASSERT_OUT_OF_TESTS(decomp) \ + do { \ + if(!is_running_in_test) { \ + if(failed) { \ + ResultBuilder rb(at, file, line, expr); \ + rb.m_failed = failed; \ + rb.m_decomp = decomp; \ + failed_out_of_a_testing_context(rb); \ + if(isDebuggerActive() && !getContextOptions()->no_breaks) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + if(checkIfShouldThrow(at)) \ + throwException(); \ + } \ + return; \ + } \ + } while(false) + +#define DOCTEST_ASSERT_IN_TESTS(decomp) \ + ResultBuilder rb(at, file, line, expr); \ + rb.m_failed = failed; \ + if(rb.m_failed || getContextOptions()->success) \ + rb.m_decomp = decomp; \ + if(rb.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + if(rb.m_failed && checkIfShouldThrow(at)) \ + throwException() + + template + DOCTEST_NOINLINE void binary_assert(assertType::Enum at, const char* file, int line, + const char* expr, const DOCTEST_REF_WRAP(L) lhs, + const DOCTEST_REF_WRAP(R) rhs) { + bool failed = !RelationalComparator()(lhs, rhs); + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + DOCTEST_ASSERT_IN_TESTS(stringifyBinaryExpr(lhs, ", ", rhs)); + } + + template + DOCTEST_NOINLINE void unary_assert(assertType::Enum at, const char* file, int line, + const char* expr, const DOCTEST_REF_WRAP(L) val) { + bool failed = !val; + + if(at & assertType::is_false) //!OCLINT bitwise operator in conditional + failed = !failed; + + // ################################################################################### + // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT + // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED + // ################################################################################### + DOCTEST_ASSERT_OUT_OF_TESTS(toString(val)); + DOCTEST_ASSERT_IN_TESTS(toString(val)); + } + + struct DOCTEST_INTERFACE IExceptionTranslator + { + IExceptionTranslator(); + virtual ~IExceptionTranslator(); + virtual bool translate(String&) const = 0; + }; + + template + class ExceptionTranslator : public IExceptionTranslator //!OCLINT destructor of virtual class + { + public: + explicit ExceptionTranslator(String (*translateFunction)(T)) + : m_translateFunction(translateFunction) {} + + bool translate(String& res) const override { +#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS + try { + throw; // lgtm [cpp/rethrow-no-exception] + // cppcheck-suppress catchExceptionByValue + } catch(T ex) { // NOLINT + res = m_translateFunction(ex); //!OCLINT parameter reassignment + return true; + } catch(...) {} //!OCLINT - empty catch statement +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + ((void)res); // to silence -Wunused-parameter + return false; + } + + private: + String (*m_translateFunction)(T); + }; + + DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et); + + template + struct StringStreamBase + { + template + static void convert(std::ostream* s, const T& in) { + *s << toString(in); + } + + // always treat char* as a string in this context - no matter + // if DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING is defined + static void convert(std::ostream* s, const char* in) { *s << String(in); } + }; + + template <> + struct StringStreamBase + { + template + static void convert(std::ostream* s, const T& in) { + *s << in; + } + }; + + template + struct StringStream : public StringStreamBase::value> + {}; + + template + void toStream(std::ostream* s, const T& value) { + StringStream::convert(s, value); + } + +#ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + DOCTEST_INTERFACE void toStream(std::ostream* s, char* in); + DOCTEST_INTERFACE void toStream(std::ostream* s, const char* in); +#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING + DOCTEST_INTERFACE void toStream(std::ostream* s, bool in); + DOCTEST_INTERFACE void toStream(std::ostream* s, float in); + DOCTEST_INTERFACE void toStream(std::ostream* s, double in); + DOCTEST_INTERFACE void toStream(std::ostream* s, double long in); + + DOCTEST_INTERFACE void toStream(std::ostream* s, char in); + DOCTEST_INTERFACE void toStream(std::ostream* s, char signed in); + DOCTEST_INTERFACE void toStream(std::ostream* s, char unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int short in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int short unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long unsigned in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long long in); + DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in); + + // ContextScope base class used to allow implementing methods of ContextScope + // that don't depend on the template parameter in doctest.cpp. + class DOCTEST_INTERFACE ContextScopeBase : public IContextScope { + protected: + ContextScopeBase(); + + void destroy(); + }; + + template class ContextScope : public ContextScopeBase + { + const L &lambda_; + + public: + explicit ContextScope(const L &lambda) : lambda_(lambda) {} + + ContextScope(ContextScope &&other) : lambda_(other.lambda_) {} + + void stringify(std::ostream* s) const override { lambda_(s); } + + ~ContextScope() override { destroy(); } + }; + + struct DOCTEST_INTERFACE MessageBuilder : public MessageData + { + std::ostream* m_stream; + + MessageBuilder(const char* file, int line, assertType::Enum severity); + MessageBuilder() = delete; + ~MessageBuilder(); + + template + MessageBuilder& operator<<(const T& in) { + toStream(m_stream, in); + return *this; + } + + bool log(); + void react(); + }; + + template + ContextScope MakeContextScope(const L &lambda) { + return ContextScope(lambda); + } +} // namespace detail + +#define DOCTEST_DEFINE_DECORATOR(name, type, def) \ + struct name \ + { \ + type data; \ + name(type in = def) \ + : data(in) {} \ + void fill(detail::TestCase& state) const { state.DOCTEST_CAT(m_, name) = data; } \ + void fill(detail::TestSuite& state) const { state.DOCTEST_CAT(m_, name) = data; } \ + } + +DOCTEST_DEFINE_DECORATOR(test_suite, const char*, ""); +DOCTEST_DEFINE_DECORATOR(description, const char*, ""); +DOCTEST_DEFINE_DECORATOR(skip, bool, true); +DOCTEST_DEFINE_DECORATOR(timeout, double, 0); +DOCTEST_DEFINE_DECORATOR(may_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(should_fail, bool, true); +DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0); + +template +int registerExceptionTranslator(String (*translateFunction)(T)) { + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") + static detail::ExceptionTranslator exceptionTranslator(translateFunction); + DOCTEST_CLANG_SUPPRESS_WARNING_POP + detail::registerExceptionTranslatorImpl(&exceptionTranslator); + return 0; +} + +} // namespace doctest + +// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro +// introduces an anonymous namespace in which getCurrentTestSuite gets overridden +namespace doctest_detail_test_suite_ns { +DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite(); +} // namespace doctest_detail_test_suite_ns + +namespace doctest { +#else // DOCTEST_CONFIG_DISABLE +template +int registerExceptionTranslator(String (*)(T)) { + return 0; +} +#endif // DOCTEST_CONFIG_DISABLE + +namespace detail { + typedef void (*assert_handler)(const AssertData&); + struct ContextState; +} // namespace detail + +class DOCTEST_INTERFACE Context +{ + detail::ContextState* p; + + void parseArgs(int argc, const char* const* argv, bool withDefaults = false); + +public: + explicit Context(int argc = 0, const char* const* argv = nullptr); + + ~Context(); + + void applyCommandLine(int argc, const char* const* argv); + + void addFilter(const char* filter, const char* value); + void clearFilters(); + void setOption(const char* option, int value); + void setOption(const char* option, const char* value); + + bool shouldExit(); + + void setAsDefaultForAssertsOutOfTestCases(); + + void setAssertHandler(detail::assert_handler ah); + + int run(); +}; + +namespace TestCaseFailureReason { + enum Enum + { + None = 0, + AssertFailure = 1, // an assertion has failed in the test case + Exception = 2, // test case threw an exception + Crash = 4, // a crash... + TooManyFailedAsserts = 8, // the abort-after option + Timeout = 16, // see the timeout decorator + ShouldHaveFailedButDidnt = 32, // see the should_fail decorator + ShouldHaveFailedAndDid = 64, // see the should_fail decorator + DidntFailExactlyNumTimes = 128, // see the expected_failures decorator + FailedExactlyNumTimes = 256, // see the expected_failures decorator + CouldHaveFailedAndDid = 512 // see the may_fail decorator + }; +} // namespace TestCaseFailureReason + +struct DOCTEST_INTERFACE CurrentTestCaseStats +{ + int numAssertsCurrentTest; + int numAssertsFailedCurrentTest; + double seconds; + int failure_flags; // use TestCaseFailureReason::Enum +}; + +struct DOCTEST_INTERFACE TestCaseException +{ + String error_string; + bool is_crash; +}; + +struct DOCTEST_INTERFACE TestRunStats +{ + unsigned numTestCases; + unsigned numTestCasesPassingFilters; + unsigned numTestSuitesPassingFilters; + unsigned numTestCasesFailed; + int numAsserts; + int numAssertsFailed; +}; + +struct QueryData +{ + const TestRunStats* run_stats = nullptr; + const TestCaseData** data = nullptr; + unsigned num_data = 0; +}; + +struct DOCTEST_INTERFACE IReporter +{ + // The constructor has to accept "const ContextOptions&" as a single argument + // which has most of the options for the run + a pointer to the stdout stream + // Reporter(const ContextOptions& in) + + // called when a query should be reported (listing test cases, printing the version, etc.) + virtual void report_query(const QueryData&) = 0; + + // called when the whole test run starts + virtual void test_run_start() = 0; + // called when the whole test run ends (caching a pointer to the input doesn't make sense here) + virtual void test_run_end(const TestRunStats&) = 0; + + // called when a test case is started (safe to cache a pointer to the input) + virtual void test_case_start(const TestCaseData&) = 0; + // called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input) + virtual void test_case_reenter(const TestCaseData&) = 0; + // called when a test case has ended + virtual void test_case_end(const CurrentTestCaseStats&) = 0; + + // called when an exception is thrown from the test case (or it crashes) + virtual void test_case_exception(const TestCaseException&) = 0; + + // called whenever a subcase is entered (don't cache pointers to the input) + virtual void subcase_start(const SubcaseSignature&) = 0; + // called whenever a subcase is exited (don't cache pointers to the input) + virtual void subcase_end() = 0; + + // called for each assert (don't cache pointers to the input) + virtual void log_assert(const AssertData&) = 0; + // called for each message (don't cache pointers to the input) + virtual void log_message(const MessageData&) = 0; + + // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator + // or isn't in the execution range (between first and last) (safe to cache a pointer to the input) + virtual void test_case_skipped(const TestCaseData&) = 0; + + // doctest will not be managing the lifetimes of reporters given to it but this would still be nice to have + virtual ~IReporter(); + + // can obtain all currently active contexts and stringify them if one wishes to do so + static int get_num_active_contexts(); + static const IContextScope* const* get_active_contexts(); + + // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown + static int get_num_stringified_contexts(); + static const String* get_stringified_contexts(); +}; + +namespace detail { + typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&); + + DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c, bool isReporter); + + template + IReporter* reporterCreator(const ContextOptions& o) { + return new Reporter(o); + } +} // namespace detail + +template +int registerReporter(const char* name, int priority, bool isReporter) { + detail::registerReporterImpl(name, priority, detail::reporterCreator, isReporter); + return 0; +} +} // namespace doctest + +// if registering is not disabled +#if !defined(DOCTEST_CONFIG_DISABLE) + +// common code in asserts - for convenience +#define DOCTEST_ASSERT_LOG_AND_REACT(b) \ + if(b.log()) \ + DOCTEST_BREAK_INTO_DEBUGGER(); \ + b.react() + +#ifdef DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x) x; +#else // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS +#define DOCTEST_WRAP_IN_TRY(x) \ + try { \ + x; \ + } catch(...) { _DOCTEST_RB.translateException(); } +#endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS + +#ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS +#define DOCTEST_CAST_TO_VOID(x) \ + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast") \ + static_cast(x); \ + DOCTEST_GCC_SUPPRESS_WARNING_POP +#else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS +#define DOCTEST_CAST_TO_VOID(x) x; +#endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS + +// registers the test by initializing a dummy var with a function +#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators) \ + global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + doctest::detail::regTest( \ + doctest::detail::TestCase( \ + f, __FILE__, __LINE__, \ + doctest_detail_test_suite_ns::getCurrentTestSuite()) * \ + decorators); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators) \ + namespace { \ + struct der : public base \ + { \ + void f(); \ + }; \ + static void func() { \ + der v; \ + v.f(); \ + } \ + DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators) \ + } \ + inline DOCTEST_NOINLINE void der::f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators) \ + static void f(); \ + DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators) \ + static void f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators) \ + static doctest::detail::funcType proxy() { return f; } \ + DOCTEST_REGISTER_FUNCTION(inline const, proxy(), decorators) \ + static void f() + +// for registering tests +#define DOCTEST_TEST_CASE(decorators) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + +// for registering tests in classes - requires C++17 for inline variables! +#if __cplusplus >= 201703L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 12, 0) && _MSVC_LANG >= 201703L) +#define DOCTEST_TEST_CASE_CLASS(decorators) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_PROXY_), \ + decorators) +#else // DOCTEST_TEST_CASE_CLASS +#define DOCTEST_TEST_CASE_CLASS(...) \ + TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER +#endif // DOCTEST_TEST_CASE_CLASS + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(c, decorators) \ + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), c, \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators) + +// for converting types to strings without the header and demangling +#define DOCTEST_TYPE_TO_STRING_IMPL(...) \ + template <> \ + inline const char* type_to_string<__VA_ARGS__>() { \ + return "<" #__VA_ARGS__ ">"; \ + } +#define DOCTEST_TYPE_TO_STRING(...) \ + namespace doctest { namespace detail { \ + DOCTEST_TYPE_TO_STRING_IMPL(__VA_ARGS__) \ + } \ + } \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func) \ + template \ + static void func(); \ + namespace { \ + template \ + struct iter; \ + template \ + struct iter> \ + { \ + iter(const char* file, unsigned line, int index) { \ + doctest::detail::regTest(doctest::detail::TestCase(func, file, line, \ + doctest_detail_test_suite_ns::getCurrentTestSuite(), \ + doctest::detail::type_to_string(), \ + int(line) * 1000 + index) \ + * dec); \ + iter>(file, line, index + 1); \ + } \ + }; \ + template <> \ + struct iter> \ + { \ + iter(const char*, unsigned, int) {} \ + }; \ + } \ + template \ + static void func() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id) \ + DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR), \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)) + +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = \ + doctest::detail::instantiationHelper(DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__>(__FILE__, __LINE__, 0));\ + DOCTEST_GLOBAL_NO_WARNINGS_END() + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), std::tuple<__VA_ARGS__>) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon); \ + DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(anon, anon, std::tuple<__VA_ARGS__>) \ + template \ + static void anon() + +#define DOCTEST_TEST_CASE_TEMPLATE(dec, T, ...) \ + DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) + +// for subcases +#define DOCTEST_SUBCASE(name) \ + if(const doctest::detail::Subcase & DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUBCASE_) DOCTEST_UNUSED = \ + doctest::detail::Subcase(name, __FILE__, __LINE__)) + +// for grouping tests in test suites by using code blocks +#define DOCTEST_TEST_SUITE_IMPL(decorators, ns_name) \ + namespace ns_name { namespace doctest_detail_test_suite_ns { \ + static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() { \ + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \ + static doctest::detail::TestSuite data; \ + static bool inited = false; \ + DOCTEST_MSVC_SUPPRESS_WARNING_POP \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP \ + if(!inited) { \ + data* decorators; \ + inited = true; \ + } \ + return data; \ + } \ + } \ + } \ + namespace ns_name + +#define DOCTEST_TEST_SUITE(decorators) \ + DOCTEST_TEST_SUITE_IMPL(decorators, DOCTEST_ANONYMOUS(_DOCTEST_ANON_SUITE_)) + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(decorators) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * decorators); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = \ + doctest::detail::setTestSuite(doctest::detail::TestSuite() * ""); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for registering exception translators +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(translatorName, signature) \ + inline doctest::String translatorName(signature); \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)) = \ + doctest::registerExceptionTranslator(translatorName); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() \ + doctest::String translatorName(signature) + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ + DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_), \ + signature) + +// for registering reporters +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \ + doctest::registerReporter(name, priority, true); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for registering listeners +#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) \ + DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \ + doctest::registerReporter(name, priority, false); \ + DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for logging +#define DOCTEST_INFO(expression) \ + DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), \ + DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), expression) + +#define DOCTEST_INFO_IMPL(lambda_name, mb_name, s_name, expression) \ + DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4626) \ + auto lambda_name = [&](std::ostream* s_name) { \ + doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \ + mb_name.m_stream = s_name; \ + mb_name << expression; \ + }; \ + DOCTEST_MSVC_SUPPRESS_WARNING_POP \ + auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope(lambda_name) + +#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := " << x) + +#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, x) \ + do { \ + doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \ + mb << x; \ + DOCTEST_ASSERT_LOG_AND_REACT(mb); \ + } while((void)0, 0) + +// clang-format off +#define DOCTEST_ADD_MESSAGE_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) +#define DOCTEST_ADD_FAIL_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) +// clang-format on + +#define DOCTEST_MESSAGE(x) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, x) +#define DOCTEST_FAIL_CHECK(x) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, x) +#define DOCTEST_FAIL(x) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, x) + +#define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility. + +#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_ASSERT_IMPLEMENT_2(assert_type, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.setResult( \ + doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ + << __VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB) \ + DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ + do { \ + DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \ + } while((void)0, 0) + +#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +// necessary for _MESSAGE +#define DOCTEST_ASSERT_IMPLEMENT_2 DOCTEST_ASSERT_IMPLEMENT_1 + +#define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ + DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \ + doctest::detail::decomp_assert( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, \ + doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \ + << __VA_ARGS__) DOCTEST_CLANG_SUPPRESS_WARNING_POP + +#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_WARN(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN, __VA_ARGS__) +#define DOCTEST_CHECK(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK, __VA_ARGS__) +#define DOCTEST_REQUIRE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE, __VA_ARGS__) +#define DOCTEST_WARN_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_WARN_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_CHECK_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__) + +// clang-format off +#define DOCTEST_WARN_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } while((void)0, 0) +#define DOCTEST_CHECK_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } while((void)0, 0) +#define DOCTEST_REQUIRE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } while((void)0, 0) +#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } while((void)0, 0) +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } while((void)0, 0) +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } while((void)0, 0) +// clang-format on + +#define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \ + do { \ + if(!doctest::getContextOptions()->no_throw) { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #expr, #__VA_ARGS__, message); \ + try { \ + DOCTEST_CAST_TO_VOID(expr) \ + } catch(const doctest::detail::remove_const< \ + doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) { \ + _DOCTEST_RB.translateException(); \ + _DOCTEST_RB.m_threw_as = true; \ + } catch(...) { _DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } \ + } while((void)0, 0) + +#define DOCTEST_ASSERT_THROWS_WITH(expr, assert_type, ...) \ + do { \ + if(!doctest::getContextOptions()->no_throw) { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #expr, "", __VA_ARGS__); \ + try { \ + DOCTEST_CAST_TO_VOID(expr) \ + } catch(...) { _DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } \ + } while((void)0, 0) + +#define DOCTEST_ASSERT_NOTHROW(expr, assert_type) \ + do { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #expr); \ + try { \ + DOCTEST_CAST_TO_VOID(expr) \ + } catch(...) { _DOCTEST_RB.translateException(); } \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } while((void)0, 0) + +// clang-format off +#define DOCTEST_WARN_THROWS(expr) DOCTEST_ASSERT_THROWS_WITH(expr, DT_WARN_THROWS, "") +#define DOCTEST_CHECK_THROWS(expr) DOCTEST_ASSERT_THROWS_WITH(expr, DT_CHECK_THROWS, "") +#define DOCTEST_REQUIRE_THROWS(expr) DOCTEST_ASSERT_THROWS_WITH(expr, DT_REQUIRE_THROWS, "") + +#define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, DT_WARN_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, DT_CHECK_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__) + +#define DOCTEST_WARN_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_WARN_NOTHROW) +#define DOCTEST_CHECK_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_CHECK_NOTHROW) +#define DOCTEST_REQUIRE_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_REQUIRE_NOTHROW) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS(expr); } while((void)0, 0) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS(expr); } while((void)0, 0) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS(expr); } while((void)0, 0) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_AS(expr, ex); } while((void)0, 0) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_AS(expr, ex); } while((void)0, 0) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } while((void)0, 0) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_WITH(expr, with); } while((void)0, 0) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_WITH(expr, with); } while((void)0, 0) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } while((void)0, 0) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } while((void)0, 0) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } while((void)0, 0) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } while((void)0, 0) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_NOTHROW(expr); } while((void)0, 0) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_NOTHROW(expr); } while((void)0, 0) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_NOTHROW(expr); } while((void)0, 0) +// clang-format on + +#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_BINARY_ASSERT(assert_type, comp, ...) \ + do { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY( \ + _DOCTEST_RB.binary_assert( \ + __VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } while((void)0, 0) + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + do { \ + doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ + __LINE__, #__VA_ARGS__); \ + DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.unary_assert(__VA_ARGS__)) \ + DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ + } while((void)0, 0) + +#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_BINARY_ASSERT(assert_type, comparison, ...) \ + doctest::detail::binary_assert( \ + doctest::assertType::assert_type, __FILE__, __LINE__, #__VA_ARGS__, __VA_ARGS__) + +#define DOCTEST_UNARY_ASSERT(assert_type, ...) \ + doctest::detail::unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \ + #__VA_ARGS__, __VA_ARGS__) + +#endif // DOCTEST_CONFIG_SUPER_FAST_ASSERTS + +#define DOCTEST_WARN_EQ(...) DOCTEST_BINARY_ASSERT(DT_WARN_EQ, eq, __VA_ARGS__) +#define DOCTEST_CHECK_EQ(...) DOCTEST_BINARY_ASSERT(DT_CHECK_EQ, eq, __VA_ARGS__) +#define DOCTEST_REQUIRE_EQ(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_EQ, eq, __VA_ARGS__) +#define DOCTEST_WARN_NE(...) DOCTEST_BINARY_ASSERT(DT_WARN_NE, ne, __VA_ARGS__) +#define DOCTEST_CHECK_NE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_NE, ne, __VA_ARGS__) +#define DOCTEST_REQUIRE_NE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_NE, ne, __VA_ARGS__) +#define DOCTEST_WARN_GT(...) DOCTEST_BINARY_ASSERT(DT_WARN_GT, gt, __VA_ARGS__) +#define DOCTEST_CHECK_GT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GT, gt, __VA_ARGS__) +#define DOCTEST_REQUIRE_GT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GT, gt, __VA_ARGS__) +#define DOCTEST_WARN_LT(...) DOCTEST_BINARY_ASSERT(DT_WARN_LT, lt, __VA_ARGS__) +#define DOCTEST_CHECK_LT(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LT, lt, __VA_ARGS__) +#define DOCTEST_REQUIRE_LT(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LT, lt, __VA_ARGS__) +#define DOCTEST_WARN_GE(...) DOCTEST_BINARY_ASSERT(DT_WARN_GE, ge, __VA_ARGS__) +#define DOCTEST_CHECK_GE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_GE, ge, __VA_ARGS__) +#define DOCTEST_REQUIRE_GE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_GE, ge, __VA_ARGS__) +#define DOCTEST_WARN_LE(...) DOCTEST_BINARY_ASSERT(DT_WARN_LE, le, __VA_ARGS__) +#define DOCTEST_CHECK_LE(...) DOCTEST_BINARY_ASSERT(DT_CHECK_LE, le, __VA_ARGS__) +#define DOCTEST_REQUIRE_LE(...) DOCTEST_BINARY_ASSERT(DT_REQUIRE_LE, le, __VA_ARGS__) + +#define DOCTEST_WARN_UNARY(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY, __VA_ARGS__) +#define DOCTEST_WARN_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_WARN_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__) + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS + +#undef DOCTEST_WARN_THROWS +#undef DOCTEST_CHECK_THROWS +#undef DOCTEST_REQUIRE_THROWS +#undef DOCTEST_WARN_THROWS_AS +#undef DOCTEST_CHECK_THROWS_AS +#undef DOCTEST_REQUIRE_THROWS_AS +#undef DOCTEST_WARN_THROWS_WITH +#undef DOCTEST_CHECK_THROWS_WITH +#undef DOCTEST_REQUIRE_THROWS_WITH +#undef DOCTEST_WARN_THROWS_WITH_AS +#undef DOCTEST_CHECK_THROWS_WITH_AS +#undef DOCTEST_REQUIRE_THROWS_WITH_AS +#undef DOCTEST_WARN_NOTHROW +#undef DOCTEST_CHECK_NOTHROW +#undef DOCTEST_REQUIRE_NOTHROW + +#undef DOCTEST_WARN_THROWS_MESSAGE +#undef DOCTEST_CHECK_THROWS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_MESSAGE +#undef DOCTEST_WARN_THROWS_AS_MESSAGE +#undef DOCTEST_CHECK_THROWS_AS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_AS_MESSAGE +#undef DOCTEST_WARN_THROWS_WITH_MESSAGE +#undef DOCTEST_CHECK_THROWS_WITH_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_WITH_MESSAGE +#undef DOCTEST_WARN_THROWS_WITH_AS_MESSAGE +#undef DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE +#undef DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE +#undef DOCTEST_WARN_NOTHROW_MESSAGE +#undef DOCTEST_CHECK_NOTHROW_MESSAGE +#undef DOCTEST_REQUIRE_NOTHROW_MESSAGE + +#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#define DOCTEST_WARN_THROWS(expr) ((void)0) +#define DOCTEST_CHECK_THROWS(expr) ((void)0) +#define DOCTEST_REQUIRE_THROWS(expr) ((void)0) +#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_WARN_NOTHROW(expr) ((void)0) +#define DOCTEST_CHECK_NOTHROW(expr) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW(expr) ((void)0) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) + +#else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#undef DOCTEST_REQUIRE +#undef DOCTEST_REQUIRE_FALSE +#undef DOCTEST_REQUIRE_MESSAGE +#undef DOCTEST_REQUIRE_FALSE_MESSAGE +#undef DOCTEST_REQUIRE_EQ +#undef DOCTEST_REQUIRE_NE +#undef DOCTEST_REQUIRE_GT +#undef DOCTEST_REQUIRE_LT +#undef DOCTEST_REQUIRE_GE +#undef DOCTEST_REQUIRE_LE +#undef DOCTEST_REQUIRE_UNARY +#undef DOCTEST_REQUIRE_UNARY_FALSE + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS + +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + +// ================================================================================================= +// == WHAT FOLLOWS IS VERSIONS OF THE MACROS THAT DO NOT DO ANY REGISTERING! == +// == THIS CAN BE ENABLED BY DEFINING DOCTEST_CONFIG_DISABLE GLOBALLY! == +// ================================================================================================= +#else // DOCTEST_CONFIG_DISABLE + +#define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, name) \ + namespace { \ + template \ + struct der : public base \ + { void f(); }; \ + } \ + template \ + inline void der::f() + +#define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, name) \ + template \ + static inline void f() + +// for registering tests +#define DOCTEST_TEST_CASE(name) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + +// for registering tests in classes +#define DOCTEST_TEST_CASE_CLASS(name) \ + DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + +// for registering tests with a fixture +#define DOCTEST_TEST_CASE_FIXTURE(x, name) \ + DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), x, \ + DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name) + +// for converting types to strings without the header and demangling +#define DOCTEST_TYPE_TO_STRING(...) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) +#define DOCTEST_TYPE_TO_STRING_IMPL(...) + +// for typed tests +#define DOCTEST_TEST_CASE_TEMPLATE(name, type, ...) \ + template \ + inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, type, id) \ + template \ + inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)() + +#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...) \ + typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for subcases +#define DOCTEST_SUBCASE(name) + +// for a testsuite block +#define DOCTEST_TEST_SUITE(name) namespace + +// for starting a testsuite block +#define DOCTEST_TEST_SUITE_BEGIN(name) typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +// for ending a testsuite block +#define DOCTEST_TEST_SUITE_END typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) + +#define DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) \ + template \ + static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature) + +#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) +#define DOCTEST_REGISTER_LISTENER(name, priority, reporter) + +#define DOCTEST_INFO(x) ((void)0) +#define DOCTEST_CAPTURE(x) ((void)0) +#define DOCTEST_ADD_MESSAGE_AT(file, line, x) ((void)0) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) ((void)0) +#define DOCTEST_ADD_FAIL_AT(file, line, x) ((void)0) +#define DOCTEST_MESSAGE(x) ((void)0) +#define DOCTEST_FAIL_CHECK(x) ((void)0) +#define DOCTEST_FAIL(x) ((void)0) + +#define DOCTEST_WARN(...) ((void)0) +#define DOCTEST_CHECK(...) ((void)0) +#define DOCTEST_REQUIRE(...) ((void)0) +#define DOCTEST_WARN_FALSE(...) ((void)0) +#define DOCTEST_CHECK_FALSE(...) ((void)0) +#define DOCTEST_REQUIRE_FALSE(...) ((void)0) + +#define DOCTEST_WARN_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_CHECK_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_REQUIRE_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) ((void)0) +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) ((void)0) + +#define DOCTEST_WARN_THROWS(expr) ((void)0) +#define DOCTEST_CHECK_THROWS(expr) ((void)0) +#define DOCTEST_REQUIRE_THROWS(expr) ((void)0) +#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0) +#define DOCTEST_WARN_NOTHROW(expr) ((void)0) +#define DOCTEST_CHECK_NOTHROW(expr) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW(expr) ((void)0) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) + +#define DOCTEST_WARN_EQ(...) ((void)0) +#define DOCTEST_CHECK_EQ(...) ((void)0) +#define DOCTEST_REQUIRE_EQ(...) ((void)0) +#define DOCTEST_WARN_NE(...) ((void)0) +#define DOCTEST_CHECK_NE(...) ((void)0) +#define DOCTEST_REQUIRE_NE(...) ((void)0) +#define DOCTEST_WARN_GT(...) ((void)0) +#define DOCTEST_CHECK_GT(...) ((void)0) +#define DOCTEST_REQUIRE_GT(...) ((void)0) +#define DOCTEST_WARN_LT(...) ((void)0) +#define DOCTEST_CHECK_LT(...) ((void)0) +#define DOCTEST_REQUIRE_LT(...) ((void)0) +#define DOCTEST_WARN_GE(...) ((void)0) +#define DOCTEST_CHECK_GE(...) ((void)0) +#define DOCTEST_REQUIRE_GE(...) ((void)0) +#define DOCTEST_WARN_LE(...) ((void)0) +#define DOCTEST_CHECK_LE(...) ((void)0) +#define DOCTEST_REQUIRE_LE(...) ((void)0) + +#define DOCTEST_WARN_UNARY(...) ((void)0) +#define DOCTEST_CHECK_UNARY(...) ((void)0) +#define DOCTEST_REQUIRE_UNARY(...) ((void)0) +#define DOCTEST_WARN_UNARY_FALSE(...) ((void)0) +#define DOCTEST_CHECK_UNARY_FALSE(...) ((void)0) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) ((void)0) + +#endif // DOCTEST_CONFIG_DISABLE + +// clang-format off +// KEPT FOR BACKWARDS COMPATIBILITY - FORWARDING TO THE RIGHT MACROS +#define DOCTEST_FAST_WARN_EQ DOCTEST_WARN_EQ +#define DOCTEST_FAST_CHECK_EQ DOCTEST_CHECK_EQ +#define DOCTEST_FAST_REQUIRE_EQ DOCTEST_REQUIRE_EQ +#define DOCTEST_FAST_WARN_NE DOCTEST_WARN_NE +#define DOCTEST_FAST_CHECK_NE DOCTEST_CHECK_NE +#define DOCTEST_FAST_REQUIRE_NE DOCTEST_REQUIRE_NE +#define DOCTEST_FAST_WARN_GT DOCTEST_WARN_GT +#define DOCTEST_FAST_CHECK_GT DOCTEST_CHECK_GT +#define DOCTEST_FAST_REQUIRE_GT DOCTEST_REQUIRE_GT +#define DOCTEST_FAST_WARN_LT DOCTEST_WARN_LT +#define DOCTEST_FAST_CHECK_LT DOCTEST_CHECK_LT +#define DOCTEST_FAST_REQUIRE_LT DOCTEST_REQUIRE_LT +#define DOCTEST_FAST_WARN_GE DOCTEST_WARN_GE +#define DOCTEST_FAST_CHECK_GE DOCTEST_CHECK_GE +#define DOCTEST_FAST_REQUIRE_GE DOCTEST_REQUIRE_GE +#define DOCTEST_FAST_WARN_LE DOCTEST_WARN_LE +#define DOCTEST_FAST_CHECK_LE DOCTEST_CHECK_LE +#define DOCTEST_FAST_REQUIRE_LE DOCTEST_REQUIRE_LE + +#define DOCTEST_FAST_WARN_UNARY DOCTEST_WARN_UNARY +#define DOCTEST_FAST_CHECK_UNARY DOCTEST_CHECK_UNARY +#define DOCTEST_FAST_REQUIRE_UNARY DOCTEST_REQUIRE_UNARY +#define DOCTEST_FAST_WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE +#define DOCTEST_FAST_CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE +#define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE + +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INVOKE +// clang-format on + +// BDD style macros +// clang-format off +#define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE(" Scenario: " name) +#define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS(" Scenario: " name) +#define DOCTEST_SCENARIO_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(" Scenario: " name, T, __VA_ARGS__) +#define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(" Scenario: " name, T, id) + +#define DOCTEST_GIVEN(name) DOCTEST_SUBCASE(" Given: " name) +#define DOCTEST_WHEN(name) DOCTEST_SUBCASE(" When: " name) +#define DOCTEST_AND_WHEN(name) DOCTEST_SUBCASE("And when: " name) +#define DOCTEST_THEN(name) DOCTEST_SUBCASE(" Then: " name) +#define DOCTEST_AND_THEN(name) DOCTEST_SUBCASE(" And: " name) +// clang-format on + +// == SHORT VERSIONS OF THE MACROS +#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES) + +#define TEST_CASE DOCTEST_TEST_CASE +#define TEST_CASE_CLASS DOCTEST_TEST_CASE_CLASS +#define TEST_CASE_FIXTURE DOCTEST_TEST_CASE_FIXTURE +#define TYPE_TO_STRING DOCTEST_TYPE_TO_STRING +#define TEST_CASE_TEMPLATE DOCTEST_TEST_CASE_TEMPLATE +#define TEST_CASE_TEMPLATE_DEFINE DOCTEST_TEST_CASE_TEMPLATE_DEFINE +#define TEST_CASE_TEMPLATE_INVOKE DOCTEST_TEST_CASE_TEMPLATE_INVOKE +#define TEST_CASE_TEMPLATE_APPLY DOCTEST_TEST_CASE_TEMPLATE_APPLY +#define SUBCASE DOCTEST_SUBCASE +#define TEST_SUITE DOCTEST_TEST_SUITE +#define TEST_SUITE_BEGIN DOCTEST_TEST_SUITE_BEGIN +#define TEST_SUITE_END DOCTEST_TEST_SUITE_END +#define REGISTER_EXCEPTION_TRANSLATOR DOCTEST_REGISTER_EXCEPTION_TRANSLATOR +#define REGISTER_REPORTER DOCTEST_REGISTER_REPORTER +#define REGISTER_LISTENER DOCTEST_REGISTER_LISTENER +#define INFO DOCTEST_INFO +#define CAPTURE DOCTEST_CAPTURE +#define ADD_MESSAGE_AT DOCTEST_ADD_MESSAGE_AT +#define ADD_FAIL_CHECK_AT DOCTEST_ADD_FAIL_CHECK_AT +#define ADD_FAIL_AT DOCTEST_ADD_FAIL_AT +#define MESSAGE DOCTEST_MESSAGE +#define FAIL_CHECK DOCTEST_FAIL_CHECK +#define FAIL DOCTEST_FAIL +#define TO_LVALUE DOCTEST_TO_LVALUE + +#define WARN DOCTEST_WARN +#define WARN_FALSE DOCTEST_WARN_FALSE +#define WARN_THROWS DOCTEST_WARN_THROWS +#define WARN_THROWS_AS DOCTEST_WARN_THROWS_AS +#define WARN_THROWS_WITH DOCTEST_WARN_THROWS_WITH +#define WARN_THROWS_WITH_AS DOCTEST_WARN_THROWS_WITH_AS +#define WARN_NOTHROW DOCTEST_WARN_NOTHROW +#define CHECK DOCTEST_CHECK +#define CHECK_FALSE DOCTEST_CHECK_FALSE +#define CHECK_THROWS DOCTEST_CHECK_THROWS +#define CHECK_THROWS_AS DOCTEST_CHECK_THROWS_AS +#define CHECK_THROWS_WITH DOCTEST_CHECK_THROWS_WITH +#define CHECK_THROWS_WITH_AS DOCTEST_CHECK_THROWS_WITH_AS +#define CHECK_NOTHROW DOCTEST_CHECK_NOTHROW +#define REQUIRE DOCTEST_REQUIRE +#define REQUIRE_FALSE DOCTEST_REQUIRE_FALSE +#define REQUIRE_THROWS DOCTEST_REQUIRE_THROWS +#define REQUIRE_THROWS_AS DOCTEST_REQUIRE_THROWS_AS +#define REQUIRE_THROWS_WITH DOCTEST_REQUIRE_THROWS_WITH +#define REQUIRE_THROWS_WITH_AS DOCTEST_REQUIRE_THROWS_WITH_AS +#define REQUIRE_NOTHROW DOCTEST_REQUIRE_NOTHROW + +#define WARN_MESSAGE DOCTEST_WARN_MESSAGE +#define WARN_FALSE_MESSAGE DOCTEST_WARN_FALSE_MESSAGE +#define WARN_THROWS_MESSAGE DOCTEST_WARN_THROWS_MESSAGE +#define WARN_THROWS_AS_MESSAGE DOCTEST_WARN_THROWS_AS_MESSAGE +#define WARN_THROWS_WITH_MESSAGE DOCTEST_WARN_THROWS_WITH_MESSAGE +#define WARN_THROWS_WITH_AS_MESSAGE DOCTEST_WARN_THROWS_WITH_AS_MESSAGE +#define WARN_NOTHROW_MESSAGE DOCTEST_WARN_NOTHROW_MESSAGE +#define CHECK_MESSAGE DOCTEST_CHECK_MESSAGE +#define CHECK_FALSE_MESSAGE DOCTEST_CHECK_FALSE_MESSAGE +#define CHECK_THROWS_MESSAGE DOCTEST_CHECK_THROWS_MESSAGE +#define CHECK_THROWS_AS_MESSAGE DOCTEST_CHECK_THROWS_AS_MESSAGE +#define CHECK_THROWS_WITH_MESSAGE DOCTEST_CHECK_THROWS_WITH_MESSAGE +#define CHECK_THROWS_WITH_AS_MESSAGE DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE +#define CHECK_NOTHROW_MESSAGE DOCTEST_CHECK_NOTHROW_MESSAGE +#define REQUIRE_MESSAGE DOCTEST_REQUIRE_MESSAGE +#define REQUIRE_FALSE_MESSAGE DOCTEST_REQUIRE_FALSE_MESSAGE +#define REQUIRE_THROWS_MESSAGE DOCTEST_REQUIRE_THROWS_MESSAGE +#define REQUIRE_THROWS_AS_MESSAGE DOCTEST_REQUIRE_THROWS_AS_MESSAGE +#define REQUIRE_THROWS_WITH_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_MESSAGE +#define REQUIRE_THROWS_WITH_AS_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE +#define REQUIRE_NOTHROW_MESSAGE DOCTEST_REQUIRE_NOTHROW_MESSAGE + +#define SCENARIO DOCTEST_SCENARIO +#define SCENARIO_CLASS DOCTEST_SCENARIO_CLASS +#define SCENARIO_TEMPLATE DOCTEST_SCENARIO_TEMPLATE +#define SCENARIO_TEMPLATE_DEFINE DOCTEST_SCENARIO_TEMPLATE_DEFINE +#define GIVEN DOCTEST_GIVEN +#define WHEN DOCTEST_WHEN +#define AND_WHEN DOCTEST_AND_WHEN +#define THEN DOCTEST_THEN +#define AND_THEN DOCTEST_AND_THEN + +#define WARN_EQ DOCTEST_WARN_EQ +#define CHECK_EQ DOCTEST_CHECK_EQ +#define REQUIRE_EQ DOCTEST_REQUIRE_EQ +#define WARN_NE DOCTEST_WARN_NE +#define CHECK_NE DOCTEST_CHECK_NE +#define REQUIRE_NE DOCTEST_REQUIRE_NE +#define WARN_GT DOCTEST_WARN_GT +#define CHECK_GT DOCTEST_CHECK_GT +#define REQUIRE_GT DOCTEST_REQUIRE_GT +#define WARN_LT DOCTEST_WARN_LT +#define CHECK_LT DOCTEST_CHECK_LT +#define REQUIRE_LT DOCTEST_REQUIRE_LT +#define WARN_GE DOCTEST_WARN_GE +#define CHECK_GE DOCTEST_CHECK_GE +#define REQUIRE_GE DOCTEST_REQUIRE_GE +#define WARN_LE DOCTEST_WARN_LE +#define CHECK_LE DOCTEST_CHECK_LE +#define REQUIRE_LE DOCTEST_REQUIRE_LE +#define WARN_UNARY DOCTEST_WARN_UNARY +#define CHECK_UNARY DOCTEST_CHECK_UNARY +#define REQUIRE_UNARY DOCTEST_REQUIRE_UNARY +#define WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE +#define CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE +#define REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE + +// KEPT FOR BACKWARDS COMPATIBILITY +#define FAST_WARN_EQ DOCTEST_FAST_WARN_EQ +#define FAST_CHECK_EQ DOCTEST_FAST_CHECK_EQ +#define FAST_REQUIRE_EQ DOCTEST_FAST_REQUIRE_EQ +#define FAST_WARN_NE DOCTEST_FAST_WARN_NE +#define FAST_CHECK_NE DOCTEST_FAST_CHECK_NE +#define FAST_REQUIRE_NE DOCTEST_FAST_REQUIRE_NE +#define FAST_WARN_GT DOCTEST_FAST_WARN_GT +#define FAST_CHECK_GT DOCTEST_FAST_CHECK_GT +#define FAST_REQUIRE_GT DOCTEST_FAST_REQUIRE_GT +#define FAST_WARN_LT DOCTEST_FAST_WARN_LT +#define FAST_CHECK_LT DOCTEST_FAST_CHECK_LT +#define FAST_REQUIRE_LT DOCTEST_FAST_REQUIRE_LT +#define FAST_WARN_GE DOCTEST_FAST_WARN_GE +#define FAST_CHECK_GE DOCTEST_FAST_CHECK_GE +#define FAST_REQUIRE_GE DOCTEST_FAST_REQUIRE_GE +#define FAST_WARN_LE DOCTEST_FAST_WARN_LE +#define FAST_CHECK_LE DOCTEST_FAST_CHECK_LE +#define FAST_REQUIRE_LE DOCTEST_FAST_REQUIRE_LE + +#define FAST_WARN_UNARY DOCTEST_FAST_WARN_UNARY +#define FAST_CHECK_UNARY DOCTEST_FAST_CHECK_UNARY +#define FAST_REQUIRE_UNARY DOCTEST_FAST_REQUIRE_UNARY +#define FAST_WARN_UNARY_FALSE DOCTEST_FAST_WARN_UNARY_FALSE +#define FAST_CHECK_UNARY_FALSE DOCTEST_FAST_CHECK_UNARY_FALSE +#define FAST_REQUIRE_UNARY_FALSE DOCTEST_FAST_REQUIRE_UNARY_FALSE + +#define TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE + +#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES + +#if !defined(DOCTEST_CONFIG_DISABLE) + +// this is here to clear the 'current test suite' for the current translation unit - at the top +DOCTEST_TEST_SUITE_END(); + +// add stringification for primitive/fundamental types +namespace doctest { namespace detail { + DOCTEST_TYPE_TO_STRING_IMPL(bool) + DOCTEST_TYPE_TO_STRING_IMPL(float) + DOCTEST_TYPE_TO_STRING_IMPL(double) + DOCTEST_TYPE_TO_STRING_IMPL(long double) + DOCTEST_TYPE_TO_STRING_IMPL(char) + DOCTEST_TYPE_TO_STRING_IMPL(signed char) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned char) +#if !DOCTEST_MSVC || defined(_NATIVE_WCHAR_T_DEFINED) + DOCTEST_TYPE_TO_STRING_IMPL(wchar_t) +#endif // not MSVC or wchar_t support enabled + DOCTEST_TYPE_TO_STRING_IMPL(short int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned short int) + DOCTEST_TYPE_TO_STRING_IMPL(int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned int) + DOCTEST_TYPE_TO_STRING_IMPL(long int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned long int) + DOCTEST_TYPE_TO_STRING_IMPL(long long int) + DOCTEST_TYPE_TO_STRING_IMPL(unsigned long long int) +}} // namespace doctest::detail + +#endif // DOCTEST_CONFIG_DISABLE + +DOCTEST_CLANG_SUPPRESS_WARNING_POP +DOCTEST_MSVC_SUPPRESS_WARNING_POP +DOCTEST_GCC_SUPPRESS_WARNING_POP + +#endif // DOCTEST_LIBRARY_INCLUDED From 31fb65b86cc3a86c4af9f73652ec56a01909e09b Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 27 Mar 2020 21:30:32 -0400 Subject: [PATCH 301/549] Improve braking logic --- Firmware/MotorControl/low_level.cpp | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index c5656f12..a285bc24 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -592,24 +592,20 @@ void update_brake_current() { } // Don't start braking until -Ibus > regen_current_allowed - float brake_current = std::max(-Ibus_sum - board_config.max_regen_current, 0.0f); - float brake_duty = std::max(brake_current * std::abs(board_config.brake_resistance) / vbus_voltage, 0.0f); - brake_duty = std::max((vbus_voltage - board_config.nominal_voltage) / (VBUS_OVERVOLTAGE_LEVEL/0.9f - board_config.nominal_voltage), brake_duty); + float brake_current = -Ibus_sum - board_config.max_regen_current; + float brake_duty = brake_current * std::abs(board_config.brake_resistance) / vbus_voltage; + brake_duty = std::max((vbus_voltage - board_config.nominal_voltage) / (VBUS_OVERVOLTAGE_LEVEL / 0.9f - board_config.nominal_voltage), brake_duty); // Clamp the duty cycle - brake_duty = brake_duty < 0.0f ? 0.0f : (brake_duty > 0.9f ? 0.9f : brake_duty); + brake_duty = std::clamp(brake_duty, 0.0f, 0.9f); // Duty limit at 90% to allow bootstrap caps to charge // If brake_duty is NaN, this expression will also evaluate to false - if ((brake_duty >= 0.0f) && (brake_duty <= 0.9f)) { - int high_on = static_cast(TIM_APB1_PERIOD_CLOCKS * (1.0f - brake_duty)); - int low_off = high_on - TIM_APB1_DEADTIME_CLOCKS; - if (low_off < 0) low_off = 0; - safety_critical_apply_brake_resistor_timings(low_off, high_on); - } else { - //shuts off all motors AND brake resistor, sets error code on all motors. - low_level_fault(Motor::ERROR_BRAKE_CURRENT_OUT_OF_RANGE); - } + int high_on = static_cast(TIM_APB1_PERIOD_CLOCKS * (1.0f - brake_duty)); + int low_off = high_on - TIM_APB1_DEADTIME_CLOCKS; + if (low_off < 0) low_off = 0; + safety_critical_apply_brake_resistor_timings(low_off, high_on); + } From 128ae3935cf6b243882a7d29cc7564c893bf7b97 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 28 Mar 2020 00:15:21 -0400 Subject: [PATCH 302/549] Make Timer generic, add comments --- Firmware/MotorControl/endstop.hpp | 2 +- Firmware/MotorControl/timer.hpp | 15 ++++++++------- Firmware/Tests/test_timer.cpp | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index 02f116b7..7c7fa8b4 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -44,6 +44,6 @@ class Endstop { private: bool pin_state_ = false; float pos_when_pressed_ = 0.0f; - Timer debounceTimer_; + Timer debounceTimer_; }; #endif \ No newline at end of file diff --git a/Firmware/MotorControl/timer.hpp b/Firmware/MotorControl/timer.hpp index e1b6b0b0..a5e571cf 100644 --- a/Firmware/MotorControl/timer.hpp +++ b/Firmware/MotorControl/timer.hpp @@ -1,13 +1,14 @@ #pragma once #include +template class Timer { public: - void setTimeout(const float timeout) { + void setTimeout(const T timeout) { timeout_ = timeout; } - void setInterval(const float interval) { + void setInterval(const T interval) { interval_ = interval; } @@ -25,7 +26,7 @@ class Timer { } void reset() { - timer_ = 0.0f; + timer_ = static_cast(0); } bool expired() { @@ -33,8 +34,8 @@ class Timer { } private: - float timer_ = 0.0f; - float timeout_ = 0.0f; - float interval_ = 0.0f; - bool running_ = false; + T timer_ = static_cast(0); // Current state + T timeout_ = static_cast(0); // Time before + T interval_ = static_cast(0); // Amount to increment each time update() is called + bool running_ = false; // update() only increments if runing_ is true }; diff --git a/Firmware/Tests/test_timer.cpp b/Firmware/Tests/test_timer.cpp index bd34b7c6..7d65ccb2 100644 --- a/Firmware/Tests/test_timer.cpp +++ b/Firmware/Tests/test_timer.cpp @@ -3,7 +3,7 @@ #include "MotorControl/timer.hpp" TEST_CASE("Timer"){ - Timer myTimer; + Timer myTimer; myTimer.setTimeout(10); myTimer.setInterval(1); CHECK(!myTimer.expired()); From f5f9514bfc855b9bcfe54bab364292ddae64ea71 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 28 Mar 2020 00:20:28 -0400 Subject: [PATCH 303/549] Fix comment --- Firmware/MotorControl/timer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/timer.hpp b/Firmware/MotorControl/timer.hpp index a5e571cf..c118ace5 100644 --- a/Firmware/MotorControl/timer.hpp +++ b/Firmware/MotorControl/timer.hpp @@ -35,7 +35,7 @@ class Timer { private: T timer_ = static_cast(0); // Current state - T timeout_ = static_cast(0); // Time before + T timeout_ = static_cast(0); // Time to count T interval_ = static_cast(0); // Amount to increment each time update() is called bool running_ = false; // update() only increments if runing_ is true }; From a8d47894cafbb9b90bc83de8fdd995c2296e87b9 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 28 Mar 2020 00:40:00 -0400 Subject: [PATCH 304/549] Add additional test types and fix missing template argument --- Firmware/MotorControl/timer.hpp | 2 +- Firmware/Tests/test_timer.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/timer.hpp b/Firmware/MotorControl/timer.hpp index c118ace5..db9557ad 100644 --- a/Firmware/MotorControl/timer.hpp +++ b/Firmware/MotorControl/timer.hpp @@ -22,7 +22,7 @@ class Timer { void update() { if (running_) - timer_ = std::min(timer_ + interval_, timeout_); + timer_ = std::min(timer_ + interval_, timeout_); } void reset() { diff --git a/Firmware/Tests/test_timer.cpp b/Firmware/Tests/test_timer.cpp index 7d65ccb2..60bf8822 100644 --- a/Firmware/Tests/test_timer.cpp +++ b/Firmware/Tests/test_timer.cpp @@ -1,9 +1,10 @@ #define DOCTEST_IMPLEMENT #include #include "MotorControl/timer.hpp" +#include -TEST_CASE("Timer"){ - Timer myTimer; +TEST_CASE_TEMPLATE("Timer2", T, float, int, char, uint32_t){ + Timer myTimer; myTimer.setTimeout(10); myTimer.setInterval(1); CHECK(!myTimer.expired()); @@ -26,5 +27,4 @@ TEST_CASE("Timer"){ myTimer.reset(); CHECK(!myTimer.expired()); - } \ No newline at end of file From 163ce7d21ad9a8f9a2f0b13f00bb8959df95c4ac Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 28 Mar 2020 00:50:44 -0400 Subject: [PATCH 305/549] Change Interval to Increment --- Firmware/MotorControl/endstop.cpp | 4 ++-- Firmware/MotorControl/timer.hpp | 9 +++++---- Firmware/Tests/test_timer.cpp | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Firmware/MotorControl/endstop.cpp b/Firmware/MotorControl/endstop.cpp index 2f8c6410..7febba16 100644 --- a/Firmware/MotorControl/endstop.cpp +++ b/Firmware/MotorControl/endstop.cpp @@ -3,7 +3,7 @@ Endstop::Endstop(Endstop::Config_t& config) : config_(config) { update_config(); - debounceTimer_.setInterval(current_meas_period); + debounceTimer_.setIncrement(current_meas_period); } @@ -33,7 +33,7 @@ bool Endstop::get_state() { void Endstop::update_config() { set_enabled(config_.enabled); - debounceTimer_.setInterval(config_.debounce_ms * 0.001f); + debounceTimer_.setIncrement(config_.debounce_ms * 0.001f); } void Endstop::set_enabled(bool enable) { diff --git a/Firmware/MotorControl/timer.hpp b/Firmware/MotorControl/timer.hpp index db9557ad..ae539bb8 100644 --- a/Firmware/MotorControl/timer.hpp +++ b/Firmware/MotorControl/timer.hpp @@ -8,8 +8,8 @@ class Timer { timeout_ = timeout; } - void setInterval(const T interval) { - interval_ = interval; + void setIncrement(const T increment) { + increment_ = increment; } void start() { @@ -20,9 +20,10 @@ class Timer { running_ = false; } + // If the timer is started, increment the timer void update() { if (running_) - timer_ = std::min(timer_ + interval_, timeout_); + timer_ = std::min(timer_ + increment_, timeout_); } void reset() { @@ -36,6 +37,6 @@ class Timer { private: T timer_ = static_cast(0); // Current state T timeout_ = static_cast(0); // Time to count - T interval_ = static_cast(0); // Amount to increment each time update() is called + T increment_ = static_cast(0); // Amount to increment each time update() is called bool running_ = false; // update() only increments if runing_ is true }; diff --git a/Firmware/Tests/test_timer.cpp b/Firmware/Tests/test_timer.cpp index 60bf8822..4b3d81ac 100644 --- a/Firmware/Tests/test_timer.cpp +++ b/Firmware/Tests/test_timer.cpp @@ -6,7 +6,7 @@ TEST_CASE_TEMPLATE("Timer2", T, float, int, char, uint32_t){ Timer myTimer; myTimer.setTimeout(10); - myTimer.setInterval(1); + myTimer.setIncrement(1); CHECK(!myTimer.expired()); myTimer.start(); From db890b0d5c7d3cf0744bdf2652e2e685d6bb54d8 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 28 Mar 2020 00:58:32 -0400 Subject: [PATCH 306/549] Use brake_current as a true FF --- Firmware/MotorControl/low_level.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index a285bc24..93eb89dc 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -594,7 +594,7 @@ void update_brake_current() { // Don't start braking until -Ibus > regen_current_allowed float brake_current = -Ibus_sum - board_config.max_regen_current; float brake_duty = brake_current * std::abs(board_config.brake_resistance) / vbus_voltage; - brake_duty = std::max((vbus_voltage - board_config.nominal_voltage) / (VBUS_OVERVOLTAGE_LEVEL / 0.9f - board_config.nominal_voltage), brake_duty); + brake_duty += std::max((vbus_voltage - board_config.nominal_voltage) / (VBUS_OVERVOLTAGE_LEVEL / 0.9f - board_config.nominal_voltage), 0.0f); // Clamp the duty cycle brake_duty = std::clamp(brake_duty, 0.0f, 0.9f); From 8a7a5aac4de49adf08ab9cbb5ff8d1d7b8bbe993 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 29 Mar 2020 21:10:38 -0400 Subject: [PATCH 307/549] Remove VBUS_OVERVOLTAGE_LEVEL because it's not being used --- Firmware/.vscode/c_cpp_properties.json | 2 +- Firmware/Board/v3/Inc/main.h | 2 -- Firmware/MotorControl/low_level.cpp | 2 +- Firmware/MotorControl/odrive_main.h | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index 26b5fc0a..9c1aec97 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -10,7 +10,7 @@ "USE_HAL_DRIVER", "HW_VERSION_MAJOR=3", "HW_VERSION_MINOR=6", - "HW_VERSION_VOLTAGE=24", + "HW_VERSION_VOLTAGE=56", "USB_PROTOCOL_NATIVE", "__weak=\"__attribute__((weak))\"", "__packed=\"__attribute__((__packed__))\"", diff --git a/Firmware/Board/v3/Inc/main.h b/Firmware/Board/v3/Inc/main.h index bfd9888c..6f663846 100644 --- a/Firmware/Board/v3/Inc/main.h +++ b/Firmware/Board/v3/Inc/main.h @@ -173,10 +173,8 @@ #if HW_VERSION_VOLTAGE >= 48 #define VBUS_S_DIVIDER_RATIO 19.0f -#define VBUS_OVERVOLTAGE_LEVEL 52.0f #elif HW_VERSION_VOLTAGE == 24 #define VBUS_S_DIVIDER_RATIO 11.0f -#define VBUS_OVERVOLTAGE_LEVEL 26.0f #else #error "unknown board voltage" #endif diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 93eb89dc..14a59b4d 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -594,7 +594,7 @@ void update_brake_current() { // Don't start braking until -Ibus > regen_current_allowed float brake_current = -Ibus_sum - board_config.max_regen_current; float brake_duty = brake_current * std::abs(board_config.brake_resistance) / vbus_voltage; - brake_duty += std::max((vbus_voltage - board_config.nominal_voltage) / (VBUS_OVERVOLTAGE_LEVEL / 0.9f - board_config.nominal_voltage), 0.0f); + brake_duty += std::max((vbus_voltage - board_config.nominal_voltage) / (board_config.dc_bus_overvoltage_trip_level / 0.9f - board_config.nominal_voltage), 0.0f); // Clamp the duty cycle brake_duty = std::clamp(brake_duty, 0.0f, 0.9f); diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 5f2f3ade..fe14446f 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -90,12 +90,12 @@ struct BoardConfig_t { #else float brake_resistance = 0.47f; // [ohm] #endif - float nominal_voltage = VBUS_OVERVOLTAGE_LEVEL; float dc_bus_undervoltage_trip_level = 8.0f; // Date: Wed, 8 Apr 2020 16:25:29 +0200 Subject: [PATCH 308/549] fix encoder direction find procedure --- Firmware/MotorControl/encoder.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 4b55bceb..c56e9d5f 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -151,11 +151,14 @@ bool Encoder::run_index_search() { bool Encoder::run_direction_find() { int32_t init_enc_val = shadow_count_; - bool orig_finish_on_distance = axis_->config_.calibration_lockin.finish_on_distance; - axis_->config_.calibration_lockin.finish_on_distance = true; axis_->motor_.config_.direction = 1; // Must test spin forwards for direction detect logic - bool status = axis_->run_lockin_spin(axis_->config_.calibration_lockin); - axis_->config_.calibration_lockin.finish_on_distance = orig_finish_on_distance; + + Axis::LockinConfig_t lockin_config = axis_->config_.calibration_lockin; + lockin_config.finish_distance = lockin_config.vel * 3.0f; // run for 3 seconds + lockin_config.finish_on_distance = true; + lockin_config.finish_on_enc_idx = false; + lockin_config.finish_on_vel = false; + bool status = axis_->run_lockin_spin(lockin_config); if (status) { // Check response and direction From 37c4fe337a7eb3472ce6a8fcc6f27a422744eeb9 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 8 Apr 2020 16:26:15 +0200 Subject: [PATCH 309/549] allow configuring motor parameters without reboot --- Firmware/MotorControl/motor.hpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 40fafce3..3714ea9a 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -239,12 +239,16 @@ public: make_protocol_ro_property("TIMING_LOG_FOC_CURRENT", &timing_log_[TIMING_LOG_FOC_CURRENT]) ), make_protocol_object("config", - make_protocol_property("pre_calibrated", &config_.pre_calibrated), + make_protocol_property("pre_calibrated", &config_.pre_calibrated, + [](void* ctx) { static_cast(ctx)->is_calibrated_ = + static_cast(ctx)->is_calibrated_ || static_cast(ctx)->config_.pre_calibrated; }, this), make_protocol_property("pole_pairs", &config_.pole_pairs), make_protocol_property("calibration_current", &config_.calibration_current), make_protocol_property("resistance_calib_max_voltage", &config_.resistance_calib_max_voltage), - make_protocol_property("phase_inductance", &config_.phase_inductance), - make_protocol_property("phase_resistance", &config_.phase_resistance), + make_protocol_property("phase_inductance", &config_.phase_inductance, + [](void* ctx) { static_cast(ctx)->update_current_controller_gains(); }, this), + make_protocol_property("phase_resistance", &config_.phase_resistance, + [](void* ctx) { static_cast(ctx)->update_current_controller_gains(); }, this), make_protocol_property("direction", &config_.direction), make_protocol_property("motor_type", &config_.motor_type), make_protocol_property("current_lim", &config_.current_lim), From 3797184deb81138f226166e8406ce20f4022a3cc Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 8 Apr 2020 16:27:08 +0200 Subject: [PATCH 310/549] add tests for various calibration procedures --- tools/odrive/tests/calibration_test.py | 205 +++++ .../odrive/tests/encoder_pass_through.ino.hex | 792 ++++++++++++++++++ tools/odrive/tests/motor_calibration_test.py | 70 -- 3 files changed, 997 insertions(+), 70 deletions(-) create mode 100644 tools/odrive/tests/calibration_test.py create mode 100644 tools/odrive/tests/encoder_pass_through.ino.hex delete mode 100644 tools/odrive/tests/motor_calibration_test.py diff --git a/tools/odrive/tests/calibration_test.py b/tools/odrive/tests/calibration_test.py new file mode 100644 index 00000000..0c179a20 --- /dev/null +++ b/tools/odrive/tests/calibration_test.py @@ -0,0 +1,205 @@ + +import test_runner + +import time +from math import pi +import os + +from fibre.utils import Logger +from test_runner import AxisTestContext, MotorTestContext, EncoderTestContext, test_assert_eq, test_assert_no_error, request_state, program_teensy +from odrive.enums import * + +def modpm(val, range): + return ((val + (range / 2)) % range) - (range / 2) + + +class TestMotorCalibration(): + """ + Runs the motor calibration (phase inductance and phase resistance measurement) + and checks if the measurements match the expectation. + """ + + def is_compatible(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext): + return axis_ctx.yaml == motor_ctx.yaml['name'] # check if connected + + def run_test(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, logger: Logger): + # reset old calibration values + axis_ctx.handle.motor.config.phase_resistance = 0.0 + axis_ctx.handle.motor.config.phase_inductance = 0.0 + axis_ctx.handle.motor.config.pre_calibrated = False + + axis_ctx.handle.clear_errors() + + # run calibration + request_state(axis_ctx, AXIS_STATE_MOTOR_CALIBRATION) + time.sleep(6) + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_no_error(axis_ctx) + + # check if measurements match expectation + test_assert_eq(axis_ctx.handle.motor.config.phase_resistance, float(motor_ctx.yaml['phase-resistance']), accuracy=0.2) + test_assert_eq(axis_ctx.handle.motor.config.phase_inductance, float(motor_ctx.yaml['phase-inductance']), accuracy=0.5) + test_assert_eq(axis_ctx.handle.motor.is_calibrated, True) + + +class TestDisconnectedMotorCalibration(): + """ + Tests if the motor calibration fails as expected if the phases are floating. + """ + + def is_compatible(self, axis_ctx: AxisTestContext): + return axis_ctx.yaml == 'floating' + + def run_test(self, axis_ctx: AxisTestContext, logger: Logger): + axis = axis_ctx.handle + + # reset old calibration values + axis_ctx.handle.motor.config.phase_resistance = 0.0 + axis_ctx.handle.motor.config.phase_inductance = 0.0 + axis_ctx.handle.motor.config.pre_calibrated = False + + axis_ctx.handle.clear_errors() + + # run test + request_state(axis_ctx, AXIS_STATE_MOTOR_CALIBRATION) + time.sleep(6) + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_eq(axis_ctx.handle.error, errors.axis.ERROR_MOTOR_FAILED) + test_assert_eq(axis_ctx.handle.motor.error, errors.motor.ERROR_PHASE_RESISTANCE_OUT_OF_RANGE) + + +class TestEncoderDirFind(): + """ + Runs the encoder index search. + """ + + def is_compatible(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, enc_ctx: EncoderTestContext): + return (axis_ctx.yaml == motor_ctx.yaml['name']) and (axis_ctx.num == enc_ctx.num) # check if connected + + def run_test(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, enc_ctx: EncoderTestContext, logger: Logger): + axis = axis_ctx.handle + # TODO: read teensy config from YAML file + hexfile = 'encoder_pass_through.ino.hex' + program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) + time.sleep(1.0) # wait for PLLs to stabilize + + # Set motor calibration values + axis_ctx.handle.motor.config.phase_resistance = float(motor_ctx.yaml['phase-resistance']) + axis_ctx.handle.motor.config.phase_inductance = float(motor_ctx.yaml['phase-inductance']) + axis_ctx.handle.motor.config.pre_calibrated = True + + # Set calibration settings + axis_ctx.handle.motor.config.direction = 0 + axis_ctx.handle.config.calibration_lockin.vel = 12.566 # 2 electrical revolutions per second + + axis_ctx.handle.clear_errors() + + # run test + request_state(axis_ctx, AXIS_STATE_ENCODER_DIR_FIND) + + time.sleep(4) # actual calibration takes 3 seconds + + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_no_error(axis_ctx) + + test_assert_eq(axis_ctx.handle.motor.config.direction in [-1, 1], True) + + +class TestEncoderOffsetCalibration(): + """ + Runs the encoder index search. + """ + + def is_compatible(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, enc_ctx: EncoderTestContext): + return (axis_ctx.yaml == motor_ctx.yaml['name']) and (axis_ctx.num == enc_ctx.num) # check if connected + + def run_test(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, enc_ctx: EncoderTestContext, logger: Logger): + axis = axis_ctx.handle + # TODO: read teensy config from YAML file + hexfile = 'encoder_pass_through.ino.hex' + program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) + time.sleep(1.0) # wait for PLLs to stabilize + + # Set motor calibration values + axis_ctx.handle.motor.config.phase_resistance = float(motor_ctx.yaml['phase-resistance']) + axis_ctx.handle.motor.config.phase_inductance = float(motor_ctx.yaml['phase-inductance']) + axis_ctx.handle.motor.config.pre_calibrated = True + + # Set calibration settings + axis_ctx.handle.motor.config.direction = 0 + enc_ctx.handle.config.use_index = False + enc_ctx.handle.config.calib_scan_omega = 12.566 # 2 electrical revolutions per second + enc_ctx.handle.config.calib_scan_distance = 50.265 # 8 revolutions + + axis_ctx.handle.clear_errors() + + # run test + request_state(axis_ctx, AXIS_STATE_ENCODER_OFFSET_CALIBRATION) + + time.sleep(9) # actual calibration takes 8 seconds + + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_no_error(axis_ctx) + + test_assert_eq(enc_ctx.handle.is_ready, True) + test_assert_eq(axis_ctx.handle.motor.config.direction in [-1, 1], True) + + +class TestEncoderIndexSearch(): + """ + Runs the encoder index search. + The index pin is triggered manually after three seconds from the testbench + host's GPIO. + """ + + def is_compatible(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, enc_ctx: EncoderTestContext): + return (axis_ctx.yaml == motor_ctx.yaml['name']) and (axis_ctx.num == enc_ctx.num) # check if connected + + def run_test(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, enc_ctx: EncoderTestContext, logger: Logger): + axis = axis_ctx.handle + # TODO: read teensy config from YAML file + hexfile = 'encoder_pass_through.ino.hex' + program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) + time.sleep(1.0) # wait for PLLs to stabilize + + # Set motor calibration values + axis_ctx.handle.motor.config.phase_resistance = float(motor_ctx.yaml['phase-resistance']) + axis_ctx.handle.motor.config.phase_inductance = float(motor_ctx.yaml['phase-inductance']) + axis_ctx.handle.motor.config.pre_calibrated = True + + # Set calibration settings + axis_ctx.handle.config.calibration_lockin.vel = 12.566 # 2 electrical revolutions per second + + axis_ctx.handle.clear_errors() + + # run test + request_state(axis_ctx, AXIS_STATE_ENCODER_INDEX_SEARCH) + + time.sleep(3) + + test_assert_eq(enc_ctx.handle.index_found, False) + with open("/sys/class/gpio/gpio{}/value".format(20), "w") as gpio: + gpio.write("0") + time.sleep(0.1) + with open("/sys/class/gpio/gpio{}/value".format(20), "w") as gpio: + gpio.write("1") + test_assert_eq(enc_ctx.handle.index_found, True) + + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_no_error(axis_ctx) + + test_assert_eq(enc_ctx.handle.shadow_count, 0.0, range=20) + test_assert_eq(enc_ctx.handle.count_in_cpr, 0.0, range=20) + test_assert_eq(enc_ctx.handle.pos_estimate, 0.0, range=20) + test_assert_eq(enc_ctx.handle.pos_cpr, 0.0, range=20) + test_assert_eq(enc_ctx.handle.pos_abs, 0.0, range=20) + + +if __name__ == '__main__': + test_runner.run([ + TestMotorCalibration(), + TestDisconnectedMotorCalibration(), + TestEncoderDirFind(), + TestEncoderOffsetCalibration(), + TestEncoderIndexSearch() + ]) diff --git a/tools/odrive/tests/encoder_pass_through.ino.hex b/tools/odrive/tests/encoder_pass_through.ino.hex new file mode 100644 index 00000000..2f0798bf --- /dev/null +++ b/tools/odrive/tests/encoder_pass_through.ino.hex @@ -0,0 +1,792 @@ +:0200000460009A +:100000004643464200000156000000000101020084 +:1000100000000000000000000000000000000000E0 +:1000200000000000000000000000000000000000D0 +:1000300000000000000000000000000000000000C0 +:1000400000000000010403000000000000000000A8 +:100050000000200000000000000000000000000080 +:100060000000000000000000000000000000000090 +:100070000000000000000000000000000000000080 +:10008000EB04180A063204260000000000000000FD +:10009000050404240000000000000000000000002F +:1000A0000000000000000000000000000000000050 +:1000B0000604000000000000000000000000000036 +:1000C0000000000000000000000000000000000030 +:1000D00020041808000000000000000000000000DC +:1000E0000000000000000000000000000000000010 +:1000F0000000000000000000000000000000000000 +:10010000D8041808000000000000000000000000F3 +:100110000204180804200000000000000000000095 +:1001200000000000000000000000000000000000CF +:10013000600400000000000000000000000000005B +:1001400000000000000000000000000000000000AF +:10015000000000000000000000000000000000009F +:10016000000000000000000000000000000000008F +:10017000000000000000000000000000000000007F +:10018000000000000000000000000000000000006F +:10019000000000000000000000000000000000005F +:1001A000000000000000000000000000000000004F +:1001B000000000000000000000000000000000003F +:1001C000000100000010000001000000000000001D +:1001D000000001000000000000000000000000001E +:1001E000000000000000000000000000000000000F +:1001F00000000000000000000000000000000000FF +:10020000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE +:10021000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEE +:10022000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDE +:10023000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCE +:10024000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBE +:10025000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAE +:10026000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E +:10027000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8E +:10028000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7E +:10029000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6E +:1002A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5E +:1002B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4E +:1002C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3E +:1002D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E +:1002E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1E +:1002F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0E +:10030000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD +:10031000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED +:10032000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD +:10033000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCD +:10034000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD +:10035000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAD +:10036000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D +:10037000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8D +:10038000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7D +:10039000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6D +:1003A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D +:1003B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4D +:1003C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3D +:1003D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2D +:1003E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1D +:1003F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0D +:10040000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC +:10041000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC +:10042000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC +:10043000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCC +:10044000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC +:10045000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAC +:10046000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9C +:10047000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8C +:10048000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7C +:10049000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C +:1004A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5C +:1004B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4C +:1004C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3C +:1004D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2C +:1004E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1C +:1004F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0C +:10050000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB +:10051000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEB +:10052000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB +:10053000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCB +:10054000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBB +:10055000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAB +:10056000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9B +:10057000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8B +:10058000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7B +:10059000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6B +:1005A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B +:1005B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4B +:1005C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3B +:1005D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2B +:1005E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1B +:1005F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0B +:10060000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA +:10061000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEA +:10062000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA +:10063000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCA +:10064000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA +:10065000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAA +:10066000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9A +:10067000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8A +:10068000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7A +:10069000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6A +:1006A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5A +:1006B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4A +:1006C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3A +:1006D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A +:1006E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1A +:1006F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A +:10070000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9 +:10071000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9 +:10072000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9 +:10073000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC9 +:10074000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB9 +:10075000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9 +:10076000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF99 +:10077000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF89 +:10078000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF79 +:10079000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF69 +:1007A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF59 +:1007B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF49 +:1007C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF39 +:1007D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF29 +:1007E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF19 +:1007F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF09 +:10080000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8 +:10081000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8 +:10082000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD8 +:10083000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC8 +:10084000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB8 +:10085000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA8 +:10086000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF98 +:10087000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF88 +:10088000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF78 +:10089000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF68 +:1008A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF58 +:1008B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF48 +:1008C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF38 +:1008D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF28 +:1008E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF18 +:1008F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF08 +:10090000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7 +:10091000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE7 +:10092000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD7 +:10093000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7 +:10094000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB7 +:10095000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA7 +:10096000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF97 +:10097000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF87 +:10098000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF77 +:10099000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF67 +:1009A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57 +:1009B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF47 +:1009C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF37 +:1009D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27 +:1009E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF17 +:1009F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF07 +:100A0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6 +:100A1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE6 +:100A2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD6 +:100A3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC6 +:100A4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB6 +:100A5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA6 +:100A6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF96 +:100A7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF86 +:100A8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF76 +:100A9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF66 +:100AA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 +:100AB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF46 +:100AC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF36 +:100AD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF26 +:100AE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16 +:100AF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF06 +:100B0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5 +:100B1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5 +:100B2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD5 +:100B3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC5 +:100B4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB5 +:100B5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA5 +:100B6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF95 +:100B7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF85 +:100B8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF75 +:100B9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF65 +:100BA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF55 +:100BB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF45 +:100BC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF35 +:100BD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF25 +:100BE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF15 +:100BF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF05 +:100C0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4 +:100C1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE4 +:100C2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD4 +:100C3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC4 +:100C4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB4 +:100C5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA4 +:100C6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF94 +:100C7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF84 +:100C8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF74 +:100C9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF64 +:100CA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF54 +:100CB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF44 +:100CC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF34 +:100CD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF24 +:100CE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF14 +:100CF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF04 +:100D0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3 +:100D1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE3 +:100D2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD3 +:100D3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC3 +:100D4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB3 +:100D5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA3 +:100D6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF93 +:100D7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83 +:100D8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF73 +:100D9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF63 +:100DA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF53 +:100DB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43 +:100DC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF33 +:100DD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF23 +:100DE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF13 +:100DF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF03 +:100E0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2 +:100E1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE2 +:100E2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD2 +:100E3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2 +:100E4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB2 +:100E5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA2 +:100E6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF92 +:100E7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF82 +:100E8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF72 +:100E9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF62 +:100EA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF52 +:100EB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF42 +:100EC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF32 +:100ED000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF22 +:100EE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF12 +:100EF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02 +:100F0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1 +:100F1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE1 +:100F2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD1 +:100F3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC1 +:100F4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB1 +:100F5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA1 +:100F6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF91 +:100F7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81 +:100F8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF71 +:100F9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF61 +:100FA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF51 +:100FB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF41 +:100FC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF31 +:100FD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF21 +:100FE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF11 +:100FF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF01 +:10100000D10020402C100060000000000000000013 +:1010100020100060001000600000000000000000D0 +:1010200000000060403100000000000000000120CE +:1010300035100060764B0720764C4FF42A01764A33 +:101040005C64186499639546744A75498A420FD066 +:10105000744B9A420CD2D4430846234423F0030332 +:1010600004330B4450F8041B984242F8041BF9D196 +:101070006D4A6E498A420FD06D4B9A420CD2D443CE +:101080000846234423F0030304330B4450F8041BA5 +:10109000984242F8041BF9D1664A674B9A420BD238 +:1010A000D04311460024034423F0030304331344C4 +:1010B00041F8044B8B42FBD1604A4FF47001604B06 +:1010C000116003F530715F4A43F8042F9942FBD158 +:1010D0005D4B80215D4A03F8011B9342FBD15C4CC0 +:1010E0004FF080375B4E4FF0FF32DFF8A8A10025AC +:1010F000DFF8A491DFF8A481574B4549C3F800A05D +:10110000C4F80471C4F80091C4F8F470C4F8F08015 +:10111000F36923F07F0343F04003F361736A23F024 +:101120007F0343F0400373628A66CA660A674A67B0 +:1011300000F0B6F8494A6320494B4A49106003223F +:101140001D60CAF8381043F8082C4749474A4848F8 +:10115000C3F8082D0B68474A43F08073CAF83C0077 +:1011600045480B601368454943F001031360036869 +:101170000B6000F0E5F8C4F804714148C4F8009130 +:10118000C4F8F470C4F8F08000F036FA00BF00BF75 +:1011900000BF00BFF16E3B4A41F440513A4BF1664B +:1011A0001560C2F80851C2F81851C2F82851C2F8A7 +:1011B00038519A6BD20708D442F615623349596503 +:1011C0001A659A6B42F001029A632F4A304C936879 +:1011D00043F00113936000F033FA2368132BFCD91A +:1011E00000F05CF900F0D0F900F022FA00F0DAF833 +:1011F00000F006FA2368B3F5967FFBD300F010FAEF +:1012000000F012FA00F008FA00F016FAFAE700BF50 +:1012100000C00A40ABAAAAAA008007200000000074 +:10122000501600602017000000000020742D0060A0 +:10123000D0030020D0030020C032002088ED00E061 +:10124000FC0F00206102000000E400E0A0E400E0E8 +:1012500000800D4000C00F4008ED00E014E000E009 +:1012600018E000E0D90D0000FCED00E000002020B7 +:10127000DD0E0000001000E0041000E0A40D0020CE +:101280000046C3230040084000400D400000C05607 +:10129000A80D0020001000201B1018200C0D1113A9 +:1012A000F0B5194A0021194B4FF0100E18480124CF +:1012B000184E194D0160194FC2F800E01E6015600C +:1012C000174E184D1F601660174F1D60174E184DB2 +:1012D00017601E60174F1560174E184D1F6016607F +:1012E0001D600460BFF34F8FBFF36F8FC3F8B01161 +:1012F000BFF34F8FBFF36F8F52F8883C43F44033F6 +:1013000042F8883CF0BD00BF9CED00E0A0ED00E09D +:1013100094ED00E0250008031100200021000207E1 +:1013200012000020250008131300202027000B13B3 +:101330001400004033001013150000602F000B074D +:10134000F0B4174A40274FF480314FF480564FF4E1 +:1013500000554FF4404443F24200136913F0020F6A +:1013600006D0946151619061136913F0020FF8D1B6 +:1013700013F4005F01D15561EFE713F4805F01D1F1 +:101380005661EAE7002BE8DA13F4803F01D091615F +:10139000E3E75B0601D45761DFE7F0BC704700BFAD +:1013A00000800D40364A03203649F3EE096A13687F +:1013B00023F00103F0B51360C2F89000D1F8E030DB +:1013C000DFF8D4E0DCB2C3F30B202F4FC0EB135394 +:1013D00007EE904AA4F15501CEF80040B8EEE77A46 +:1013E00003EB830407EE900A03FB01F13B6003EB80 +:1013F0008313B8EEE75A07EE901A091B77EE666A78 +:10140000B8EE677A214D07EE901A0B44C5ED006ADD +:10141000F8EE677A1E4EC7EE265A1E4930601068F5 +:1014200087EEA66A07EE903AF8EE677A87EEA67A1C +:1014300075EE855A76EE056AFCEEE55AFCEEE66A34 +:1014400015EE903A40EA035316EE900A77EE057ACD +:10145000136001EA0041D2F81031FCEEE77A0B4349 +:10146000C2F8103117EE903AD2F81011C3F30B0303 +:101470000B43C2F81031136843F002031360F0BD50 +:1014800080810D4000441F40F8030020F403002039 +:10149000FC0300200000FF0FF0030020304B40F65B +:1014A000617270B5C3F8202140F2044500F0B0F835 +:1014B0002C492D48D1F880202C4C42F003022C4BB3 +:1014C000C1F88020C0F86051226813401BB9D0F8E1 +:1014D000A8319A071AD0244B4FF00041234A516398 +:1014E0001A46D3F8401141F00201C3F84011D2F876 +:1014F00040319B07FBD44FF400301E491B4B4FF08B +:101500000042086019209A6300F072F81A4D002218 +:10151000164B4FF08041144C0A26996328461A60F6 +:101520001146C4F8A8614FF4207200F06DF84FF432 +:1015300081064FF4800040F24313104A10492E6098 +:101540002864C4F85851C2F80412C4F848310D4A4E +:101550004FF4003101231160C4F8403170BD00BF69 +:1015600000800D4000C00F4000002E4000900D4054 +:10157000001C1E008CE200E0003000200010002063 +:101580003D0400000CE100E0114B1249D86E0A4600 +:1015900040F4403030B4D86640F2B765D86EA0242D +:1015A00040F44070D8664D648C64936C1B06FCD488 +:1015B000094B40F2B760A021064A58649964936CC5 +:1015C00013F08003FBD1054A137030BC704700BF95 +:1015D00000C00F4000400C4000800C40A10D0020D6 +:1015E000F8B500BFF8BC08BC9E467047FFFFFFFF80 +:1015F0005FF800F0311200005FF800F09D0F00006E +:101600005FF800F07D0200005FF800F07D1600003A +:101610005FF800F03D0B00005FF800F005010000EE +:101620005FF800F02D1600005FF800F001010000E7 +:101630005FF800F0411300005FF800F0FD0E0000BD +:101640005FF800F07902000045000000FFFFFFFF97 +:10165000000000000000000000000000000000008A +:10166000000000000000000000000000000000007A +:1016700010B5054C237833B9044B13B10448AFF3CC +:1016800000800123237010BDD00300200000000063 +:1016900024170000084B10B51BB108490848AFF3E8 +:1016A00000800848036803B910BD074B002BFBD02E +:1016B000BDE81040184700BF00000000D403002020 +:1016C00024170000D00300200000000008B5174BCD +:1016D0000121187800F0ACF8154B0121187800F0C2 +:1016E000A7F8144B0121187800F0A2F8124B012141 +:1016F000187800F09DF8114B0121187800F098F847 +:101700000F4B0121187800F093F80E4B0021187848 +:1017100000F08EF80C4B0021187800F089F80B4B84 +:1017200000211878BDE8084000F082B8F4020020DB +:1017300000030020040300200C0300200803002005 +:10174000F8020020EC030020EC020020F002002050 +:10175000FFF7BCBF174B70B5187800F063F8164B55 +:101760000646187800F05EF8144B0546187800F02D +:1017700059F8134B04463146187800F04DF8114BD8 +:101780002946187800F048F80F4B2146187800F0E9 +:1017900043F80E4B3146187800F03EF80C4B2946C2 +:1017A000187800F039F80B4B21461878BDE87040E6 +:1017B00000F032B8FC020020EC020020F002002011 +:1017C000F402002000030020040300200C0300208A +:1017D00008030020F80200200001074B1A181B58CC +:1017E000D2685868104202D011B9C3F888207047F7 +:1017F000C3F88420704700BF000000200001054BA3 +:101800001A5819189268CB681A4214BF0120002098 +:10181000704700BF00000020272801D8FFF7DCBF79 +:10182000704700BF272801D8FFF7E8BF00207047A6 +:1018300027281CD800011A4A012902EB000310587E +:1018400010B415D0042913D0DC68426822EA0402DF +:1018500042609A68E9B102291ED003295B685DF8ED +:10186000044B0CBF0F491049116015221A607047D4 +:10187000DA680129446842EA040242609A6808D0A2 +:1018800040F6380111605B6815225DF8044B1A6060 +:10189000704738211160F6E704491160F3E700BF93 +:1018A00000000020383001003800010038F001004D +:1018B00004207146084203D0EFF3098000F008B815 +:1018C000EFF3088000F004B8704700BF704700BF16 +:1018D0001B4B05211B4A382030B5C2F8481108219E +:1018E000C2F8380383B05A68174C0A4317485A6045 +:1018F000C3F88410C3F888102368834202D91448BF +:1019000000F074FE0E4D08240020124A1249C5F85A +:1019100084400190019B934205D8019B01330193C0 +:10192000019B9342F9D9C5F888400190019B8B42F5 +:10193000EDD8019B01330193019B8B42F9D9E6E776 +:1019400000C01B4000801F40C4030020FF45C3238C +:1019500000A3E1113F420F003F548900836B30B474 +:101960001BB1536843F40043536072B6446B9CB19F +:10197000104B2260D3F8B0410C4217D1D3F840414C +:1019800044F48044C3F84041D3F8B851D3F84041FF +:101990006404F3D5294209D10023064C8260C36058 +:1019A000D4F8B0311943C4F8B0110263426362B68F +:1019B00030BC704700002E4038B50546036B06E08A +:1019C000AA6B1C6890476A6B2346944208D018465D +:1019D00033B1012B04D05A681206F1D52B6338BD00 +:1019E00000232B636B6338BDF0B5F1B9224C23495A +:1019F0000020234B01228025A1600A601A464D6019 +:101A0000E060D3F8BC41886044F001141D4DC3F878 +:101A1000BC41D3F8B01141F00101C3F8B011286006 +:101A2000D2F8B031002BFBD1F0BD0904164B144D98 +:101A30000126114C41F0800100221E60596000F522 +:101A4000805CE26400F5005EA36400F54057D5F8C1 +:101A5000B01100F580462A46986041F48031C3F801 +:101A60000CC0C3F810E05F619E61C5F8B011D2F8F8 +:101A7000B031002BFBD1BAE700300020202000203D +:101A800000002E400C040020002000202DE9F04F23 +:101A9000BB4C83B0D4F8448118F0010FC4F84481E2 +:101AA0005ED0D4F8AC31002B55D04FF00119DFF8DF +:101AB000F0B22646B34FCA46B96AFA6AC4F8AC31E6 +:101AC000D4F8403143F40053C4F84031D4F84031E5 +:101AD0009D04F5D5D6F8403123F40053C6F84031C3 +:101AE000C6F8B4A1D4F8B43113F00113FAD188B216 +:101AF00040F28165CBF80030A84200F29680B0F544 +:101B0000D06F80F0D881B0F5817F00F0578100F26E +:101B1000D580802800F02681822840F0C78092B2CC +:101B200002F07F01072900F2C18089009648974D95 +:101B30000844016810062B706B7040F1E581C90301 +:101B400001D501232B7002219048FFF74DFFD4F8F7 +:101B5000AC31002BB0D18A4AD2F8BC31002B44D131 +:101B600018F0400F18D0864BD3F8AC111A46C3F8C2 +:101B7000AC11D3F8BC11C3F8BC11D2F8B041804B02 +:101B8000002CFAD14FF0FF32C3F8B421D3F88431DE +:101B900000F074FB7E4B1C6018F0807F03D07D4BFF +:101BA0001B6803B1984718F0007F03D07A4B1B687D +:101BB00003B1984718F0040F02D0714BD3F8843169 +:101BC0006F4BD3F8482112060CD518F0800F09D0BE +:101BD00072490A78002A00F08E81531EDBB20B7026 +:101BE00003B9FBBE03B0BDE8F08F6D49C2F8BC314C +:101BF0000868034240F0CC81654A14681C40AFD0AD +:101C0000630700F1E281670300F1EF81260700F12D +:101C1000E881250300F1E181E00600F1DA81E102CB +:101C20009ED56048FFF7C8FE9AE742F22105A84218 +:101C300038D06FD8B0F5086F00F02F81B0F5106F75 +:101C400034D1C1F30741584A584811705849594A8C +:101C50000193C6F8C801C6F8CC11C6F8D02100F02F +:101C60000FFB554A019B80210120FB6410605160ED +:101C7000D6F8BC11936041F00111BA64C6F8BC11EA +:101C8000D6F8B02142F48032C6F8B021444A13603D +:101C9000D4F8B031002BFBD1D4F8AC31002B7FF459 +:101CA0000BAF58E7100C072800F03281C4F8C09140 +:101CB000D4F8AC31002B7FF4FFAE4CE740F2023594 +:101CC000A84200F0B780B0F5A06FEFD13A4A80206B +:101CD0000125FB6450601560D6F8BC01936040F0AC +:101CE0000113BA64C6F8BC31D6F8B03143F480337E +:101CF000C6F8B031D4F8B031002BFBD10B0C5B0629 +:101D000043F08073C6F85431D4F8AC31002B7FF423 +:101D1000D3AE20E742F22123984200F0828042F2C3 +:101D200021339842C2D1244B012180221960002125 +:101D30005A60D4F8BC21BB6442F001129960F96486 +:101D4000C4F8BC21D4F8B03143F48033C4F8B031C6 +:101D5000D4F8B031002BFBD1D4F8AC31002B7FF498 +:101D6000ABAEF8E6094A0221104613705370FFF734 +:101D70003BFED4F8AC31002B7FF49EAEEBE600BF07 +:101D800000002E4000300020C0012E402004002022 +:101D90000404002000040020080400201004002097 +:101DA0000C04002040320020300400200200CC004F +:101DB000C80002000200C8002020002092B202F0F9 +:101DC0007F03072B3FF672AF12F0800F4FEA8303B9 +:101DD000884A4FF001011A44136814BF23F480337A +:101DE00023F0010313608022834B196000215A60A5 +:101DF000D4F8BC21BB6442F001129960F964C4F8C4 +:101E0000BC21D4F8B03143F48033C4F8B031D4F8F5 +:101E1000B031002BFBD1D4F8AC31002B7FF44CAEA9 +:101E200099E6764BC1F30741754A1868754B106007 +:101E3000197078E792B202F07F03072B3FF636AFB6 +:101E400012F0800F4FEA83036A4A4FF001011A44EF +:101E5000136814BF43F4803343F0010313608022FE +:101E6000654B196000215A60D4F8BC21BB6442F074 +:101E700001129960F964C4F8BC21D4F8B03143F47C +:101E80008033C4F8B031D4F8B031002BFBD1D4F892 +:101E9000AC31002B7FF410AE5DE65B4A01215B4B59 +:101EA000127818461A70FFF79FFDD4F8AC31002B5A +:101EB0007FF402AE4FE6564B586800283FF4F6AE6A +:101EC000090C1FFA82FE04E00C33586800283FF426 +:101ED000EDAE1D888D42F7D15D887545F4D1090AB4 +:101EE000120C03290CBF01781989914228BF1146B1 +:101EF000FFF77AFD2BE6D3F8482122F08002C3F8E1 +:101F0000482103B0BDE8F08FCA077FF51CAE18E684 +:101F1000404D0120FB6029603F4B404918605960EB +:101F20003F49D6F8B0016A6001F5005E40F0010259 +:101F3000A1F5005001F58055BB609860A1F5805077 +:101F4000D860C6F8B02119615D61C3F818E0D4F813 +:101F5000B031002BFBD1284A012048F28001FB64FC +:101F600010604FF480305160D6F8BC11936041F09E +:101F70000113BA64C6F8BC31D6F8B0310343C6F8D1 +:101F8000B031CBF80000D4F8B031002BFBD1DEE546 +:101F9000204C42F22100002524880D6084427FF409 +:101FA0002BAE2049204C03C90D0C86282060A1804F +:101FB000A5717FF421AED2F8481150241B4841F09E +:101FC0008001C2F84811047016E61948FFF7F4FCC6 +:101FD00019E61848FFF7F0FC21E61748FFF7ECFC7C +:101FE0001AE61648FFF7E8FC13E61548FFF7E4FC8D +:101FF0000CE600BFC0012E4020200020A80D0020CC +:1020000088320020800D00203004002020040020B1 +:10201000800200201804002000200020800007001B +:1020200028240020280400208032002010040020F2 +:102030000031002000320020C0310020803100201B +:1020400040310020002AA0F102022DE9F04714BF20 +:1020500000274FF00057022A01D9BDE8F0874FEA68 +:10206000C01ADFF840900D4604460AEB090600212D +:1020700040229846304600F029FE012047EA0541FB +:102080004AF80910C6F83880B060B8F1000FE4D003 +:10209000034BA0401C6820431860BDE8F08700BFD8 +:1020A0000404002000300020002AA0F102022DE9E3 +:1020B000F04714BF00274FF00057022A01D9BDE8AE +:1020C000F0874022C501DFF844908846154400217E +:1020D00004461E4605EB090A504600F0F7FD0122B2 +:1020E00047EA084145F80910CAF83860CAF80820DC +:1020F000002EE4D004F11000034B82401868024324 +:102100001A60BDE8F08700BF040400200030002002 +:1021100012048160C36142F08002F0B44260012782 +:1021200001F5805601F5005501F5405401F5804256 +:102130000760C660056144618261F0BC704700BF02 +:10214000831E022B00D9704730B4064B00F11004F7 +:1021500001250A4603EBC01005FA04F130BCFFF775 +:10216000FDBB00BF40300020831E022B00D970470A +:1021700010B4054B01240A4604FA00F103EBC01029 +:102180005DF8044BFFF7EABB00300020124A134B06 +:10219000D2F8200220F07F40984210B584B002D8D7 +:1021A00000EB800040000E4C01A90A2200F0C8FBA1 +:1021B00001A90023204611F8012B01333AB10A2B63 +:1021C00020F8022FF7D11623237004B010BD5B0056 +:1021D000DBB2237004B010BD00441F407F9698000E +:1021E000A80300204368C269C3F30E43054930B415 +:1021F000C3F14003044C002521F8123024F812509A +:1022000030BC7047000C0020F80B0020F8B5154BCF +:102210001B783BB903F0FF04134B1B7813B1134D2C +:102220002A8802B9F8BD124F2346124EC2F58072B9 +:102230003978114806EB411600EB01213046FFF7D3 +:1022400067FF31460420FFF77BFF3B780133DBB2A9 +:10225000062B98BF3B704FF0000388BF3C702B806B +:10226000F8BD00BF340B002030040020800C00209B +:10227000350B0020A00C002034040020704700BF64 +:102280000021E0222048F8B50C46204E204D00F0F9 +:102290001DFD204F2146204B6022347028461F4EE2 +:1022A0001C8000F013FD23462246102102203C60D2 +:1022B000BC803460B480FFF7F7FE2246184B402103 +:1022C0000320FFF7BFFE2346224640210420FFF7EC +:1022D000EBFE2346402228461249FFF719FF294604 +:1022E0000320FFF741FF104B4A22104910480860B5 +:1022F000C3F88440C3F88020D3F8482142F08072AC +:10230000C3F84821F8BD00BFA00C0020350B002009 +:10231000200C0020000C0020800C0020F80B002076 +:10232000950B0000380B002000002E400004002018 +:10233000BD0B0000024A034B10881B88C01A70476F +:10234000000C0020F80B002010B4EFF3108272B6DE +:10235000437F33B9017F012908D0032910D001231D +:10236000437702B962B65DF8044B7047114C21689F +:10237000A1B1114943610B68086083615861EEE7C0 +:102380000E4C216881B10E4943610B68086083617E +:1023900058610C4B4FF080511960E0E7064B4161EA +:1023A000816120601860DAE7054B4161816120603E +:1023B0001860EEE7940D0020900D0020840D0020A1 +:1023C000880D002004ED00E010B4047F4160022C71 +:1023D000C26003D05DF8044BFFF7B6BF83685DF8B9 +:1023E000044B184770B5EFF3108172B60C4C23689C +:1023F0008BB10C4E00255A6922607AB1956101B902 +:1024000062B65D7718469B689847EFF3108172B605 +:102410002368002BEFD101B962B670BD3260EEE7E0 +:10242000840D0020880D0020FFF7DCBF184A30B46F +:102430001468002C28D0036821688B420FD2CB1A75 +:1024400000218460C1602360E0601060022330BC22 +:10245000037570470360144611688B4208D3A26865 +:102460005B1A002AF6D18260C4600360A060EDE7C9 +:10247000D568CB1A82600222C560E060C1688860BE +:102480002360027530BC70478460C4601060DDE773 +:102490008C0D0020F8B5224E34682CB32368002B35 +:1024A0003AD11D461F4F04E03468ECB12368002B7D +:1024B00032D1A36803B1DD602069336003682575FC +:1024C0001B68BB4221D1037F4560022BC46020D032 +:1024D000FFF73AFF6368002BE6D023602046FFF742 +:1024E000A5FF3468002CE1D1EFF3108372B60E4AD9 +:1024F00000211068116003B962B628B18468FFF743 +:1025000095FF20460028F9D1F8BD224600219847C2 +:10251000E0E783689847DDE7013B2360E4E700BF1D +:102520008C0D0020790D00009C0D0020044A054B05 +:102530001168054A1960136801331360FFF7AABFD9 +:10254000041000E0A40D0020A80D002070B5214C5F +:10255000237883B9204B01221B7822701BBB1F4BB1 +:102560001B78002B29D11E4B00211A68217012B153 +:10257000EFF3058202B170BDEFF3108072B61A68F6 +:10258000F2B1184C2178D9B90126556926701D6021 +:10259000D5B1A96100B962B6002593681046557798 +:1025A0009847257070BDFFF7C5FE0028D7D000F012 +:1025B0005FFB0A4B1B78002BD5D000F045FBD2E720 +:1025C0000028D8D162B670BD074B1D600028E3D14A +:1025D000E1E700BFA00D0020C8030020C80D0020C7 +:1025E000940D0020980D0020900D00208C4A8D4BFA +:1025F00090422DE9F0438C4D5C699969EF681DD9D3 +:102600008A4B984240F20181894B40F22766DFF8FD +:1026100060E20344874D1A0AAEFB0232D30903EB92 +:10262000830303EB830202F2E243B34228BF334643 +:10263000A3F54873A5FB0336F60804E07E4EB042CE +:1026400094BF06260E26774A07F01F0ED2F8803078 +:10265000B64543F0C003C2F880300AD2724B27F06F +:102660001F071A463743DF601368002BFCDA07F0B8 +:102670001F0E14F000732ED1704D714AD5F810C0A2 +:1026800015460CEA0202AA420ABF4FF0C0534FF4AB +:102690008052002284EA030515F0605F06D024F022 +:1026A000605403F060535F4D1C436C6181EA020388 +:1026B00013F4405F08D05B4B21F4405111431A469C +:1026C0009961936C1D07FCD444F00074554A546121 +:1026D000936C9906FCD401215A4D0A4601FB02F382 +:1026E00000FB03F3AB4209D8072A00F284800132D1 +:1026F00001FB02F300FB03F3AB42F5D95248534D03 +:102700001844A5FB0030030D6C2B79D8352B7ED8EF +:10271000DFF8608136234E48DFF820C14D4DDCF8EC +:102720000090B0FBF2F009EA05054545B0FBF1F079 +:102730000BD043F400534FF480586546CCF800802A +:10274000CCF800302B68002BFCDADFF8D8C0013A57 +:10275000DCF8103003F00703934207D002F00702C1 +:102760006546CCF81020AB6CDB03FCD40139890240 +:1027700084EA010313F4E05F0AD02A4B24F4E05406 +:1027800001F4E0511A460C435C61936C9907FCD448 +:10279000314B32490344DB09A1FB0331090B042906 +:1027A00028BF04214B1E1B0284EA030212F4407F5F +:1027B00006D024F4407403F440731A4A1C43546155 +:1027C000184B24F000741A465C61936C9B06FCD491 +:1027D000B0FBF1F1224A7645224B1060196008D215 +:1027E000114B27F01F071A463743DF601368002B91 +:1027F000FCDABDE8F083042980D8013101226DE7BD +:10280000DFF874806C23184886E712261BE7174808 +:10281000DFF8688000FB03F043EA08087CE700BFAC +:1028200000A4781F00C00F40000008400046C323EA +:1028300000BA3CDC1F85EB5100366E0100800D4074 +:1028400040300080FFB19F26808D5B00819F5E1627 +:1028500000B29F267F3001807FD1F0089F10E500F5 +:10286000C4030020C003002000643F4D001BB700DC +:1028700023B24C00362000806C20008000200080B5 +:10288000002852D02DE9F04F814683B0274C01201B +:10289000274D284E54E8003F2A68316844E8000379 +:1028A000002BF7D1244F4FF47A7E2448D7F800C08C +:1028B000BB4607F1C6470368C1EB0C0107F5DE17FD +:1028C00007F67F67A7FB03C3BA4601279B0CB1FB42 +:1028D000F3F30EFB023854E8003F2A68316844E8FD +:1028E0000073002BF7D1DBF800C04FF47A7E036849 +:1028F0000EFB02F2C1EB0C01AAFB033EC8EB020384 +:102900004FEA9E42B1FBF2F1CA18B2F57A7F07D3C3 +:10291000B9F1010908F57A78DDD103B0BDE8F08F8F +:102920000190FFF713FE0198D5E770478C32002025 +:10293000A80D0020A40D0020041000E0C403002016 +:10294000F0B44E1E0025374600E00135B0FBF2F32F +:1029500002FB130000F13704092800F13000E4B253 +:1029600098BFC4B2184607F8014F002BEDD14A19A1 +:1029700053704DB1013316F8014F1778E81A3770CC +:10298000834202F80149F5DB0846F0BC704700BFFE +:10299000A4484FF00F0CA44B826F42F47F02F0B5B5 +:1029A00082670025D0F880204FF470469F4C4FF48A +:1029B000604E29464FF4806714432A46C0F8804091 +:1029C000A3F88C6148F2B826A3F88EC1A3F8905101 +:1029D000B3F8880180B240F0F000A3F8880101EB61 +:1029E0004100914B0131002540011C46042903445C +:1029F000A3F804E0DF805A841A865A805A81DE8167 +:102A00005A82DA825A83DA83E9D1B4F888014FF026 +:102A10000F0C874B4FF4704680B229464FF4604745 +:102A20002A4640EA0C004FF4806EA4F88801B4F8FE +:102A3000880180B240F47060A4F88801A3F88C612A +:102A400048F2B826A3F88EC1A3F89051B3F88801D4 +:102A500080B240F0F000A3F8880101EB4100744B14 +:102A60000131002540011C46042903449F80A3F83E +:102A700006E05A841A865A805A81DE815A82DA82A6 +:102A80005A83DA83E9D1B4F888014FF00F0C694B0F +:102A90004FF4704680B229464FF460472A4640EA18 +:102AA0000C004FF4806EA4F88801B4F8880180B25D +:102AB00040F47060A4F88801A3F88C6148F2B8264D +:102AC000A3F88EC1A3F89051B3F8880180B240F00A +:102AD000F000A3F8880101EB4100564B01310025BD +:102AE00040011C46042903449F80A3F806E05A8451 +:102AF0001A865A805A81DE815A82DA825A83DA83B0 +:102B0000E9D1B4F888014FF00F0C4B4B4FF47047EC +:102B100080B229464FF460462A4640EA0C004FF442 +:102B2000806EA4F88801B4F8880180B240F4706027 +:102B3000A4F88801A3F88C7148F2B827A3F88EC1D5 +:102B4000A3F89051B3F8880180B240F0F000A3F8E8 +:102B5000880101EB4100384B013140011C4604293A +:102B600003449E80A3F806E05A841A865A805A814C +:102B7000DF815A82DA825A83DA83EAD1B4F8883163 +:102B80000F27002241F201069BB245F6C05E1146B6 +:102B900043F226053B43A4F88831B4F888319BB250 +:102BA00043F47063A4F888315001244B013203448C +:102BB000042A99815981DF819E82A3F806E0198059 +:102BC00019829D81F0D100220F2741F2010645F6BE +:102BD000C055114643F226045001194B01320344FB +:102BE000042A99815981DF819E82DD8019801982B2 +:102BF0009C81F1D100220F2741F2010645F6C05514 +:102C0000114643F2260450010E4B01320344042ABC +:102C100099815981DF819E82DD80198019829C8192 +:102C2000F1D1F0BD00C00F4000C03D40000003FCEA +:102C300000003E4000403E4000803E4000C01D403D +:102C400000001E4000401E4038B5074B1C784CB1B8 +:102C5000064D55F8043F002BFBD09847631E13F038 +:102C6000FF04F6D138BD00BFC80D0020A80D00201C +:102C7000014B00221A707047C803002070B50F4E38 +:102C80000F4D761BB61018BF002405D0013455F83F +:102C9000043B9847A642F9D10A4E0B4D761B00F033 +:102CA00063F8B61018BF002406D0013455F8043B71 +:102CB0009847A642F9D170BD70BD00BF48160060AC +:102CC000481600604C1600604816006070B4840717 +:102CD00046D0541E002A41D0CDB2034602E0621E07 +:102CE000E4B3144603F8015B9A07F8D1032C2ED9FC +:102CF000CDB245EA05250F2C45EA054519D903F162 +:102D000010022646103E0F2E42F8105C42F80C5C72 +:102D100042F8085C42F8045C02F11002F2D8A4F117 +:102D2000100222F00F0204F00F041032032C13449F +:102D30000DD91E462246043A032A46F8045BFAD807 +:102D4000221F22F003020432134404F003042CB1C6 +:102D5000C9B21C4403F8011BA342FBD170BC7047ED +:102D600014460346C2E700BF5FF800F0E1150060BB +:042D7000F8B500BFF3 +:102D740000000042C8801F40B8821F4008000000C5 +:102D840000000042C4801F40B4821F4004000000C1 +:102D940000C0004224801F4014821F401000000025 +:102DA40000C0004228801F4018821F4020000000FD +:102DB40000C000422C801F401C821F4040000000C5 +:102DC40000C0004234801F4024821F4000010000E4 +:102DD4000040004264811F4054831F4000040000EF +:102DE4000040004280811F4070831F4000000200A9 +:102DF400004000427C811F406C831F4000000100A2 +:102E04000040004268811F4058831F4000080000B2 +:102E1400004000423C811F402C831F400100000001 +:102E24000040004244811F4034831F4004000000DE +:102E34000040004240811F4030831F4002000000D8 +:102E44000040004248811F4038831F4008000000B2 +:102E54000000004204811F40F4821F40000004006F +:102E64000000004208811F40F8821F400000080053 +:102E74000000004218811F4008831F4000008000AA +:102E84000000004214811F4004831F4000004000E2 +:102E94000000004200811F40F0821F400000020039 +:102EA40000000042FC801F40EC821F400000010033 +:102EB4000000004224811F4014831F4000000004CE +:102EC4000000004228811F4018831F4000000008B2 +:102ED400000000421C811F400C831F4000000001C1 +:102EE4000000004220811F4010831F4000000002A8 +:102EF40000000042EC801F40DC821F4000100000F4 +:102F040000000042F0801F40E0821F4000200000CB +:102F14000000004234811F4024831F400000004011 +:102F24000000004238811F4028831F4000000080B9 +:102F34000080004294801F4084821F4000000400EF +:102F440000C0004290801F4080821F40000000802B +:102F540000800042A8801F4098821F40000080002B +:102F640000800042A4801F4094821F400000400063 +:102F7400004000426C811F405C831F400010000031 +:102F840000C0004230801F4020821F4080000000AB +:102F940000800042C8811F40B8831F4000800000A9 +:102FA40000800042C4811F40B4831F4000400000E1 +:102FB40000800042C0811F40B0831F4000200000F9 +:102FC40000800042BC811F40AC831F400010000001 +:102FD40000800042D0811F40C0831F4000000200D7 +:102FE40000800042CC811F40BC831F4000000100D0 +:102FF4000001000078030020120000000006000019 +:10300400100300200A000000000200003403002026 +:1030140043000000000700003403002043000000C8 +:1030240000030000A40300200000000001030904C1 +:103034001C03002000000000020309048C0300208C +:103044000000000003030904A8030020000000009E +:10305400000000000000000000000000010000006B +:10306400020000001700000012000000040000002D +:1030740016000000150000001300000014000000FA +:103084000A06000202000040010000001803540078 +:10309400650065006E0073007900640075006900C6 +:1030A4006E006F0009024300020100C032090400EF +:1030B40000010202010005240010010524010101A0 +:1030C40004240206052406000107058203100010EB +:1030D40009040100020A0000000705030240000081 +:1030E40007058402400000001201000202000040B3 +:1030F400C01683047902010203010000160355007F +:103104005300420020005300650072006900610012 +:103114006C000000040309040C030000000000001C +:10312400000000000000000000000000000000009B +:103134000029DE07007B9A17010000000000000050 +:040000056000100087 +:00000001FF diff --git a/tools/odrive/tests/motor_calibration_test.py b/tools/odrive/tests/motor_calibration_test.py deleted file mode 100644 index e755f832..00000000 --- a/tools/odrive/tests/motor_calibration_test.py +++ /dev/null @@ -1,70 +0,0 @@ - -import test_runner - -import time -from math import pi -import os - -from fibre.utils import Logger -from test_runner import AxisTestContext, MotorTestContext, test_assert_eq, test_assert_no_error, request_state -from odrive.enums import * - -def modpm(val, range): - return ((val + (range / 2)) % range) - (range / 2) - -class TestMotorCalibration(): - """ - Runs the motor calibration and checks if the measurements match the expectation. - """ - - def is_compatible(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext): - return axis_ctx.yaml == motor_ctx.yaml['name'] # check if connected - - def run_test(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, logger: Logger): - # reset old calibration values - axis_ctx.handle.motor.config.phase_resistance = 0.0 - axis_ctx.handle.motor.config.phase_inductance = 0.0 - - axis_ctx.handle.clear_errors() - - # run calibration - request_state(axis_ctx, AXIS_STATE_MOTOR_CALIBRATION) - time.sleep(6) - test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) - test_assert_no_error(axis_ctx) - - # check if measurements match expectation - test_assert_eq(axis_ctx.handle.motor.config.phase_resistance, float(motor_ctx.yaml['phase-resistance']), accuracy=0.2) - test_assert_eq(axis_ctx.handle.motor.config.phase_inductance, float(motor_ctx.yaml['phase-inductance']), accuracy=0.5) - - -class TestDisconnectedMotorCalibration(): - """ - Tests if the motor calibration fails as expected if the phases are floating. - """ - - def is_compatible(self, axis_ctx: AxisTestContext): - return axis_ctx.yaml == 'floating' - - def run_test(self, axis_ctx: AxisTestContext, logger: Logger): - axis = axis_ctx.handle - - # reset old calibration values - axis_ctx.handle.motor.config.phase_resistance = 0.0 - axis_ctx.handle.motor.config.phase_inductance = 0.0 - - axis_ctx.handle.clear_errors() - - # run test - request_state(axis_ctx, AXIS_STATE_MOTOR_CALIBRATION) - time.sleep(6) - test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) - test_assert_eq(axis_ctx.handle.error, errors.axis.ERROR_MOTOR_FAILED) - test_assert_eq(axis_ctx.handle.motor.error, errors.motor.ERROR_PHASE_RESISTANCE_OUT_OF_RANGE) - - -if __name__ == '__main__': - test_runner.run([ - TestMotorCalibration(), - TestDisconnectedMotorCalibration() - ]) From 245e2bfe25ed86e1823e82281566de9637d2c544 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 9 Apr 2020 23:12:57 +0200 Subject: [PATCH 311/549] add partial uart ascii test --- tools/odrive/tests/calibration_test.py | 2 + tools/odrive/tests/test_runner.py | 20 + tools/odrive/tests/uart_ascii_test.py | 149 ++++ tools/odrive/tests/uart_pass_through.ino.hex | 786 +++++++++++++++++++ 4 files changed, 957 insertions(+) create mode 100644 tools/odrive/tests/uart_ascii_test.py create mode 100644 tools/odrive/tests/uart_pass_through.ino.hex diff --git a/tools/odrive/tests/calibration_test.py b/tools/odrive/tests/calibration_test.py index 0c179a20..5316f8d4 100644 --- a/tools/odrive/tests/calibration_test.py +++ b/tools/odrive/tests/calibration_test.py @@ -178,6 +178,8 @@ class TestEncoderIndexSearch(): time.sleep(3) test_assert_eq(enc_ctx.handle.index_found, False) + with open("/sys/class/gpio/gpio{}/direction".format(20), "w") as fp: + fp.write("out") with open("/sys/class/gpio/gpio{}/value".format(20), "w") as gpio: gpio.write("0") time.sleep(0.1) diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index 0c4ec395..cab97478 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -4,6 +4,7 @@ import sys, os sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) +import stat import odrive from fibre import Logger, Event import argparse @@ -210,6 +211,8 @@ def program_teensy(hex_file_path, program_gpio: int, logger: Logger): """ # Put Teensy into program mode by pulling it's program pin down + with open("/sys/class/gpio/gpio{}/direction".format(program_gpio), "w") as fp: + fp.write("out") with open("/sys/class/gpio/gpio{}/value".format(program_gpio), "w") as gpio: gpio.write("0") time.sleep(0.1) @@ -256,6 +259,8 @@ parser.add_argument("--ignore", metavar='DEVICE', action='store', nargs='+', # TODO: implement parser.add_argument("--test-rig-yaml", type=argparse.FileType('r'), required=True, help="test rig YAML file") +parser.add_argument("--setup-host", action='store_true', default=False, + help="configure operating system functions such as GPIOs (requires root)") parser.set_defaults(ignore=[]) args = parser.parse_args() @@ -265,3 +270,18 @@ test_rig_yaml = yaml.load(args.test_rig_yaml, Loader=yaml.BaseLoader) logger = Logger() available_test_objects = yaml_to_test_objects(test_rig_yaml, logger) + + +if args.setup_host: + def export_gpio(gpio): + if not os.path.isdir("/sys/class/gpio/gpio{}".format(gpio)): + with open("/sys/class/gpio/export", "w") as fp: + fp.write(str(gpio)) + os.chmod("/sys/class/gpio/gpio{}/value".format(gpio), stat.S_IROTH | stat.S_IWOTH) + os.chmod("/sys/class/gpio/gpio{}/direction".format(gpio), stat.S_IROTH | stat.S_IWOTH) + + # TODO: read configuration from yaml file + export_gpio(20) # connected to Teensy GPIO + export_gpio(26) # connected to Teensy Program pin + + os.chmod("/dev/ttyS0", stat.S_IROTH | stat.S_IWOTH) diff --git a/tools/odrive/tests/uart_ascii_test.py b/tools/odrive/tests/uart_ascii_test.py new file mode 100644 index 00000000..c538bfa2 --- /dev/null +++ b/tools/odrive/tests/uart_ascii_test.py @@ -0,0 +1,149 @@ + +import test_runner + +import struct +import time +import os +import io +import serial +import functools +import operator + +from fibre.utils import Logger +from odrive.enums import errors +from test_runner import ODriveTestContext, test_assert_eq, program_teensy + + +def append_checksum(command): + return command + b'*' + str(functools.reduce(operator.xor, command)).encode('ascii') + +def strip_checksum(command): + command, _, checksum = command.partition(b'*') + test_assert_eq(int(checksum.strip()), functools.reduce(operator.xor, command)) + return command + +def reset_state(ser): + """Resets the state of the ASCII protocol by flushing all buffers""" + ser.write(b'\n') + ser.flushOutput() + time.sleep(0.1) + ser.flushInput() + +class TestUartAscii(): + def is_compatible(self, odrive: ODriveTestContext): + return True + + def run_test(self, odrive: ODriveTestContext, logger: Logger): + """ + Tests the most important functions of the ASCII protocol. + """ + + # Disable noise + with open("/sys/class/gpio/gpio{}/direction".format(20), "w") as fp: + fp.write("out") + with open("/sys/class/gpio/gpio{}/value".format(20), "w") as gpio: + gpio.write("0") + + hexfile = 'uart_pass_through.ino.hex' + program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) + time.sleep(1.0) + + odrive.handle.axis0.config.enable_step_dir = False + odrive.handle.config.enable_uart = True + + with serial.Serial('/dev/ttyS0', 115200, timeout=1) as ser: + # reset port to known state + reset_state(ser) + + # Read a top-level attribute + ser.write(b'r vbus_voltage\n') + response = float(ser.readline().strip()) + test_assert_eq(response, odrive.handle.vbus_voltage, accuracy=0.1) + + # Read an unknown attribute + ser.write(b'r blahblah\n') + response = ser.readline().strip() + test_assert_eq(response, b'invalid property') + + # Send command with delays in between + for byte in b'r vbus_voltage\n': + ser.write([byte]) + time.sleep(0.1) + response = float(ser.readline().strip()) + test_assert_eq(response, odrive.handle.vbus_voltage, accuracy=0.1) + + # Test GCode checksum and comments + ser.write(b'r vbus_voltage *12\n') # invalid checksum + test_assert_eq(ser.readline(), b'') + ser.write(append_checksum(b'r vbus_voltage ') + b' ; this is a comment\n') # valid checksum + response = float(strip_checksum(ser.readline()).strip()) + test_assert_eq(response, odrive.handle.vbus_voltage, accuracy=0.1) + + # Read an attribute with a long name + ser.write(b'r axis0.motor.current_control.v_current_control_integral_d\n') + response = float(ser.readline().strip()) + test_assert_eq(response, odrive.handle.axis0.motor.current_control.v_current_control_integral_d, accuracy=0.1) + + + # ascii: `r vbus_voltage` + + + +class TestUartNoise(): + def is_compatible(self, odrive: ODriveTestContext): + return True + + def run_test(self, odrive: ODriveTestContext, logger: Logger): + """ + Tests the most important functions of the ASCII protocol. + """ + + # Disable noise + with open("/sys/class/gpio/gpio{}/direction".format(20), "w") as fp: + fp.write("out") + with open("/sys/class/gpio/gpio{}/value".format(20), "w") as gpio: + gpio.write("0") + + hexfile = 'uart_pass_through.ino.hex' + program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) + time.sleep(1.0) + + odrive.handle.axis0.config.enable_step_dir = False + odrive.handle.config.enable_uart = True + + with serial.Serial('/dev/ttyS0', 115200, timeout=1) as ser: + # reset port to known state + reset_state(ser) + + # Enable square wave of ~1.6MHz on the ODrive's RX line + with open("/sys/class/gpio/gpio{}/value".format(20), "w") as gpio: + gpio.write("1") + + time.sleep(0.1) + reset_state(ser) + + # Read an attribute (should fail because the command is not passed through) + ser.write(b'r vbus_voltage\n') + test_assert_eq(ser.readline(), b'') + + # Disable square wave + with open("/sys/class/gpio/gpio{}/value".format(20), "w") as gpio: + gpio.write("0") + + # Give receiver some time to recover + time.sleep(0.1) + + # reset port to known state + reset_state(ser) + + # Try again + ser.write(b'r vbus_voltage\n') + response = float(ser.readline().strip()) + test_assert_eq(response, odrive.handle.vbus_voltage, accuracy=0.1) + + +if __name__ == '__main__': + test_runner.run([ + TestUartAscii(), + TestUartNoise() + ]) diff --git a/tools/odrive/tests/uart_pass_through.ino.hex b/tools/odrive/tests/uart_pass_through.ino.hex new file mode 100644 index 00000000..8175c58e --- /dev/null +++ b/tools/odrive/tests/uart_pass_through.ino.hex @@ -0,0 +1,786 @@ +:0200000460009A +:100000004643464200000156000000000101020084 +:1000100000000000000000000000000000000000E0 +:1000200000000000000000000000000000000000D0 +:1000300000000000000000000000000000000000C0 +:1000400000000000010403000000000000000000A8 +:100050000000200000000000000000000000000080 +:100060000000000000000000000000000000000090 +:100070000000000000000000000000000000000080 +:10008000EB04180A063204260000000000000000FD +:10009000050404240000000000000000000000002F +:1000A0000000000000000000000000000000000050 +:1000B0000604000000000000000000000000000036 +:1000C0000000000000000000000000000000000030 +:1000D00020041808000000000000000000000000DC +:1000E0000000000000000000000000000000000010 +:1000F0000000000000000000000000000000000000 +:10010000D8041808000000000000000000000000F3 +:100110000204180804200000000000000000000095 +:1001200000000000000000000000000000000000CF +:10013000600400000000000000000000000000005B +:1001400000000000000000000000000000000000AF +:10015000000000000000000000000000000000009F +:10016000000000000000000000000000000000008F +:10017000000000000000000000000000000000007F +:10018000000000000000000000000000000000006F +:10019000000000000000000000000000000000005F +:1001A000000000000000000000000000000000004F +:1001B000000000000000000000000000000000003F +:1001C000000100000010000001000000000000001D +:1001D000000001000000000000000000000000001E +:1001E000000000000000000000000000000000000F +:1001F00000000000000000000000000000000000FF +:10020000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE +:10021000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEE +:10022000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDE +:10023000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCE +:10024000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBE +:10025000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAE +:10026000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E +:10027000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8E +:10028000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7E +:10029000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6E +:1002A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5E +:1002B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4E +:1002C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3E +:1002D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E +:1002E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1E +:1002F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0E +:10030000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD +:10031000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED +:10032000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD +:10033000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCD +:10034000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD +:10035000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAD +:10036000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D +:10037000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8D +:10038000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7D +:10039000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6D +:1003A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D +:1003B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4D +:1003C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3D +:1003D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2D +:1003E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1D +:1003F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0D +:10040000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC +:10041000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC +:10042000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC +:10043000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCC +:10044000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC +:10045000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAC +:10046000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9C +:10047000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8C +:10048000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7C +:10049000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C +:1004A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5C +:1004B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4C +:1004C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3C +:1004D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2C +:1004E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1C +:1004F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0C +:10050000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB +:10051000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEB +:10052000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB +:10053000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCB +:10054000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBB +:10055000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAB +:10056000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9B +:10057000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8B +:10058000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7B +:10059000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6B +:1005A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B +:1005B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4B +:1005C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3B +:1005D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2B +:1005E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1B +:1005F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0B +:10060000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA +:10061000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEA +:10062000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA +:10063000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCA +:10064000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA +:10065000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAA +:10066000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9A +:10067000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8A +:10068000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7A +:10069000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6A +:1006A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5A +:1006B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4A +:1006C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3A +:1006D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A +:1006E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1A +:1006F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A +:10070000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9 +:10071000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9 +:10072000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9 +:10073000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC9 +:10074000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB9 +:10075000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9 +:10076000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF99 +:10077000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF89 +:10078000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF79 +:10079000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF69 +:1007A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF59 +:1007B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF49 +:1007C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF39 +:1007D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF29 +:1007E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF19 +:1007F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF09 +:10080000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8 +:10081000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8 +:10082000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD8 +:10083000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC8 +:10084000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB8 +:10085000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA8 +:10086000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF98 +:10087000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF88 +:10088000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF78 +:10089000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF68 +:1008A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF58 +:1008B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF48 +:1008C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF38 +:1008D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF28 +:1008E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF18 +:1008F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF08 +:10090000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7 +:10091000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE7 +:10092000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD7 +:10093000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7 +:10094000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB7 +:10095000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA7 +:10096000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF97 +:10097000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF87 +:10098000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF77 +:10099000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF67 +:1009A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57 +:1009B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF47 +:1009C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF37 +:1009D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27 +:1009E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF17 +:1009F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF07 +:100A0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6 +:100A1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE6 +:100A2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD6 +:100A3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC6 +:100A4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB6 +:100A5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA6 +:100A6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF96 +:100A7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF86 +:100A8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF76 +:100A9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF66 +:100AA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 +:100AB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF46 +:100AC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF36 +:100AD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF26 +:100AE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16 +:100AF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF06 +:100B0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5 +:100B1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5 +:100B2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD5 +:100B3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC5 +:100B4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB5 +:100B5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA5 +:100B6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF95 +:100B7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF85 +:100B8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF75 +:100B9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF65 +:100BA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF55 +:100BB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF45 +:100BC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF35 +:100BD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF25 +:100BE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF15 +:100BF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF05 +:100C0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4 +:100C1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE4 +:100C2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD4 +:100C3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC4 +:100C4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB4 +:100C5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA4 +:100C6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF94 +:100C7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF84 +:100C8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF74 +:100C9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF64 +:100CA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF54 +:100CB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF44 +:100CC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF34 +:100CD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF24 +:100CE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF14 +:100CF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF04 +:100D0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3 +:100D1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE3 +:100D2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD3 +:100D3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC3 +:100D4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB3 +:100D5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA3 +:100D6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF93 +:100D7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83 +:100D8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF73 +:100D9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF63 +:100DA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF53 +:100DB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43 +:100DC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF33 +:100DD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF23 +:100DE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF13 +:100DF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF03 +:100E0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2 +:100E1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE2 +:100E2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD2 +:100E3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2 +:100E4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB2 +:100E5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA2 +:100E6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF92 +:100E7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF82 +:100E8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF72 +:100E9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF62 +:100EA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF52 +:100EB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF42 +:100EC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF32 +:100ED000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF22 +:100EE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF12 +:100EF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02 +:100F0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1 +:100F1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE1 +:100F2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD1 +:100F3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC1 +:100F4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB1 +:100F5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA1 +:100F6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF91 +:100F7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81 +:100F8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF71 +:100F9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF61 +:100FA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF51 +:100FB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF41 +:100FC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF31 +:100FD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF21 +:100FE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF11 +:100FF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF01 +:10100000D10020402C100060000000000000000013 +:1010100020100060001000600000000000000000D0 +:1010200000000060E030000000000000000001202F +:1010300035100060764B0720764C4FF42A01764A33 +:101040005C64186499639546744A75498A420FD066 +:10105000744B9A420CD2D4430846234423F0030332 +:1010600004330B4450F8041B984242F8041BF9D196 +:101070006D4A6E498A420FD06D4B9A420CD2D443CE +:101080000846234423F0030304330B4450F8041BA5 +:10109000984242F8041BF9D1664A674B9A420BD238 +:1010A000D04311460024034423F0030304331344C4 +:1010B00041F8044B8B42FBD1604A4FF47001604B06 +:1010C000116003F530715F4A43F8042F9942FBD158 +:1010D0005D4B80215D4A03F8011B9342FBD15C4CC0 +:1010E0004FF080375B4E4FF0FF32DFF8A8A10025AC +:1010F000DFF8A491DFF8A481574B4549C3F800A05D +:10110000C4F80471C4F80091C4F8F470C4F8F08015 +:10111000F36923F07F0343F04003F361736A23F024 +:101120007F0343F0400373628A66CA660A674A67B0 +:1011300000F0B6F8494A6320494B4A49106003223F +:101140001D60CAF8381043F8082C4749474A4848F8 +:10115000C3F8082D0B68474A43F08073CAF83C0077 +:1011600045480B601368454943F001031360036869 +:101170000B6000F0E5F8C4F804714148C4F8009130 +:10118000C4F8F470C4F8F08000F056FA00BF00BF55 +:1011900000BF00BFF16E3B4A41F440513A4BF1664B +:1011A0001560C2F80851C2F81851C2F82851C2F8A7 +:1011B00038519A6BD20708D442F615623349596503 +:1011C0001A659A6B42F001029A632F4A304C936879 +:1011D00043F00113936000F013FA2368132BFCD93A +:1011E00000F05CF900F0D0F900F012FA00F0DAF843 +:1011F00000F00AFA2368B3F5967FFBD300F020FADB +:1012000000F012FA00F0F4F900F0F6F9FAE700BF86 +:1012100000C00A40ABAAAAAA008007200000000074 +:1012200050160060D016000000000020242D006041 +:10123000C0030020C0030020C032002088ED00E081 +:10124000FC0F00201102000000E400E0A0E400E038 +:1012500000800D4000C00F4008ED00E014E000E009 +:1012600018E000E0890D0000FCED00E00000202007 +:101270008D0E0000001000E0041000E0840D00203E +:101280000046C3230040084000400D400000C05607 +:10129000880D0020001000201B1018200C0D1113C9 +:1012A000F0B5194A0021194B4FF0100E18480124CF +:1012B000184E194D0160194FC2F800E01E6015600C +:1012C000174E184D1F601660174F1D60174E184DB2 +:1012D00017601E60174F1560174E184D1F6016607F +:1012E0001D600460BFF34F8FBFF36F8FC3F8B01161 +:1012F000BFF34F8FBFF36F8F52F8883C43F44033F6 +:1013000042F8883CF0BD00BF9CED00E0A0ED00E09D +:1013100094ED00E0250008031100200021000207E1 +:1013200012000020250008131300202027000B13B3 +:101330001400004033001013150000602F000B074D +:10134000F0B4174A40274FF480314FF480564FF4E1 +:1013500000554FF4404443F24200136913F0020F6A +:1013600006D0946151619061136913F0020FF8D1B6 +:1013700013F4005F01D15561EFE713F4805F01D1F1 +:101380005661EAE7002BE8DA13F4803F01D091615F +:10139000E3E75B0601D45761DFE7F0BC704700BFAD +:1013A00000800D40364A03203649F3EE096A13687F +:1013B00023F00103F0B51360C2F89000D1F8E030DB +:1013C000DFF8D4E0DCB2C3F30B202F4FC0EB135394 +:1013D00007EE904AA4F15501CEF80040B8EEE77A46 +:1013E00003EB830407EE900A03FB01F13B6003EB80 +:1013F0008313B8EEE75A07EE901A091B77EE666A78 +:10140000B8EE677A214D07EE901A0B44C5ED006ADD +:10141000F8EE677A1E4EC7EE265A1E4930601068F5 +:1014200087EEA66A07EE903AF8EE677A87EEA67A1C +:1014300075EE855A76EE056AFCEEE55AFCEEE66A34 +:1014400015EE903A40EA035316EE900A77EE057ACD +:10145000136001EA0041D2F81031FCEEE77A0B4349 +:10146000C2F8103117EE903AD2F81011C3F30B0303 +:101470000B43C2F81031136843F002031360F0BD50 +:1014800080810D4000441F40F0030020EC03002049 +:10149000F40300200000FF0FE8030020304B40F66B +:1014A000617270B5C3F8202140F2044500F0B8F82D +:1014B0002C492D48D1F880202C4C42F003022C4BB3 +:1014C000C1F88020C0F86051226813401BB9D0F8E1 +:1014D000A8319A071AD0244B4FF00041234A516398 +:1014E0001A46D3F8401141F00201C3F84011D2F876 +:1014F00040319B07FBD44FF400301E491B4B4FF08B +:101500000042086019209A6300F092F81A4D0022F8 +:10151000164B4FF08041144C0A26996328461A60F6 +:101520001146C4F8A8614FF4207200F075F84FF42A +:1015300081064FF4800040F24313104A10492E6098 +:101540002864C4F85851C2F80412C4F848310D4A4E +:101550004FF4003101231160C4F8403170BD00BF69 +:1015600000800D4000C00F4000002E4000900D4054 +:10157000001C1E008CE200E0003000200010002063 +:10158000ED0300000CE100E0114B1249D86E0A4651 +:1015900040F4403030B4D86640F2B765D86EA0242D +:1015A00040F44070D8664D648C64936C1B06FCD488 +:1015B000094B40F2B760A021064A58649964936CC5 +:1015C00013F08003FBD1054A137030BC704700BF95 +:1015D00000C00F4000400C4000800C40810D0020F6 +:1015E000F8B500BFF8BC08BC9E467047FFFFFFFF80 +:1015F0005FF800F0B10000005FF800F0AD0E0000F1 +:101600005FF800F0290200005FF800F02D020000F2 +:101610005FF800F0F11200005FF800F02D160000F6 +:101620005FF800F0ED0A00005FF800F0AD00000088 +:101630005FF800F0E11100005FF800F04D0F0000CE +:101640005FF800F0DD150000450000001501000006 +:10165000000000000000000000000000000000008A +:10166000000000000000000000000000000000007A +:1016700010B5054C237833B9044B13B10448AFF3CC +:1016800000800123237010BDC00300200000000073 +:10169000D4160000084B10B51BB108490848AFF339 +:1016A00000800848036803B910BD074B002BFBD02E +:1016B000BDE81040184700BF00000000C403002030 +:1016C000D4160000C00300200000000008B5084B3D +:1016D0000121187800F084F8064B0121187800F0F9 +:1016E0007FF8054B00211878BDE8084000F078B875 +:1016F000DC030020F0020020EC020020FFF7E6BF30 +:10170000124B70B5187800F065F8114B114C054676 +:10171000187800F05FF823780F4A064683F001033B +:101720001078237000F056F80C4B50B931461878F9 +:1017300000F04AF80A4B29461878BDE8704000F0DE +:1017400043B82178187800F03FF8F3E7E40300206D +:10175000F8020020E0030020EC020020DC0300205F +:10176000F0020020044A054B106805491A68054B31 +:1017700008601A60704700BFFC020020F4020020DD +:10178000E4030020DC0300200001074B1A181B585B +:10179000D2685868104202D011B9C3F88820704747 +:1017A000C3F88420704700BF000000200001054BF3 +:1017B0001A5819189268CB681A4214BF01200020E9 +:1017C000704700BF00000020272801D8FFF7DCBFCA +:1017D000704700BF272801D8FFF7E8BF00207047F7 +:1017E00027281CD800011A4A012902EB00031058CF +:1017F00010B415D0042913D0DC68426822EA040230 +:1018000042609A68E9B102291ED003295B685DF83D +:10181000044B0CBF0F491049116015221A60704724 +:10182000DA680129446842EA040242609A6808D0F2 +:1018300040F6380111605B6815225DF8044B1A60B0 +:10184000704738211160F6E704491160F3E700BFE3 +:1018500000000020383001003800010038F001009D +:1018600004207146084203D0EFF3098000F008B865 +:10187000EFF3088000F004B8704700BF704700BF66 +:101880001B4B05211B4A382030B5C2F848110821EE +:10189000C2F8380383B05A68174C0A4317485A6095 +:1018A000C3F88410C3F888102368834202D914480F +:1018B00000F074FE0E4D08240020124A1249C5F8AB +:1018C00084400190019B934205D8019B0133019311 +:1018D000019B9342F9D9C5F888400190019B8B4246 +:1018E000EDD8019B01330193019B8B42F9D9E6E7C7 +:1018F00000C01B4000801F40B4030020FF45C323ED +:1019000000A3E1113F420F003F548900836B30B4C4 +:101910001BB1536843F40043536072B6446B9CB1EF +:10192000104B2260D3F8B0410C4217D1D3F840419C +:1019300044F48044C3F84041D3F8B851D3F840414F +:101940006404F3D5294209D10023064C8260C360A8 +:10195000D4F8B0311943C4F8B0110263426362B6DF +:1019600030BC704700002E4038B50546036B06E0DA +:10197000AA6B1C6890476A6B2346944208D01846AD +:1019800033B1012B04D05A681206F1D52B6338BD50 +:1019900000232B636B6338BDF0B5F1B9224C2349AA +:1019A0000020234B01228025A1600A601A464D6069 +:1019B000E060D3F8BC41886044F001141D4DC3F8C9 +:1019C000BC41D3F8B01141F00101C3F8B011286057 +:1019D000D2F8B031002BFBD1F0BD0904164B144DE9 +:1019E0000126114C41F0800100221E60596000F573 +:1019F000805CE26400F5005EA36400F54057D5F812 +:101A0000B01100F580462A46986041F48031C3F851 +:101A10000CC0C3F810E05F619E61C5F8B011D2F848 +:101A2000B031002BFBD1BAE700300020202000208D +:101A300000002E4004040020002000202DE9F04F7B +:101A4000BB4C83B0D4F8448118F0010FC4F8448132 +:101A50005ED0D4F8AC31002B55D04FF00119DFF82F +:101A6000F0B22646B34FCA46B96AFA6AC4F8AC3136 +:101A7000D4F8403143F40053C4F84031D4F8403135 +:101A80009D04F5D5D6F8403123F40053C6F8403113 +:101A9000C6F8B4A1D4F8B43113F00113FAD188B266 +:101AA00040F28165CBF80030A84200F29680B0F594 +:101AB000D06F80F0D881B0F5817F00F0578100F2BF +:101AC000D580802800F02681822840F0C78092B21D +:101AD00002F07F01072900F2C18089009648974DE6 +:101AE0000844016810062B706B7040F1E581C90352 +:101AF00001D501232B7002219048FFF74DFFD4F848 +:101B0000AC31002BB0D18A4AD2F8BC31002B44D181 +:101B100018F0400F18D0864BD3F8AC111A46C3F812 +:101B2000AC11D3F8BC11C3F8BC11D2F8B041804B52 +:101B3000002CFAD14FF0FF32C3F8B421D3F884312E +:101B400000F074FB7E4B1C6018F0807F03D07D4B4F +:101B50001B6803B1984718F0007F03D07A4B1B68CD +:101B600003B1984718F0040F02D0714BD3F88431B9 +:101B70006F4BD3F8482112060CD518F0800F09D00E +:101B800072490A78002A00F08E81531EDBB20B7076 +:101B900003B9FBBE03B0BDE8F08F6D49C2F8BC319C +:101BA0000868034240F0CC81654A14681C40AFD0FD +:101BB000630700F1E281670300F1EF81260700F17E +:101BC000E881250300F1E181E00600F1DA81E1021C +:101BD0009ED56048FFF7C8FE9AE742F22105A84269 +:101BE00038D06FD8B0F5086F00F02F81B0F5106FC6 +:101BF00034D1C1F30741584A584811705849594ADD +:101C00000193C6F8C801C6F8CC11C6F8D02100F07F +:101C10000FFB554A019B80210120FB64106051603D +:101C2000D6F8BC11936041F00111BA64C6F8BC113A +:101C3000D6F8B02142F48032C6F8B021444A13608D +:101C4000D4F8B031002BFBD1D4F8AC31002B7FF4A9 +:101C50000BAF58E7100C072800F03281C4F8C09190 +:101C6000D4F8AC31002B7FF4FFAE4CE740F20235E4 +:101C7000A84200F0B780B0F5A06FEFD13A4A8020BB +:101C80000125FB6450601560D6F8BC01936040F0FC +:101C90000113BA64C6F8BC31D6F8B03143F48033CE +:101CA000C6F8B031D4F8B031002BFBD10B0C5B0679 +:101CB00043F08073C6F85431D4F8AC31002B7FF474 +:101CC000D3AE20E742F22123984200F0828042F214 +:101CD00021339842C2D1244B012180221960002176 +:101CE0005A60D4F8BC21BB6442F001129960F964D7 +:101CF000C4F8BC21D4F8B03143F48033C4F8B03117 +:101D0000D4F8B031002BFBD1D4F8AC31002B7FF4E8 +:101D1000ABAEF8E6094A0221104613705370FFF784 +:101D20003BFED4F8AC31002B7FF49EAEEBE600BF57 +:101D300000002E4000300020C0012E40180400207A +:101D4000FC030020F8030020000400200804002009 +:101D50000404002040320020280400200200CC00AF +:101D6000C80002000200C8002020002092B202F049 +:101D70007F03072B3FF672AF12F0800F4FEA830309 +:101D8000884A4FF001011A44136814BF23F48033CA +:101D900023F0010313608022834B196000215A60F5 +:101DA000D4F8BC21BB6442F001129960F964C4F814 +:101DB000BC21D4F8B03143F48033C4F8B031D4F846 +:101DC000B031002BFBD1D4F8AC31002B7FF44CAEFA +:101DD00099E6764BC1F30741754A1868754B106058 +:101DE000197078E792B202F07F03072B3FF636AF07 +:101DF00012F0800F4FEA83036A4A4FF001011A4440 +:101E0000136814BF43F4803343F00103136080224E +:101E1000654B196000215A60D4F8BC21BB6442F0C4 +:101E200001129960F964C4F8BC21D4F8B03143F4CC +:101E30008033C4F8B031D4F8B031002BFBD1D4F8E2 +:101E4000AC31002B7FF410AE5DE65B4A01215B4BA9 +:101E5000127818461A70FFF79FFDD4F8AC31002BAA +:101E60007FF402AE4FE6564B586800283FF4F6AEBA +:101E7000090C1FFA82FE04E00C33586800283FF476 +:101E8000EDAE1D888D42F7D15D887545F4D1090A04 +:101E9000120C03290CBF01781989914228BF114601 +:101EA000FFF77AFD2BE6D3F8482122F08002C3F831 +:101EB000482103B0BDE8F08FCA077FF51CAE18E6D5 +:101EC000404D0120FB6029603F4B4049186059603C +:101ED0003F49D6F8B0016A6001F5005E40F00102AA +:101EE000A1F5005001F58055BB609860A1F58050C8 +:101EF000D860C6F8B02119615D61C3F818E0D4F864 +:101F0000B031002BFBD1284A012048F28001FB644C +:101F100010604FF480305160D6F8BC11936041F0EE +:101F20000113BA64C6F8BC31D6F8B0310343C6F821 +:101F3000B031CBF80000D4F8B031002BFBD1DEE596 +:101F4000204C42F22100002524880D6084427FF459 +:101F50002BAE2049204C03C90D0C86282060A1809F +:101F6000A5717FF421AED2F8481150241B4841F0EE +:101F70008001C2F84811047016E61948FFF7F4FC16 +:101F800019E61848FFF7F0FC21E61748FFF7ECFCCC +:101F90001AE61648FFF7E8FC13E61548FFF7E4FCDD +:101FA0000CE600BFC0012E4020200020880D00203C +:101FB00088320020600D0020280400201804002032 +:101FC0008002002010040020002000208000070074 +:101FD000202400202004002080320020080400205B +:101FE0000031002000320020C0310020803100206C +:101FF00040310020002AA0F102022DE9F04714BF71 +:1020000000274FF00057022A01D9BDE8F0874FEAB8 +:10201000C01ADFF840900D4604460AEB090600217D +:1020200040229846304600F029FE012047EA05414B +:102030004AF80910C6F83880B060B8F1000FE4D053 +:10204000034BA0401C6820431860BDE8F08700BF28 +:10205000FC03002000300020002AA0F102022DE93C +:10206000F04714BF00274FF00057022A01D9BDE8FE +:10207000F0874022C501DFF84490884615440021CE +:1020800004461E4605EB090A504600F0F7FD012202 +:1020900047EA084145F80910CAF83860CAF808202C +:1020A000002EE4D004F11000034B82401868024374 +:1020B0001A60BDE8F08700BFFC030020003000205C +:1020C00012048160C36142F08002F0B442600127D3 +:1020D00001F5805601F5005501F5405401F58042A7 +:1020E0000760C660056144618261F0BC704700BF53 +:1020F000831E022B00D9704730B4064B00F1100448 +:1021000001250A4603EBC01005FA04F130BCFFF7C5 +:10211000FDBB00BF40300020831E022B00D970475A +:1021200010B4054B01240A4604FA00F103EBC01079 +:102130005DF8044BFFF7EABB00300020124A134B56 +:10214000D2F8200220F07F40984210B584B002D827 +:1021500000EB800040000E4C01A90A2200F0C8FBF1 +:1021600001A90023204611F8012B01333AB10A2BB3 +:1021700020F8022FF7D11623237004B010BD5B00A6 +:10218000DBB2237004B010BD00441F407F9698005E +:10219000980300204368C269C3F30E43054930B475 +:1021A000C3F14003044C002521F8123024F81250EA +:1021B00030BC7047F80B0020F00B0020F8B5154B31 +:1021C0001B783BB903F0FF04134B1B7813B1134D7D +:1021D0002A8802B9F8BD124F2346124EC2F580720A +:1021E0003978114806EB411600EB01213046FFF724 +:1021F00067FF31460420FFF77BFF3B780133DBB2FA +:10220000062B98BF3B704FF0000388BF3C702B80BB +:10221000F8BD00BF2C0B002028040020600C00201B +:102220002D0B0020800C00202C040020704700BFE4 +:102230000021E0222048F8B50C46204E204D00F049 +:102240001DFD204F2146204B6022347028461F4E32 +:102250001C8000F013FD23462246102102203C6022 +:10226000BC803460B480FFF7F7FE2246184B402153 +:102270000320FFF7BFFE2346224640210420FFF73C +:10228000EBFE2346402228461249FFF719FF294654 +:102290000320FFF741FF104B4A2210491048086005 +:1022A000C3F88440C3F88020D3F8482142F08072FC +:1022B000C3F84821F8BD00BF800C00202D0B002082 +:1022C000000C0020F80B0020600C0020F00B002018 +:1022D000450B0000300B002000002E40F8030020CA +:1022E0006D0B0000024A034B10881B88C01A704710 +:1022F000F80B0020F00B002010B4EFF3108272B640 +:10230000437F33B9017F012908D0032910D001236D +:10231000437702B962B65DF8044B7047114C2168EF +:10232000A1B1114943610B68086083615861EEE710 +:102330000E4C216881B10E4943610B6808608361CE +:1023400058610C4B4FF080511960E0E7064B41613A +:10235000816120601860DAE7054B4161816120608E +:102360001860EEE7740D0020700D0020640D002051 +:10237000680D002004ED00E010B4047F4160022CE1 +:10238000C26003D05DF8044BFFF7B6BF83685DF809 +:10239000044B184770B5EFF3108172B60C4C2368EC +:1023A0008BB10C4E00255A6922607AB1956101B952 +:1023B00062B65D7718469B689847EFF3108172B656 +:1023C0002368002BEFD101B962B670BD3260EEE731 +:1023D000640D0020680D0020FFF7DCBF184A30B400 +:1023E0001468002C28D0036821688B420FD2CB1AC6 +:1023F00000218460C1602360E0601060022330BC73 +:10240000037570470360144611688B4208D3A268B5 +:102410005B1A002AF6D18260C4600360A060EDE719 +:10242000D568CB1A82600222C560E060C16888600E +:102430002360027530BC70478460C4601060DDE7C3 +:102440006C0D0020F8B5224E34682CB32368002BA5 +:102450003AD11D461F4F04E03468ECB12368002BCD +:1024600032D1A36803B1DD6020693360036825754C +:102470001B68BB4221D1037F4560022BC46020D082 +:10248000FFF73AFF6368002BE6D023602046FFF792 +:10249000A5FF3468002CE1D1EFF3108372B60E4A29 +:1024A00000211068116003B962B628B18468FFF793 +:1024B00095FF20460028F9D1F8BD22460021984713 +:1024C000E0E783689847DDE7013B2360E4E700BF6E +:1024D0006C0D0020290D00007C0D0020044A054BE6 +:1024E0001168054A1960136801331360FFF7AABF2A +:1024F000041000E0840D0020880D002070B5214CF0 +:10250000237883B9204B01221B7822701BBB1F4B01 +:102510001B78002B29D11E4B00211A68217012B1A3 +:10252000EFF3058202B170BDEFF3108072B61A6846 +:10253000F2B1184C2178D9B90126556926701D6071 +:10254000D5B1A96100B962B60025936810465577E8 +:102550009847257070BDFFF7C5FE0028D7D000F062 +:102560005FFB0A4B1B78002BD5D000F045FBD2E770 +:102570000028D8D162B670BD074B1D600028E3D19A +:10258000E1E700BF800D0020B8030020A80D002067 +:10259000740D0020780D0020700D00208C4A8D4BAA +:1025A00090422DE9F0438C4D5C699969EF681DD923 +:1025B0008A4B984240F20181894B40F22766DFF84E +:1025C00060E20344874D1A0AAEFB0232D30903EBE3 +:1025D000830303EB830202F2E243B34228BF334694 +:1025E000A3F54873A5FB0336F60804E07E4EB0421F +:1025F00094BF06260E26774A07F01F0ED2F88030C9 +:10260000B64543F0C003C2F880300AD2724B27F0BF +:102610001F071A463743DF601368002BFCDA07F008 +:102620001F0E14F000732ED1704D714AD5F810C0F2 +:1026300015460CEA0202AA420ABF4FF0C0534FF4FB +:102640008052002284EA030515F0605F06D024F072 +:10265000605403F060535F4D1C436C6181EA0203D8 +:1026600013F4405F08D05B4B21F4405111431A46EC +:102670009961936C1D07FCD444F00074554A546171 +:10268000936C9906FCD401215A4D0A4601FB02F3D2 +:1026900000FB03F3AB4209D8072A00F28480013221 +:1026A00001FB02F300FB03F3AB42F5D95248534D53 +:1026B0001844A5FB0030030D6C2B79D8352B7ED840 +:1026C000DFF8608136234E48DFF820C14D4DDCF83D +:1026D0000090B0FBF2F009EA05054545B0FBF1F0CA +:1026E0000BD043F400534FF480586546CCF800807B +:1026F000CCF800302B68002BFCDADFF8D8C0013AA8 +:10270000DCF8103003F00703934207D002F0070211 +:102710006546CCF81020AB6CDB03FCD40139890290 +:1027200084EA010313F4E05F0AD02A4B24F4E05456 +:1027300001F4E0511A460C435C61936C9907FCD498 +:10274000314B32490344DB09A1FB0331090B042956 +:1027500028BF04214B1E1B0284EA030212F4407FAF +:1027600006D024F4407403F440731A4A1C435461A5 +:10277000184B24F000741A465C61936C9B06FCD4E1 +:10278000B0FBF1F1224A7645224B1060196008D265 +:10279000114B27F01F071A463743DF601368002BE1 +:1027A000FCDABDE8F083042980D8013101226DE70D +:1027B000DFF874806C23184886E712261BE7174859 +:1027C000DFF8688000FB03F043EA08087CE700BFFD +:1027D00000A4781F00C00F40000008400046C3233B +:1027E00000BA3CDC1F85EB5100366E0100800D40C5 +:1027F00040300080FFB19F26808D5B00819F5E1678 +:1028000000B29F267F3001807FD1F0089F10E50045 +:10281000B4030020B003002000643F4D001BB7004C +:1028200023B24C00362000806C2000800020008005 +:10283000002852D02DE9F04F814683B0274C01206B +:10284000274D284E54E8003F2A68316844E80003C9 +:10285000002BF7D1244F4FF47A7E2448D7F800C0DC +:10286000BB4607F1C6470368C1EB0C0107F5DE174D +:1028700007F67F67A7FB03C3BA4601279B0CB1FB92 +:10288000F3F30EFB023854E8003F2A68316844E84D +:102890000073002BF7D1DBF800C04FF47A7E036899 +:1028A0000EFB02F2C1EB0C01AAFB033EC8EB0203D4 +:1028B0004FEA9E42B1FBF2F1CA18B2F57A7F07D314 +:1028C000B9F1010908F57A78DDD103B0BDE8F08FE0 +:1028D0000190FFF713FE0198D5E770478C32002076 +:1028E000880D0020840D0020041000E0B4030020B7 +:1028F000F0B44E1E0025374600E00135B0FBF2F380 +:1029000002FB130000F13704092800F13000E4B2A3 +:1029100098BFC4B2184607F8014F002BEDD14A19F1 +:1029200053704DB1013316F8014F1778E81A37701C +:10293000834202F80149F5DB0846F0BC704700BF4E +:10294000A4484FF00F0CA44B826F42F47F02F0B505 +:1029500082670025D0F880204FF470469F4C4FF4DA +:10296000604E29464FF4806714432A46C0F88040E1 +:10297000A3F88C6148F2B826A3F88EC1A3F8905151 +:10298000B3F8880180B240F0F000A3F8880101EBB1 +:102990004100914B0131002540011C4604290344AC +:1029A000A3F804E0DF805A841A865A805A81DE81B7 +:1029B0005A82DA825A83DA83E9D1B4F888014FF077 +:1029C0000F0C874B4FF4704680B229464FF4604796 +:1029D0002A4640EA0C004FF4806EA4F88801B4F84F +:1029E000880180B240F47060A4F88801A3F88C617B +:1029F00048F2B826A3F88EC1A3F89051B3F8880125 +:102A000080B240F0F000A3F8880101EB4100744B64 +:102A10000131002540011C46042903449F80A3F88E +:102A200006E05A841A865A805A81DE815A82DA82F6 +:102A30005A83DA83E9D1B4F888014FF00F0C694B5F +:102A40004FF4704680B229464FF460472A4640EA68 +:102A50000C004FF4806EA4F88801B4F8880180B2AD +:102A600040F47060A4F88801A3F88C6148F2B8269D +:102A7000A3F88EC1A3F89051B3F8880180B240F05A +:102A8000F000A3F8880101EB4100564B013100250D +:102A900040011C46042903449F80A3F806E05A84A1 +:102AA0001A865A805A81DE815A82DA825A83DA8300 +:102AB000E9D1B4F888014FF00F0C4B4B4FF470473D +:102AC00080B229464FF460462A4640EA0C004FF493 +:102AD000806EA4F88801B4F8880180B240F4706078 +:102AE000A4F88801A3F88C7148F2B827A3F88EC126 +:102AF000A3F89051B3F8880180B240F0F000A3F839 +:102B0000880101EB4100384B013140011C4604298A +:102B100003449E80A3F806E05A841A865A805A819C +:102B2000DF815A82DA825A83DA83EAD1B4F88831B3 +:102B30000F27002241F201069BB245F6C05E114606 +:102B400043F226053B43A4F88831B4F888319BB2A0 +:102B500043F47063A4F888315001244B01320344DC +:102B6000042A99815981DF819E82A3F806E01980A9 +:102B700019829D81F0D100220F2741F2010645F60E +:102B8000C055114643F226045001194B013203444B +:102B9000042A99815981DF819E82DD801980198202 +:102BA0009C81F1D100220F2741F2010645F6C05564 +:102BB000114643F2260450010E4B01320344042A0D +:102BC00099815981DF819E82DD80198019829C81E3 +:102BD000F1D1F0BD00C00F4000C03D40000003FC3B +:102BE00000003E4000403E4000803E4000C01D408E +:102BF00000001E4000401E4038B5074B1C784CB109 +:102C0000064D55F8043F002BFBD09847631E13F088 +:102C1000FF04F6D138BD00BFA80D0020880D0020AC +:102C2000014B00221A707047B803002070B50F4E98 +:102C30000F4D761BB61018BF002405D0013455F88F +:102C4000043B9847A642F9D10A4E0B4D761B00F083 +:102C500063F8B61018BF002406D0013455F8043BC1 +:102C60009847A642F9D170BD70BD00BF48160060FC +:102C700048160060501600604816006070B4840763 +:102C800046D0541E002A41D0CDB2034602E0621E57 +:102C9000E4B3144603F8015B9A07F8D1032C2ED94C +:102CA000CDB245EA05250F2C45EA054519D903F1B2 +:102CB00010022646103E0F2E42F8105C42F80C5CC3 +:102CC00042F8085C42F8045C02F11002F2D8A4F168 +:102CD000100222F00F0204F00F041032032C1344F0 +:102CE0000DD91E462246043A032A46F8045BFAD858 +:102CF000221F22F003020432134404F003042CB117 +:102D0000C9B21C4403F8011BA342FBD170BC70473D +:102D100014460346C2E700BF5FF800F0E11500600B +:042D2000F8B500BF43 +:102D240000000042C8801F40B8821F400800000015 +:102D340000000042C4801F40B4821F400400000011 +:102D440000C0004224801F4014821F401000000075 +:102D540000C0004228801F4018821F40200000004D +:102D640000C000422C801F401C821F404000000015 +:102D740000C0004234801F4024821F400001000034 +:102D84000040004264811F4054831F40000400003F +:102D94000040004280811F4070831F4000000200F9 +:102DA400004000427C811F406C831F4000000100F2 +:102DB4000040004268811F4058831F400008000003 +:102DC400004000423C811F402C831F400100000052 +:102DD4000040004244811F4034831F40040000002F +:102DE4000040004240811F4030831F400200000029 +:102DF4000040004248811F4038831F400800000003 +:102E04000000004204811F40F4821F4000000400BF +:102E14000000004208811F40F8821F4000000800A3 +:102E24000000004218811F4008831F4000008000FA +:102E34000000004214811F4004831F400000400032 +:102E44000000004200811F40F0821F400000020089 +:102E540000000042FC801F40EC821F400000010083 +:102E64000000004224811F4014831F40000000041E +:102E74000000004228811F4018831F400000000802 +:102E8400000000421C811F400C831F400000000111 +:102E94000000004220811F4010831F4000000002F8 +:102EA40000000042EC801F40DC821F400010000044 +:102EB40000000042F0801F40E0821F40002000001C +:102EC4000000004234811F4024831F400000004062 +:102ED4000000004238811F4028831F40000000800A +:102EE4000080004294801F4084821F400000040040 +:102EF40000C0004290801F4080821F40000000807C +:102F040000800042A8801F4098821F40000080007B +:102F140000800042A4801F4094821F4000004000B3 +:102F2400004000426C811F405C831F400010000081 +:102F340000C0004230801F4020821F4080000000FB +:102F440000800042C8811F40B8831F4000800000F9 +:102F540000800042C4811F40B4831F400040000031 +:102F640000800042C0811F40B0831F400020000049 +:102F740000800042BC811F40AC831F400010000051 +:102F840000800042D0811F40C0831F400000020027 +:102F940000800042CC811F40BC831F400000010020 +:102FA4000001000068030020120000000006000079 +:102FB400000300200A000000000200002403002097 +:102FC4004300000000070000240300204300000029 +:102FD4000003000094030020000000000103090422 +:102FE4000C03002000000000020309047C030020FD +:102FF40000000000030309049803002000000000FF +:1030040000000000000000000000000004000000B8 +:103014000C000000090000000B0000000A00000082 +:103024000A060002020000400100000018035400D8 +:10303400650065006E007300790064007500690026 +:103044006E006F0009024300020100C0320904004F +:103054000001020201000524001001052401010100 +:10306400042402060524060001070582031000104B +:1030740009040100020A00000007050302400000E1 +:103084000705840240000000120100020200004013 +:10309400C0168304790201020301000016035500DF +:1030A4005300420020005300650072006900610073 +:1030B4006C000000040309040C030000000000007D +:1030C40000000000000000000000000000000000FC +:1030D4000029DE07007B9A170100000000000000B1 +:040000056000100087 +:00000001FF From 19ac132f51a43d6ad53a80d0c005a90610c14613 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 9 Apr 2020 23:13:21 +0200 Subject: [PATCH 312/549] fix UART error handling --- Firmware/communication/interface_uart.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Firmware/communication/interface_uart.cpp b/Firmware/communication/interface_uart.cpp index ae08d90c..eeffafbd 100644 --- a/Firmware/communication/interface_uart.cpp +++ b/Firmware/communication/interface_uart.cpp @@ -66,6 +66,7 @@ static void uart_server_thread(void * ctx) { if (huart4.ErrorCode != HAL_UART_ERROR_NONE) { HAL_UART_AbortReceive(&huart4); HAL_UART_Receive_DMA(&huart4, dma_rx_buffer, sizeof(dma_rx_buffer)); + dma_last_rcv_idx = 0; } // Fetch the circular buffer "write pointer", where it would write next uint32_t new_rcv_idx = UART_RX_BUFFER_SIZE - huart4.hdmarx->Instance->NDTR; @@ -96,7 +97,7 @@ void start_uart_server() { // We dont use interrupts to fetch the data, instead we periodically read // data out of the circular buffer into a parse buffer, controlled by a state machine HAL_UART_Receive_DMA(&huart4, dma_rx_buffer, sizeof(dma_rx_buffer)); - dma_last_rcv_idx = UART_RX_BUFFER_SIZE - huart4.hdmarx->Instance->NDTR; + dma_last_rcv_idx = 0; // Start UART communication thread osThreadDef(uart_server_thread_def, uart_server_thread, osPriorityNormal, 0, stack_size_uart_thread / sizeof(StackType_t) /* the ascii protocol needs considerable stack space */); From e6f95d4d4792ae899f526b597860365bbccb3e96 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 9 Apr 2020 18:53:53 -0400 Subject: [PATCH 313/549] Make sin/cos encoder pins configurable. --- Firmware/MotorControl/encoder.cpp | 4 ++-- Firmware/MotorControl/encoder.hpp | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index c56e9d5f..47b7aa95 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -304,8 +304,8 @@ void Encoder::sample_now() { } break; case MODE_SINCOS: { - sincos_sample_s_ = (get_adc_voltage(GPIO_3_GPIO_Port, GPIO_3_Pin) / 3.3f) - 0.5f; - sincos_sample_c_ = (get_adc_voltage(GPIO_4_GPIO_Port, GPIO_4_Pin) / 3.3f) - 0.5f; + sincos_sample_s_ = (get_adc_voltage(get_gpio_port_by_pin(config_.sincos_gpio_pin_sin), get_gpio_pin_by_pin(config_.sincos_gpio_pin_sin)) / 3.3f) - 0.5f; + sincos_sample_c_ = (get_adc_voltage(get_gpio_port_by_pin(config_.sincos_gpio_pin_cos), get_gpio_pin_by_pin(config_.sincos_gpio_pin_cos)) / 3.3f) - 0.5f; } break; case MODE_SPI_ABS_AMS: diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 34b85482..842d70d9 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -51,6 +51,8 @@ public: bool idx_search_unidirectional = false; // Only allow index search in known direction bool ignore_illegal_hall_state = false; // dont error on bad states like 000 or 111 uint16_t abs_spi_cs_gpio_pin = 1; + uint16_t sincos_gpio_pin_sin = 3; + uint16_t sincos_gpio_pin_cos = 4; }; Encoder(const EncoderHardwareConfig_t& hw_config, @@ -161,7 +163,9 @@ public: make_protocol_property("calib_scan_distance", &config_.calib_scan_distance), make_protocol_property("calib_scan_omega", &config_.calib_scan_omega), make_protocol_property("idx_search_unidirectional", &config_.idx_search_unidirectional), - make_protocol_property("ignore_illegal_hall_state", &config_.ignore_illegal_hall_state) + make_protocol_property("ignore_illegal_hall_state", &config_.ignore_illegal_hall_state), + make_protocol_property("sincos_gpio_pin_sin", &config_.sincos_gpio_pin_sin), + make_protocol_property("sincos_gpio_pin_cos", &config_.sincos_gpio_pin_cos) ), make_protocol_function("set_linear_count", *this, &Encoder::set_linear_count, "count") ); From 5852706a9acf17dcc64b52de896019d5fb97202b Mon Sep 17 00:00:00 2001 From: camrbuss Date: Sun, 12 Apr 2020 19:13:12 -0600 Subject: [PATCH 314/549] Joined Message and Signal CAN Table --- docs/can-protocol.md | 89 ++++++++++++++------------------------------ 1 file changed, 28 insertions(+), 61 deletions(-) diff --git a/docs/can-protocol.md b/docs/can-protocol.md index 7fb0c312..75dde574 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -32,72 +32,39 @@ Receive PDO 0x200 + nodeID = 0x223, which does not conflict with the range [0x20 Be careful that you don't assign too many nodeIDs per PDO group. Four CAN Simple nodes (32*4) is all of the available address space of a single PDO. If the bus is strictly ODrive CAN Simple nodes, a simple sequential Node ID assignment will work fine. ### Messages -CMD ID | Name | Sender | Signals | Start byte ---: | :-- | :-- | :-- | :-- -0x000 | CANOpen NMT Message\*\* | Master | - | - -0x001 | ODrive Heartbeat Message | Axis | Axis Error
    Axis Current State | 0
    4 -0x002 | ODrive Estop Message | Master | - | - -0x003 | Get Motor Error\* | Axis | Motor Error | 0 -0x004 | Get Encoder Error\* | Axis | Encoder Error | 0 -0x005 | Get Sensorless Error\* | Axis | Sensorless Error | 0 -0x006 | Set Axis Node ID | Master | Axis CAN Node ID | 0 -0x007 | Set Axis Requested State | Master | Axis Requested State | 0 -0x008 | Set Axis Startup Config | Master | - Not yet implemented - | - -0x009 | Get Encoder Estimates\* | Master | Encoder Pos Estimate
    Encoder Vel Estimate | 0
    4 -0x00A | Get Encoder Count\* | Master | Encoder Shadow Count
    Encoder Count in CPR | 0
    4 -0x00B | Set Controller Modes | Master | Control Mode
    Input Mode | 0
    4 -0x00C | Set Input Pos | Master | Input Pos
    Vel FF
    Current FF | 0
    4
    6 -0x00D | Set Input Vel | Master | Input Vel
    Current FF | 0
    4 -0x00E | Set Input Current | Master | Input Current | 0 -0x00F | Set Velocity Limit | Master | Velocity Limit | 0 -0x010 | Start Anticogging | Master | - | - -0x011 | Set Traj Vel Limit | Master | Traj Vel Limit | 0 -0x012 | Set Traj Accel Limits | Master | Traj Accel Limit
    Traj Decel Limit | 0
    4 -0x013 | Set Traj A per Count / s^2 | Master | Traj A per CSS | 0 -0x014 | Get IQ\* | Axis | Iq Setpoint
    Iq Measured | 0
    4 -0x015 | Get Sensorless Estimates\* | Master | Sensorless Pos Estimate
    Sensorless Vel Estimate | 0
    4 -0x016 | Reboot ODrive | Master\*\*\* | - | - -0x017 | Get Vbus Voltage | Master\*\*\* | Vbus Voltage | 0 -0x018 | Clear Errors | Master | - | - -0x700 | CANOpen Heartbeat Message\*\* | Slave | - | - +CMD ID | Name | Sender | Signals | Start byte | Signal Type | Bits | Factor | Offset | Byte Order +--: | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- +0x000 | CANOpen NMT Message\*\* | Master | - | - | - | - | - | - | - +0x001 | ODrive Heartbeat Message | Axis | Axis Error
    Axis Current State | 0
    4 | Unsigned Int
    Unsigned Int | 32
    32 | 1
    1 | 0
    0 | Intel
    Intel +0x002 | ODrive Estop Message | Master | - | - | - | - | - | - | - +0x003 | Get Motor Error\* | Axis | Motor Error | 0 | Unsigned Int | 32 | 1 | 0 | Intel +0x004 | Get Encoder Error\* | Axis | Encoder Error | 0 | Unsigned Int | 32 | 1 | 0 | Intel +0x005 | Get Sensorless Error\* | Axis | Sensorless Error | 0 | Unsigned Int | 32 | 1 | 0 | Intel +0x006 | Set Axis Node ID | Master | Axis CAN Node ID | 0 | Unsigned Int | 16 | 1 | 0 | Intel +0x007 | Set Axis Requested State | Master | Axis Requested State | 0 | Unsigned Int | 32 | 1 | 0 | Intel +0x008 | Set Axis Startup Config | Master | - Not yet implemented - | - | - | - | - | - | - +0x009 | Get Encoder Estimates\* | Master | Encoder Pos Estimate
    Encoder Vel Estimate | 0
    4 | IEEE 754 Float
    IEEE 754 Float | 32
    32 | 1
    1 | 0
    0 | Intel
    Intel +0x00A | Get Encoder Count\* | Master | Encoder Shadow Count
    Encoder Count in CPR | 0
    4 | Signed Int
    Signed Int | 32
    32 | 1
    1 | 0
    0 | Intel
    Intel +0x00B | Set Controller Modes | Master | Control Mode
    Input Mode | 0
    4 | Signed Int
    Signed Int | 32
    32 | 1
    1 | 0
    0 | Intel
    Intel +0x00C | Set Input Pos | Master | Input Pos
    Vel FF
    Current FF | 0
    4
    6 | Signed Int
    Signed Int
    Signed Int | 32
    16
    16 | 1
    0.1
    0.01 | 0
    0
    0 | Intel
    Intel
    Intel +0x00D | Set Input Vel | Master | Input Vel
    Current FF | 0
    4 | Signed Int
    Signed Int | 32
    32 | 0.01
    0.01 | 0
    0 | Intel
    Intel +0x00E | Set Input Current | Master | Input Current | 0 | Signed Int | 32 | 0.01 | 0 | Intel +0x00F | Set Velocity Limit | Master | Velocity Limit | 0 | IEEE 754 Float | 32 | 1 | 0 | Intel +0x010 | Start Anticogging | Master | - | - | - | - | - | - | - +0x011 | Set Traj Vel Limit | Master | Traj Vel Limit | 0 | IEEE 754 Float | 32 | 1 | 0 | Intel +0x012 | Set Traj Accel Limits | Master | Traj Accel Limit
    Traj Decel Limit | 0
    4 | IEEE 754 Float
    IEEE 754 Float | 32
    32 | 1
    1 | 0
    0 | Intel
    Intel +0x013 | Set Traj A per Count / s^2 | Master | Traj A per CSS | 0 | IEEE 754 Float | 32 | 1 | 0 | Intel +0x014 | Get IQ\* | Axis | Iq Setpoint
    Iq Measured | 0
    4 | IEEE 754 Float
    IEEE 754 Float | 32
    32 | 1
    1 | 0
    0 | Intel
    Intel +0x015 | Get Sensorless Estimates\* | Master | Sensorless Pos Estimate
    Sensorless Vel Estimate | 0
    4 | IEEE 754 Float
    IEEE 754 Float | 32
    32 | 1
    1 | 0
    0 | Intel
    Intel +0x016 | Reboot ODrive | Master\*\*\* | - | - | - | - | - | - | - +0x017 | Get Vbus Voltage | Master\*\*\* | Vbus Voltage | 0 | IEEE 754 Float | 32 | 1 | 0 | Intel +0x018 | Clear Errors | Master | - | - | - | - | - | - | - +0x700 | CANOpen Heartbeat Message\*\* | Slave | - | - | - | - | - | - | - \* Note: These messages are call & response. The Master node sends a message with the RTR bit set, and the axis responds with the same ID and specified payload. \*\* Note: These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple. \*\*\* Note: These messages can be sent to either address on a given ODrive board. ---- -### Signals -Name | Type | Bits | Factor | Offset | Byte Order -:-- | :-- | :--: | --: | :--: | :--: -Axis Error | Unsigned Int | 32 | 1 | 0 | Intel -Axis Current State | Unsigned Int | 32 | 1 | 0 | Intel -Motor Error | Unsigned Int | 32 | 1 | 0 | Intel -Encoder Error | Unsigned Int | 32 | 1 | 0 | Intel -Sensorless Error | Unsigned Int | 32 | 1 | 0 | Intel -Axis CAN Node ID | Unsigned Int | 16 | 1 | 0 | Intel -Axis Requested State | Unsigned Int | 32 | 1 | 0 | Intel -Encoder Pos Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel -Encoder Vel Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel -Encoder Shadow Count | Signed Int | 32 | 1 | 0 | Intel -Encoder Count In CPR | Signed Int | 32 | 1 | 0 | Intel -Control Mode | Signed Int | 32 | 1 | 0 | Intel -Input Mode | Signed Int | 32 | 1 | 0 | Intel -Input Pos | Signed Int | 32 | 1 | 0 | Intel -Vel FF | Signed Int | 16 | 0.1 | 0 | Intel -Current FF | Signed Int | 16 | 0.01 | 0 | Intel -Input Vel | Signed Int | 32 | 0.01 | 0 | Intel -Input Current | Signed Int | 32 | 0.01 | 0 | Intel -Velocity Limit | IEEE 754 Float | 32 | 1 | 0 | Intel -Traj Vel Limit | IEEE 754 Float | 32 | 1 | 0 | Intel -Traj Accel Limit | IEEE 754 Float | 32 | 1 | 0 | Intel -Traj Decel Limit | IEEE 754 Float | 32 | 1 | 0 | Intel -Traj A per CSS | IEEE 754 Float | 32 | 1 | 0 | Intel -Iq Setpoint | IEEE 754 Float | 32 | 1 | 0 | Intel -Iq Measured | IEEE 754 Float | 32 | 1 | 0 | Intel -Sensorless Pos Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel -Sensorless Vel Estimate | IEEE 754 Float | 32 | 1 | 0 | Intel -Vbus Voltage | IEEE 754 Float | 32 | 1 | 0 | Intel - --- ## Configuring ODrive for CAN Configuration of the CAN parameters should be done via USB before putting the device on the bus. From ac9bb3aa4a8f4bf1d1836b2dc357c59655f35e8b Mon Sep 17 00:00:00 2001 From: camrbuss Date: Sun, 12 Apr 2020 19:30:21 -0600 Subject: [PATCH 315/549] Prevent Table wrap - CAN doc --- docs/can-protocol.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/can-protocol.md b/docs/can-protocol.md index 75dde574..2287b540 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -32,6 +32,7 @@ Receive PDO 0x200 + nodeID = 0x223, which does not conflict with the range [0x20 Be careful that you don't assign too many nodeIDs per PDO group. Four CAN Simple nodes (32*4) is all of the available address space of a single PDO. If the bus is strictly ODrive CAN Simple nodes, a simple sequential Node ID assignment will work fine. ### Messages + CMD ID | Name | Sender | Signals | Start byte | Signal Type | Bits | Factor | Offset | Byte Order --: | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- 0x000 | CANOpen NMT Message\*\* | Master | - | - | - | - | - | - | - From c10753a58a7a22f13d089015490a37542f3c5947 Mon Sep 17 00:00:00 2001 From: camrbuss Date: Sun, 12 Apr 2020 20:31:29 -0600 Subject: [PATCH 316/549] Prevent Table wrap - CAN doc --- docs/can-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/can-protocol.md b/docs/can-protocol.md index 75dde574..17a3cc43 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -60,7 +60,7 @@ CMD ID | Name | Sender | Signals | Start byte | Signal Type | Bits | Factor | Of 0x017 | Get Vbus Voltage | Master\*\*\* | Vbus Voltage | 0 | IEEE 754 Float | 32 | 1 | 0 | Intel 0x018 | Clear Errors | Master | - | - | - | - | - | - | - 0x700 | CANOpen Heartbeat Message\*\* | Slave | - | - | - | - | - | - | - - +|_______|___________________________|_________|____________________|_____|_____________|________|______|______|______ \* Note: These messages are call & response. The Master node sends a message with the RTR bit set, and the axis responds with the same ID and specified payload. \*\* Note: These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple. \*\*\* Note: These messages can be sent to either address on a given ODrive board. From 622a461b6fd83de8e49d0c6ddfebe4cc23c6bf36 Mon Sep 17 00:00:00 2001 From: camrbuss Date: Sun, 12 Apr 2020 20:33:19 -0600 Subject: [PATCH 317/549] fix of formatting --- docs/can-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/can-protocol.md b/docs/can-protocol.md index 0611a9e8..f316e73e 100644 --- a/docs/can-protocol.md +++ b/docs/can-protocol.md @@ -32,7 +32,6 @@ Receive PDO 0x200 + nodeID = 0x223, which does not conflict with the range [0x20 Be careful that you don't assign too many nodeIDs per PDO group. Four CAN Simple nodes (32*4) is all of the available address space of a single PDO. If the bus is strictly ODrive CAN Simple nodes, a simple sequential Node ID assignment will work fine. ### Messages - CMD ID | Name | Sender | Signals | Start byte | Signal Type | Bits | Factor | Offset | Byte Order --: | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- 0x000 | CANOpen NMT Message\*\* | Master | - | - | - | - | - | - | - @@ -62,6 +61,7 @@ CMD ID | Name | Sender | Signals | Start byte | Signal Type | Bits | Factor | Of 0x018 | Clear Errors | Master | - | - | - | - | - | - | - 0x700 | CANOpen Heartbeat Message\*\* | Slave | - | - | - | - | - | - | - |_______|___________________________|_________|____________________|_____|_____________|________|______|______|______ + \* Note: These messages are call & response. The Master node sends a message with the RTR bit set, and the axis responds with the same ID and specified payload. \*\* Note: These CANOpen messages are reserved to avoid bus collisions with CANOpen devices. They are not used by CAN Simple. \*\*\* Note: These messages can be sent to either address on a given ODrive board. From 7988492774826bbc28388e88d4f614ae9ac3eb88 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 14 Apr 2020 14:50:57 +0200 Subject: [PATCH 318/549] improve ascii command test coverage --- tools/odrive/tests/uart_ascii_test.py | 75 +++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/tools/odrive/tests/uart_ascii_test.py b/tools/odrive/tests/uart_ascii_test.py index c538bfa2..f0912896 100644 --- a/tools/odrive/tests/uart_ascii_test.py +++ b/tools/odrive/tests/uart_ascii_test.py @@ -10,8 +10,8 @@ import functools import operator from fibre.utils import Logger -from odrive.enums import errors -from test_runner import ODriveTestContext, test_assert_eq, program_teensy +from odrive.enums import * +from test_runner import ODriveTestContext, test_assert_eq, test_assert_no_error, program_teensy def append_checksum(command): @@ -84,8 +84,77 @@ class TestUartAscii(): response = float(ser.readline().strip()) test_assert_eq(response, odrive.handle.axis0.motor.current_control.v_current_control_integral_d, accuracy=0.1) + # Write an attribute + ser.write(b'w test_property 12345\n') + ser.write(b'r test_property\n') + response = int(ser.readline().strip()) + test_assert_eq(response, 12345) - # ascii: `r vbus_voltage` + + # Test 'c', 'v', 'p', 'q' and 'f' commands + + odrive.handle.axis0.controller.input_current = 0 + ser.write(b'c 0 12.5\n') + test_assert_eq(ser.readline(), b'') + test_assert_eq(odrive.handle.axis0.controller.input_current, 12.5, accuracy=0.001) + test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CtrlMode.CTRL_MODE_CURRENT_CONTROL) + + odrive.handle.axis0.controller.input_vel = 0 + odrive.handle.axis0.controller.input_current = 0 + ser.write(b'v 0 567.8 12.5\n') + test_assert_eq(ser.readline(), b'') + test_assert_eq(odrive.handle.axis0.controller.input_vel, 567.8, accuracy=0.001) + test_assert_eq(odrive.handle.axis0.controller.input_current, 12.5, accuracy=0.001) + test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CTRL_MODE_VELOCITY_CONTROL) + + odrive.handle.axis0.controller.input_pos = 0 + odrive.handle.axis0.controller.input_vel = 0 + odrive.handle.axis0.controller.input_current = 0 + ser.write(b'p 0 123.4 567.8 12.5\n') + test_assert_eq(ser.readline(), b'') + test_assert_eq(odrive.handle.axis0.controller.input_pos, 123.4, accuracy=0.001) + test_assert_eq(odrive.handle.axis0.controller.input_vel, 567.8, accuracy=0.001) + test_assert_eq(odrive.handle.axis0.controller.input_current, 12.5, accuracy=0.001) + test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CTRL_MODE_POSITION_CONTROL) + + odrive.handle.axis0.controller.input_pos = 0 + odrive.handle.axis0.controller.config.vel_limit = 0 + odrive.handle.axis0.motor.config.current_lim = 0 + ser.write(b'q 0 123.4 567.8 12.5\n') + test_assert_eq(ser.readline(), b'') + test_assert_eq(odrive.handle.axis0.controller.input_pos, 123.4, accuracy=0.001) + test_assert_eq(odrive.handle.axis0.controller.config.vel_limit, 567.8, accuracy=0.001) + test_assert_eq(odrive.handle.axis0.motor.config.current_lim, 12.5, accuracy=0.001) + test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CTRL_MODE_POSITION_CONTROL) + + ser.write(b'f 0\n') + response = ser.readline().strip() + test_assert_eq(float(response.split()[0]), odrive.handle.axis0.encoder.pos_estimate, accuracy=0.001) + test_assert_eq(float(response.split()[1]), odrive.handle.axis0.encoder.vel_estimate, accuracy=0.001) + + + # Test watchdog (assumes that the testing host has no more than 300ms random delays) + start = time.monotonic() + odrive.handle.axis0.config.enable_watchdog = False + odrive.handle.axis0.error = 0 + odrive.handle.axis0.config.watchdog_timeout = 1.0 + odrive.handle.axis0.watchdog_feed() + odrive.handle.axis0.config.enable_watchdog = True + test_assert_eq(odrive.handle.axis0.error, 0) + for _ in range(5): # keep the watchdog alive for 3.5 seconds + time.sleep(0.7) + print('feeding watchdog at {}s'.format(time.monotonic() - start)) + ser.write(b'u 0\n') + err = odrive.handle.axis0.error + print('checking error at {}s'.format(time.monotonic() - start)) + test_assert_eq(err, 0) + + time.sleep(1.3) # let the watchdog expire + test_assert_eq(odrive.handle.axis0.error, errors.axis.ERROR_WATCHDOG_TIMER_EXPIRED) + test_assert_eq(ser.readline(), b'') # check if the device remained silent during the test + + + # TODO: test cases for 't', 'ss', 'se', 'sr' commands From ce57e8325f3354112f20f84b6ae847479c4742e0 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 14 Apr 2020 22:42:21 +0200 Subject: [PATCH 319/549] add UART burn-in test --- tools/odrive/tests/uart_ascii_test.py | 54 +++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/tools/odrive/tests/uart_ascii_test.py b/tools/odrive/tests/uart_ascii_test.py index f0912896..a4e78a49 100644 --- a/tools/odrive/tests/uart_ascii_test.py +++ b/tools/odrive/tests/uart_ascii_test.py @@ -24,10 +24,12 @@ def strip_checksum(command): def reset_state(ser): """Resets the state of the ASCII protocol by flushing all buffers""" - ser.write(b'\n') - ser.flushOutput() - time.sleep(0.1) - ser.flushInput() + ser.flushOutput() # ensure that all previous bytes are sent + time.sleep(0.1) # wait for ODrive to handle last input (buffer might be full) + ser.write(b'\n') # terminate line + ser.flushOutput() # ensure that end-of-line is sent + time.sleep(0.1) # wait for any response that this may generate + ser.flushInput() # discard response class TestUartAscii(): def is_compatible(self, odrive: ODriveTestContext): @@ -97,7 +99,7 @@ class TestUartAscii(): ser.write(b'c 0 12.5\n') test_assert_eq(ser.readline(), b'') test_assert_eq(odrive.handle.axis0.controller.input_current, 12.5, accuracy=0.001) - test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CtrlMode.CTRL_MODE_CURRENT_CONTROL) + test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CTRL_MODE_CURRENT_CONTROL) odrive.handle.axis0.controller.input_vel = 0 odrive.handle.axis0.controller.input_current = 0 @@ -164,7 +166,7 @@ class TestUartNoise(): def run_test(self, odrive: ODriveTestContext, logger: Logger): """ - Tests the most important functions of the ASCII protocol. + Tests if the UART can handle invalid signals. """ # Disable noise @@ -211,8 +213,46 @@ class TestUartNoise(): test_assert_eq(response, odrive.handle.vbus_voltage, accuracy=0.1) + +class TestUartBurnIn(): + def is_compatible(self, odrive: ODriveTestContext): + return True + + def run_test(self, odrive: ODriveTestContext, logger: Logger): + """ + Tests if the ASCII protocol can handle 64kB of random data being thrown at it. + """ + + # Disable noise + with open("/sys/class/gpio/gpio{}/direction".format(20), "w") as fp: + fp.write("out") + with open("/sys/class/gpio/gpio{}/value".format(20), "w") as gpio: + gpio.write("0") + + hexfile = 'uart_pass_through.ino.hex' + program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) + time.sleep(1.0) + + odrive.handle.axis0.config.enable_step_dir = False + odrive.handle.config.enable_uart = True + + with serial.Serial('/dev/ttyS0', 115200, timeout=1) as ser: + with open('/dev/random', 'rb') as rand: + buf = rand.read(65536) + ser.write(buf) + + # reset port to known state + reset_state(ser) + + # Check if protocol still works + ser.write(b'r vbus_voltage\n') + response = float(ser.readline().strip()) + test_assert_eq(response, odrive.handle.vbus_voltage, accuracy=0.1) + + if __name__ == '__main__': test_runner.run([ TestUartAscii(), - TestUartNoise() + TestUartNoise(), + TestUartBurnIn(), ]) From 93287acd42172dbca91baf8946c1596b2bd703be Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 14 Apr 2020 22:43:08 +0200 Subject: [PATCH 320/549] fix UART hang-up, harden ASCII protocol code --- Firmware/communication/ascii_protocol.cpp | 9 +++++---- Firmware/communication/interface_uart.cpp | 9 ++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 54e6cccc..f0ee9f9c 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -72,18 +72,19 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& char cmd[MAX_LINE_LENGTH + 1]; if (len > MAX_LINE_LENGTH) len = MAX_LINE_LENGTH; memcpy(cmd, buffer, len); + cmd[len] = 0; // null-terminate // optional checksum validation bool use_checksum = (checksum_start < len); if (use_checksum) { unsigned int received_checksum; - sscanf((const char *)cmd + checksum_start, "%u", &received_checksum); - if (received_checksum != checksum) + int numscan = sscanf((const char *)cmd + checksum_start, "%u", &received_checksum); + if ((numscan < 1) || (received_checksum != checksum)) return; len = checksum_start - 1; // prune checksum and asterisk + cmd[len] = 0; // null-terminate } - cmd[len] = 0; // null-terminate // check incoming packet type if (cmd[0] == 'p') { // position control @@ -256,7 +257,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& } } - }else if (cmd[0] == 'u') { // Update axis watchdog. + } else if (cmd[0] == 'u') { // Update axis watchdog. unsigned motor_number; int numscan = sscanf(cmd, "u %u", &motor_number); if(numscan < 1){ diff --git a/Firmware/communication/interface_uart.cpp b/Firmware/communication/interface_uart.cpp index eeffafbd..aa195574 100644 --- a/Firmware/communication/interface_uart.cpp +++ b/Firmware/communication/interface_uart.cpp @@ -62,14 +62,19 @@ static void uart_server_thread(void * ctx) { (void) ctx; for (;;) { + osDelay(1); + // Check for UART errors and restart recieve DMA transfer if required - if (huart4.ErrorCode != HAL_UART_ERROR_NONE) { + if (huart4.RxState != HAL_UART_STATE_BUSY_RX) { HAL_UART_AbortReceive(&huart4); HAL_UART_Receive_DMA(&huart4, dma_rx_buffer, sizeof(dma_rx_buffer)); dma_last_rcv_idx = 0; } // Fetch the circular buffer "write pointer", where it would write next uint32_t new_rcv_idx = UART_RX_BUFFER_SIZE - huart4.hdmarx->Instance->NDTR; + if (new_rcv_idx > UART_RX_BUFFER_SIZE) { // defensive programming + continue; + } // deadline_ms = timeout_to_deadline(PROTOCOL_SERVER_TIMEOUT_MS); // Process bytes in one or two chunks (two in case there was a wrap) @@ -87,8 +92,6 @@ static void uart_server_thread(void * ctx) { new_rcv_idx - dma_last_rcv_idx, uart4_stream_output); dma_last_rcv_idx = new_rcv_idx; } - - osDelay(1); }; } From e5cd33495f48c42c4548c68d5dd4b3ff395a478b Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 17 Apr 2020 20:28:27 +0200 Subject: [PATCH 321/549] upgrade test runner to use more yaml and less hardcoding --- tools/odrive/tests/calibration_test.py | 116 ++- tools/odrive/tests/can_test.py | 14 +- tools/odrive/tests/enc0_sim_-4096cps.ino.hex | 795 ----------------- tools/odrive/tests/enc1_sim_-4096cps.ino.hex | 795 ----------------- .../odrive/tests/encoder_pass_through.ino.hex | 792 ----------------- tools/odrive/tests/encoder_test.py | 68 +- tools/odrive/tests/nvm_test.py | 19 +- tools/odrive/tests/pwm_input_test.py | 77 +- tools/odrive/tests/pwm_sim.ino.hex | 801 ------------------ tools/odrive/tests/test_runner.py | 550 +++++++++--- tools/odrive/tests/uart_ascii_test.py | 182 ++-- tools/odrive/tests/uart_pass_through.ino.hex | 786 ----------------- tools/test-rig-rpi.yaml | 49 +- 13 files changed, 785 insertions(+), 4259 deletions(-) delete mode 100644 tools/odrive/tests/enc0_sim_-4096cps.ino.hex delete mode 100644 tools/odrive/tests/enc1_sim_-4096cps.ino.hex delete mode 100644 tools/odrive/tests/encoder_pass_through.ino.hex delete mode 100644 tools/odrive/tests/pwm_sim.ino.hex delete mode 100644 tools/odrive/tests/uart_pass_through.ino.hex diff --git a/tools/odrive/tests/calibration_test.py b/tools/odrive/tests/calibration_test.py index 5316f8d4..595b3b92 100644 --- a/tools/odrive/tests/calibration_test.py +++ b/tools/odrive/tests/calibration_test.py @@ -6,7 +6,7 @@ from math import pi import os from fibre.utils import Logger -from test_runner import AxisTestContext, MotorTestContext, EncoderTestContext, test_assert_eq, test_assert_no_error, request_state, program_teensy +from test_runner import * from odrive.enums import * def modpm(val, range): @@ -19,14 +19,19 @@ class TestMotorCalibration(): and checks if the measurements match the expectation. """ - def is_compatible(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext): - return axis_ctx.yaml == motor_ctx.yaml['name'] # check if connected + def get_test_cases(self, testrig: TestRig): + """Returns all axes that are connected to a motor, along with the corresponding motor(s)""" + for odrive in testrig.get_components(ODriveComponent): + for axis in odrive.axes: + for motor in testrig.get_connected_components(axis, MotorComponent): + yield (axis, motor) - def run_test(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, logger: Logger): + def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, logger: Logger): # reset old calibration values axis_ctx.handle.motor.config.phase_resistance = 0.0 axis_ctx.handle.motor.config.phase_inductance = 0.0 axis_ctx.handle.motor.config.pre_calibrated = False + axis_ctx.handle.config.enable_watchdog = False axis_ctx.handle.clear_errors() @@ -47,10 +52,14 @@ class TestDisconnectedMotorCalibration(): Tests if the motor calibration fails as expected if the phases are floating. """ - def is_compatible(self, axis_ctx: AxisTestContext): - return axis_ctx.yaml == 'floating' + def get_test_cases(self, testrig: TestRig): + """Returns all axes that are disconnected""" + for odrive in testrig.get_components(ODriveComponent): + for axis in odrive.axes: + if axis.yaml == 'floating': + yield (axis,) - def run_test(self, axis_ctx: AxisTestContext, logger: Logger): + def run_test(self, axis_ctx: ODriveAxisComponent, logger: Logger): axis = axis_ctx.handle # reset old calibration values @@ -73,14 +82,21 @@ class TestEncoderDirFind(): Runs the encoder index search. """ - def is_compatible(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, enc_ctx: EncoderTestContext): - return (axis_ctx.yaml == motor_ctx.yaml['name']) and (axis_ctx.num == enc_ctx.num) # check if connected + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + for num in range(2): + encoders = testrig.get_connected_components({ + 'a': (odrive.encoders[num].a, False), + 'b': (odrive.encoders[num].b, False) + }, EncoderComponent) + motors = testrig.get_connected_components(odrive.axes[num], MotorComponent) - def run_test(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, enc_ctx: EncoderTestContext, logger: Logger): + for motor, encoder in itertools.product(motors, encoders): + if encoder.impl in testrig.get_connected_components(motor): + yield (odrive.axes[num], motor, encoder) + + def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): axis = axis_ctx.handle - # TODO: read teensy config from YAML file - hexfile = 'encoder_pass_through.ino.hex' - program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) time.sleep(1.0) # wait for PLLs to stabilize # Set motor calibration values @@ -110,14 +126,21 @@ class TestEncoderOffsetCalibration(): Runs the encoder index search. """ - def is_compatible(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, enc_ctx: EncoderTestContext): - return (axis_ctx.yaml == motor_ctx.yaml['name']) and (axis_ctx.num == enc_ctx.num) # check if connected + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + for num in range(2): + encoders = testrig.get_connected_components({ + 'a': (odrive.encoders[num].a, False), + 'b': (odrive.encoders[num].b, False) + }, EncoderComponent) + motors = testrig.get_connected_components(odrive.axes[num], MotorComponent) - def run_test(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, enc_ctx: EncoderTestContext, logger: Logger): + for motor, encoder in itertools.product(motors, encoders): + if encoder.impl in testrig.get_connected_components(motor): + yield (odrive.axes[num], motor, encoder) + + def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): axis = axis_ctx.handle - # TODO: read teensy config from YAML file - hexfile = 'encoder_pass_through.ino.hex' - program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) time.sleep(1.0) # wait for PLLs to stabilize # Set motor calibration values @@ -127,9 +150,9 @@ class TestEncoderOffsetCalibration(): # Set calibration settings axis_ctx.handle.motor.config.direction = 0 - enc_ctx.handle.config.use_index = False - enc_ctx.handle.config.calib_scan_omega = 12.566 # 2 electrical revolutions per second - enc_ctx.handle.config.calib_scan_distance = 50.265 # 8 revolutions + axis_ctx.handle.encoder.config.use_index = False + axis_ctx.handle.encoder.config.calib_scan_omega = 12.566 # 2 electrical revolutions per second + axis_ctx.handle.encoder.config.calib_scan_distance = 50.265 # 8 revolutions axis_ctx.handle.clear_errors() @@ -141,7 +164,7 @@ class TestEncoderOffsetCalibration(): test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) test_assert_no_error(axis_ctx) - test_assert_eq(enc_ctx.handle.is_ready, True) + test_assert_eq(axis_ctx.handle.encoder.is_ready, True) test_assert_eq(axis_ctx.handle.motor.config.direction in [-1, 1], True) @@ -152,14 +175,27 @@ class TestEncoderIndexSearch(): host's GPIO. """ - def is_compatible(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, enc_ctx: EncoderTestContext): - return (axis_ctx.yaml == motor_ctx.yaml['name']) and (axis_ctx.num == enc_ctx.num) # check if connected + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + for num in range(2): + encoders = testrig.get_connected_components({ + 'a': (odrive.encoders[num].a, False), + 'b': (odrive.encoders[num].b, False) + }, EncoderComponent) + motors = testrig.get_connected_components(odrive.axes[num], MotorComponent) + z_gpio = list(testrig.get_connected_components((odrive.encoders[num].z, False), LinuxGpioComponent)) - def run_test(self, axis_ctx: AxisTestContext, motor_ctx: MotorTestContext, enc_ctx: EncoderTestContext, logger: Logger): + for motor, encoder in itertools.product(motors, encoders): + if encoder.impl in testrig.get_connected_components(motor): + yield (odrive.axes[num], motor, encoder, z_gpio) + + def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, z_gpio: LinuxGpioComponent, logger: Logger): axis = axis_ctx.handle - # TODO: read teensy config from YAML file - hexfile = 'encoder_pass_through.ino.hex' - program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) + cpr = int(enc_ctx.yaml['cpr']) + + z_gpio.config(output=True) + z_gpio.write(False) + time.sleep(1.0) # wait for PLLs to stabilize # Set motor calibration values @@ -177,24 +213,20 @@ class TestEncoderIndexSearch(): time.sleep(3) - test_assert_eq(enc_ctx.handle.index_found, False) - with open("/sys/class/gpio/gpio{}/direction".format(20), "w") as fp: - fp.write("out") - with open("/sys/class/gpio/gpio{}/value".format(20), "w") as gpio: - gpio.write("0") + test_assert_eq(axis_ctx.handle.encoder.index_found, False) time.sleep(0.1) - with open("/sys/class/gpio/gpio{}/value".format(20), "w") as gpio: - gpio.write("1") - test_assert_eq(enc_ctx.handle.index_found, True) + z_gpio.write(True) + test_assert_eq(axis_ctx.handle.encoder.index_found, True) + z_gpio.write(False) test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) test_assert_no_error(axis_ctx) - test_assert_eq(enc_ctx.handle.shadow_count, 0.0, range=20) - test_assert_eq(enc_ctx.handle.count_in_cpr, 0.0, range=20) - test_assert_eq(enc_ctx.handle.pos_estimate, 0.0, range=20) - test_assert_eq(enc_ctx.handle.pos_cpr, 0.0, range=20) - test_assert_eq(enc_ctx.handle.pos_abs, 0.0, range=20) + test_assert_eq(axis_ctx.handle.encoder.shadow_count, 0.0, range=20) + test_assert_eq(modpm(axis_ctx.handle.encoder.count_in_cpr, cpr), 0.0, range=20) + test_assert_eq(axis_ctx.handle.encoder.pos_estimate, 0.0, range=20) + test_assert_eq(axis_ctx.handle.encoder.pos_cpr, 0.0, range=20) + test_assert_eq(axis_ctx.handle.encoder.pos_abs, 0.0, range=20) if __name__ == '__main__': diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index 344d94fe..7db9c5a6 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -7,8 +7,8 @@ import asyncio import time from fibre.utils import Logger -from odrive.enums import errors -from test_runner import CANTestContext, ODriveTestContext, test_assert_eq +from odrive.enums import * +from test_runner import * # Each argument is described as tuple (name, format, scale). # Struct format codes: https://docs.python.org/2/library/struct.html @@ -81,10 +81,12 @@ async def request(bus, node_id, cmd_name, timeout = 1.0): class TestSimpleCAN(): - def is_compatible(self, canbus: CANTestContext, odrive: ODriveTestContext): - return canbus.yaml['bus'] == odrive.yaml['can'] # check if connected + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + can_interfaces = testrig.get_connected_components(odrive.can, CanInterfaceComponent) + yield (odrive, list(can_interfaces)) - def run_test(self, canbus: CANTestContext, odrive: ODriveTestContext, logger: Logger): + def run_test(self, odrive: ODriveComponent, canbus: CanInterfaceComponent, logger: Logger): node_id = 0 axis = odrive.handle.axis0 axis.config.can_node_id = node_id @@ -139,11 +141,13 @@ class TestSimpleCAN(): test_assert_eq(axis.controller.input_vel, 2.0, range=0.01) test_assert_eq(axis.controller.input_current, 3.0, range=0.001) + axis.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL my_cmd('set_input_vel', input_vel=-10.0, cur_ff=30.1234) fence() test_assert_eq(axis.controller.input_vel, -10.0, range=0.01) test_assert_eq(axis.controller.input_current, 30.1234, range=0.01) + axis.controller.config.control_mode = CTRL_MODE_CURRENT_CONTROL my_cmd('set_input_current', input_current=3.1415) fence() test_assert_eq(axis.controller.input_current, 3.1415, range=0.01) diff --git a/tools/odrive/tests/enc0_sim_-4096cps.ino.hex b/tools/odrive/tests/enc0_sim_-4096cps.ino.hex deleted file mode 100644 index 78b4d734..00000000 --- a/tools/odrive/tests/enc0_sim_-4096cps.ino.hex +++ /dev/null @@ -1,795 +0,0 @@ -:0200000460009A -:100000004643464200000156000000000101020084 -:1000100000000000000000000000000000000000E0 -:1000200000000000000000000000000000000000D0 -:1000300000000000000000000000000000000000C0 -:1000400000000000010403000000000000000000A8 -:100050000000200000000000000000000000000080 -:100060000000000000000000000000000000000090 -:100070000000000000000000000000000000000080 -:10008000EB04180A063204260000000000000000FD -:10009000050404240000000000000000000000002F -:1000A0000000000000000000000000000000000050 -:1000B0000604000000000000000000000000000036 -:1000C0000000000000000000000000000000000030 -:1000D00020041808000000000000000000000000DC -:1000E0000000000000000000000000000000000010 -:1000F0000000000000000000000000000000000000 -:10010000D8041808000000000000000000000000F3 -:100110000204180804200000000000000000000095 -:1001200000000000000000000000000000000000CF -:10013000600400000000000000000000000000005B -:1001400000000000000000000000000000000000AF -:10015000000000000000000000000000000000009F -:10016000000000000000000000000000000000008F -:10017000000000000000000000000000000000007F -:10018000000000000000000000000000000000006F -:10019000000000000000000000000000000000005F -:1001A000000000000000000000000000000000004F -:1001B000000000000000000000000000000000003F -:1001C000000100000010000001000000000000001D -:1001D000000001000000000000000000000000001E -:1001E000000000000000000000000000000000000F -:1001F00000000000000000000000000000000000FF -:10020000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE -:10021000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEE -:10022000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDE -:10023000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCE -:10024000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBE -:10025000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAE -:10026000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E -:10027000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8E -:10028000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7E -:10029000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6E -:1002A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5E -:1002B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4E -:1002C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3E -:1002D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E -:1002E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1E -:1002F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0E -:10030000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD -:10031000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED -:10032000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD -:10033000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCD -:10034000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD -:10035000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAD -:10036000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D -:10037000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8D -:10038000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7D -:10039000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6D -:1003A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D -:1003B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4D -:1003C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3D -:1003D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2D -:1003E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1D -:1003F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0D -:10040000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC -:10041000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC -:10042000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC -:10043000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCC -:10044000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC -:10045000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAC -:10046000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9C -:10047000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8C -:10048000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7C -:10049000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C -:1004A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5C -:1004B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4C -:1004C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3C -:1004D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2C -:1004E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1C -:1004F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0C -:10050000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB -:10051000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEB -:10052000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB -:10053000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCB -:10054000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBB -:10055000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAB -:10056000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9B -:10057000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8B -:10058000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7B -:10059000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6B -:1005A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B -:1005B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4B -:1005C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3B -:1005D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2B -:1005E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1B -:1005F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0B -:10060000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA -:10061000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEA -:10062000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA -:10063000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCA -:10064000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA -:10065000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAA -:10066000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9A -:10067000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8A -:10068000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7A -:10069000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6A -:1006A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5A -:1006B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4A -:1006C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3A -:1006D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A -:1006E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1A -:1006F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A -:10070000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9 -:10071000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9 -:10072000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9 -:10073000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC9 -:10074000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB9 -:10075000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9 -:10076000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF99 -:10077000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF89 -:10078000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF79 -:10079000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF69 -:1007A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF59 -:1007B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF49 -:1007C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF39 -:1007D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF29 -:1007E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF19 -:1007F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF09 -:10080000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8 -:10081000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8 -:10082000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD8 -:10083000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC8 -:10084000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB8 -:10085000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA8 -:10086000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF98 -:10087000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF88 -:10088000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF78 -:10089000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF68 -:1008A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF58 -:1008B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF48 -:1008C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF38 -:1008D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF28 -:1008E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF18 -:1008F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF08 -:10090000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7 -:10091000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE7 -:10092000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD7 -:10093000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7 -:10094000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB7 -:10095000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA7 -:10096000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF97 -:10097000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF87 -:10098000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF77 -:10099000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF67 -:1009A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57 -:1009B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF47 -:1009C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF37 -:1009D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27 -:1009E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF17 -:1009F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF07 -:100A0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6 -:100A1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE6 -:100A2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD6 -:100A3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC6 -:100A4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB6 -:100A5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA6 -:100A6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF96 -:100A7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF86 -:100A8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF76 -:100A9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF66 -:100AA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 -:100AB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF46 -:100AC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF36 -:100AD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF26 -:100AE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16 -:100AF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF06 -:100B0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5 -:100B1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5 -:100B2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD5 -:100B3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC5 -:100B4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB5 -:100B5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA5 -:100B6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF95 -:100B7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF85 -:100B8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF75 -:100B9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF65 -:100BA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF55 -:100BB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF45 -:100BC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF35 -:100BD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF25 -:100BE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF15 -:100BF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF05 -:100C0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4 -:100C1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE4 -:100C2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD4 -:100C3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC4 -:100C4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB4 -:100C5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA4 -:100C6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF94 -:100C7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF84 -:100C8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF74 -:100C9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF64 -:100CA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF54 -:100CB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF44 -:100CC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF34 -:100CD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF24 -:100CE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF14 -:100CF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF04 -:100D0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3 -:100D1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE3 -:100D2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD3 -:100D3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC3 -:100D4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB3 -:100D5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA3 -:100D6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF93 -:100D7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83 -:100D8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF73 -:100D9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF63 -:100DA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF53 -:100DB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43 -:100DC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF33 -:100DD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF23 -:100DE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF13 -:100DF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF03 -:100E0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2 -:100E1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE2 -:100E2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD2 -:100E3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2 -:100E4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB2 -:100E5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA2 -:100E6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF92 -:100E7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF82 -:100E8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF72 -:100E9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF62 -:100EA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF52 -:100EB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF42 -:100EC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF32 -:100ED000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF22 -:100EE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF12 -:100EF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02 -:100F0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1 -:100F1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE1 -:100F2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD1 -:100F3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC1 -:100F4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB1 -:100F5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA1 -:100F6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF91 -:100F7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81 -:100F8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF71 -:100F9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF61 -:100FA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF51 -:100FB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF41 -:100FC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF31 -:100FD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF21 -:100FE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF11 -:100FF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF01 -:10100000D10020402C100060000000000000000013 -:1010100020100060001000600000000000000000D0 -:10102000000000607031000000000000000001209E -:1010300035100060764B0720764C4FF42A01764A33 -:101040005C64186499639546744A75498A420FD066 -:10105000744B9A420CD2D4430846234423F0030332 -:1010600004330B4450F8041B984242F8041BF9D196 -:101070006D4A6E498A420FD06D4B9A420CD2D443CE -:101080000846234423F0030304330B4450F8041BA5 -:10109000984242F8041BF9D1664A674B9A420BD238 -:1010A000D04311460024034423F0030304331344C4 -:1010B00041F8044B8B42FBD1604A4FF47001604B06 -:1010C000116003F530715F4A43F8042F9942FBD158 -:1010D0005D4B80215D4A03F8011B9342FBD15C4CC0 -:1010E0004FF080375B4E4FF0FF32DFF8A8A10025AC -:1010F000DFF8A491DFF8A481574B4549C3F800A05D -:10110000C4F80471C4F80091C4F8F470C4F8F08015 -:10111000F36923F07F0343F04003F361736A23F024 -:101120007F0343F0400373628A66CA660A674A67B0 -:1011300000F0B6F8494A6320494B4A49106003223F -:101140001D60CAF8381043F8082C4749474A4848F8 -:10115000C3F8082D0B68474A43F08073CAF83C0077 -:1011600045480B601368454943F001031360036869 -:101170000B6000F0E5F8C4F804714148C4F8009130 -:10118000C4F8F470C4F8F08000F03AFA00BF00BF71 -:1011900000BF00BFF16E3B4A41F440513A4BF1664B -:1011A0001560C2F80851C2F81851C2F82851C2F8A7 -:1011B00038519A6BD20708D442F615623349596503 -:1011C0001A659A6B42F001029A632F4A304C936879 -:1011D00043F00113936000F01BFA2368132BFCD932 -:1011E00000F05CF900F0D0F900F016FA00F0DAF83F -:1011F00000F0FEF92368B3F5967FFBD300F0FCF90D -:1012000000F00EFA00F018FA00F012FAFAE700BF48 -:1012100000C00A40ABAAAAAA008007200000000074 -:10122000501600605017000000000020A42D006040 -:10123000D0030020D0030020C032002088ED00E061 -:10124000FC0F00201D05000000E400E0A0E400E029 -:1012500000800D4000C00F4008ED00E014E000E009 -:1012600018E000E095100000FCED00E000002020F8 -:1012700099110000001000E0041000E0A40D00200F -:101280000046C3230040084000400D400000C05607 -:10129000A80D0020001000201B1018200C0D1113A9 -:1012A000F0B5194A0021194B4FF0100E18480124CF -:1012B000184E194D0160194FC2F800E01E6015600C -:1012C000174E184D1F601660174F1D60174E184DB2 -:1012D00017601E60174F1560174E184D1F6016607F -:1012E0001D600460BFF34F8FBFF36F8FC3F8B01161 -:1012F000BFF34F8FBFF36F8F52F8883C43F44033F6 -:1013000042F8883CF0BD00BF9CED00E0A0ED00E09D -:1013100094ED00E0250008031100200021000207E1 -:1013200012000020250008131300202027000B13B3 -:101330001400004033001013150000602F000B074D -:10134000F0B4174A40274FF480314FF480564FF4E1 -:1013500000554FF4404443F24200136913F0020F6A -:1013600006D0946151619061136913F0020FF8D1B6 -:1013700013F4005F01D15561EFE713F4805F01D1F1 -:101380005661EAE7002BE8DA13F4803F01D091615F -:10139000E3E75B0601D45761DFE7F0BC704700BFAD -:1013A00000800D40364A03203649F3EE096A13687F -:1013B00023F00103F0B51360C2F89000D1F8E030DB -:1013C000DFF8D4E0DCB2C3F30B202F4FC0EB135394 -:1013D00007EE904AA4F15501CEF80040B8EEE77A46 -:1013E00003EB830407EE900A03FB01F13B6003EB80 -:1013F0008313B8EEE75A07EE901A091B77EE666A78 -:10140000B8EE677A214D07EE901A0B44C5ED006ADD -:10141000F8EE677A1E4EC7EE265A1E4930601068F5 -:1014200087EEA66A07EE903AF8EE677A87EEA67A1C -:1014300075EE855A76EE056AFCEEE55AFCEEE66A34 -:1014400015EE903A40EA035316EE900A77EE057ACD -:10145000136001EA0041D2F81031FCEEE77A0B4349 -:10146000C2F8103117EE903AD2F81011C3F30B0303 -:101470000B43C2F81031136843F002031360F0BD50 -:1014800080810D4000441F40F8030020F403002039 -:10149000FC0300200000FF0FF0030020304B40F65B -:1014A000617270B5C3F8202140F2044500F0ACF839 -:1014B0002C492D48D1F880202C4C42F003022C4BB3 -:1014C000C1F88020C0F86051226813401BB9D0F8E1 -:1014D000A8319A071AD0244B4FF00041234A516398 -:1014E0001A46D3F8401141F00201C3F84011D2F876 -:1014F00040319B07FBD44FF400301E491B4B4FF08B -:101500000042086019209A6300F09AF81A4D0022F0 -:10151000164B4FF08041144C0A26996328461A60F6 -:101520001146C4F8A8614FF4207200F07DF84FF422 -:1015300081064FF4800040F24313104A10492E6098 -:101540002864C4F85851C2F80412C4F848310D4A4E -:101550004FF4003101231160C4F8403170BD00BF69 -:1015600000800D4000C00F4000002E4000900D4054 -:10157000001C1E008CE200E0003000200010002063 -:10158000F90600000CE100E0114B1249D86E0A4642 -:1015900040F4403030B4D86640F2B765D86EA0242D -:1015A00040F44070D8664D648C64936C1B06FCD488 -:1015B000094B40F2B760A021064A58649964936CC5 -:1015C00013F08003FBD1054A137030BC704700BF95 -:1015D00000C00F4000400C4000800C40A10D0020D6 -:1015E000F8B500BFF8BC08BC9E467047FFFFFFFF80 -:1015F0005FF800F0390500005FF800F055160000B4 -:101600005FF800F0D90100005FF800F0F90D00006C -:101610005FF800F0350500005FF800F06913000086 -:101620005FF800F0010100005FF800F0A51600006F -:101630005FF800F0B91100005FF800F0050100004C -:101640005FF800F05912000045000000FFFFFFFFA7 -:10165000000000000000000000000000000000008A -:10166000000000000000000000000000000000007A -:1016700010B5054C237833B9044B13B10448AFF3CC -:1016800000800123237010BDD00300200000000063 -:1016900054170000084B10B51BB108490848AFF3B8 -:1016A00000800848036803B910BD074B002BFBD02E -:1016B000BDE81040184700BF00000000D403002020 -:1016C00054170000D00300200000000008B5174B9D -:1016D0000121187800F00AFA154B0121187800F062 -:1016E00005FA144B0121187800F000FA124B012181 -:1016F000187800F0FBF9114B0121187800F0F6F989 -:101700000F4B0121187800F0F1F90E4B00211878E9 -:1017100000F0ECF90C4B0021187800F0E7F90B4BC6 -:1017200000211878BDE8084000F0E0B9F40200207C -:10173000FC0200200003002010030020080300200A -:10174000F8020020EC030020EC020020F002002050 -:10175000FFF7BCBF2C4A2D4B2DE9F04714682C4AEB -:10176000DFF8B8A012685746DFF8B490294EDFF8CA -:10177000B480294D93FBF4F393FBF2F4012199F823 -:10178000000000F0ADF9DAF800103268A5FB023273 -:10179000920C04FB02F2DAF800305B1A9A42FAD893 -:1017A000012198F8000000F09BF939683268A5FB28 -:1017B0000232920C04FB02F2DAF800305B1A9A4211 -:1017C000FAD8002199F8000000F08AF939683268E7 -:1017D000A5FB0232920C04FB02F2DAF800305B1A2D -:1017E0009A42FAD8002198F8000000F079F9396897 -:1017F0003268A5FB0232920C04FB02F2DAF80030E8 -:101800005B1A9A42FAD8B9E70C03002000879303C9 -:10181000040300201803002083DE1B43041000E0B3 -:10182000FC020020000300208C4A8D4B90422DE9E1 -:10183000F0438C4D5C699969EF681DD98A4B9842D9 -:1018400040F20181894B40F22766DFF860E20344F1 -:10185000874D1A0AAEFB0232D30903EB830303EB75 -:10186000830202F2E243B34228BF3346A3F5487332 -:10187000A5FB0336F60804E07E4EB04294BF062670 -:101880000E26774A07F01F0ED2F88030B64543F097 -:10189000C003C2F880300AD2724B27F01F071A46E5 -:1018A0003743DF601368002BFCDA07F01F0E14F0DB -:1018B00000732ED1704D714AD5F810C015460CEA50 -:1018C0000202AA420ABF4FF0C0534FF480520022D6 -:1018D00084EA030515F0605F06D024F0605403F03D -:1018E00060535F4D1C436C6181EA020313F4405F57 -:1018F00008D05B4B21F4405111431A469961936C17 -:101900001D07FCD444F00074554A5461936C990649 -:10191000FCD401215A4D0A4601FB02F300FB03F3FC -:10192000AB4209D8072A00F28480013201FB02F39E -:1019300000FB03F3AB42F5D95248534D1844A5FBC5 -:101940000030030D6C2B79D8352B7ED8DFF8608101 -:1019500036234E48DFF820C14D4DDCF80090B0FB37 -:10196000F2F009EA05054545B0FBF1F00BD043F470 -:1019700000534FF480586546CCF80080CCF8003016 -:101980002B68002BFCDADFF8D8C0013ADCF8103005 -:1019900003F00703934207D002F007026546CCF834 -:1019A0001020AB6CDB03FCD40139890284EA01030B -:1019B00013F4E05F0AD02A4B24F4E05401F4E05120 -:1019C0001A460C435C61936C9907FCD4314B324945 -:1019D0000344DB09A1FB0331090B042928BF0421BF -:1019E0004B1E1B0284EA030212F4407F06D024F44B -:1019F000407403F440731A4A1C435461184B24F09A -:101A000000741A465C61936C9B06FCD4B0FBF1F148 -:101A1000224A7645224B1060196008D2114B27F0FC -:101A20001F071A463743DF601368002BFCDABDE856 -:101A3000F083042980D8013101226DE7DFF874803A -:101A40006C23184886E712261BE71748DFF86880E2 -:101A500000FB03F043EA08087CE700BF00A4781FFE -:101A600000C00F40000008400046C32300BA3CDC21 -:101A70001F85EB5100366E0100800D404030008024 -:101A8000FFB19F26808D5B00819F5E1600B29F266E -:101A90007F3001807FD1F0089F10E50018030020FF -:101AA0001403002000643F4D001BB70023B24C001C -:101AB000362000806C200080002000800001074B51 -:101AC0001A181B58D2685868104202D011B9C3F8CE -:101AD00088207047C3F88420704700BF00000020B2 -:101AE000272801D8FFF7EABF704700BF27281CD876 -:101AF00000011A4A012902EB0003105810B415D056 -:101B0000042913D0DC68426822EA040242609A6821 -:101B1000E9B102291ED003295B685DF8044B0CBFB4 -:101B20000F491049116015221A607047DA680129BF -:101B3000446842EA040242609A6808D040F63801DC -:101B400011605B6815225DF8044B1A6070473821FC -:101B50001160F6E704491160F3E700BF00000020C0 -:101B6000383001003800010038F0010004207146CF -:101B7000084203D0EFF3098000F008B8EFF30880C3 -:101B800000F004B8704700BF704700BF1B4B052131 -:101B90001B4A382030B5C2F848110821C2F8380372 -:101BA00083B05A68174C0A4317485A60C3F8841028 -:101BB000C3F888102368834202D91448FFF734FE23 -:101BC0000E4D08240020124A1249C5F884400190A5 -:101BD000019B934205D8019B01330193019B9342E2 -:101BE000F9D9C5F888400190019B8B42EDD8019B43 -:101BF00001330193019B8B42F9D9E6E700C01B40FA -:101C000000801F4018030020FF45C32300A3E111FB -:101C10003F420F003F548900836B30B41BB15368BF -:101C200043F40043536072B6446B9CB1104B226086 -:101C3000D3F8B0410C4217D1D3F8404144F480446A -:101C4000C3F84041D3F8B851D3F840416404F3D508 -:101C5000294209D10023064C8260C360D4F8B03118 -:101C60001943C4F8B0110263426362B630BC7047D6 -:101C700000002E4038B50546036B06E0AA6B1C68D1 -:101C800090476A6B2346944208D0184633B1012B23 -:101C900004D05A681206F1D52B6338BD00232B639C -:101CA0006B6338BDF0B5F1B9224C23490020234BBA -:101CB00001228025A1600A601A464D60E060D3F8D9 -:101CC000BC41886044F001141D4DC3F8BC41D3F8F9 -:101CD000B01141F00101C3F8B0112860D2F8B03161 -:101CE000002BFBD1F0BD0904164B144D0126114CFD -:101CF00041F0800100221E60596000F5805CE264C2 -:101D000000F5005EA36400F54057D5F8B01100F56A -:101D100080462A46986041F48031C3F80CC0C3F86D -:101D200010E05F619E61C5F8B011D2F8B031002BB0 -:101D3000FBD1BAE7003000202020002000002E4018 -:101D40000C040020002000202DE9F04FBB4C83B094 -:101D5000D4F8448118F0010FC4F844815ED0D4F85F -:101D6000AC31002B55D04FF00119DFF8F0B2264608 -:101D7000B34FCA46B96AFA6AC4F8AC31D4F84031F4 -:101D800043F40053C4F84031D4F840319D04F5D5F4 -:101D9000D6F8403123F40053C6F84031C6F8B4A158 -:101DA000D4F8B43113F00113FAD188B240F281654E -:101DB000CBF80030A84200F29680B0F5D06F80F0EA -:101DC000D881B0F5817F00F0578100F2D58080285E -:101DD00000F02681822840F0C78092B202F07F0195 -:101DE000072900F2C18089009648974D0844016890 -:101DF00010062B706B7040F1E581C90301D50123FA -:101E00002B7002219048FFF74DFFD4F8AC31002B26 -:101E1000B0D18A4AD2F8BC31002B44D118F0400F1F -:101E200018D0864BD3F8AC111A46C3F8AC11D3F8CE -:101E3000BC11C3F8BC11D2F8B041804B002CFAD1D0 -:101E40004FF0FF32C3F8B421D3F8843100F074FBB3 -:101E50007E4B1C6018F0807F03D07D4B1B6803B164 -:101E6000984718F0007F03D07A4B1B6803B198475E -:101E700018F0040F02D0714BD3F884316F4BD3F8B4 -:101E8000482112060CD518F0800F09D072490A7843 -:101E9000002A00F08E81531EDBB20B7003B9FBBE2B -:101EA00003B0BDE8F08F6D49C2F8BC310868034249 -:101EB00040F0CC81654A14681C40AFD0630700F144 -:101EC000E281670300F1EF81260700F1E881250335 -:101ED00000F1E181E00600F1DA81E1029ED560487F -:101EE000FFF7C8FE9AE742F22105A84238D06FD822 -:101EF000B0F5086F00F02F81B0F5106F34D1C1F349 -:101F00000741584A584811705849594A0193C6F830 -:101F1000C801C6F8CC11C6F8D02100F00FFB554A15 -:101F2000019B80210120FB6410605160D6F8BC1138 -:101F3000936041F00111BA64C6F8BC11D6F8B02123 -:101F400042F48032C6F8B021444A1360D4F8B0316C -:101F5000002BFBD1D4F8AC31002B7FF40BAF58E74A -:101F6000100C072800F03281C4F8C091D4F8AC31CD -:101F7000002B7FF4FFAE4CE740F20235A84200F0A0 -:101F8000B780B0F5A06FEFD13A4A80200125FB64FD -:101F900050601560D6F8BC01936040F00113BA643C -:101FA000C6F8BC31D6F8B03143F48033C6F8B0314E -:101FB000D4F8B031002BFBD10B0C5B0643F08073DF -:101FC000C6F85431D4F8AC31002B7FF4D3AE20E7FF -:101FD00042F22123984200F0828042F2213398425B -:101FE000C2D1244B01218022196000215A60D4F80B -:101FF000BC21BB6442F001129960F964C4F8BC21B1 -:10200000D4F8B03143F48033C4F8B031D4F8B031EF -:10201000002BFBD1D4F8AC31002B7FF4ABAEF8E64B -:10202000094A0221104613705370FFF73BFED4F8A3 -:10203000AC31002B7FF49EAEEBE600BF00002E40DB -:1020400000300020C0012E402004002004040020A5 -:102050000004002008040020100400200C040020CC -:1020600040320020300400200200CC00C8000200F2 -:102070000200C8002020002092B202F07F03072B4C -:102080003FF672AF12F0800F4FEA8303884A4FF099 -:1020900001011A44136814BF23F4803323F00103B1 -:1020A00013608022834B196000215A60D4F8BC2150 -:1020B000BB6442F001129960F964C4F8BC21D4F801 -:1020C000B03143F48033C4F8B031D4F8B031002BD0 -:1020D000FBD1D4F8AC31002B7FF44CAE99E6764BB3 -:1020E000C1F30741754A1868754B1060197078E79D -:1020F00092B202F07F03072B3FF636AF12F0800F4B -:102100004FEA83036A4A4FF001011A44136814BF6F -:1021100043F4803343F0010313608022654B196060 -:1021200000215A60D4F8BC21BB6442F001129960CE -:10213000F964C4F8BC21D4F8B03143F48033C4F856 -:10214000B031D4F8B031002BFBD1D4F8AC31002B36 -:102150007FF410AE5DE65B4A01215B4B12781846B6 -:102160001A70FFF79FFDD4F8AC31002B7FF402AE5C -:102170004FE6564B586800283FF4F6AE090C1FFA9C -:1021800082FE04E00C33586800283FF4EDAE1D8851 -:102190008D42F7D15D887545F4D1090A120C0329E7 -:1021A0000CBF01781989914228BF1146FFF77AFDCB -:1021B0002BE6D3F8482122F08002C3F8482103B06F -:1021C000BDE8F08FCA077FF51CAE18E6404D012030 -:1021D000FB6029603F4B4049186059603F49D6F881 -:1021E000B0016A6001F5005E40F00102A1F5005007 -:1021F00001F58055BB609860A1F58050D860C6F8A5 -:10220000B02119615D61C3F818E0D4F8B031002B3A -:10221000FBD1284A012048F28001FB6410604FF492 -:1022200080305160D6F8BC11936041F00113BA645C -:10223000C6F8BC31D6F8B0310343C6F8B031CBF89C -:102240000000D4F8B031002BFBD1DEE5204C42F287 -:102250002100002524880D6084427FF42BAE2049A4 -:10226000204C03C90D0C86282060A180A5717FF445 -:1022700021AED2F8481150241B4841F08001C2F829 -:102280004811047016E61948FFF7F4FC19E61848DF -:10229000FFF7F0FC21E61748FFF7ECFC1AE61648BA -:1022A000FFF7E8FC13E61548FFF7E4FC0CE600BF77 -:1022B000C0012E4020200020A80D002088320020E0 -:1022C000800D002030040020200400208002002027 -:1022D000180400200020002080000700282400208F -:1022E000280400208032002010040020003100204B -:1022F00000320020C0310020803100204031002019 -:10230000002AA0F102022DE9F04714BF00274FF088 -:102310000057022A01D9BDE8F0874FEAC01ADFF85A -:1023200040900D4604460AEB0906002140229846DB -:10233000304600F0DFFC012047EA05414AF8091069 -:10234000C6F83880B060B8F1000FE4D0034BA0406D -:102350001C6820431860BDE8F08700BF040400201B -:1023600000300020002AA0F102022DE9F04714BF3E -:1023700000274FF00057022A01D9BDE8F08740221C -:10238000C501DFF8449088461544002104461E46E6 -:1023900005EB090A504600F0ADFC012247EA08416E -:1023A00045F80910CAF83860CAF80820002EE4D0B1 -:1023B00004F11000034B8240186802431A60BDE824 -:1023C000F08700BF04040020003000201204816068 -:1023D000C36142F08002F0B44260012701F58056EB -:1023E00001F5005501F5405401F580420760C660D3 -:1023F000056144618261F0BC704700BF831E022BFF -:1024000000D9704730B4064B00F1100401250A468C -:1024100003EBC01005FA04F130BCFFF7FDBB00BFB1 -:1024200040300020831E022B00D9704710B4054BAA -:1024300001240A4604FA00F103EBC0105DF8044BD6 -:10244000FFF7EABB00300020124A134BD2F82002FB -:1024500020F07F40984210B584B002D800EB800095 -:1024600040000E4C01A90A2200F07EFA01A90023C7 -:10247000204611F8012B01333AB10A2B20F8022F24 -:10248000F7D11623237004B010BD5B00DBB22370BC -:1024900004B010BD00441F407F969800B403002094 -:1024A0004368C269C3F30E43054930B4C3F1400326 -:1024B000044C002521F8123024F8125030BC70472B -:1024C000000C0020F80B0020F8B5154B1B783BB929 -:1024D00003F0FF04134B1B7813B1134D2A8802B984 -:1024E000F8BD124F2346124EC2F58072397811485A -:1024F00006EB411600EB01213046FFF767FF31463E -:102500000420FFF77BFF3B780133DBB2062B98BF3B -:102510003B704FF0000388BF3C702B80F8BD00BFBC -:10252000340B002030040020800C0020350B0020EC -:10253000A00C002034040020704700BF0021E022DE -:102540002048F8B50C46204E204D00F0D3FB204F1C -:102550002146204B6022347028461F4E1C8000F01C -:10256000C9FB23462246102102203C60BC80346017 -:10257000B480FFF7F7FE2246184B40210320FFF7F7 -:10258000BFFE2346224640210420FFF7EBFE2346F0 -:10259000402228461249FFF719FF29460320FFF77A -:1025A00041FF104B4A22104910480860C3F884408C -:1025B000C3F88020D3F8482142F08072C3F8482144 -:1025C000F8BD00BFA00C0020350B0020200C00201F -:1025D000000C0020800C0020F80B0020510E0000A1 -:1025E000380B002000002E4000040020790E00006F -:1025F000024A034B10881B88C01A7047000C002049 -:10260000F80B002010B4EFF3108272B6437F33B999 -:10261000017F012908D0032910D00123437702B993 -:1026200062B65DF8044B7047114C2168A1B11149A5 -:1026300043610B68086083615861EEE70E4C2168C6 -:1026400081B10E4943610B680860836158610C4B8E -:102650004FF080511960E0E7064B416181612060D5 -:102660001860DAE7054B4161816120601860EEE790 -:10267000940D0020900D0020840D0020880D002076 -:1026800004ED00E010B4047F4160022CC26003D06E -:102690005DF8044BFFF7B6BF83685DF8044B18473D -:1026A00070B5EFF3108172B60C4C23688BB10C4EF1 -:1026B00000255A6922607AB1956101B962B65D77E9 -:1026C00018469B689847EFF3108172B62368002B79 -:1026D000EFD101B962B670BD3260EEE7840D002023 -:1026E000880D0020FFF7DCBF184A30B41468002CB6 -:1026F00028D0036821688B420FD2CB1A0021846056 -:10270000C1602360E0601060022330BC0375704735 -:102710000360144611688B4208D3A2685B1A002A32 -:10272000F6D18260C4600360A060EDE7D568CB1A83 -:1027300082600222C560E060C16888602360027523 -:1027400030BC70478460C4601060DDE78C0D0020F1 -:10275000F8B5224E34682CB32368002B3AD11D46BD -:102760001F4F04E03468ECB12368002B32D1A3681A -:1027700003B1DD6020693360036825751B68BB42C7 -:1027800021D1037F4560022BC46020D0FFF73AFFC0 -:102790006368002BE6D023602046FFF7A5FF34686E -:1027A000002CE1D1EFF3108372B60E4A00211068BD -:1027B000116003B962B628B18468FFF795FF20461F -:1027C0000028F9D1F8BD224600219847E0E7836848 -:1027D0009847DDE7013B2360E4E700BF8C0D002054 -:1027E000351000009C0D0020044A054B1168054A75 -:1027F0001960136801331360FFF7AABF041000E0EB -:10280000A40D0020A80D002070B5214C237883B9B9 -:10281000204B01221B7822701BBB1F4B1B78002B07 -:1028200029D11E4B00211A68217012B1EFF30582E5 -:1028300002B170BDEFF3108072B61A68F2B1184C95 -:102840002178D9B90126556926701D60D5B1A961D5 -:1028500000B962B6002593681046557798472570F1 -:1028600070BDFFF7C5FE0028D7D000F015FA0A4B5F -:102870001B78002BD5D000F0FBF9D2E70028D8D187 -:1028800062B670BD074B1D600028E3D1E1E700BFD1 -:10289000A00D0020CA030020C80D0020940D0020C8 -:1028A000980D0020900D0020002852D02DE9F04F07 -:1028B000814683B0274C0120274D284E54E8003F25 -:1028C0002A68316844E80003002BF7D1244F4FF405 -:1028D0007A7E2448D7F800C0BB4607F1C647036894 -:1028E000C1EB0C0107F5DE1707F67F67A7FB03C3F3 -:1028F000BA4601279B0CB1FBF3F30EFB023854E8F8 -:10290000003F2A68316844E80073002BF7D1DBF8F8 -:1029100000C04FF47A7E03680EFB02F2C1EB0C019B -:10292000AAFB033EC8EB02034FEA9E42B1FBF2F161 -:10293000CA18B2F57A7F07D3B9F1010908F57A7898 -:10294000DDD103B0BDE8F08F0190FFF75DFF019886 -:10295000D5E770478C320020A80D0020A40D002080 -:10296000041000E018030020F0B44E1E0025374686 -:1029700000E00135B0FBF2F302FB130000F1370475 -:10298000092800F13000E4B298BFC4B2184607F835 -:10299000014F002BEDD14A1953704DB1013316F898 -:1029A000014F1778E81A3770834202F80149F5DBC6 -:1029B0000846F0BC704700BFA4484FF00F0CA44B72 -:1029C000826F42F47F02F0B582670025D0F8802044 -:1029D0004FF470469F4C4FF4604E29464FF4806789 -:1029E00014432A46C0F88040A3F88C6148F2B82608 -:1029F000A3F88EC1A3F89051B3F8880180B240F0DB -:102A0000F000A3F8880101EB4100914B0131002552 -:102A100040011C4604290344A3F804E0DF805A84E3 -:102A20001A865A805A81DE815A82DA825A83DA8380 -:102A3000E9D1B4F888014FF00F0C874B4FF4704682 -:102A400080B229464FF460472A4640EA0C004FF412 -:102A5000806EA4F88801B4F8880180B240F47060F8 -:102A6000A4F88801A3F88C6148F2B826A3F88EC1B7 -:102A7000A3F89051B3F8880180B240F0F000A3F8B9 -:102A8000880101EB4100744B0131002540011C46D7 -:102A9000042903449F80A3F806E05A841A865A80CA -:102AA0005A81DE815A82DA825A83DA83E9D1B4F814 -:102AB00088014FF00F0C694B4FF4704680B22946E5 -:102AC0004FF460472A4640EA0C004FF4806EA4F8A9 -:102AD0008801B4F8880180B240F47060A4F88801DD -:102AE000A3F88C6148F2B826A3F88EC1A3F89051E0 -:102AF000B3F8880180B240F0F000A3F8880101EB40 -:102B00004100564B0131002540011C460429034475 -:102B10009F80A3F806E05A841A865A805A81DE8183 -:102B20005A82DA825A83DA83E9D1B4F888014FF005 -:102B30000F0C4B4B4FF4704780B229464FF4604660 -:102B40002A4640EA0C004FF4806EA4F88801B4F8DD -:102B5000880180B240F47060A4F88801A3F88C71F9 -:102B600048F2B827A3F88EC1A3F89051B3F88801B2 -:102B700080B240F0F000A3F8880101EB4100384B2F -:102B8000013140011C46042903449E80A3F806E05D -:102B90005A841A865A805A81DF815A82DA825A838D -:102BA000DA83EAD1B4F888310F27002241F2010616 -:102BB0009BB245F6C05E114643F226053B43A4F89E -:102BC0008831B4F888319BB243F47063A4F888313B -:102BD0005001244B01320344042A99815981DF8139 -:102BE0009E82A3F806E0198019829D81F0D100220F -:102BF0000F2741F2010645F6C055114643F226045F -:102C00005001194B01320344042A99815981DF8113 -:102C10009E82DD80198019829C81F1D100220F27CC -:102C200041F2010645F6C055114643F22604500113 -:102C30000E4B01320344042A99815981DF819E821F -:102C4000DD80198019829C81F1D1F0BD00C00F4058 -:102C500000C03D40000003FC00003E4000403E40FC -:102C600000803E4000C01D4000001E4000401E404D -:102C700038B5074B1C784CB1064D55F8043F002B76 -:102C8000FBD09847631E13F0FF04F6D138BD00BF98 -:102C9000C80D0020A80D0020014B00221A707047BB -:102CA000CA03002070B50F4E0F4D761BB61018BF2B -:102CB000002405D0013455F8043B9847A642F9D1C9 -:102CC0000A4E0B4D761B00F063F8B61018BF0024B7 -:102CD00006D0013455F8043B9847A642F9D170BD9F -:102CE00070BD00BF48160060481600604C160060BA -:102CF0004816006070B4840746D0541E002A41D0A4 -:102D0000CDB2034602E0621EE4B3144603F8015B51 -:102D10009A07F8D1032C2ED9CDB245EA05250F2C00 -:102D200045EA054519D903F110022646103E0F2E3B -:102D300042F8105C42F80C5C42F8085C42F8045C13 -:102D400002F11002F2D8A4F1100222F00F0204F0F6 -:102D50000F041032032C13440DD91E462246043AA8 -:102D6000032A46F8045BFAD8221F22F00302043239 -:102D7000134404F003042CB1C9B21C4403F8011B32 -:102D8000A342FBD170BC704714460346C2E700BFA4 -:102D90005FF800F0E1150060000000000000000096 -:042DA000F8B500BFC3 -:102DA40000000042C8801F40B8821F400800000095 -:102DB40000000042C4801F40B4821F400400000091 -:102DC40000C0004224801F4014821F4010000000F5 -:102DD40000C0004228801F4018821F4020000000CD -:102DE40000C000422C801F401C821F404000000095 -:102DF40000C0004234801F4024821F4000010000B4 -:102E04000040004264811F4054831F4000040000BE -:102E14000040004280811F4070831F400000020078 -:102E2400004000427C811F406C831F400000010071 -:102E34000040004268811F4058831F400008000082 -:102E4400004000423C811F402C831F4001000000D1 -:102E54000040004244811F4034831F4004000000AE -:102E64000040004240811F4030831F4002000000A8 -:102E74000040004248811F4038831F400800000082 -:102E84000000004204811F40F4821F40000004003F -:102E94000000004208811F40F8821F400000080023 -:102EA4000000004218811F4008831F40000080007A -:102EB4000000004214811F4004831F4000004000B2 -:102EC4000000004200811F40F0821F400000020009 -:102ED40000000042FC801F40EC821F400000010003 -:102EE4000000004224811F4014831F40000000049E -:102EF4000000004228811F4018831F400000000882 -:102F0400000000421C811F400C831F400000000190 -:102F14000000004220811F4010831F400000000277 -:102F240000000042EC801F40DC821F4000100000C3 -:102F340000000042F0801F40E0821F40002000009B -:102F44000000004234811F4024831F4000000040E1 -:102F54000000004238811F4028831F400000008089 -:102F64000080004294801F4084821F4000000400BF -:102F740000C0004290801F4080821F4000000080FB -:102F840000800042A8801F4098821F4000008000FB -:102F940000800042A4801F4094821F400000400033 -:102FA400004000426C811F405C831F400010000001 -:102FB40000C0004230801F4020821F40800000007B -:102FC40000800042C8811F40B8831F400080000079 -:102FD40000800042C4811F40B4831F4000400000B1 -:102FE40000800042C0811F40B0831F4000200000C9 -:102FF40000800042BC811F40AC831F4000100000D1 -:1030040000800042D0811F40C0831F4000000200A6 -:1030140000800042CC811F40BC831F40000001009F -:1030240000010000840300201200000000060000DC -:103034001C0300200A0000000002000040030020DE -:10304400430000000007000040030020430000008C -:1030540000030000B0030020000000000103090485 -:103064002803002000000000020309049803002044 -:103074000000000003030904B40300200000000062 -:10308400000000000000000000000000010000003B -:1030940002000000170000001200000016000000EB -:1030A400150000001E0000001300000000200000B6 -:1030B400140000000029DE07007B9A170A060002AC -:1030C4000200004001000000180354006500650080 -:1030D4006E00730079006400750069006E006F0073 -:1030E40009024300020100C0320904000001020287 -:1030F4000100052400100105240101010424020635 -:1031040005240600010705820310001009040100CC -:10311400020A0000000705030240000007058402BC -:10312400400000001201000202000040C0168304A7 -:103134007902010203010000160355005300420006 -:103144002000530065007200690061006C000000FB -:10315400040309040C030000000000000000000048 -:10316400000000000000000000000100000000005A -:040000056000100087 -:00000001FF diff --git a/tools/odrive/tests/enc1_sim_-4096cps.ino.hex b/tools/odrive/tests/enc1_sim_-4096cps.ino.hex deleted file mode 100644 index 43eabc4b..00000000 --- a/tools/odrive/tests/enc1_sim_-4096cps.ino.hex +++ /dev/null @@ -1,795 +0,0 @@ -:0200000460009A -:100000004643464200000156000000000101020084 -:1000100000000000000000000000000000000000E0 -:1000200000000000000000000000000000000000D0 -:1000300000000000000000000000000000000000C0 -:1000400000000000010403000000000000000000A8 -:100050000000200000000000000000000000000080 -:100060000000000000000000000000000000000090 -:100070000000000000000000000000000000000080 -:10008000EB04180A063204260000000000000000FD -:10009000050404240000000000000000000000002F -:1000A0000000000000000000000000000000000050 -:1000B0000604000000000000000000000000000036 -:1000C0000000000000000000000000000000000030 -:1000D00020041808000000000000000000000000DC -:1000E0000000000000000000000000000000000010 -:1000F0000000000000000000000000000000000000 -:10010000D8041808000000000000000000000000F3 -:100110000204180804200000000000000000000095 -:1001200000000000000000000000000000000000CF -:10013000600400000000000000000000000000005B -:1001400000000000000000000000000000000000AF -:10015000000000000000000000000000000000009F -:10016000000000000000000000000000000000008F -:10017000000000000000000000000000000000007F -:10018000000000000000000000000000000000006F -:10019000000000000000000000000000000000005F -:1001A000000000000000000000000000000000004F -:1001B000000000000000000000000000000000003F -:1001C000000100000010000001000000000000001D -:1001D000000001000000000000000000000000001E -:1001E000000000000000000000000000000000000F -:1001F00000000000000000000000000000000000FF -:10020000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE -:10021000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEE -:10022000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDE -:10023000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCE -:10024000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBE -:10025000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAE -:10026000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E -:10027000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8E -:10028000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7E -:10029000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6E -:1002A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5E -:1002B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4E -:1002C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3E -:1002D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E -:1002E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1E -:1002F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0E -:10030000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD -:10031000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED -:10032000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD -:10033000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCD -:10034000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD -:10035000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAD -:10036000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D -:10037000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8D -:10038000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7D -:10039000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6D -:1003A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D -:1003B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4D -:1003C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3D -:1003D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2D -:1003E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1D -:1003F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0D -:10040000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC -:10041000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC -:10042000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC -:10043000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCC -:10044000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC -:10045000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAC -:10046000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9C -:10047000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8C -:10048000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7C -:10049000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C -:1004A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5C -:1004B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4C -:1004C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3C -:1004D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2C -:1004E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1C -:1004F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0C -:10050000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB -:10051000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEB -:10052000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB -:10053000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCB -:10054000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBB -:10055000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAB -:10056000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9B -:10057000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8B -:10058000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7B -:10059000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6B -:1005A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B -:1005B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4B -:1005C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3B -:1005D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2B -:1005E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1B -:1005F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0B -:10060000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA -:10061000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEA -:10062000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA -:10063000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCA -:10064000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA -:10065000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAA -:10066000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9A -:10067000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8A -:10068000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7A -:10069000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6A -:1006A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5A -:1006B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4A -:1006C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3A -:1006D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A -:1006E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1A -:1006F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A -:10070000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9 -:10071000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9 -:10072000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9 -:10073000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC9 -:10074000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB9 -:10075000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9 -:10076000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF99 -:10077000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF89 -:10078000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF79 -:10079000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF69 -:1007A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF59 -:1007B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF49 -:1007C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF39 -:1007D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF29 -:1007E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF19 -:1007F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF09 -:10080000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8 -:10081000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8 -:10082000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD8 -:10083000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC8 -:10084000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB8 -:10085000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA8 -:10086000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF98 -:10087000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF88 -:10088000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF78 -:10089000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF68 -:1008A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF58 -:1008B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF48 -:1008C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF38 -:1008D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF28 -:1008E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF18 -:1008F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF08 -:10090000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7 -:10091000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE7 -:10092000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD7 -:10093000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7 -:10094000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB7 -:10095000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA7 -:10096000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF97 -:10097000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF87 -:10098000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF77 -:10099000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF67 -:1009A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57 -:1009B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF47 -:1009C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF37 -:1009D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27 -:1009E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF17 -:1009F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF07 -:100A0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6 -:100A1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE6 -:100A2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD6 -:100A3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC6 -:100A4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB6 -:100A5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA6 -:100A6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF96 -:100A7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF86 -:100A8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF76 -:100A9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF66 -:100AA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 -:100AB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF46 -:100AC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF36 -:100AD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF26 -:100AE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16 -:100AF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF06 -:100B0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5 -:100B1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5 -:100B2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD5 -:100B3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC5 -:100B4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB5 -:100B5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA5 -:100B6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF95 -:100B7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF85 -:100B8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF75 -:100B9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF65 -:100BA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF55 -:100BB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF45 -:100BC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF35 -:100BD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF25 -:100BE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF15 -:100BF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF05 -:100C0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4 -:100C1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE4 -:100C2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD4 -:100C3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC4 -:100C4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB4 -:100C5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA4 -:100C6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF94 -:100C7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF84 -:100C8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF74 -:100C9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF64 -:100CA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF54 -:100CB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF44 -:100CC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF34 -:100CD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF24 -:100CE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF14 -:100CF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF04 -:100D0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3 -:100D1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE3 -:100D2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD3 -:100D3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC3 -:100D4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB3 -:100D5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA3 -:100D6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF93 -:100D7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83 -:100D8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF73 -:100D9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF63 -:100DA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF53 -:100DB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43 -:100DC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF33 -:100DD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF23 -:100DE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF13 -:100DF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF03 -:100E0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2 -:100E1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE2 -:100E2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD2 -:100E3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2 -:100E4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB2 -:100E5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA2 -:100E6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF92 -:100E7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF82 -:100E8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF72 -:100E9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF62 -:100EA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF52 -:100EB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF42 -:100EC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF32 -:100ED000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF22 -:100EE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF12 -:100EF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02 -:100F0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1 -:100F1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE1 -:100F2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD1 -:100F3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC1 -:100F4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB1 -:100F5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA1 -:100F6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF91 -:100F7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81 -:100F8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF71 -:100F9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF61 -:100FA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF51 -:100FB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF41 -:100FC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF31 -:100FD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF21 -:100FE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF11 -:100FF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF01 -:10100000D10020402C100060000000000000000013 -:1010100020100060001000600000000000000000D0 -:10102000000000607031000000000000000001209E -:1010300035100060764B0720764C4FF42A01764A33 -:101040005C64186499639546744A75498A420FD066 -:10105000744B9A420CD2D4430846234423F0030332 -:1010600004330B4450F8041B984242F8041BF9D196 -:101070006D4A6E498A420FD06D4B9A420CD2D443CE -:101080000846234423F0030304330B4450F8041BA5 -:10109000984242F8041BF9D1664A674B9A420BD238 -:1010A000D04311460024034423F0030304331344C4 -:1010B00041F8044B8B42FBD1604A4FF47001604B06 -:1010C000116003F530715F4A43F8042F9942FBD158 -:1010D0005D4B80215D4A03F8011B9342FBD15C4CC0 -:1010E0004FF080375B4E4FF0FF32DFF8A8A10025AC -:1010F000DFF8A491DFF8A481574B4549C3F800A05D -:10110000C4F80471C4F80091C4F8F470C4F8F08015 -:10111000F36923F07F0343F04003F361736A23F024 -:101120007F0343F0400373628A66CA660A674A67B0 -:1011300000F0B6F8494A6320494B4A49106003223F -:101140001D60CAF8381043F8082C4749474A4848F8 -:10115000C3F8082D0B68474A43F08073CAF83C0077 -:1011600045480B601368454943F001031360036869 -:101170000B6000F0E5F8C4F804714148C4F8009130 -:10118000C4F8F470C4F8F08000F03AFA00BF00BF71 -:1011900000BF00BFF16E3B4A41F440513A4BF1664B -:1011A0001560C2F80851C2F81851C2F82851C2F8A7 -:1011B00038519A6BD20708D442F615623349596503 -:1011C0001A659A6B42F001029A632F4A304C936879 -:1011D00043F00113936000F01BFA2368132BFCD932 -:1011E00000F05CF900F0D0F900F016FA00F0DAF83F -:1011F00000F0FEF92368B3F5967FFBD300F0FCF90D -:1012000000F00EFA00F018FA00F012FAFAE700BF48 -:1012100000C00A40ABAAAAAA008007200000000074 -:10122000501600605017000000000020A42D006040 -:10123000D0030020D0030020C032002088ED00E061 -:10124000FC0F00201D05000000E400E0A0E400E029 -:1012500000800D4000C00F4008ED00E014E000E009 -:1012600018E000E095100000FCED00E000002020F8 -:1012700099110000001000E0041000E0A40D00200F -:101280000046C3230040084000400D400000C05607 -:10129000A80D0020001000201B1018200C0D1113A9 -:1012A000F0B5194A0021194B4FF0100E18480124CF -:1012B000184E194D0160194FC2F800E01E6015600C -:1012C000174E184D1F601660174F1D60174E184DB2 -:1012D00017601E60174F1560174E184D1F6016607F -:1012E0001D600460BFF34F8FBFF36F8FC3F8B01161 -:1012F000BFF34F8FBFF36F8F52F8883C43F44033F6 -:1013000042F8883CF0BD00BF9CED00E0A0ED00E09D -:1013100094ED00E0250008031100200021000207E1 -:1013200012000020250008131300202027000B13B3 -:101330001400004033001013150000602F000B074D -:10134000F0B4174A40274FF480314FF480564FF4E1 -:1013500000554FF4404443F24200136913F0020F6A -:1013600006D0946151619061136913F0020FF8D1B6 -:1013700013F4005F01D15561EFE713F4805F01D1F1 -:101380005661EAE7002BE8DA13F4803F01D091615F -:10139000E3E75B0601D45761DFE7F0BC704700BFAD -:1013A00000800D40364A03203649F3EE096A13687F -:1013B00023F00103F0B51360C2F89000D1F8E030DB -:1013C000DFF8D4E0DCB2C3F30B202F4FC0EB135394 -:1013D00007EE904AA4F15501CEF80040B8EEE77A46 -:1013E00003EB830407EE900A03FB01F13B6003EB80 -:1013F0008313B8EEE75A07EE901A091B77EE666A78 -:10140000B8EE677A214D07EE901A0B44C5ED006ADD -:10141000F8EE677A1E4EC7EE265A1E4930601068F5 -:1014200087EEA66A07EE903AF8EE677A87EEA67A1C -:1014300075EE855A76EE056AFCEEE55AFCEEE66A34 -:1014400015EE903A40EA035316EE900A77EE057ACD -:10145000136001EA0041D2F81031FCEEE77A0B4349 -:10146000C2F8103117EE903AD2F81011C3F30B0303 -:101470000B43C2F81031136843F002031360F0BD50 -:1014800080810D4000441F40F8030020F403002039 -:10149000FC0300200000FF0FF0030020304B40F65B -:1014A000617270B5C3F8202140F2044500F0ACF839 -:1014B0002C492D48D1F880202C4C42F003022C4BB3 -:1014C000C1F88020C0F86051226813401BB9D0F8E1 -:1014D000A8319A071AD0244B4FF00041234A516398 -:1014E0001A46D3F8401141F00201C3F84011D2F876 -:1014F00040319B07FBD44FF400301E491B4B4FF08B -:101500000042086019209A6300F09AF81A4D0022F0 -:10151000164B4FF08041144C0A26996328461A60F6 -:101520001146C4F8A8614FF4207200F07DF84FF422 -:1015300081064FF4800040F24313104A10492E6098 -:101540002864C4F85851C2F80412C4F848310D4A4E -:101550004FF4003101231160C4F8403170BD00BF69 -:1015600000800D4000C00F4000002E4000900D4054 -:10157000001C1E008CE200E0003000200010002063 -:10158000F90600000CE100E0114B1249D86E0A4642 -:1015900040F4403030B4D86640F2B765D86EA0242D -:1015A00040F44070D8664D648C64936C1B06FCD488 -:1015B000094B40F2B760A021064A58649964936CC5 -:1015C00013F08003FBD1054A137030BC704700BF95 -:1015D00000C00F4000400C4000800C40A10D0020D6 -:1015E000F8B500BFF8BC08BC9E467047FFFFFFFF80 -:1015F0005FF800F0390500005FF800F055160000B4 -:101600005FF800F0D90100005FF800F0F90D00006C -:101610005FF800F0350500005FF800F06913000086 -:101620005FF800F0010100005FF800F0A51600006F -:101630005FF800F0B91100005FF800F0050100004C -:101640005FF800F05912000045000000FFFFFFFFA7 -:10165000000000000000000000000000000000008A -:10166000000000000000000000000000000000007A -:1016700010B5054C237833B9044B13B10448AFF3CC -:1016800000800123237010BDD00300200000000063 -:1016900054170000084B10B51BB108490848AFF3B8 -:1016A00000800848036803B910BD074B002BFBD02E -:1016B000BDE81040184700BF00000000D403002020 -:1016C00054170000D00300200000000008B5174B9D -:1016D0000121187800F00AFA154B0121187800F062 -:1016E00005FA144B0121187800F000FA124B012181 -:1016F000187800F0FBF9114B0121187800F0F6F989 -:101700000F4B0121187800F0F1F90E4B00211878E9 -:1017100000F0ECF90C4B0021187800F0E7F90B4BC6 -:1017200000211878BDE8084000F0E0B9F40200207C -:10173000FC0200200003002010030020080300200A -:10174000F8020020EC030020EC020020F002002050 -:10175000FFF7BCBF2C4A2D4B2DE9F04714682C4AEB -:10176000DFF8B8A012685746DFF8B490294EDFF8CA -:10177000B480294D93FBF4F393FBF2F4012199F823 -:10178000000000F0ADF9DAF800103268A5FB023273 -:10179000920C04FB02F2DAF800305B1A9A42FAD893 -:1017A000012198F8000000F09BF939683268A5FB28 -:1017B0000232920C04FB02F2DAF800305B1A9A4211 -:1017C000FAD8002199F8000000F08AF939683268E7 -:1017D000A5FB0232920C04FB02F2DAF800305B1A2D -:1017E0009A42FAD8002198F8000000F079F9396897 -:1017F0003268A5FB0232920C04FB02F2DAF80030E8 -:101800005B1A9A42FAD8B9E70C03002000879303C9 -:10181000040300201803002083DE1B43041000E0B3 -:1018200008030020F80200208C4A8D4B90422DE9DD -:10183000F0438C4D5C699969EF681DD98A4B9842D9 -:1018400040F20181894B40F22766DFF860E20344F1 -:10185000874D1A0AAEFB0232D30903EB830303EB75 -:10186000830202F2E243B34228BF3346A3F5487332 -:10187000A5FB0336F60804E07E4EB04294BF062670 -:101880000E26774A07F01F0ED2F88030B64543F097 -:10189000C003C2F880300AD2724B27F01F071A46E5 -:1018A0003743DF601368002BFCDA07F01F0E14F0DB -:1018B00000732ED1704D714AD5F810C015460CEA50 -:1018C0000202AA420ABF4FF0C0534FF480520022D6 -:1018D00084EA030515F0605F06D024F0605403F03D -:1018E00060535F4D1C436C6181EA020313F4405F57 -:1018F00008D05B4B21F4405111431A469961936C17 -:101900001D07FCD444F00074554A5461936C990649 -:10191000FCD401215A4D0A4601FB02F300FB03F3FC -:10192000AB4209D8072A00F28480013201FB02F39E -:1019300000FB03F3AB42F5D95248534D1844A5FBC5 -:101940000030030D6C2B79D8352B7ED8DFF8608101 -:1019500036234E48DFF820C14D4DDCF80090B0FB37 -:10196000F2F009EA05054545B0FBF1F00BD043F470 -:1019700000534FF480586546CCF80080CCF8003016 -:101980002B68002BFCDADFF8D8C0013ADCF8103005 -:1019900003F00703934207D002F007026546CCF834 -:1019A0001020AB6CDB03FCD40139890284EA01030B -:1019B00013F4E05F0AD02A4B24F4E05401F4E05120 -:1019C0001A460C435C61936C9907FCD4314B324945 -:1019D0000344DB09A1FB0331090B042928BF0421BF -:1019E0004B1E1B0284EA030212F4407F06D024F44B -:1019F000407403F440731A4A1C435461184B24F09A -:101A000000741A465C61936C9B06FCD4B0FBF1F148 -:101A1000224A7645224B1060196008D2114B27F0FC -:101A20001F071A463743DF601368002BFCDABDE856 -:101A3000F083042980D8013101226DE7DFF874803A -:101A40006C23184886E712261BE71748DFF86880E2 -:101A500000FB03F043EA08087CE700BF00A4781FFE -:101A600000C00F40000008400046C32300BA3CDC21 -:101A70001F85EB5100366E0100800D404030008024 -:101A8000FFB19F26808D5B00819F5E1600B29F266E -:101A90007F3001807FD1F0089F10E50018030020FF -:101AA0001403002000643F4D001BB70023B24C001C -:101AB000362000806C200080002000800001074B51 -:101AC0001A181B58D2685868104202D011B9C3F8CE -:101AD00088207047C3F88420704700BF00000020B2 -:101AE000272801D8FFF7EABF704700BF27281CD876 -:101AF00000011A4A012902EB0003105810B415D056 -:101B0000042913D0DC68426822EA040242609A6821 -:101B1000E9B102291ED003295B685DF8044B0CBFB4 -:101B20000F491049116015221A607047DA680129BF -:101B3000446842EA040242609A6808D040F63801DC -:101B400011605B6815225DF8044B1A6070473821FC -:101B50001160F6E704491160F3E700BF00000020C0 -:101B6000383001003800010038F0010004207146CF -:101B7000084203D0EFF3098000F008B8EFF30880C3 -:101B800000F004B8704700BF704700BF1B4B052131 -:101B90001B4A382030B5C2F848110821C2F8380372 -:101BA00083B05A68174C0A4317485A60C3F8841028 -:101BB000C3F888102368834202D91448FFF734FE23 -:101BC0000E4D08240020124A1249C5F884400190A5 -:101BD000019B934205D8019B01330193019B9342E2 -:101BE000F9D9C5F888400190019B8B42EDD8019B43 -:101BF00001330193019B8B42F9D9E6E700C01B40FA -:101C000000801F4018030020FF45C32300A3E111FB -:101C10003F420F003F548900836B30B41BB15368BF -:101C200043F40043536072B6446B9CB1104B226086 -:101C3000D3F8B0410C4217D1D3F8404144F480446A -:101C4000C3F84041D3F8B851D3F840416404F3D508 -:101C5000294209D10023064C8260C360D4F8B03118 -:101C60001943C4F8B0110263426362B630BC7047D6 -:101C700000002E4038B50546036B06E0AA6B1C68D1 -:101C800090476A6B2346944208D0184633B1012B23 -:101C900004D05A681206F1D52B6338BD00232B639C -:101CA0006B6338BDF0B5F1B9224C23490020234BBA -:101CB00001228025A1600A601A464D60E060D3F8D9 -:101CC000BC41886044F001141D4DC3F8BC41D3F8F9 -:101CD000B01141F00101C3F8B0112860D2F8B03161 -:101CE000002BFBD1F0BD0904164B144D0126114CFD -:101CF00041F0800100221E60596000F5805CE264C2 -:101D000000F5005EA36400F54057D5F8B01100F56A -:101D100080462A46986041F48031C3F80CC0C3F86D -:101D200010E05F619E61C5F8B011D2F8B031002BB0 -:101D3000FBD1BAE7003000202020002000002E4018 -:101D40000C040020002000202DE9F04FBB4C83B094 -:101D5000D4F8448118F0010FC4F844815ED0D4F85F -:101D6000AC31002B55D04FF00119DFF8F0B2264608 -:101D7000B34FCA46B96AFA6AC4F8AC31D4F84031F4 -:101D800043F40053C4F84031D4F840319D04F5D5F4 -:101D9000D6F8403123F40053C6F84031C6F8B4A158 -:101DA000D4F8B43113F00113FAD188B240F281654E -:101DB000CBF80030A84200F29680B0F5D06F80F0EA -:101DC000D881B0F5817F00F0578100F2D58080285E -:101DD00000F02681822840F0C78092B202F07F0195 -:101DE000072900F2C18089009648974D0844016890 -:101DF00010062B706B7040F1E581C90301D50123FA -:101E00002B7002219048FFF74DFFD4F8AC31002B26 -:101E1000B0D18A4AD2F8BC31002B44D118F0400F1F -:101E200018D0864BD3F8AC111A46C3F8AC11D3F8CE -:101E3000BC11C3F8BC11D2F8B041804B002CFAD1D0 -:101E40004FF0FF32C3F8B421D3F8843100F074FBB3 -:101E50007E4B1C6018F0807F03D07D4B1B6803B164 -:101E6000984718F0007F03D07A4B1B6803B198475E -:101E700018F0040F02D0714BD3F884316F4BD3F8B4 -:101E8000482112060CD518F0800F09D072490A7843 -:101E9000002A00F08E81531EDBB20B7003B9FBBE2B -:101EA00003B0BDE8F08F6D49C2F8BC310868034249 -:101EB00040F0CC81654A14681C40AFD0630700F144 -:101EC000E281670300F1EF81260700F1E881250335 -:101ED00000F1E181E00600F1DA81E1029ED560487F -:101EE000FFF7C8FE9AE742F22105A84238D06FD822 -:101EF000B0F5086F00F02F81B0F5106F34D1C1F349 -:101F00000741584A584811705849594A0193C6F830 -:101F1000C801C6F8CC11C6F8D02100F00FFB554A15 -:101F2000019B80210120FB6410605160D6F8BC1138 -:101F3000936041F00111BA64C6F8BC11D6F8B02123 -:101F400042F48032C6F8B021444A1360D4F8B0316C -:101F5000002BFBD1D4F8AC31002B7FF40BAF58E74A -:101F6000100C072800F03281C4F8C091D4F8AC31CD -:101F7000002B7FF4FFAE4CE740F20235A84200F0A0 -:101F8000B780B0F5A06FEFD13A4A80200125FB64FD -:101F900050601560D6F8BC01936040F00113BA643C -:101FA000C6F8BC31D6F8B03143F48033C6F8B0314E -:101FB000D4F8B031002BFBD10B0C5B0643F08073DF -:101FC000C6F85431D4F8AC31002B7FF4D3AE20E7FF -:101FD00042F22123984200F0828042F2213398425B -:101FE000C2D1244B01218022196000215A60D4F80B -:101FF000BC21BB6442F001129960F964C4F8BC21B1 -:10200000D4F8B03143F48033C4F8B031D4F8B031EF -:10201000002BFBD1D4F8AC31002B7FF4ABAEF8E64B -:10202000094A0221104613705370FFF73BFED4F8A3 -:10203000AC31002B7FF49EAEEBE600BF00002E40DB -:1020400000300020C0012E402004002004040020A5 -:102050000004002008040020100400200C040020CC -:1020600040320020300400200200CC00C8000200F2 -:102070000200C8002020002092B202F07F03072B4C -:102080003FF672AF12F0800F4FEA8303884A4FF099 -:1020900001011A44136814BF23F4803323F00103B1 -:1020A00013608022834B196000215A60D4F8BC2150 -:1020B000BB6442F001129960F964C4F8BC21D4F801 -:1020C000B03143F48033C4F8B031D4F8B031002BD0 -:1020D000FBD1D4F8AC31002B7FF44CAE99E6764BB3 -:1020E000C1F30741754A1868754B1060197078E79D -:1020F00092B202F07F03072B3FF636AF12F0800F4B -:102100004FEA83036A4A4FF001011A44136814BF6F -:1021100043F4803343F0010313608022654B196060 -:1021200000215A60D4F8BC21BB6442F001129960CE -:10213000F964C4F8BC21D4F8B03143F48033C4F856 -:10214000B031D4F8B031002BFBD1D4F8AC31002B36 -:102150007FF410AE5DE65B4A01215B4B12781846B6 -:102160001A70FFF79FFDD4F8AC31002B7FF402AE5C -:102170004FE6564B586800283FF4F6AE090C1FFA9C -:1021800082FE04E00C33586800283FF4EDAE1D8851 -:102190008D42F7D15D887545F4D1090A120C0329E7 -:1021A0000CBF01781989914228BF1146FFF77AFDCB -:1021B0002BE6D3F8482122F08002C3F8482103B06F -:1021C000BDE8F08FCA077FF51CAE18E6404D012030 -:1021D000FB6029603F4B4049186059603F49D6F881 -:1021E000B0016A6001F5005E40F00102A1F5005007 -:1021F00001F58055BB609860A1F58050D860C6F8A5 -:10220000B02119615D61C3F818E0D4F8B031002B3A -:10221000FBD1284A012048F28001FB6410604FF492 -:1022200080305160D6F8BC11936041F00113BA645C -:10223000C6F8BC31D6F8B0310343C6F8B031CBF89C -:102240000000D4F8B031002BFBD1DEE5204C42F287 -:102250002100002524880D6084427FF42BAE2049A4 -:10226000204C03C90D0C86282060A180A5717FF445 -:1022700021AED2F8481150241B4841F08001C2F829 -:102280004811047016E61948FFF7F4FC19E61848DF -:10229000FFF7F0FC21E61748FFF7ECFC1AE61648BA -:1022A000FFF7E8FC13E61548FFF7E4FC0CE600BF77 -:1022B000C0012E4020200020A80D002088320020E0 -:1022C000800D002030040020200400208002002027 -:1022D000180400200020002080000700282400208F -:1022E000280400208032002010040020003100204B -:1022F00000320020C0310020803100204031002019 -:10230000002AA0F102022DE9F04714BF00274FF088 -:102310000057022A01D9BDE8F0874FEAC01ADFF85A -:1023200040900D4604460AEB0906002140229846DB -:10233000304600F0DFFC012047EA05414AF8091069 -:10234000C6F83880B060B8F1000FE4D0034BA0406D -:102350001C6820431860BDE8F08700BF040400201B -:1023600000300020002AA0F102022DE9F04714BF3E -:1023700000274FF00057022A01D9BDE8F08740221C -:10238000C501DFF8449088461544002104461E46E6 -:1023900005EB090A504600F0ADFC012247EA08416E -:1023A00045F80910CAF83860CAF80820002EE4D0B1 -:1023B00004F11000034B8240186802431A60BDE824 -:1023C000F08700BF04040020003000201204816068 -:1023D000C36142F08002F0B44260012701F58056EB -:1023E00001F5005501F5405401F580420760C660D3 -:1023F000056144618261F0BC704700BF831E022BFF -:1024000000D9704730B4064B00F1100401250A468C -:1024100003EBC01005FA04F130BCFFF7FDBB00BFB1 -:1024200040300020831E022B00D9704710B4054BAA -:1024300001240A4604FA00F103EBC0105DF8044BD6 -:10244000FFF7EABB00300020124A134BD2F82002FB -:1024500020F07F40984210B584B002D800EB800095 -:1024600040000E4C01A90A2200F07EFA01A90023C7 -:10247000204611F8012B01333AB10A2B20F8022F24 -:10248000F7D11623237004B010BD5B00DBB22370BC -:1024900004B010BD00441F407F969800B403002094 -:1024A0004368C269C3F30E43054930B4C3F1400326 -:1024B000044C002521F8123024F8125030BC70472B -:1024C000000C0020F80B0020F8B5154B1B783BB929 -:1024D00003F0FF04134B1B7813B1134D2A8802B984 -:1024E000F8BD124F2346124EC2F58072397811485A -:1024F00006EB411600EB01213046FFF767FF31463E -:102500000420FFF77BFF3B780133DBB2062B98BF3B -:102510003B704FF0000388BF3C702B80F8BD00BFBC -:10252000340B002030040020800C0020350B0020EC -:10253000A00C002034040020704700BF0021E022DE -:102540002048F8B50C46204E204D00F0D3FB204F1C -:102550002146204B6022347028461F4E1C8000F01C -:10256000C9FB23462246102102203C60BC80346017 -:10257000B480FFF7F7FE2246184B40210320FFF7F7 -:10258000BFFE2346224640210420FFF7EBFE2346F0 -:10259000402228461249FFF719FF29460320FFF77A -:1025A00041FF104B4A22104910480860C3F884408C -:1025B000C3F88020D3F8482142F08072C3F8482144 -:1025C000F8BD00BFA00C0020350B0020200C00201F -:1025D000000C0020800C0020F80B0020510E0000A1 -:1025E000380B002000002E4000040020790E00006F -:1025F000024A034B10881B88C01A7047000C002049 -:10260000F80B002010B4EFF3108272B6437F33B999 -:10261000017F012908D0032910D00123437702B993 -:1026200062B65DF8044B7047114C2168A1B11149A5 -:1026300043610B68086083615861EEE70E4C2168C6 -:1026400081B10E4943610B680860836158610C4B8E -:102650004FF080511960E0E7064B416181612060D5 -:102660001860DAE7054B4161816120601860EEE790 -:10267000940D0020900D0020840D0020880D002076 -:1026800004ED00E010B4047F4160022CC26003D06E -:102690005DF8044BFFF7B6BF83685DF8044B18473D -:1026A00070B5EFF3108172B60C4C23688BB10C4EF1 -:1026B00000255A6922607AB1956101B962B65D77E9 -:1026C00018469B689847EFF3108172B62368002B79 -:1026D000EFD101B962B670BD3260EEE7840D002023 -:1026E000880D0020FFF7DCBF184A30B41468002CB6 -:1026F00028D0036821688B420FD2CB1A0021846056 -:10270000C1602360E0601060022330BC0375704735 -:102710000360144611688B4208D3A2685B1A002A32 -:10272000F6D18260C4600360A060EDE7D568CB1A83 -:1027300082600222C560E060C16888602360027523 -:1027400030BC70478460C4601060DDE78C0D0020F1 -:10275000F8B5224E34682CB32368002B3AD11D46BD -:102760001F4F04E03468ECB12368002B32D1A3681A -:1027700003B1DD6020693360036825751B68BB42C7 -:1027800021D1037F4560022BC46020D0FFF73AFFC0 -:102790006368002BE6D023602046FFF7A5FF34686E -:1027A000002CE1D1EFF3108372B60E4A00211068BD -:1027B000116003B962B628B18468FFF795FF20461F -:1027C0000028F9D1F8BD224600219847E0E7836848 -:1027D0009847DDE7013B2360E4E700BF8C0D002054 -:1027E000351000009C0D0020044A054B1168054A75 -:1027F0001960136801331360FFF7AABF041000E0EB -:10280000A40D0020A80D002070B5214C237883B9B9 -:10281000204B01221B7822701BBB1F4B1B78002B07 -:1028200029D11E4B00211A68217012B1EFF30582E5 -:1028300002B170BDEFF3108072B61A68F2B1184C95 -:102840002178D9B90126556926701D60D5B1A961D5 -:1028500000B962B6002593681046557798472570F1 -:1028600070BDFFF7C5FE0028D7D000F015FA0A4B5F -:102870001B78002BD5D000F0FBF9D2E70028D8D187 -:1028800062B670BD074B1D600028E3D1E1E700BFD1 -:10289000A00D0020CA030020C80D0020940D0020C8 -:1028A000980D0020900D0020002852D02DE9F04F07 -:1028B000814683B0274C0120274D284E54E8003F25 -:1028C0002A68316844E80003002BF7D1244F4FF405 -:1028D0007A7E2448D7F800C0BB4607F1C647036894 -:1028E000C1EB0C0107F5DE1707F67F67A7FB03C3F3 -:1028F000BA4601279B0CB1FBF3F30EFB023854E8F8 -:10290000003F2A68316844E80073002BF7D1DBF8F8 -:1029100000C04FF47A7E03680EFB02F2C1EB0C019B -:10292000AAFB033EC8EB02034FEA9E42B1FBF2F161 -:10293000CA18B2F57A7F07D3B9F1010908F57A7898 -:10294000DDD103B0BDE8F08F0190FFF75DFF019886 -:10295000D5E770478C320020A80D0020A40D002080 -:10296000041000E018030020F0B44E1E0025374686 -:1029700000E00135B0FBF2F302FB130000F1370475 -:10298000092800F13000E4B298BFC4B2184607F835 -:10299000014F002BEDD14A1953704DB1013316F898 -:1029A000014F1778E81A3770834202F80149F5DBC6 -:1029B0000846F0BC704700BFA4484FF00F0CA44B72 -:1029C000826F42F47F02F0B582670025D0F8802044 -:1029D0004FF470469F4C4FF4604E29464FF4806789 -:1029E00014432A46C0F88040A3F88C6148F2B82608 -:1029F000A3F88EC1A3F89051B3F8880180B240F0DB -:102A0000F000A3F8880101EB4100914B0131002552 -:102A100040011C4604290344A3F804E0DF805A84E3 -:102A20001A865A805A81DE815A82DA825A83DA8380 -:102A3000E9D1B4F888014FF00F0C874B4FF4704682 -:102A400080B229464FF460472A4640EA0C004FF412 -:102A5000806EA4F88801B4F8880180B240F47060F8 -:102A6000A4F88801A3F88C6148F2B826A3F88EC1B7 -:102A7000A3F89051B3F8880180B240F0F000A3F8B9 -:102A8000880101EB4100744B0131002540011C46D7 -:102A9000042903449F80A3F806E05A841A865A80CA -:102AA0005A81DE815A82DA825A83DA83E9D1B4F814 -:102AB00088014FF00F0C694B4FF4704680B22946E5 -:102AC0004FF460472A4640EA0C004FF4806EA4F8A9 -:102AD0008801B4F8880180B240F47060A4F88801DD -:102AE000A3F88C6148F2B826A3F88EC1A3F89051E0 -:102AF000B3F8880180B240F0F000A3F8880101EB40 -:102B00004100564B0131002540011C460429034475 -:102B10009F80A3F806E05A841A865A805A81DE8183 -:102B20005A82DA825A83DA83E9D1B4F888014FF005 -:102B30000F0C4B4B4FF4704780B229464FF4604660 -:102B40002A4640EA0C004FF4806EA4F88801B4F8DD -:102B5000880180B240F47060A4F88801A3F88C71F9 -:102B600048F2B827A3F88EC1A3F89051B3F88801B2 -:102B700080B240F0F000A3F8880101EB4100384B2F -:102B8000013140011C46042903449E80A3F806E05D -:102B90005A841A865A805A81DF815A82DA825A838D -:102BA000DA83EAD1B4F888310F27002241F2010616 -:102BB0009BB245F6C05E114643F226053B43A4F89E -:102BC0008831B4F888319BB243F47063A4F888313B -:102BD0005001244B01320344042A99815981DF8139 -:102BE0009E82A3F806E0198019829D81F0D100220F -:102BF0000F2741F2010645F6C055114643F226045F -:102C00005001194B01320344042A99815981DF8113 -:102C10009E82DD80198019829C81F1D100220F27CC -:102C200041F2010645F6C055114643F22604500113 -:102C30000E4B01320344042A99815981DF819E821F -:102C4000DD80198019829C81F1D1F0BD00C00F4058 -:102C500000C03D40000003FC00003E4000403E40FC -:102C600000803E4000C01D4000001E4000401E404D -:102C700038B5074B1C784CB1064D55F8043F002B76 -:102C8000FBD09847631E13F0FF04F6D138BD00BF98 -:102C9000C80D0020A80D0020014B00221A707047BB -:102CA000CA03002070B50F4E0F4D761BB61018BF2B -:102CB000002405D0013455F8043B9847A642F9D1C9 -:102CC0000A4E0B4D761B00F063F8B61018BF0024B7 -:102CD00006D0013455F8043B9847A642F9D170BD9F -:102CE00070BD00BF48160060481600604C160060BA -:102CF0004816006070B4840746D0541E002A41D0A4 -:102D0000CDB2034602E0621EE4B3144603F8015B51 -:102D10009A07F8D1032C2ED9CDB245EA05250F2C00 -:102D200045EA054519D903F110022646103E0F2E3B -:102D300042F8105C42F80C5C42F8085C42F8045C13 -:102D400002F11002F2D8A4F1100222F00F0204F0F6 -:102D50000F041032032C13440DD91E462246043AA8 -:102D6000032A46F8045BFAD8221F22F00302043239 -:102D7000134404F003042CB1C9B21C4403F8011B32 -:102D8000A342FBD170BC704714460346C2E700BFA4 -:102D90005FF800F0E1150060000000000000000096 -:042DA000F8B500BFC3 -:102DA40000000042C8801F40B8821F400800000095 -:102DB40000000042C4801F40B4821F400400000091 -:102DC40000C0004224801F4014821F4010000000F5 -:102DD40000C0004228801F4018821F4020000000CD -:102DE40000C000422C801F401C821F404000000095 -:102DF40000C0004234801F4024821F4000010000B4 -:102E04000040004264811F4054831F4000040000BE -:102E14000040004280811F4070831F400000020078 -:102E2400004000427C811F406C831F400000010071 -:102E34000040004268811F4058831F400008000082 -:102E4400004000423C811F402C831F4001000000D1 -:102E54000040004244811F4034831F4004000000AE -:102E64000040004240811F4030831F4002000000A8 -:102E74000040004248811F4038831F400800000082 -:102E84000000004204811F40F4821F40000004003F -:102E94000000004208811F40F8821F400000080023 -:102EA4000000004218811F4008831F40000080007A -:102EB4000000004214811F4004831F4000004000B2 -:102EC4000000004200811F40F0821F400000020009 -:102ED40000000042FC801F40EC821F400000010003 -:102EE4000000004224811F4014831F40000000049E -:102EF4000000004228811F4018831F400000000882 -:102F0400000000421C811F400C831F400000000190 -:102F14000000004220811F4010831F400000000277 -:102F240000000042EC801F40DC821F4000100000C3 -:102F340000000042F0801F40E0821F40002000009B -:102F44000000004234811F4024831F4000000040E1 -:102F54000000004238811F4028831F400000008089 -:102F64000080004294801F4084821F4000000400BF -:102F740000C0004290801F4080821F4000000080FB -:102F840000800042A8801F4098821F4000008000FB -:102F940000800042A4801F4094821F400000400033 -:102FA400004000426C811F405C831F400010000001 -:102FB40000C0004230801F4020821F40800000007B -:102FC40000800042C8811F40B8831F400080000079 -:102FD40000800042C4811F40B4831F4000400000B1 -:102FE40000800042C0811F40B0831F4000200000C9 -:102FF40000800042BC811F40AC831F4000100000D1 -:1030040000800042D0811F40C0831F4000000200A6 -:1030140000800042CC811F40BC831F40000001009F -:1030240000010000840300201200000000060000DC -:103034001C0300200A0000000002000040030020DE -:10304400430000000007000040030020430000008C -:1030540000030000B0030020000000000103090485 -:103064002803002000000000020309049803002044 -:103074000000000003030904B40300200000000062 -:10308400000000000000000000000000010000003B -:1030940002000000170000001200000016000000EB -:1030A400150000001E0000001300000000200000B6 -:1030B400140000000029DE07007B9A170A060002AC -:1030C4000200004001000000180354006500650080 -:1030D4006E00730079006400750069006E006F0073 -:1030E40009024300020100C0320904000001020287 -:1030F4000100052400100105240101010424020635 -:1031040005240600010705820310001009040100CC -:10311400020A0000000705030240000007058402BC -:10312400400000001201000202000040C0168304A7 -:103134007902010203010000160355005300420006 -:103144002000530065007200690061006C000000FB -:10315400040309040C030000000000000000000048 -:10316400000000000000000000000100000000005A -:040000056000100087 -:00000001FF diff --git a/tools/odrive/tests/encoder_pass_through.ino.hex b/tools/odrive/tests/encoder_pass_through.ino.hex deleted file mode 100644 index 2f0798bf..00000000 --- a/tools/odrive/tests/encoder_pass_through.ino.hex +++ /dev/null @@ -1,792 +0,0 @@ -:0200000460009A -:100000004643464200000156000000000101020084 -:1000100000000000000000000000000000000000E0 -:1000200000000000000000000000000000000000D0 -:1000300000000000000000000000000000000000C0 -:1000400000000000010403000000000000000000A8 -:100050000000200000000000000000000000000080 -:100060000000000000000000000000000000000090 -:100070000000000000000000000000000000000080 -:10008000EB04180A063204260000000000000000FD -:10009000050404240000000000000000000000002F -:1000A0000000000000000000000000000000000050 -:1000B0000604000000000000000000000000000036 -:1000C0000000000000000000000000000000000030 -:1000D00020041808000000000000000000000000DC -:1000E0000000000000000000000000000000000010 -:1000F0000000000000000000000000000000000000 -:10010000D8041808000000000000000000000000F3 -:100110000204180804200000000000000000000095 -:1001200000000000000000000000000000000000CF -:10013000600400000000000000000000000000005B -:1001400000000000000000000000000000000000AF -:10015000000000000000000000000000000000009F -:10016000000000000000000000000000000000008F -:10017000000000000000000000000000000000007F -:10018000000000000000000000000000000000006F -:10019000000000000000000000000000000000005F -:1001A000000000000000000000000000000000004F -:1001B000000000000000000000000000000000003F -:1001C000000100000010000001000000000000001D -:1001D000000001000000000000000000000000001E -:1001E000000000000000000000000000000000000F -:1001F00000000000000000000000000000000000FF -:10020000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE -:10021000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEE -:10022000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDE -:10023000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCE -:10024000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBE -:10025000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAE -:10026000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E -:10027000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8E -:10028000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7E -:10029000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6E -:1002A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5E -:1002B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4E -:1002C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3E -:1002D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E -:1002E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1E -:1002F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0E -:10030000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD -:10031000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED -:10032000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD -:10033000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCD -:10034000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD -:10035000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAD -:10036000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D -:10037000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8D -:10038000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7D -:10039000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6D -:1003A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D -:1003B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4D -:1003C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3D -:1003D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2D -:1003E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1D -:1003F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0D -:10040000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC -:10041000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC -:10042000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC -:10043000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCC -:10044000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC -:10045000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAC -:10046000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9C -:10047000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8C -:10048000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7C -:10049000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C -:1004A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5C -:1004B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4C -:1004C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3C -:1004D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2C -:1004E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1C -:1004F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0C -:10050000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB -:10051000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEB -:10052000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB -:10053000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCB -:10054000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBB -:10055000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAB -:10056000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9B -:10057000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8B -:10058000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7B -:10059000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6B -:1005A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B -:1005B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4B -:1005C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3B -:1005D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2B -:1005E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1B -:1005F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0B -:10060000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA -:10061000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEA -:10062000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA -:10063000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCA -:10064000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA -:10065000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAA -:10066000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9A -:10067000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8A -:10068000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7A -:10069000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6A -:1006A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5A -:1006B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4A -:1006C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3A -:1006D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A -:1006E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1A -:1006F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A -:10070000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9 -:10071000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9 -:10072000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9 -:10073000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC9 -:10074000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB9 -:10075000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9 -:10076000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF99 -:10077000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF89 -:10078000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF79 -:10079000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF69 -:1007A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF59 -:1007B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF49 -:1007C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF39 -:1007D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF29 -:1007E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF19 -:1007F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF09 -:10080000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8 -:10081000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8 -:10082000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD8 -:10083000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC8 -:10084000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB8 -:10085000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA8 -:10086000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF98 -:10087000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF88 -:10088000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF78 -:10089000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF68 -:1008A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF58 -:1008B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF48 -:1008C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF38 -:1008D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF28 -:1008E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF18 -:1008F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF08 -:10090000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7 -:10091000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE7 -:10092000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD7 -:10093000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7 -:10094000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB7 -:10095000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA7 -:10096000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF97 -:10097000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF87 -:10098000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF77 -:10099000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF67 -:1009A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57 -:1009B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF47 -:1009C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF37 -:1009D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27 -:1009E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF17 -:1009F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF07 -:100A0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6 -:100A1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE6 -:100A2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD6 -:100A3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC6 -:100A4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB6 -:100A5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA6 -:100A6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF96 -:100A7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF86 -:100A8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF76 -:100A9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF66 -:100AA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 -:100AB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF46 -:100AC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF36 -:100AD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF26 -:100AE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16 -:100AF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF06 -:100B0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5 -:100B1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5 -:100B2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD5 -:100B3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC5 -:100B4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB5 -:100B5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA5 -:100B6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF95 -:100B7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF85 -:100B8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF75 -:100B9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF65 -:100BA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF55 -:100BB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF45 -:100BC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF35 -:100BD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF25 -:100BE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF15 -:100BF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF05 -:100C0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4 -:100C1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE4 -:100C2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD4 -:100C3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC4 -:100C4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB4 -:100C5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA4 -:100C6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF94 -:100C7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF84 -:100C8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF74 -:100C9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF64 -:100CA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF54 -:100CB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF44 -:100CC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF34 -:100CD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF24 -:100CE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF14 -:100CF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF04 -:100D0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3 -:100D1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE3 -:100D2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD3 -:100D3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC3 -:100D4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB3 -:100D5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA3 -:100D6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF93 -:100D7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83 -:100D8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF73 -:100D9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF63 -:100DA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF53 -:100DB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43 -:100DC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF33 -:100DD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF23 -:100DE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF13 -:100DF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF03 -:100E0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2 -:100E1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE2 -:100E2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD2 -:100E3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2 -:100E4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB2 -:100E5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA2 -:100E6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF92 -:100E7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF82 -:100E8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF72 -:100E9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF62 -:100EA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF52 -:100EB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF42 -:100EC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF32 -:100ED000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF22 -:100EE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF12 -:100EF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02 -:100F0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1 -:100F1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE1 -:100F2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD1 -:100F3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC1 -:100F4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB1 -:100F5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA1 -:100F6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF91 -:100F7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81 -:100F8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF71 -:100F9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF61 -:100FA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF51 -:100FB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF41 -:100FC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF31 -:100FD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF21 -:100FE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF11 -:100FF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF01 -:10100000D10020402C100060000000000000000013 -:1010100020100060001000600000000000000000D0 -:1010200000000060403100000000000000000120CE -:1010300035100060764B0720764C4FF42A01764A33 -:101040005C64186499639546744A75498A420FD066 -:10105000744B9A420CD2D4430846234423F0030332 -:1010600004330B4450F8041B984242F8041BF9D196 -:101070006D4A6E498A420FD06D4B9A420CD2D443CE -:101080000846234423F0030304330B4450F8041BA5 -:10109000984242F8041BF9D1664A674B9A420BD238 -:1010A000D04311460024034423F0030304331344C4 -:1010B00041F8044B8B42FBD1604A4FF47001604B06 -:1010C000116003F530715F4A43F8042F9942FBD158 -:1010D0005D4B80215D4A03F8011B9342FBD15C4CC0 -:1010E0004FF080375B4E4FF0FF32DFF8A8A10025AC -:1010F000DFF8A491DFF8A481574B4549C3F800A05D -:10110000C4F80471C4F80091C4F8F470C4F8F08015 -:10111000F36923F07F0343F04003F361736A23F024 -:101120007F0343F0400373628A66CA660A674A67B0 -:1011300000F0B6F8494A6320494B4A49106003223F -:101140001D60CAF8381043F8082C4749474A4848F8 -:10115000C3F8082D0B68474A43F08073CAF83C0077 -:1011600045480B601368454943F001031360036869 -:101170000B6000F0E5F8C4F804714148C4F8009130 -:10118000C4F8F470C4F8F08000F036FA00BF00BF75 -:1011900000BF00BFF16E3B4A41F440513A4BF1664B -:1011A0001560C2F80851C2F81851C2F82851C2F8A7 -:1011B00038519A6BD20708D442F615623349596503 -:1011C0001A659A6B42F001029A632F4A304C936879 -:1011D00043F00113936000F033FA2368132BFCD91A -:1011E00000F05CF900F0D0F900F022FA00F0DAF833 -:1011F00000F006FA2368B3F5967FFBD300F010FAEF -:1012000000F012FA00F008FA00F016FAFAE700BF50 -:1012100000C00A40ABAAAAAA008007200000000074 -:10122000501600602017000000000020742D0060A0 -:10123000D0030020D0030020C032002088ED00E061 -:10124000FC0F00206102000000E400E0A0E400E0E8 -:1012500000800D4000C00F4008ED00E014E000E009 -:1012600018E000E0D90D0000FCED00E000002020B7 -:10127000DD0E0000001000E0041000E0A40D0020CE -:101280000046C3230040084000400D400000C05607 -:10129000A80D0020001000201B1018200C0D1113A9 -:1012A000F0B5194A0021194B4FF0100E18480124CF -:1012B000184E194D0160194FC2F800E01E6015600C -:1012C000174E184D1F601660174F1D60174E184DB2 -:1012D00017601E60174F1560174E184D1F6016607F -:1012E0001D600460BFF34F8FBFF36F8FC3F8B01161 -:1012F000BFF34F8FBFF36F8F52F8883C43F44033F6 -:1013000042F8883CF0BD00BF9CED00E0A0ED00E09D -:1013100094ED00E0250008031100200021000207E1 -:1013200012000020250008131300202027000B13B3 -:101330001400004033001013150000602F000B074D -:10134000F0B4174A40274FF480314FF480564FF4E1 -:1013500000554FF4404443F24200136913F0020F6A -:1013600006D0946151619061136913F0020FF8D1B6 -:1013700013F4005F01D15561EFE713F4805F01D1F1 -:101380005661EAE7002BE8DA13F4803F01D091615F -:10139000E3E75B0601D45761DFE7F0BC704700BFAD -:1013A00000800D40364A03203649F3EE096A13687F -:1013B00023F00103F0B51360C2F89000D1F8E030DB -:1013C000DFF8D4E0DCB2C3F30B202F4FC0EB135394 -:1013D00007EE904AA4F15501CEF80040B8EEE77A46 -:1013E00003EB830407EE900A03FB01F13B6003EB80 -:1013F0008313B8EEE75A07EE901A091B77EE666A78 -:10140000B8EE677A214D07EE901A0B44C5ED006ADD -:10141000F8EE677A1E4EC7EE265A1E4930601068F5 -:1014200087EEA66A07EE903AF8EE677A87EEA67A1C -:1014300075EE855A76EE056AFCEEE55AFCEEE66A34 -:1014400015EE903A40EA035316EE900A77EE057ACD -:10145000136001EA0041D2F81031FCEEE77A0B4349 -:10146000C2F8103117EE903AD2F81011C3F30B0303 -:101470000B43C2F81031136843F002031360F0BD50 -:1014800080810D4000441F40F8030020F403002039 -:10149000FC0300200000FF0FF0030020304B40F65B -:1014A000617270B5C3F8202140F2044500F0B0F835 -:1014B0002C492D48D1F880202C4C42F003022C4BB3 -:1014C000C1F88020C0F86051226813401BB9D0F8E1 -:1014D000A8319A071AD0244B4FF00041234A516398 -:1014E0001A46D3F8401141F00201C3F84011D2F876 -:1014F00040319B07FBD44FF400301E491B4B4FF08B -:101500000042086019209A6300F072F81A4D002218 -:10151000164B4FF08041144C0A26996328461A60F6 -:101520001146C4F8A8614FF4207200F06DF84FF432 -:1015300081064FF4800040F24313104A10492E6098 -:101540002864C4F85851C2F80412C4F848310D4A4E -:101550004FF4003101231160C4F8403170BD00BF69 -:1015600000800D4000C00F4000002E4000900D4054 -:10157000001C1E008CE200E0003000200010002063 -:101580003D0400000CE100E0114B1249D86E0A4600 -:1015900040F4403030B4D86640F2B765D86EA0242D -:1015A00040F44070D8664D648C64936C1B06FCD488 -:1015B000094B40F2B760A021064A58649964936CC5 -:1015C00013F08003FBD1054A137030BC704700BF95 -:1015D00000C00F4000400C4000800C40A10D0020D6 -:1015E000F8B500BFF8BC08BC9E467047FFFFFFFF80 -:1015F0005FF800F0311200005FF800F09D0F00006E -:101600005FF800F07D0200005FF800F07D1600003A -:101610005FF800F03D0B00005FF800F005010000EE -:101620005FF800F02D1600005FF800F001010000E7 -:101630005FF800F0411300005FF800F0FD0E0000BD -:101640005FF800F07902000045000000FFFFFFFF97 -:10165000000000000000000000000000000000008A -:10166000000000000000000000000000000000007A -:1016700010B5054C237833B9044B13B10448AFF3CC -:1016800000800123237010BDD00300200000000063 -:1016900024170000084B10B51BB108490848AFF3E8 -:1016A00000800848036803B910BD074B002BFBD02E -:1016B000BDE81040184700BF00000000D403002020 -:1016C00024170000D00300200000000008B5174BCD -:1016D0000121187800F0ACF8154B0121187800F0C2 -:1016E000A7F8144B0121187800F0A2F8124B012141 -:1016F000187800F09DF8114B0121187800F098F847 -:101700000F4B0121187800F093F80E4B0021187848 -:1017100000F08EF80C4B0021187800F089F80B4B84 -:1017200000211878BDE8084000F082B8F4020020DB -:1017300000030020040300200C0300200803002005 -:10174000F8020020EC030020EC020020F002002050 -:10175000FFF7BCBF174B70B5187800F063F8164B55 -:101760000646187800F05EF8144B0546187800F02D -:1017700059F8134B04463146187800F04DF8114BD8 -:101780002946187800F048F80F4B2146187800F0E9 -:1017900043F80E4B3146187800F03EF80C4B2946C2 -:1017A000187800F039F80B4B21461878BDE87040E6 -:1017B00000F032B8FC020020EC020020F002002011 -:1017C000F402002000030020040300200C0300208A -:1017D00008030020F80200200001074B1A181B58CC -:1017E000D2685868104202D011B9C3F888207047F7 -:1017F000C3F88420704700BF000000200001054BA3 -:101800001A5819189268CB681A4214BF0120002098 -:10181000704700BF00000020272801D8FFF7DCBF79 -:10182000704700BF272801D8FFF7E8BF00207047A6 -:1018300027281CD800011A4A012902EB000310587E -:1018400010B415D0042913D0DC68426822EA0402DF -:1018500042609A68E9B102291ED003295B685DF8ED -:10186000044B0CBF0F491049116015221A607047D4 -:10187000DA680129446842EA040242609A6808D0A2 -:1018800040F6380111605B6815225DF8044B1A6060 -:10189000704738211160F6E704491160F3E700BF93 -:1018A00000000020383001003800010038F001004D -:1018B00004207146084203D0EFF3098000F008B815 -:1018C000EFF3088000F004B8704700BF704700BF16 -:1018D0001B4B05211B4A382030B5C2F8481108219E -:1018E000C2F8380383B05A68174C0A4317485A6045 -:1018F000C3F88410C3F888102368834202D91448BF -:1019000000F074FE0E4D08240020124A1249C5F85A -:1019100084400190019B934205D8019B01330193C0 -:10192000019B9342F9D9C5F888400190019B8B42F5 -:10193000EDD8019B01330193019B8B42F9D9E6E776 -:1019400000C01B4000801F40C4030020FF45C3238C -:1019500000A3E1113F420F003F548900836B30B474 -:101960001BB1536843F40043536072B6446B9CB19F -:10197000104B2260D3F8B0410C4217D1D3F840414C -:1019800044F48044C3F84041D3F8B851D3F84041FF -:101990006404F3D5294209D10023064C8260C36058 -:1019A000D4F8B0311943C4F8B0110263426362B68F -:1019B00030BC704700002E4038B50546036B06E08A -:1019C000AA6B1C6890476A6B2346944208D018465D -:1019D00033B1012B04D05A681206F1D52B6338BD00 -:1019E00000232B636B6338BDF0B5F1B9224C23495A -:1019F0000020234B01228025A1600A601A464D6019 -:101A0000E060D3F8BC41886044F001141D4DC3F878 -:101A1000BC41D3F8B01141F00101C3F8B011286006 -:101A2000D2F8B031002BFBD1F0BD0904164B144D98 -:101A30000126114C41F0800100221E60596000F522 -:101A4000805CE26400F5005EA36400F54057D5F8C1 -:101A5000B01100F580462A46986041F48031C3F801 -:101A60000CC0C3F810E05F619E61C5F8B011D2F8F8 -:101A7000B031002BFBD1BAE700300020202000203D -:101A800000002E400C040020002000202DE9F04F23 -:101A9000BB4C83B0D4F8448118F0010FC4F84481E2 -:101AA0005ED0D4F8AC31002B55D04FF00119DFF8DF -:101AB000F0B22646B34FCA46B96AFA6AC4F8AC31E6 -:101AC000D4F8403143F40053C4F84031D4F84031E5 -:101AD0009D04F5D5D6F8403123F40053C6F84031C3 -:101AE000C6F8B4A1D4F8B43113F00113FAD188B216 -:101AF00040F28165CBF80030A84200F29680B0F544 -:101B0000D06F80F0D881B0F5817F00F0578100F26E -:101B1000D580802800F02681822840F0C78092B2CC -:101B200002F07F01072900F2C18089009648974D95 -:101B30000844016810062B706B7040F1E581C90301 -:101B400001D501232B7002219048FFF74DFFD4F8F7 -:101B5000AC31002BB0D18A4AD2F8BC31002B44D131 -:101B600018F0400F18D0864BD3F8AC111A46C3F8C2 -:101B7000AC11D3F8BC11C3F8BC11D2F8B041804B02 -:101B8000002CFAD14FF0FF32C3F8B421D3F88431DE -:101B900000F074FB7E4B1C6018F0807F03D07D4BFF -:101BA0001B6803B1984718F0007F03D07A4B1B687D -:101BB00003B1984718F0040F02D0714BD3F8843169 -:101BC0006F4BD3F8482112060CD518F0800F09D0BE -:101BD00072490A78002A00F08E81531EDBB20B7026 -:101BE00003B9FBBE03B0BDE8F08F6D49C2F8BC314C -:101BF0000868034240F0CC81654A14681C40AFD0AD -:101C0000630700F1E281670300F1EF81260700F12D -:101C1000E881250300F1E181E00600F1DA81E102CB -:101C20009ED56048FFF7C8FE9AE742F22105A84218 -:101C300038D06FD8B0F5086F00F02F81B0F5106F75 -:101C400034D1C1F30741584A584811705849594A8C -:101C50000193C6F8C801C6F8CC11C6F8D02100F02F -:101C60000FFB554A019B80210120FB6410605160ED -:101C7000D6F8BC11936041F00111BA64C6F8BC11EA -:101C8000D6F8B02142F48032C6F8B021444A13603D -:101C9000D4F8B031002BFBD1D4F8AC31002B7FF459 -:101CA0000BAF58E7100C072800F03281C4F8C09140 -:101CB000D4F8AC31002B7FF4FFAE4CE740F2023594 -:101CC000A84200F0B780B0F5A06FEFD13A4A80206B -:101CD0000125FB6450601560D6F8BC01936040F0AC -:101CE0000113BA64C6F8BC31D6F8B03143F480337E -:101CF000C6F8B031D4F8B031002BFBD10B0C5B0629 -:101D000043F08073C6F85431D4F8AC31002B7FF423 -:101D1000D3AE20E742F22123984200F0828042F2C3 -:101D200021339842C2D1244B012180221960002125 -:101D30005A60D4F8BC21BB6442F001129960F96486 -:101D4000C4F8BC21D4F8B03143F48033C4F8B031C6 -:101D5000D4F8B031002BFBD1D4F8AC31002B7FF498 -:101D6000ABAEF8E6094A0221104613705370FFF734 -:101D70003BFED4F8AC31002B7FF49EAEEBE600BF07 -:101D800000002E4000300020C0012E402004002022 -:101D90000404002000040020080400201004002097 -:101DA0000C04002040320020300400200200CC004F -:101DB000C80002000200C8002020002092B202F0F9 -:101DC0007F03072B3FF672AF12F0800F4FEA8303B9 -:101DD000884A4FF001011A44136814BF23F480337A -:101DE00023F0010313608022834B196000215A60A5 -:101DF000D4F8BC21BB6442F001129960F964C4F8C4 -:101E0000BC21D4F8B03143F48033C4F8B031D4F8F5 -:101E1000B031002BFBD1D4F8AC31002B7FF44CAEA9 -:101E200099E6764BC1F30741754A1868754B106007 -:101E3000197078E792B202F07F03072B3FF636AFB6 -:101E400012F0800F4FEA83036A4A4FF001011A44EF -:101E5000136814BF43F4803343F0010313608022FE -:101E6000654B196000215A60D4F8BC21BB6442F074 -:101E700001129960F964C4F8BC21D4F8B03143F47C -:101E80008033C4F8B031D4F8B031002BFBD1D4F892 -:101E9000AC31002B7FF410AE5DE65B4A01215B4B59 -:101EA000127818461A70FFF79FFDD4F8AC31002B5A -:101EB0007FF402AE4FE6564B586800283FF4F6AE6A -:101EC000090C1FFA82FE04E00C33586800283FF426 -:101ED000EDAE1D888D42F7D15D887545F4D1090AB4 -:101EE000120C03290CBF01781989914228BF1146B1 -:101EF000FFF77AFD2BE6D3F8482122F08002C3F8E1 -:101F0000482103B0BDE8F08FCA077FF51CAE18E684 -:101F1000404D0120FB6029603F4B404918605960EB -:101F20003F49D6F8B0016A6001F5005E40F0010259 -:101F3000A1F5005001F58055BB609860A1F5805077 -:101F4000D860C6F8B02119615D61C3F818E0D4F813 -:101F5000B031002BFBD1284A012048F28001FB64FC -:101F600010604FF480305160D6F8BC11936041F09E -:101F70000113BA64C6F8BC31D6F8B0310343C6F8D1 -:101F8000B031CBF80000D4F8B031002BFBD1DEE546 -:101F9000204C42F22100002524880D6084427FF409 -:101FA0002BAE2049204C03C90D0C86282060A1804F -:101FB000A5717FF421AED2F8481150241B4841F09E -:101FC0008001C2F84811047016E61948FFF7F4FCC6 -:101FD00019E61848FFF7F0FC21E61748FFF7ECFC7C -:101FE0001AE61648FFF7E8FC13E61548FFF7E4FC8D -:101FF0000CE600BFC0012E4020200020A80D0020CC -:1020000088320020800D00203004002020040020B1 -:10201000800200201804002000200020800007001B -:1020200028240020280400208032002010040020F2 -:102030000031002000320020C0310020803100201B -:1020400040310020002AA0F102022DE9F04714BF20 -:1020500000274FF00057022A01D9BDE8F0874FEA68 -:10206000C01ADFF840900D4604460AEB090600212D -:1020700040229846304600F029FE012047EA0541FB -:102080004AF80910C6F83880B060B8F1000FE4D003 -:10209000034BA0401C6820431860BDE8F08700BFD8 -:1020A0000404002000300020002AA0F102022DE9E3 -:1020B000F04714BF00274FF00057022A01D9BDE8AE -:1020C000F0874022C501DFF844908846154400217E -:1020D00004461E4605EB090A504600F0F7FD0122B2 -:1020E00047EA084145F80910CAF83860CAF80820DC -:1020F000002EE4D004F11000034B82401868024324 -:102100001A60BDE8F08700BF040400200030002002 -:1021100012048160C36142F08002F0B44260012782 -:1021200001F5805601F5005501F5405401F5804256 -:102130000760C660056144618261F0BC704700BF02 -:10214000831E022B00D9704730B4064B00F11004F7 -:1021500001250A4603EBC01005FA04F130BCFFF775 -:10216000FDBB00BF40300020831E022B00D970470A -:1021700010B4054B01240A4604FA00F103EBC01029 -:102180005DF8044BFFF7EABB00300020124A134B06 -:10219000D2F8200220F07F40984210B584B002D8D7 -:1021A00000EB800040000E4C01A90A2200F0C8FBA1 -:1021B00001A90023204611F8012B01333AB10A2B63 -:1021C00020F8022FF7D11623237004B010BD5B0056 -:1021D000DBB2237004B010BD00441F407F9698000E -:1021E000A80300204368C269C3F30E43054930B415 -:1021F000C3F14003044C002521F8123024F812509A -:1022000030BC7047000C0020F80B0020F8B5154BCF -:102210001B783BB903F0FF04134B1B7813B1134D2C -:102220002A8802B9F8BD124F2346124EC2F58072B9 -:102230003978114806EB411600EB01213046FFF7D3 -:1022400067FF31460420FFF77BFF3B780133DBB2A9 -:10225000062B98BF3B704FF0000388BF3C702B806B -:10226000F8BD00BF340B002030040020800C00209B -:10227000350B0020A00C002034040020704700BF64 -:102280000021E0222048F8B50C46204E204D00F0F9 -:102290001DFD204F2146204B6022347028461F4EE2 -:1022A0001C8000F013FD23462246102102203C60D2 -:1022B000BC803460B480FFF7F7FE2246184B402103 -:1022C0000320FFF7BFFE2346224640210420FFF7EC -:1022D000EBFE2346402228461249FFF719FF294604 -:1022E0000320FFF741FF104B4A22104910480860B5 -:1022F000C3F88440C3F88020D3F8482142F08072AC -:10230000C3F84821F8BD00BFA00C0020350B002009 -:10231000200C0020000C0020800C0020F80B002076 -:10232000950B0000380B002000002E400004002018 -:10233000BD0B0000024A034B10881B88C01A70476F -:10234000000C0020F80B002010B4EFF3108272B6DE -:10235000437F33B9017F012908D0032910D001231D -:10236000437702B962B65DF8044B7047114C21689F -:10237000A1B1114943610B68086083615861EEE7C0 -:102380000E4C216881B10E4943610B68086083617E -:1023900058610C4B4FF080511960E0E7064B4161EA -:1023A000816120601860DAE7054B4161816120603E -:1023B0001860EEE7940D0020900D0020840D0020A1 -:1023C000880D002004ED00E010B4047F4160022C71 -:1023D000C26003D05DF8044BFFF7B6BF83685DF8B9 -:1023E000044B184770B5EFF3108172B60C4C23689C -:1023F0008BB10C4E00255A6922607AB1956101B902 -:1024000062B65D7718469B689847EFF3108172B605 -:102410002368002BEFD101B962B670BD3260EEE7E0 -:10242000840D0020880D0020FFF7DCBF184A30B46F -:102430001468002C28D0036821688B420FD2CB1A75 -:1024400000218460C1602360E0601060022330BC22 -:10245000037570470360144611688B4208D3A26865 -:102460005B1A002AF6D18260C4600360A060EDE7C9 -:10247000D568CB1A82600222C560E060C1688860BE -:102480002360027530BC70478460C4601060DDE773 -:102490008C0D0020F8B5224E34682CB32368002B35 -:1024A0003AD11D461F4F04E03468ECB12368002B7D -:1024B00032D1A36803B1DD602069336003682575FC -:1024C0001B68BB4221D1037F4560022BC46020D032 -:1024D000FFF73AFF6368002BE6D023602046FFF742 -:1024E000A5FF3468002CE1D1EFF3108372B60E4AD9 -:1024F00000211068116003B962B628B18468FFF743 -:1025000095FF20460028F9D1F8BD224600219847C2 -:10251000E0E783689847DDE7013B2360E4E700BF1D -:102520008C0D0020790D00009C0D0020044A054B05 -:102530001168054A1960136801331360FFF7AABFD9 -:10254000041000E0A40D0020A80D002070B5214C5F -:10255000237883B9204B01221B7822701BBB1F4BB1 -:102560001B78002B29D11E4B00211A68217012B153 -:10257000EFF3058202B170BDEFF3108072B61A68F6 -:10258000F2B1184C2178D9B90126556926701D6021 -:10259000D5B1A96100B962B6002593681046557798 -:1025A0009847257070BDFFF7C5FE0028D7D000F012 -:1025B0005FFB0A4B1B78002BD5D000F045FBD2E720 -:1025C0000028D8D162B670BD074B1D600028E3D14A -:1025D000E1E700BFA00D0020C8030020C80D0020C7 -:1025E000940D0020980D0020900D00208C4A8D4BFA -:1025F00090422DE9F0438C4D5C699969EF681DD9D3 -:102600008A4B984240F20181894B40F22766DFF8FD -:1026100060E20344874D1A0AAEFB0232D30903EB92 -:10262000830303EB830202F2E243B34228BF334643 -:10263000A3F54873A5FB0336F60804E07E4EB042CE -:1026400094BF06260E26774A07F01F0ED2F8803078 -:10265000B64543F0C003C2F880300AD2724B27F06F -:102660001F071A463743DF601368002BFCDA07F0B8 -:102670001F0E14F000732ED1704D714AD5F810C0A2 -:1026800015460CEA0202AA420ABF4FF0C0534FF4AB -:102690008052002284EA030515F0605F06D024F022 -:1026A000605403F060535F4D1C436C6181EA020388 -:1026B00013F4405F08D05B4B21F4405111431A469C -:1026C0009961936C1D07FCD444F00074554A546121 -:1026D000936C9906FCD401215A4D0A4601FB02F382 -:1026E00000FB03F3AB4209D8072A00F284800132D1 -:1026F00001FB02F300FB03F3AB42F5D95248534D03 -:102700001844A5FB0030030D6C2B79D8352B7ED8EF -:10271000DFF8608136234E48DFF820C14D4DDCF8EC -:102720000090B0FBF2F009EA05054545B0FBF1F079 -:102730000BD043F400534FF480586546CCF800802A -:10274000CCF800302B68002BFCDADFF8D8C0013A57 -:10275000DCF8103003F00703934207D002F00702C1 -:102760006546CCF81020AB6CDB03FCD40139890240 -:1027700084EA010313F4E05F0AD02A4B24F4E05406 -:1027800001F4E0511A460C435C61936C9907FCD448 -:10279000314B32490344DB09A1FB0331090B042906 -:1027A00028BF04214B1E1B0284EA030212F4407F5F -:1027B00006D024F4407403F440731A4A1C43546155 -:1027C000184B24F000741A465C61936C9B06FCD491 -:1027D000B0FBF1F1224A7645224B1060196008D215 -:1027E000114B27F01F071A463743DF601368002B91 -:1027F000FCDABDE8F083042980D8013101226DE7BD -:10280000DFF874806C23184886E712261BE7174808 -:10281000DFF8688000FB03F043EA08087CE700BFAC -:1028200000A4781F00C00F40000008400046C323EA -:1028300000BA3CDC1F85EB5100366E0100800D4074 -:1028400040300080FFB19F26808D5B00819F5E1627 -:1028500000B29F267F3001807FD1F0089F10E500F5 -:10286000C4030020C003002000643F4D001BB700DC -:1028700023B24C00362000806C20008000200080B5 -:10288000002852D02DE9F04F814683B0274C01201B -:10289000274D284E54E8003F2A68316844E8000379 -:1028A000002BF7D1244F4FF47A7E2448D7F800C08C -:1028B000BB4607F1C6470368C1EB0C0107F5DE17FD -:1028C00007F67F67A7FB03C3BA4601279B0CB1FB42 -:1028D000F3F30EFB023854E8003F2A68316844E8FD -:1028E0000073002BF7D1DBF800C04FF47A7E036849 -:1028F0000EFB02F2C1EB0C01AAFB033EC8EB020384 -:102900004FEA9E42B1FBF2F1CA18B2F57A7F07D3C3 -:10291000B9F1010908F57A78DDD103B0BDE8F08F8F -:102920000190FFF713FE0198D5E770478C32002025 -:10293000A80D0020A40D0020041000E0C403002016 -:10294000F0B44E1E0025374600E00135B0FBF2F32F -:1029500002FB130000F13704092800F13000E4B253 -:1029600098BFC4B2184607F8014F002BEDD14A19A1 -:1029700053704DB1013316F8014F1778E81A3770CC -:10298000834202F80149F5DB0846F0BC704700BFFE -:10299000A4484FF00F0CA44B826F42F47F02F0B5B5 -:1029A00082670025D0F880204FF470469F4C4FF48A -:1029B000604E29464FF4806714432A46C0F8804091 -:1029C000A3F88C6148F2B826A3F88EC1A3F8905101 -:1029D000B3F8880180B240F0F000A3F8880101EB61 -:1029E0004100914B0131002540011C46042903445C -:1029F000A3F804E0DF805A841A865A805A81DE8167 -:102A00005A82DA825A83DA83E9D1B4F888014FF026 -:102A10000F0C874B4FF4704680B229464FF4604745 -:102A20002A4640EA0C004FF4806EA4F88801B4F8FE -:102A3000880180B240F47060A4F88801A3F88C612A -:102A400048F2B826A3F88EC1A3F89051B3F88801D4 -:102A500080B240F0F000A3F8880101EB4100744B14 -:102A60000131002540011C46042903449F80A3F83E -:102A700006E05A841A865A805A81DE815A82DA82A6 -:102A80005A83DA83E9D1B4F888014FF00F0C694B0F -:102A90004FF4704680B229464FF460472A4640EA18 -:102AA0000C004FF4806EA4F88801B4F8880180B25D -:102AB00040F47060A4F88801A3F88C6148F2B8264D -:102AC000A3F88EC1A3F89051B3F8880180B240F00A -:102AD000F000A3F8880101EB4100564B01310025BD -:102AE00040011C46042903449F80A3F806E05A8451 -:102AF0001A865A805A81DE815A82DA825A83DA83B0 -:102B0000E9D1B4F888014FF00F0C4B4B4FF47047EC -:102B100080B229464FF460462A4640EA0C004FF442 -:102B2000806EA4F88801B4F8880180B240F4706027 -:102B3000A4F88801A3F88C7148F2B827A3F88EC1D5 -:102B4000A3F89051B3F8880180B240F0F000A3F8E8 -:102B5000880101EB4100384B013140011C4604293A -:102B600003449E80A3F806E05A841A865A805A814C -:102B7000DF815A82DA825A83DA83EAD1B4F8883163 -:102B80000F27002241F201069BB245F6C05E1146B6 -:102B900043F226053B43A4F88831B4F888319BB250 -:102BA00043F47063A4F888315001244B013203448C -:102BB000042A99815981DF819E82A3F806E0198059 -:102BC00019829D81F0D100220F2741F2010645F6BE -:102BD000C055114643F226045001194B01320344FB -:102BE000042A99815981DF819E82DD8019801982B2 -:102BF0009C81F1D100220F2741F2010645F6C05514 -:102C0000114643F2260450010E4B01320344042ABC -:102C100099815981DF819E82DD80198019829C8192 -:102C2000F1D1F0BD00C00F4000C03D40000003FCEA -:102C300000003E4000403E4000803E4000C01D403D -:102C400000001E4000401E4038B5074B1C784CB1B8 -:102C5000064D55F8043F002BFBD09847631E13F038 -:102C6000FF04F6D138BD00BFC80D0020A80D00201C -:102C7000014B00221A707047C803002070B50F4E38 -:102C80000F4D761BB61018BF002405D0013455F83F -:102C9000043B9847A642F9D10A4E0B4D761B00F033 -:102CA00063F8B61018BF002406D0013455F8043B71 -:102CB0009847A642F9D170BD70BD00BF48160060AC -:102CC000481600604C1600604816006070B4840717 -:102CD00046D0541E002A41D0CDB2034602E0621E07 -:102CE000E4B3144603F8015B9A07F8D1032C2ED9FC -:102CF000CDB245EA05250F2C45EA054519D903F162 -:102D000010022646103E0F2E42F8105C42F80C5C72 -:102D100042F8085C42F8045C02F11002F2D8A4F117 -:102D2000100222F00F0204F00F041032032C13449F -:102D30000DD91E462246043A032A46F8045BFAD807 -:102D4000221F22F003020432134404F003042CB1C6 -:102D5000C9B21C4403F8011BA342FBD170BC7047ED -:102D600014460346C2E700BF5FF800F0E1150060BB -:042D7000F8B500BFF3 -:102D740000000042C8801F40B8821F4008000000C5 -:102D840000000042C4801F40B4821F4004000000C1 -:102D940000C0004224801F4014821F401000000025 -:102DA40000C0004228801F4018821F4020000000FD -:102DB40000C000422C801F401C821F4040000000C5 -:102DC40000C0004234801F4024821F4000010000E4 -:102DD4000040004264811F4054831F4000040000EF -:102DE4000040004280811F4070831F4000000200A9 -:102DF400004000427C811F406C831F4000000100A2 -:102E04000040004268811F4058831F4000080000B2 -:102E1400004000423C811F402C831F400100000001 -:102E24000040004244811F4034831F4004000000DE -:102E34000040004240811F4030831F4002000000D8 -:102E44000040004248811F4038831F4008000000B2 -:102E54000000004204811F40F4821F40000004006F -:102E64000000004208811F40F8821F400000080053 -:102E74000000004218811F4008831F4000008000AA -:102E84000000004214811F4004831F4000004000E2 -:102E94000000004200811F40F0821F400000020039 -:102EA40000000042FC801F40EC821F400000010033 -:102EB4000000004224811F4014831F4000000004CE -:102EC4000000004228811F4018831F4000000008B2 -:102ED400000000421C811F400C831F4000000001C1 -:102EE4000000004220811F4010831F4000000002A8 -:102EF40000000042EC801F40DC821F4000100000F4 -:102F040000000042F0801F40E0821F4000200000CB -:102F14000000004234811F4024831F400000004011 -:102F24000000004238811F4028831F4000000080B9 -:102F34000080004294801F4084821F4000000400EF -:102F440000C0004290801F4080821F40000000802B -:102F540000800042A8801F4098821F40000080002B -:102F640000800042A4801F4094821F400000400063 -:102F7400004000426C811F405C831F400010000031 -:102F840000C0004230801F4020821F4080000000AB -:102F940000800042C8811F40B8831F4000800000A9 -:102FA40000800042C4811F40B4831F4000400000E1 -:102FB40000800042C0811F40B0831F4000200000F9 -:102FC40000800042BC811F40AC831F400010000001 -:102FD40000800042D0811F40C0831F4000000200D7 -:102FE40000800042CC811F40BC831F4000000100D0 -:102FF4000001000078030020120000000006000019 -:10300400100300200A000000000200003403002026 -:1030140043000000000700003403002043000000C8 -:1030240000030000A40300200000000001030904C1 -:103034001C03002000000000020309048C0300208C -:103044000000000003030904A8030020000000009E -:10305400000000000000000000000000010000006B -:10306400020000001700000012000000040000002D -:1030740016000000150000001300000014000000FA -:103084000A06000202000040010000001803540078 -:10309400650065006E0073007900640075006900C6 -:1030A4006E006F0009024300020100C032090400EF -:1030B40000010202010005240010010524010101A0 -:1030C40004240206052406000107058203100010EB -:1030D40009040100020A0000000705030240000081 -:1030E40007058402400000001201000202000040B3 -:1030F400C01683047902010203010000160355007F -:103104005300420020005300650072006900610012 -:103114006C000000040309040C030000000000001C -:10312400000000000000000000000000000000009B -:103134000029DE07007B9A17010000000000000050 -:040000056000100087 -:00000001FF diff --git a/tools/odrive/tests/encoder_test.py b/tools/odrive/tests/encoder_test.py index efde608b..016f39da 100644 --- a/tools/odrive/tests/encoder_test.py +++ b/tools/odrive/tests/encoder_test.py @@ -6,15 +6,59 @@ from math import pi import os from fibre.utils import Logger -from test_runner import EncoderTestContext, test_assert_eq, program_teensy +from test_runner import * + + +teensy_code_template = """ +void setup() { + pinMode({enc_a}, OUTPUT); + pinMode({enc_b}, OUTPUT); +} + +int cpr = 8192; +int rpm = 30; + +// the loop routine runs over and over again forever: +void loop() { + int microseconds_per_count = (1000000 * 60 / cpr / rpm); + + for (;;) { + digitalWrite({enc_a}, HIGH); + delayMicroseconds(microseconds_per_count); + digitalWrite({enc_b}, HIGH); + delayMicroseconds(microseconds_per_count); + digitalWrite({enc_a}, LOW); + delayMicroseconds(microseconds_per_count); + digitalWrite({enc_b}, LOW); + delayMicroseconds(microseconds_per_count); + } +} + +""" + def modpm(val, range): return ((val + (range / 2)) % range) - (range / 2) class TestIncrementalEncoder(): - def is_compatible(self, enc_ctx: EncoderTestContext): - return True + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + for encoder in odrive.encoders: + # Find the Teensy that is connected to the encoder pins and the corresponding Teensy GPIOs + + gpio_conns = [ + testrig.get_directly_connected_components(encoder.a), + testrig.get_directly_connected_components(encoder.b), + ] + + valid_combinations = [ + [combination[0].parent] + list(combination) + for combination in itertools.product(*gpio_conns) + if ((len(set(c.parent for c in combination)) == 1) and isinstance(combination[0].parent, TeensyComponent)) + ] + + yield (encoder, valid_combinations) def run_delta_test(self, encoder, true_cps, with_cpr): encoder.config.cpr = with_cpr @@ -45,17 +89,15 @@ class TestIncrementalEncoder(): time.sleep(0.01) - def run_test(self, enc_ctx: EncoderTestContext, logger: Logger): + def run_test(self, enc: EncoderComponent, teensy: TeensyComponent, teensy_gpio_a: int, teensy_gpio_b: int, logger: Logger): true_cps = 8192*-0.5 # counts per second generated by the virtual encoder - # TODO: read teensy config from YAML file - if enc_ctx.num == 0: - hexfile = 'enc0_sim_-4096cps.ino.hex' - else: - hexfile = 'enc1_sim_-4096cps.ino.hex' - program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) + + code = teensy_code_template.replace("{enc_a}", str(teensy_gpio_a.num)).replace("{enc_b}", str(teensy_gpio_b.num)) + teensy.compile_and_program(code) + time.sleep(1.0) # wait for PLLs to stabilize - encoder = enc_ctx.handle + encoder = enc.handle # The true encoder count and PLL output should be roughly the same. # At 8192 CPR and 0.5 RPM, the delta because of sequential reading is @@ -67,8 +109,8 @@ class TestIncrementalEncoder(): #encoder.config.cpr = configured_cpr #expected_delta = true_cps/1200 #for _ in range(1000): - # first = enc_ctx.handle.axis0.encoder.count_in_cpr - # second = enc_ctx.handle.axis0.encoder.pos_cpr + # first = enc.handle.axis0.encoder.count_in_cpr + # second = enc.handle.axis0.encoder.pos_cpr # test_assert_eq(modpm(second - first, configured_cpr), expected_delta, range=abs(true_cps/500)) # time.sleep(0.001) diff --git a/tools/odrive/tests/nvm_test.py b/tools/odrive/tests/nvm_test.py index 99da037f..72ed2061 100644 --- a/tools/odrive/tests/nvm_test.py +++ b/tools/odrive/tests/nvm_test.py @@ -7,17 +7,18 @@ import os import fibre from fibre.utils import Logger -from test_runner import ODriveTestContext, test_assert_eq +from test_runner import * class TestStoreAndReboot(): """ Stores the current configuration to NVM and reboots. """ - def is_compatible(self, odrive: ODriveTestContext): - return True + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + yield (odrive,) - def run_with_values(self, values, odrive: ODriveTestContext, logger: Logger): + def run_with_values(self, odrive: ODriveComponent, values: list, logger: Logger): logger.debug("storing configuration and rebooting...") for value in values: @@ -31,15 +32,15 @@ class TestStoreAndReboot(): odrive.handle = None time.sleep(2) - odrive.make_available(logger) + odrive.prepare(logger) logger.debug("verifying configuration after reboot...") test_assert_eq(odrive.handle.config.brake_resistance, values[-1], accuracy=0.01) - def run_test(self, odrive: ODriveTestContext, logger: Logger): - self.run_with_values([0.5, 1.0, 1.5], odrive, logger) - self.run_with_values([2.5, 3.7], odrive, logger) - self.run_with_values([0.47], odrive, logger) + def run_test(self, odrive: ODriveComponent, logger: Logger): + self.run_with_values(odrive, [0.5, 1.0, 1.5], logger) + self.run_with_values(odrive, [2.5, 3.7], logger) + self.run_with_values(odrive, [0.47], logger) if __name__ == '__main__': test_runner.run(TestStoreAndReboot()) diff --git a/tools/odrive/tests/pwm_input_test.py b/tools/odrive/tests/pwm_input_test.py index e70f8e1e..fc603eb3 100644 --- a/tools/odrive/tests/pwm_input_test.py +++ b/tools/odrive/tests/pwm_input_test.py @@ -5,13 +5,38 @@ import time import math import os -import fibre -from fibre.utils import Logger from odrive.enums import errors -from test_runner import ODriveTestContext, test_assert_eq, program_teensy +from test_runner import * + + +teensy_code_template = """ +float position = 0; // between 0 and 1 +float velocity = 1; // [position per second] + +void setup() { +{setup_code} +} + +// the loop routine runs over and over again forever: +void loop() { + int high_microseconds = 1000 + (int)(position * 1000.0f); + +{set_high_code} + delayMicroseconds(high_microseconds); +{set_low_code} + + // Wait for a total of 20ms. + // delayMicroseconds() only works well for values <= 16383 + delayMicroseconds(10000 - high_microseconds); + delayMicroseconds(10000); + + position += velocity * 0.02; + while (position > 1.0) + position -= 1.0; +} +""" + -#def modpm(val, lower_bound, upper_bound): -# return ((val - lower_bound) % (upper_bound - lower_bound)) - lower_bound def modpm(val, range): return ((val + (range / 2)) % range) - (range / 2) @@ -28,8 +53,24 @@ class TestPwmInput(): Note: this test is currently only written for ODrive 3.6 (or similar GPIO layout). """ - def is_compatible(self, odrive: ODriveTestContext): - return True + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + # Find the Teensy that is connected to gpios 1-4 of the ODrive and the corresponding Teensy GPIOs + + gpio_conns = [ + testrig.get_directly_connected_components(odrive.gpio1), + testrig.get_directly_connected_components(odrive.gpio2), + testrig.get_directly_connected_components(odrive.gpio3), + testrig.get_directly_connected_components(odrive.gpio4) + ] + + valid_combinations = [ + [combination[0].parent] + list(combination) + for combination in itertools.product(*gpio_conns) + if ((len(set(c.parent for c in combination)) == 1) and isinstance(combination[0].parent, TeensyComponent)) + ] + + yield (odrive, valid_combinations) def run_delta_test(self, attr, with_min, with_max, timeout = 5.0): rounds_per_s = 1.0 @@ -84,10 +125,16 @@ class TestPwmInput(): test_assert_eq(min_val, with_min, range = step_size) test_assert_eq(max_val, with_max, range = step_size) - def run_test(self, odrive: ODriveTestContext, logger: Logger): + def run_test(self, odrive: ODriveComponent, teensy: TeensyComponent, teensy_gpio1: int, teensy_gpio2: int, teensy_gpio3: int, teensy_gpio4: int, logger: Logger): # TODO: test each GPIO separately - hexfile = 'pwm_sim.ino.hex' - program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) + + setup_code = "\n".join(" pinMode(" + str(gpio.num) + ", OUTPUT);" for gpio in [teensy_gpio1, teensy_gpio2, teensy_gpio3, teensy_gpio4]) + set_high_code = "\n".join(" digitalWrite(" + str(gpio.num) + ", HIGH);" for gpio in [teensy_gpio1, teensy_gpio2, teensy_gpio3, teensy_gpio4]) + set_low_code = "\n".join(" digitalWrite(" + str(gpio.num) + ", LOW);" for gpio in [teensy_gpio1, teensy_gpio2, teensy_gpio3, teensy_gpio4]) + + code = teensy_code_template.replace("{setup_code}", setup_code).replace("{set_high_code}", set_high_code).replace("{set_low_code}", set_low_code) + teensy.compile_and_program(code) + time.sleep(1.0) # wait for PLLs to stabilize logger.debug("Set up PWM input...") @@ -106,15 +153,7 @@ class TestPwmInput(): odrive.handle.config.gpio4_pwm_mapping.min = -20000 odrive.handle.config.gpio4_pwm_mapping.max = 20000 - # Save and reboot - odrive.handle.save_configuration() - try: - odrive.handle.reboot() - except fibre.ChannelBrokenException: - pass # this is expected - odrive.handle = None - time.sleep(2) - odrive.make_available(logger) + odrive.save_config_and_reboot() logger.debug("Check if PWM on GPIO1 works...") self.run_delta_test(odrive.handle.axis0.controller._remote_attributes['input_pos'], -50, 200) diff --git a/tools/odrive/tests/pwm_sim.ino.hex b/tools/odrive/tests/pwm_sim.ino.hex deleted file mode 100644 index efd5bf65..00000000 --- a/tools/odrive/tests/pwm_sim.ino.hex +++ /dev/null @@ -1,801 +0,0 @@ -:0200000460009A -:100000004643464200000156000000000101020084 -:1000100000000000000000000000000000000000E0 -:1000200000000000000000000000000000000000D0 -:1000300000000000000000000000000000000000C0 -:1000400000000000010403000000000000000000A8 -:100050000000200000000000000000000000000080 -:100060000000000000000000000000000000000090 -:100070000000000000000000000000000000000080 -:10008000EB04180A063204260000000000000000FD -:10009000050404240000000000000000000000002F -:1000A0000000000000000000000000000000000050 -:1000B0000604000000000000000000000000000036 -:1000C0000000000000000000000000000000000030 -:1000D00020041808000000000000000000000000DC -:1000E0000000000000000000000000000000000010 -:1000F0000000000000000000000000000000000000 -:10010000D8041808000000000000000000000000F3 -:100110000204180804200000000000000000000095 -:1001200000000000000000000000000000000000CF -:10013000600400000000000000000000000000005B -:1001400000000000000000000000000000000000AF -:10015000000000000000000000000000000000009F -:10016000000000000000000000000000000000008F -:10017000000000000000000000000000000000007F -:10018000000000000000000000000000000000006F -:10019000000000000000000000000000000000005F -:1001A000000000000000000000000000000000004F -:1001B000000000000000000000000000000000003F -:1001C000000100000010000001000000000000001D -:1001D000000001000000000000000000000000001E -:1001E000000000000000000000000000000000000F -:1001F00000000000000000000000000000000000FF -:10020000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE -:10021000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEE -:10022000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDE -:10023000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCE -:10024000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBE -:10025000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAE -:10026000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E -:10027000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8E -:10028000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7E -:10029000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6E -:1002A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5E -:1002B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4E -:1002C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3E -:1002D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E -:1002E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1E -:1002F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0E -:10030000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD -:10031000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED -:10032000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD -:10033000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCD -:10034000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD -:10035000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAD -:10036000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D -:10037000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8D -:10038000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7D -:10039000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6D -:1003A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D -:1003B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4D -:1003C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3D -:1003D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2D -:1003E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1D -:1003F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0D -:10040000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC -:10041000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC -:10042000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC -:10043000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCC -:10044000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC -:10045000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAC -:10046000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9C -:10047000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8C -:10048000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7C -:10049000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C -:1004A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5C -:1004B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4C -:1004C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3C -:1004D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2C -:1004E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1C -:1004F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0C -:10050000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB -:10051000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEB -:10052000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB -:10053000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCB -:10054000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBB -:10055000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAB -:10056000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9B -:10057000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8B -:10058000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7B -:10059000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6B -:1005A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B -:1005B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4B -:1005C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3B -:1005D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2B -:1005E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1B -:1005F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0B -:10060000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA -:10061000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEA -:10062000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA -:10063000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCA -:10064000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA -:10065000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAA -:10066000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9A -:10067000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8A -:10068000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7A -:10069000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6A -:1006A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5A -:1006B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4A -:1006C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3A -:1006D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A -:1006E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1A -:1006F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A -:10070000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9 -:10071000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9 -:10072000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9 -:10073000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC9 -:10074000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB9 -:10075000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9 -:10076000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF99 -:10077000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF89 -:10078000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF79 -:10079000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF69 -:1007A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF59 -:1007B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF49 -:1007C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF39 -:1007D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF29 -:1007E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF19 -:1007F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF09 -:10080000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8 -:10081000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8 -:10082000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD8 -:10083000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC8 -:10084000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB8 -:10085000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA8 -:10086000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF98 -:10087000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF88 -:10088000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF78 -:10089000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF68 -:1008A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF58 -:1008B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF48 -:1008C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF38 -:1008D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF28 -:1008E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF18 -:1008F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF08 -:10090000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7 -:10091000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE7 -:10092000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD7 -:10093000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7 -:10094000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB7 -:10095000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA7 -:10096000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF97 -:10097000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF87 -:10098000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF77 -:10099000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF67 -:1009A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57 -:1009B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF47 -:1009C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF37 -:1009D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27 -:1009E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF17 -:1009F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF07 -:100A0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6 -:100A1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE6 -:100A2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD6 -:100A3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC6 -:100A4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB6 -:100A5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA6 -:100A6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF96 -:100A7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF86 -:100A8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF76 -:100A9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF66 -:100AA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 -:100AB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF46 -:100AC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF36 -:100AD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF26 -:100AE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16 -:100AF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF06 -:100B0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5 -:100B1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5 -:100B2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD5 -:100B3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC5 -:100B4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB5 -:100B5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA5 -:100B6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF95 -:100B7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF85 -:100B8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF75 -:100B9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF65 -:100BA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF55 -:100BB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF45 -:100BC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF35 -:100BD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF25 -:100BE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF15 -:100BF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF05 -:100C0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4 -:100C1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE4 -:100C2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD4 -:100C3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC4 -:100C4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB4 -:100C5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA4 -:100C6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF94 -:100C7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF84 -:100C8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF74 -:100C9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF64 -:100CA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF54 -:100CB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF44 -:100CC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF34 -:100CD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF24 -:100CE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF14 -:100CF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF04 -:100D0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3 -:100D1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE3 -:100D2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD3 -:100D3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC3 -:100D4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB3 -:100D5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA3 -:100D6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF93 -:100D7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83 -:100D8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF73 -:100D9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF63 -:100DA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF53 -:100DB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43 -:100DC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF33 -:100DD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF23 -:100DE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF13 -:100DF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF03 -:100E0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2 -:100E1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE2 -:100E2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD2 -:100E3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2 -:100E4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB2 -:100E5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA2 -:100E6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF92 -:100E7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF82 -:100E8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF72 -:100E9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF62 -:100EA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF52 -:100EB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF42 -:100EC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF32 -:100ED000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF22 -:100EE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF12 -:100EF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02 -:100F0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1 -:100F1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE1 -:100F2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD1 -:100F3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC1 -:100F4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB1 -:100F5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA1 -:100F6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF91 -:100F7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81 -:100F8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF71 -:100F9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF61 -:100FA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF51 -:100FB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF41 -:100FC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF31 -:100FD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF21 -:100FE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF11 -:100FF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF01 -:10100000D10020402C100060000000000000000013 -:1010100020100060001000600000000000000000D0 -:1010200000000060D031000000000000000001203E -:1010300035100060764B0720764C4FF42A01764A33 -:101040005C64186499639546744A75498A420FD066 -:10105000744B9A420CD2D4430846234423F0030332 -:1010600004330B4450F8041B984242F8041BF9D196 -:101070006D4A6E498A420FD06D4B9A420CD2D443CE -:101080000846234423F0030304330B4450F8041BA5 -:10109000984242F8041BF9D1664A674B9A420BD238 -:1010A000D04311460024034423F0030304331344C4 -:1010B00041F8044B8B42FBD1604A4FF47001604B06 -:1010C000116003F530715F4A43F8042F9942FBD158 -:1010D0005D4B80215D4A03F8011B9342FBD15C4CC0 -:1010E0004FF080375B4E4FF0FF32DFF8A8A10025AC -:1010F000DFF8A491DFF8A481574B4549C3F800A05D -:10110000C4F80471C4F80091C4F8F470C4F8F08015 -:10111000F36923F07F0343F04003F361736A23F024 -:101120007F0343F0400373628A66CA660A674A67B0 -:1011300000F0B6F8494A6320494B4A49106003223F -:101140001D60CAF8381043F8082C4749474A4848F8 -:10115000C3F8082D0B68474A43F08073CAF83C0077 -:1011600045480B601368454943F001031360036869 -:101170000B6000F0E5F8C4F804714148C4F8009130 -:10118000C4F8F470C4F8F08000F04AFA00BF00BF61 -:1011900000BF00BFF16E3B4A41F440513A4BF1664B -:1011A0001560C2F80851C2F81851C2F82851C2F8A7 -:1011B00038519A6BD20708D442F615623349596503 -:1011C0001A659A6B42F001029A632F4A304C936879 -:1011D00043F00113936000F01BFA2368132BFCD932 -:1011E00000F05CF900F0D0F900F00EFA00F0DAF847 -:1011F00000F002FA2368B3F5967FFBD300F018FAEB -:1012000000F0FEF900F01CFA00F016FAFAE700BF51 -:1012100000C00A40ABAAAAAA008007200000000074 -:1012200050160060C017000000000020142E00605F -:10123000C0030020C0030020C032002088ED00E081 -:10124000FC0F00208905000000E400E0A0E400E0BD -:1012500000800D4000C00F4008ED00E014E000E009 -:1012600018E000E001110000FCED00E0000020208B -:1012700005120000001000E0041000E0840D0020C2 -:101280000046C3230040084000400D400000C05607 -:10129000880D0020001000201B1018200C0D1113C9 -:1012A000F0B5194A0021194B4FF0100E18480124CF -:1012B000184E194D0160194FC2F800E01E6015600C -:1012C000174E184D1F601660174F1D60174E184DB2 -:1012D00017601E60174F1560174E184D1F6016607F -:1012E0001D600460BFF34F8FBFF36F8FC3F8B01161 -:1012F000BFF34F8FBFF36F8F52F8883C43F44033F6 -:1013000042F8883CF0BD00BF9CED00E0A0ED00E09D -:1013100094ED00E0250008031100200021000207E1 -:1013200012000020250008131300202027000B13B3 -:101330001400004033001013150000602F000B074D -:10134000F0B4174A40274FF480314FF480564FF4E1 -:1013500000554FF4404443F24200136913F0020F6A -:1013600006D0946151619061136913F0020FF8D1B6 -:1013700013F4005F01D15561EFE713F4805F01D1F1 -:101380005661EAE7002BE8DA13F4803F01D091615F -:10139000E3E75B0601D45761DFE7F0BC704700BFAD -:1013A00000800D40364A03203649F3EE096A13687F -:1013B00023F00103F0B51360C2F89000D1F8E030DB -:1013C000DFF8D4E0DCB2C3F30B202F4FC0EB135394 -:1013D00007EE904AA4F15501CEF80040B8EEE77A46 -:1013E00003EB830407EE900A03FB01F13B6003EB80 -:1013F0008313B8EEE75A07EE901A091B77EE666A78 -:10140000B8EE677A214D07EE901A0B44C5ED006ADD -:10141000F8EE677A1E4EC7EE265A1E4930601068F5 -:1014200087EEA66A07EE903AF8EE677A87EEA67A1C -:1014300075EE855A76EE056AFCEEE55AFCEEE66A34 -:1014400015EE903A40EA035316EE900A77EE057ACD -:10145000136001EA0041D2F81031FCEEE77A0B4349 -:10146000C2F8103117EE903AD2F81011C3F30B0303 -:101470000B43C2F81031136843F002031360F0BD50 -:1014800080810D4000441F40E8030020E403002059 -:10149000EC0300200000FF0FE0030020304B40F67B -:1014A000617270B5C3F8202140F2044500F0B4F831 -:1014B0002C492D48D1F880202C4C42F003022C4BB3 -:1014C000C1F88020C0F86051226813401BB9D0F8E1 -:1014D000A8319A071AD0244B4FF00041234A516398 -:1014E0001A46D3F8401141F00201C3F84011D2F876 -:1014F00040319B07FBD44FF400301E491B4B4FF08B -:101500000042086019209A6300F08EF81A4D0022FC -:10151000164B4FF08041144C0A26996328461A60F6 -:101520001146C4F8A8614FF4207200F061F84FF43E -:1015300081064FF4800040F24313104A10492E6098 -:101540002864C4F85851C2F80412C4F848310D4A4E -:101550004FF4003101231160C4F8403170BD00BF69 -:1015600000800D4000C00F4000002E4000900D4054 -:10157000001C1E008CE200E0003000200010002063 -:10158000650700000CE100E0114B1249D86E0A46D5 -:1015900040F4403030B4D86640F2B765D86EA0242D -:1015A00040F44070D8664D648C64936C1B06FCD488 -:1015B000094B40F2B760A021064A58649964936CC5 -:1015C00013F08003FBD1054A137030BC704700BF95 -:1015D00000C00F4000400C4000800C40810D0020F6 -:1015E000F8B500BFF8BC08BC9E467047FFFFFFFF80 -:1015F0005FF800F0111700005FF800F0A50500008B -:101600005FF800F0C90000005FF800F0D51300009B -:101610005FF800F0A10500005FF800F0650E000023 -:101620005FF800F0450200005FF800F0C51200000E -:101630005FF800F0C11600005FF800F0251200000E -:101640005FF800F0D100000045000000FFFFFFFF41 -:10165000000000000000000000000000000000008A -:10166000000000000000000000000000000000007A -:1016700010B5054C237833B9044B13B10448AFF3CC -:1016800000800123237010BDC00300200000000073 -:10169000C4170000084B10B51BB108490848AFF348 -:1016A00000800848036803B910BD074B002BFBD02E -:1016B000BDE81040184700BF00000000C403002030 -:1016C000C4170000C00300200000000008B50D4B47 -:1016D0000121187800F040FA0B4B0121187800F036 -:1016E0003BFA0A4B0121187800F036FA084B012129 -:1016F000187800F031FA074B01211878BDE808404E -:1017000000F02ABAEC020020F0020020F4020020CF -:10171000F8020020FC020020FFF7D8BF0000000004 -:101720002DE9F04F504D83B09FED507A0121D5ED5A -:10173000007ADFF850B167EE877ADFF84CA19BF8AA -:101740000000DFF84891FDEEE77ADFF84481484F6A -:10175000484E17EE904A00F0F9F99AF8000001217E -:1017600000F0F4F999F80000012104F57A7400F012 -:10177000EDF998F80000012100F0E8F9387801212E -:1017800000F0E4F93C483D49D0F800E03368A1FBA3 -:101790000331890C04FB01F10368374ACEEB0303E4 -:1017A0009942F9D89BF800000021019200F0CEF98F -:1017B0009AF80000002100F0C9F999F80000002112 -:1017C00000F0C4F998F80000002100F0BFF9387863 -:1017D000002100F0BBF9019A284942F21070176805 -:1017E0003368001BA1FB03139B0C03FB00F0136881 -:1017F0002149DB1B9842FAD8086842F2107332681C -:101800001E4CA4FB0242920C03FB02F20B681B1A53 -:101810009A42FBD81A4BF7EE006A95ED007A93EDE9 -:10182000005AB7EEC77AB7EEC55A9FED0D4BA5EE3D -:10183000047BB7EEC77BB4EEE67A85ED007AF1EE75 -:1018400010FA0ADDF0EE667A37EE677AB4EEE77AE0 -:10185000F1EE10FAF8DC85ED007A03B0BDE8F08F08 -:101860007B14AE47E17A943FDC03002000007A4409 -:10187000FC02002008030020041000E083DE1B436C -:1018800000030020EC020020F0020020F4020020FF -:10189000F80200208C4A8D4B90422DE9F0438C4D8C -:1018A0005C699969EF681DD98A4B984240F20181C1 -:1018B000894B40F22766DFF860E20344874D1A0A3D -:1018C000AEFB0232D30903EB830303EB830202F284 -:1018D000E243B34228BF3346A3F54873A5FB033662 -:1018E000F60804E07E4EB04294BF06260E26774AE4 -:1018F00007F01F0ED2F88030B64543F0C003C2F89F -:1019000080300AD2724B27F01F071A463743DF6038 -:101910001368002BFCDA07F01F0E14F000732ED1B1 -:10192000704D714AD5F810C015460CEA0202AA4261 -:101930000ABF4FF0C0534FF48052002284EA0305DF -:1019400015F0605F06D024F0605403F060535F4DE3 -:101950001C436C6181EA020313F4405F08D05B4BC7 -:1019600021F4405111431A469961936C1D07FCD430 -:1019700044F00074554A5461936C9906FCD40121DB -:101980005A4D0A4601FB02F300FB03F3AB4209D8B0 -:10199000072A00F28480013201FB02F300FB03F30B -:1019A000AB42F5D95248534D1844A5FB0030030D06 -:1019B0006C2B79D8352B7ED8DFF8608136234E48E2 -:1019C000DFF820C14D4DDCF80090B0FBF2F009EAE1 -:1019D00005054545B0FBF1F00BD043F400534FF43F -:1019E00080586546CCF80080CCF800302B68002B7E -:1019F000FCDADFF8D8C0013ADCF8103003F0070356 -:101A0000934207D002F007026546CCF81020AB6C79 -:101A1000DB03FCD40139890284EA010313F4E05F9B -:101A20000AD02A4B24F4E05401F4E0511A460C4346 -:101A30005C61936C9907FCD4314B32490344DB0958 -:101A4000A1FB0331090B042928BF04214B1E1B02F3 -:101A500084EA030212F4407F06D024F4407403F4B5 -:101A600040731A4A1C435461184B24F000741A4600 -:101A70005C61936C9B06FCD4B0FBF1F1224A764585 -:101A8000224B1060196008D2114B27F01F071A462D -:101A90003743DF601368002BFCDABDE8F0830429CC -:101AA00080D8013101226DE7DFF874806C2318487B -:101AB00086E712261BE71748DFF8688000FB03F073 -:101AC00043EA08087CE700BF00A4781F00C00F406D -:101AD000000008400046C32300BA3CDC1F85EB51E0 -:101AE00000366E0100800D4040300080FFB19F261F -:101AF000808D5B00819F5E1600B29F267F30018043 -:101B00007FD1F0089F10E5000803002004030020A7 -:101B100000643F4D001BB70023B24C00362000800C -:101B20006C200080002000800001074B1A181B5811 -:101B3000D2685868104202D011B9C3F888207047A3 -:101B4000C3F88420704700BF00000020272801D878 -:101B5000FFF7EABF704700BF27281CD800011A4AC8 -:101B6000012902EB0003105810B415D0042913D03A -:101B7000DC68426822EA040242609A68E9B10229FC -:101B80001ED003295B685DF8044B0CBF0F49104958 -:101B9000116015221A607047DA680129446842EA28 -:101BA000040242609A6808D040F6380111605B6810 -:101BB00015225DF8044B1A60704738211160F6E772 -:101BC00004491160F3E700BF000000203830010035 -:101BD0003800010038F0010004207146084203D0AB -:101BE000EFF3098000F008B8EFF3088000F004B8C4 -:101BF000704700BF704700BF1B4B05211B4A3820B0 -:101C000030B5C2F848110821C2F8380383B05A68C9 -:101C1000174C0A4317485A60C3F88410C3F8881059 -:101C20002368834202D91448FFF734FE0E4D08247E -:101C30000020124A1249C5F884400190019B93424A -:101C400005D8019B01330193019B9342F9D9C5F853 -:101C500088400190019B8B42EDD8019B0133019399 -:101C6000019B8B42F9D9E6E700C01B4000801F4072 -:101C700008030020FF45C32300A3E1113F420F00EA -:101C80003F548900836B30B41BB1536843F4004365 -:101C9000536072B6446B9CB1104B2260D3F8B041D4 -:101CA0000C4217D1D3F8404144F48044C3F840417A -:101CB000D3F8B851D3F840416404F3D5294209D18F -:101CC0000023064C8260C360D4F8B0311943C4F8D5 -:101CD000B0110263426362B630BC704700002E4010 -:101CE00038B50546036B06E0AA6B1C6890476A6B23 -:101CF0002346944208D0184633B1012B04D05A68C9 -:101D00001206F1D52B6338BD00232B636B6338BDFE -:101D1000F0B5F1B9224C23490020234B0122802544 -:101D2000A1600A601A464D60E060D3F8BC4188604B -:101D300044F001141D4DC3F8BC41D3F8B01141F07B -:101D40000101C3F8B0112860D2F8B031002BFBD1EB -:101D5000F0BD0904164B144D0126114C41F08001D1 -:101D600000221E60596000F5805CE26400F5005EB0 -:101D7000A36400F54057D5F8B01100F580462A4617 -:101D8000986041F48031C3F80CC0C3F810E05F6183 -:101D90009E61C5F8B011D2F8B031002BFBD1BAE783 -:101DA000003000202020002000002E40FC030020F6 -:101DB000002000202DE9F04FBB4C83B0D4F84481C3 -:101DC00018F0010FC4F844815ED0D4F8AC31002B78 -:101DD00055D04FF00119DFF8F0B22646B34FCA468E -:101DE000B96AFA6AC4F8AC31D4F8403143F400530C -:101DF000C4F84031D4F840319D04F5D5D6F84031CF -:101E000023F40053C6F84031C6F8B4A1D4F8B43175 -:101E100013F00113FAD188B240F28165CBF800309B -:101E2000A84200F29680B0F5D06F80F0D881B0F56E -:101E3000817F00F0578100F2D580802800F0268154 -:101E4000822840F0C78092B202F07F01072900F299 -:101E5000C18089009648974D0844016810062B7090 -:101E60006B7040F1E581C90301D501232B7002217C -:101E70009048FFF74DFFD4F8AC31002BB0D18A4A1F -:101E8000D2F8BC31002B44D118F0400F18D0864B4B -:101E9000D3F8AC111A46C3F8AC11D3F8BC11C3F88F -:101EA000BC11D2F8B041804B002CFAD14FF0FF3278 -:101EB000C3F8B421D3F8843100F074FB7E4B1C606E -:101EC00018F0807F03D07D4B1B6803B1984718F052 -:101ED000007F03D07A4B1B6803B1984718F0040FBA -:101EE00002D0714BD3F884316F4BD3F848211206DE -:101EF0000CD518F0800F09D072490A78002A00F03A -:101F00008E81531EDBB20B7003B9FBBE03B0BDE87C -:101F1000F08F6D49C2F8BC310868034240F0CC81B3 -:101F2000654A14681C40AFD0630700F1E281670383 -:101F300000F1EF81260700F1E881250300F1E1813E -:101F4000E00600F1DA81E1029ED56048FFF7C8FEA5 -:101F50009AE742F22105A84238D06FD8B0F5086F51 -:101F600000F02F81B0F5106F34D1C1F30741584A0A -:101F7000584811705849594A0193C6F8C801C6F823 -:101F8000CC11C6F8D02100F00FFB554A019B8021EF -:101F90000120FB6410605160D6F8BC11936041F0E1 -:101FA0000111BA64C6F8BC11D6F8B02142F48032EF -:101FB000C6F8B021444A1360D4F8B031002BFBD1ED -:101FC000D4F8AC31002B7FF40BAF58E7100C072886 -:101FD00000F03281C4F8C091D4F8AC31002B7FF40A -:101FE000FFAE4CE740F20235A84200F0B780B0F5F2 -:101FF000A06FEFD13A4A80200125FB645060156044 -:10200000D6F8BC01936040F00113BA64C6F8BC3145 -:10201000D6F8B03143F48033C6F8B031D4F8B031DB -:10202000002BFBD10B0C5B0643F08073C6F85431D8 -:10203000D4F8AC31002B7FF4D3AE20E742F2212359 -:10204000984200F0828042F221339842C2D1244B60 -:1020500001218022196000215A60D4F8BC21BB64A0 -:1020600042F001129960F964C4F8BC21D4F8B0318F -:1020700043F48033C4F8B031D4F8B031002BFBD135 -:10208000D4F8AC31002B7FF4ABAEF8E6094A02215C -:10209000104613705370FFF73BFED4F8AC31002BA1 -:1020A0007FF49EAEEBE600BF00002E400030002023 -:1020B000C0012E4010040020F4030020F003002093 -:1020C000F803002000040020FC0300204032002020 -:1020D000200400200200CC00C80002000200C8005A -:1020E0002020002092B202F07F03072B3FF672AF50 -:1020F00012F0800F4FEA8303884A4FF001011A441F -:10210000136814BF23F4803323F00103136080228B -:10211000834B196000215A60D4F8BC21BB6442F0A3 -:1021200001129960F964C4F8BC21D4F8B03143F4C9 -:102130008033C4F8B031D4F8B031002BFBD1D4F8DF -:10214000AC31002B7FF44CAE99E6764BC1F30741DE -:10215000754A1868754B1060197078E792B202F0F2 -:102160007F03072B3FF636AF12F0800F4FEA830351 -:102170006A4A4FF001011A44136814BF43F48033D4 -:1021800043F0010313608022654B196000215A60FF -:10219000D4F8BC21BB6442F001129960F964C4F820 -:1021A000BC21D4F8B03143F48033C4F8B031D4F852 -:1021B000B031002BFBD1D4F8AC31002B7FF410AE42 -:1021C0005DE65B4A01215B4B127818461A70FFF7F7 -:1021D0009FFDD4F8AC31002B7FF402AE4FE6564B96 -:1021E000586800283FF4F6AE090C1FFA82FE04E09E -:1021F0000C33586800283FF4EDAE1D888D42F7D1AE -:102200005D887545F4D1090A120C03290CBF0178C9 -:102210001989914228BF1146FFF77AFD2BE6D3F8C2 -:10222000482122F08002C3F8482103B0BDE8F08FB6 -:10223000CA077FF51CAE18E6404D0120FB602960FF -:102240003F4B4049186059603F49D6F8B0016A6079 -:1022500001F5005E40F00102A1F5005001F5805546 -:10226000BB609860A1F58050D860C6F8B0211961B4 -:102270005D61C3F818E0D4F8B031002BFBD1284AD7 -:10228000012048F28001FB6410604FF480305160FF -:10229000D6F8BC11936041F00113BA64C6F8BC31A2 -:1022A000D6F8B0310343C6F8B031CBF80000D4F80B -:1022B000B031002BFBD1DEE5204C42F2210000259D -:1022C00024880D6084427FF42BAE2049204C03C942 -:1022D0000D0C86282060A180A5717FF421AED2F874 -:1022E000481150241B4841F08001C2F84811047085 -:1022F00016E61948FFF7F4FC19E61848FFF7F0FC5A -:1023000021E61748FFF7ECFC1AE61648FFF7E8FC51 -:1023100013E61548FFF7E4FC0CE600BFC0012E40B1 -:1023200020200020880D002088320020600D002031 -:102330002004002010040020800200200804002057 -:10234000002000208000070018240020180400202E -:1023500080320020000400200031002000320020E4 -:10236000C03100208031002040310020002AA0F13F -:1023700002022DE9F04714BF00274FF00057022A50 -:1023800001D9BDE8F0874FEAC01ADFF840900D464A -:1023900004460AEB0906002140229846304600F028 -:1023A000DFFC012047EA05414AF80910C6F83880E9 -:1023B000B060B8F1000FE4D0034BA0401C6820438C -:1023C0001860BDE8F08700BFF40300200030002053 -:1023D000002AA0F102022DE9F04714BF00274FF0B8 -:1023E0000057022A01D9BDE8F0874022C501DFF875 -:1023F000449088461544002104461E4605EB090A10 -:10240000504600F0ADFC012247EA084145F80910AA -:10241000CAF83860CAF80820002EE4D004F1100091 -:10242000034B8240186802431A60BDE8F08700BF82 -:10243000F40300200030002012048160C36142F0E8 -:102440008002F0B44260012701F5805601F5005585 -:1024500001F5405401F580420760C66005614461A2 -:102460008261F0BC704700BF831E022B00D9704709 -:1024700030B4064B00F1100401250A4603EBC010EE -:1024800005FA04F130BCFFF7FDBB00BF403000206F -:10249000831E022B00D9704710B4054B01240A4655 -:1024A00004FA00F103EBC0105DF8044BFFF7EABB40 -:1024B00000300020124A134BD2F8200220F07F4057 -:1024C000984210B584B002D800EB800040000E4C5A -:1024D00001A90A2200F07EFA01A90023204611F882 -:1024E000012B01333AB10A2B20F8022FF7D1162322 -:1024F000237004B010BD5B00DBB2237004B010BDCC -:1025000000441F407F969800A40300204368C269DE -:10251000C3F30E43054930B4C3F14003044C002516 -:1025200021F8123024F8125030BC7047F00B002014 -:10253000E80B0020F8B5154B1B783BB903F0FF04FE -:10254000134B1B7813B1134D2A8802B9F8BD124FF3 -:102550002346124EC2F580723978114806EB4116B7 -:1025600000EB01213046FFF767FF31460420FFF7FB -:102570007BFF3B780133DBB2062B98BF3B704FF0FB -:10258000000388BF3C702B80F8BD00BF240B0020E7 -:1025900020040020600C0020250B0020800C00206F -:1025A00024040020704700BF0021E0222048F8B535 -:1025B0000C46204E204D00F0D3FB204F2146204BEF -:1025C0006022347028461F4E1C8000F0C9FB234651 -:1025D0002246102102203C60BC803460B480FFF7AA -:1025E000F7FE2246184B40210320FFF7BFFE23468B -:1025F000224640210420FFF7EBFE234640222846D6 -:102600001249FFF719FF29460320FFF741FF104B3E -:102610004A22104910480860C3F88440C3F880205B -:10262000D3F8482142F08072C3F84821F8BD00BFBA -:10263000800C0020250B0020000C0020F00B002057 -:10264000600C0020E80B0020BD0E0000280B0020CD -:1026500000002E40F0030020E50E0000024A034B6C -:1026600010881B88C01A7047F00B0020E80B002070 -:1026700010B4EFF3108272B6437F33B9017F0129A2 -:1026800008D0032910D00123437702B962B65DF860 -:10269000044B7047114C2168A1B1114943610B688B -:1026A000086083615861EEE70E4C216881B10E49E4 -:1026B00043610B680860836158610C4B4FF0805197 -:1026C0001960E0E7064B4161816120601860DAE73C -:1026D000054B4161816120601860EEE7740D0020B8 -:1026E000700D0020640D0020680D002004ED00E056 -:1026F00010B4047F4160022CC26003D05DF8044B2B -:10270000FFF7B6BF83685DF8044B184770B5EFF369 -:10271000108172B60C4C23688BB10C4E00255A699F -:1027200022607AB1956101B962B65D7718469B68FF -:102730009847EFF3108172B62368002BEFD101B9EF -:1027400062B670BD3260EEE7640D0020680D0020B7 -:10275000FFF7DCBF184A30B41468002C28D0036897 -:1027600021688B420FD2CB1A00218460C1602360A4 -:10277000E0601060022330BC0375704703601446AC -:1027800011688B4208D3A2685B1A002AF6D18260D6 -:10279000C4600360A060EDE7D568CB1A82600222B6 -:1027A000C560E060C16888602360027530BC704716 -:1027B0008460C4601060DDE76C0D0020F8B5224E27 -:1027C00034682CB32368002B3AD11D461F4F04E018 -:1027D0003468ECB12368002B32D1A36803B1DD600B -:1027E00020693360036825751B68BB4221D1037FD4 -:1027F0004560022BC46020D0FFF73AFF6368002BCE -:10280000E6D023602046FFF7A5FF3468002CE1D115 -:10281000EFF3108372B60E4A00211068116003B9FD -:1028200062B628B18468FFF795FF20460028F9D1E9 -:10283000F8BD224600219847E0E783689847DDE726 -:10284000013B2360E4E700BF6C0D0020A1100000F5 -:102850007C0D0020044A054B1168054A1960136875 -:1028600001331360FFF7AABF041000E0840D0020BD -:10287000880D002070B5214C237883B9204B0122AC -:102880001B7822701BBB1F4B1B78002B29D11E4BC2 -:1028900000211A68217012B1EFF3058202B170BDF8 -:1028A000EFF3108072B61A68F2B1184C2178D9B9DA -:1028B0000126556926701D60D5B1A96100B962B6BF -:1028C00000259368104655779847257070BDFFF72F -:1028D000C5FE0028D7D000F015FA0A4B1B78002B54 -:1028E000D5D000F0FBF9D2E70028D8D162B670BD90 -:1028F000074B1D600028E3D1E1E700BF800D0020F9 -:10290000BA030020A80D0020740D0020780D0020CF -:10291000700D0020002852D02DE9F04F814683B081 -:10292000274C0120274D284E54E8003F2A68316883 -:1029300044E80003002BF7D1244F4FF47A7E24485B -:10294000D7F800C0BB4607F1C6470368C1EB0C01CE -:1029500007F5DE1707F67F67A7FB03C3BA46012713 -:102960009B0CB1FBF3F30EFB023854E8003F2A68DE -:10297000316844E80073002BF7D1DBF800C04FF456 -:102980007A7E03680EFB02F2C1EB0C01AAFB033E48 -:10299000C8EB02034FEA9E42B1FBF2F1CA18B2F54E -:1029A0007A7F07D3B9F1010908F57A78DDD103B050 -:1029B000BDE8F08F0190FFF75DFF0198D5E7704704 -:1029C0008C320020880D0020840D0020041000E0CF -:1029D00008030020F0B44E1E0025374600E0013504 -:1029E000B0FBF2F302FB130000F13704092800F1F9 -:1029F0003000E4B298BFC4B2184607F8014F002B6C -:102A0000EDD14A1953704DB1013316F8014F1778C3 -:102A1000E81A3770834202F80149F5DB0846F0BC3A -:102A2000704700BFA4484FF00F0CA44B826F42F4D4 -:102A30007F02F0B582670025D0F880204FF4704601 -:102A40009F4C4FF4604E29464FF4806714432A464A -:102A5000C0F88040A3F88C6148F2B826A3F88EC174 -:102A6000A3F89051B3F8880180B240F0F000A3F8C9 -:102A7000880101EB4100914B0131002540011C46CA -:102A800004290344A3F804E0DF805A841A865A809C -:102A90005A81DE815A82DA825A83DA83E9D1B4F824 -:102AA00088014FF00F0C874B4FF4704680B22946D7 -:102AB0004FF460472A4640EA0C004FF4806EA4F8B9 -:102AC0008801B4F8880180B240F47060A4F88801ED -:102AD000A3F88C6148F2B826A3F88EC1A3F89051F0 -:102AE000B3F8880180B240F0F000A3F8880101EB50 -:102AF0004100744B0131002540011C460429034468 -:102B00009F80A3F806E05A841A865A805A81DE8193 -:102B10005A82DA825A83DA83E9D1B4F888014FF015 -:102B20000F0C694B4FF4704680B229464FF4604752 -:102B30002A4640EA0C004FF4806EA4F88801B4F8ED -:102B4000880180B240F47060A4F88801A3F88C6119 -:102B500048F2B826A3F88EC1A3F89051B3F88801C3 -:102B600080B240F0F000A3F8880101EB4100564B21 -:102B70000131002540011C46042903449F80A3F82D -:102B800006E05A841A865A805A81DE815A82DA8295 -:102B90005A83DA83E9D1B4F888014FF00F0C4B4B1C -:102BA0004FF4704780B229464FF460462A4640EA07 -:102BB0000C004FF4806EA4F88801B4F8880180B24C -:102BC00040F47060A4F88801A3F88C7148F2B8272B -:102BD000A3F88EC1A3F89051B3F8880180B240F0F9 -:102BE000F000A3F8880101EB4100384B01314001AE -:102BF0001C46042903449E80A3F806E05A841A86E2 -:102C00005A805A81DF815A82DA825A83DA83EAD182 -:102C1000B4F888310F27002241F201069BB245F635 -:102C2000C05E114643F226053B43A4F88831B4F850 -:102C300088319BB243F47063A4F888315001244B6F -:102C400001320344042A99815981DF819E82A3F8CD -:102C500006E0198019829D81F0D100220F2741F2F0 -:102C6000010645F6C055114643F226045001194BA2 -:102C700001320344042A99815981DF819E82DD80DB -:102C8000198019829C81F1D100220F2741F201069F -:102C900045F6C055114643F2260450010E4B013251 -:102CA0000344042A99815981DF819E82DD80198045 -:102CB00019829C81F1D1F0BD00C00F4000C03D40A1 -:102CC000000003FC00003E4000403E4000803E40CB -:102CD00000C01D4000001E4000401E4038B5074B9C -:102CE0001C784CB1064D55F8043F002BFBD098479B -:102CF000631E13F0FF04F6D138BD00BFA80D0020FD -:102D0000880D0020014B00221A707047BA03002082 -:102D100070B50F4E0F4D761BB61018BF002405D0AE -:102D2000013455F8043B9847A642F9D10A4E0B4DA1 -:102D3000761B00F065F8B61018BF002406D00134E9 -:102D400055F8043B9847A642F9D170BD70BD00BF4D -:102D500048160060481600604C1600604816006077 -:102D600070B4840746D0541E002A41D0CDB2034629 -:102D700002E0621EE4B3144603F8015B9A07F8D13F -:102D8000032C2ED9CDB245EA05250F2C45EA054581 -:102D900019D903F110022646103E0F2E42F8105C9E -:102DA00042F80C5C42F8085C42F8045C02F1100244 -:102DB000F2D8A4F1100222F00F0204F00F04103236 -:102DC000032C13440DD91E462246043A032A46F822 -:102DD000045BFAD8221F22F003020432134404F0E9 -:102DE00003042CB1C9B21C4403F8011BA342FBD15C -:102DF00070BC704714460346C2E700BF00000000E5 -:102E00005FF800F0E1150060000000000000000025 -:042E1000F8B500BF52 -:102E140000000042C8801F40B8821F400800000024 -:102E240000000042C4801F40B4821F400400000020 -:102E340000C0004224801F4014821F401000000084 -:102E440000C0004228801F4018821F40200000005C -:102E540000C000422C801F401C821F404000000024 -:102E640000C0004234801F4024821F400001000043 -:102E74000040004264811F4054831F40000400004E -:102E84000040004280811F4070831F400000020008 -:102E9400004000427C811F406C831F400000010001 -:102EA4000040004268811F4058831F400008000012 -:102EB400004000423C811F402C831F400100000061 -:102EC4000040004244811F4034831F40040000003E -:102ED4000040004240811F4030831F400200000038 -:102EE4000040004248811F4038831F400800000012 -:102EF4000000004204811F40F4821F4000000400CF -:102F04000000004208811F40F8821F4000000800B2 -:102F14000000004218811F4008831F400000800009 -:102F24000000004214811F4004831F400000400041 -:102F34000000004200811F40F0821F400000020098 -:102F440000000042FC801F40EC821F400000010092 -:102F54000000004224811F4014831F40000000042D -:102F64000000004228811F4018831F400000000811 -:102F7400000000421C811F400C831F400000000120 -:102F84000000004220811F4010831F400000000207 -:102F940000000042EC801F40DC821F400010000053 -:102FA40000000042F0801F40E0821F40002000002B -:102FB4000000004234811F4024831F400000004071 -:102FC4000000004238811F4028831F400000008019 -:102FD4000080004294801F4084821F40000004004F -:102FE40000C0004290801F4080821F40000000808B -:102FF40000800042A8801F4098821F40000080008B -:1030040000800042A4801F4094821F4000004000C2 -:10301400004000426C811F405C831F400010000090 -:1030240000C0004230801F4020821F40800000000A -:1030340000800042C8811F40B8831F400080000008 -:1030440000800042C4811F40B4831F400040000040 -:1030540000800042C0811F40B0831F400020000058 -:1030640000800042BC811F40AC831F400010000060 -:1030740000800042D0811F40C0831F400000020036 -:1030840000800042CC811F40BC831F40000001002F -:10309400000100007403002012000000000600007C -:1030A4000C0300200A00000000020000300300208E -:1030B400430000000007000030030020430000002C -:1030C40000030000A0030020000000000103090425 -:1030D40018030020000000000203090488030020F4 -:1030E4000000000003030904A40300200000000002 -:1030F4000000000000000000000000000D000000BF -:103104000E0000000F00000010000000110000007D -:103114000000803F0029DE07007B9A170A060002A0 -:10312400020000400100000018035400650065001F -:103134006E00730079006400750069006E006F0012 -:1031440009024300020100C0320904000001020226 -:1031540001000524001001052401010104240206D4 -:10316400052406000107058203100010090401006C -:10317400020A00000007050302400000070584025C -:10318400400000001201000202000040C016830447 -:1031940079020102030100001603550053004200A6 -:1031A4002000530065007200690061006C0000009B -:1031B400040309040C0300000000000000000000E8 -:1031C40000000000000000000000010000000000FA -:040000056000100087 -:00000001FF diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index cab97478..ecd56e9d 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -6,12 +6,16 @@ sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) import stat import odrive +import fibre from fibre import Logger, Event import argparse import yaml from inspect import signature import itertools import time +import tempfile +import io +from typing import Union, Tuple # Assert utils ----------------------------------------------------------------# @@ -39,20 +43,51 @@ def test_assert_eq(observed, expected, range=None, accuracy=None): raise TestFailed("value mismatch: expected {} but observed {}".format(expected, observed)) +# Other utils -----------------------------------------------------------------# + +def disjoint_sets(list_of_sets: list): + while len(list_of_sets): + current_set, list_of_sets = list_of_sets[0], list_of_sets[1:] + did_update = True + while did_update: + did_update = False + for i, s in enumerate(list_of_sets): + if len(current_set.intersection(s)): + current_set = current_set.union(s) + list_of_sets = list_of_sets[:i] + list_of_sets[(i+1):] + did_update = True + yield current_set + +def is_list_like(arg): + return hasattr(arg, '__iter__') and not isinstance(arg, str) + # Test Components -------------------------------------------------------------# -class ODriveTestContext(): +class Component(object): + def __init__(self, parent): + self.parent = parent + +class ODriveComponent(Component): def __init__(self, yaml: dict): self.handle = None self.yaml = yaml - #self.axes = [AxisTestContext(None), AxisTestContext(None)] - self.encoders = [EncoderTestContext(self, 0, None), EncoderTestContext(self, 1, None)] - self.axes = [AxisTestContext(self, 0, None), AxisTestContext(self, 1, None)] + #self.axes = [ODriveAxisComponent(None), ODriveAxisComponent(None)] + self.encoders = [ODriveEncoderComponent(self, 0, None), ODriveEncoderComponent(self, 1, None)] + self.axes = [ODriveAxisComponent(self, 0, None), ODriveAxisComponent(self, 1, None)] + for i in range(1,9): + self.__setattr__('gpio' + str(i), Component(self)) + self.can = Component(self) - def __repr__(self): - return self.yaml['name'] + def get_subcomponents(self): + for enc_ctx in self.encoders: + yield 'encoder' + str(enc_ctx.num), enc_ctx + for axis_ctx in self.axes: + yield 'axis' + str(axis_ctx.num), axis_ctx + for i in range(1,9): + yield ('gpio' + str(i)), getattr(self, 'gpio' + str(i)) + yield 'can', self.can - def make_available(self, logger: Logger): + def prepare(self, logger: Logger): """ Connects to the ODrive """ @@ -70,58 +105,334 @@ class ODriveTestContext(): for axis_idx, axis_ctx in enumerate(self.axes): axis_ctx.handle = self.handle.__dict__['axis{}'.format(axis_idx)] -class MotorTestContext(): + def save_config_and_reboot(self): + self.handle.save_configuration() + try: + self.handle.reboot() + except fibre.ChannelBrokenException: + pass # this is expected + self.handle = None + time.sleep(2) + self.prepare(logger) + +class MotorComponent(Component): def __init__(self, yaml: dict): self.yaml = yaml - - def __repr__(self): - return self.yaml['name'] - def make_available(self, logger: Logger): + def prepare(self, logger: Logger): pass -class AxisTestContext(): - def __init__(self, odrv_ctx: ODriveTestContext, num: int, yaml: dict): +class ODriveAxisComponent(Component): + def __init__(self, odrv_ctx: ODriveComponent, num: int, yaml: dict): self.handle = None self.yaml = odrv_ctx.yaml[f'motor{num}'] # TODO: this is bad naming self.odrv_ctx = odrv_ctx self.num = num - - def __repr__(self): - return str(self.odrv_ctx) + '.axis' + str(self.num) - def make_available(self, logger: Logger): - self.odrv_ctx.make_available(logger) + def prepare(self, logger: Logger): + self.odrv_ctx.prepare(logger) -class EncoderTestContext(): - def __init__(self, odrv_ctx: ODriveTestContext, num: int, yaml: dict): +class ODriveEncoderComponent(Component): + def __init__(self, odrv_ctx: ODriveComponent, num: int, yaml: dict): self.handle = None self.odrv_ctx = odrv_ctx self.num = num - - def __repr__(self): - return str(self.odrv_ctx) + '.encoder' + str(self.num) + self.z = Component(self) + self.a = Component(self) + self.b = Component(self) - def make_available(self, logger: Logger): - self.odrv_ctx.make_available(logger) + def get_subcomponents(self): + return [('z', self.z), ('a', self.a), ('b', self.b)] -class CANTestContext(): + def prepare(self, logger: Logger): + self.odrv_ctx.prepare(logger) + +class EncoderComponent(Component): + def __init__(self, parent: Component, yaml: dict): + Component.__init__(self, parent) + self.yaml = yaml + self.z = Component(self) + self.a = Component(self) + self.b = Component(self) + + def get_subcomponents(self): + return [('z', self.z), ('a', self.a), ('b', self.b)] + +class GeneralPurposeComponent(Component): def __init__(self, yaml: dict): + self.components = {} + for component_yaml in yaml.get('components', []): + if component_yaml['type'] == 'can': + self.components[component_yaml['name']] = CanInterfaceComponent(self, component_yaml) + if component_yaml['type'] == 'uart': + self.components[component_yaml['name']] = SerialPortComponent(self, component_yaml) + if component_yaml['type'] == 'gpio': + self.components['gpio' + str(component_yaml['num'])] = LinuxGpioComponent(self, component_yaml) + + def get_subcomponents(self): + return self.components.items() + +class LinuxGpioComponent(Component): + def __init__(self, parent: Component, yaml: dict): + Component.__init__(self, parent) + self.num = int(yaml['num']) + + def config(self, output: bool): + with open("/sys/class/gpio/gpio{}/direction".format(self.num), "w") as fp: + fp.write('out' if output else '0') + + def write(self, state: bool): + with open("/sys/class/gpio/gpio{}/value".format(self.num), "w") as fp: + fp.write('1' if state else '0') + + +class SerialPortComponent(Component): + def __init__(self, parent: Component, yaml: dict): + Component.__init__(self, parent) + self.yaml = yaml + + def get_subcomponents(self): + yield 'tx', Component(self) + yield 'rx', Component(self) + + def open(self, baudrate: int): + import serial + return serial.Serial(self.yaml['port'], baudrate, timeout=1) + +class CanInterfaceComponent(Component): + def __init__(self, parent: Component, yaml: dict): + Component.__init__(self, parent) self.handle = None self.yaml = yaml - def make_available(self, logger: Logger): + def prepare(self, logger: Logger): if not self.handle is None: return - # TODO: read bus name from yaml import can - self.handle = can.interface.Bus(bustype='socketcan', channel=self.yaml['id'], bitrate=250000) + self.handle = can.interface.Bus(bustype='socketcan', channel=self.yaml['interface'], bitrate=250000) + +class TeensyGpio(Component): + def __init__(self, parent: Component, num: int): + Component.__init__(self, parent) + self.num = num + +class TeensyComponent(Component): + def __init__(self, testrig, yaml: dict): + self.testrig = testrig + self.yaml = yaml + self.gpios = [TeensyGpio(self, i) for i in range(24)] + self.routes = [] + self.previous_routes = object() + + def get_subcomponents(self): + for i, gpio in enumerate(self.gpios): + yield ('gpio' + str(i)), gpio + yield 'program', Component(self) + + def add_route(self, input: TeensyGpio, output: TeensyGpio, noise_enable: TeensyGpio): + self.routes.append((input, output, noise_enable)) + + def commit_routing_config(self, logger: Logger): + if self.previous_routes == self.routes: + self.routes = [] + return + + code = '' + code += 'bool noise = false;\n' + code += 'void setup() {\n' + for i, o, n in self.routes: + code += ' pinMode({}, OUTPUT);\n'.format(o.num) + code += '}\n' + code += 'void loop() {\n' + code += ' noise = !noise;\n' + for i, o, n in self.routes: + if n: + # with noise enable + code += ' digitalWrite({}, digitalRead({}) ? noise : digitalRead({}));\n'.format(o.num, n.num, i.num) + else: + # no noise enable + code += ' digitalWrite({}, digitalRead({}));\n'.format(o.num, i.num) + code += '}\n' + + self.compile_and_program(code) + + self.previous_routes = self.routes + self.routes = [] + + def compile(self, sketchfile, hexfile): + env = os.environ.copy() + env['ARDUINO_COMPILE_DESTINATION'] = hexfile + run_shell( + ['arduino', '--board', 'teensy:avr:teensy40', '--verify', sketchfile], + logger, env = env, timeout = 60) + + def program(self, hex_file_path: str, logger: Logger): + """ + Programs the specified hex file onto the Teensy. + To reset the Teensy, a GPIO of the local system must be connected to the + Teensy's "Program" pin. + """ + + # todo: this should be treated like a regular setup resource + program_gpio = self.testrig.get_directly_connected_components(self.testrig.get_component_name(self) + '.program')[0] + + # Put Teensy into program mode by pulling it's program pin down + program_gpio.config(output = True) + program_gpio.write(False) + time.sleep(0.1) + program_gpio.write(True) + + run_shell(["teensy-loader-cli", "-mmcu=imxrt1062", "-w", hex_file_path], logger, timeout = 5) + time.sleep(0.5) # give it some time to boot + + def compile_and_program(self, code: str): + with io.TextIOWrapper(tempfile.NamedTemporaryFile(suffix='.ino')) as code_fp: + code_fp.write(code) + code_fp.flush() + code_fp.seek(0) + print('Writing code to teensy: ') + print(code_fp.read()) + with tempfile.NamedTemporaryFile(suffix='.hex') as hex_fp: + self.compile(code_fp.name, hex_fp.name) + self.program(hex_fp.name, logger) + +class ProxiedComponent(Component): + def __init__(self, impl, *gpio_tuples): + """ + Each element in gpio_tuples should be a tuple of the form: + (teensy: TeensyComponent, gpio_in, gpio_out, gpio_noise_enable) + """ + Component.__init__(self, getattr(impl, 'parent', None)) + self.impl = impl + assert(all([len(t) == 4 for t in gpio_tuples])) + self.gpio_tuples = list(gpio_tuples) + + def __repr__(self): + return testrig.get_component_name(self.impl) + ' (routed via ' + ', '.join((testrig.get_component_name(t) + ': ' + str(i.num) + ' => ' + str(o.num)) for t, i, o, n in self.gpio_tuples) + ')' + + def prepare(self): + for teensy, gpio_in, gpio_out, gpio_noise_enable in self.gpio_tuples: + teensy.add_route(gpio_in, gpio_out, gpio_noise_enable) + +class TestRig(): + def __init__(self, yaml: dict, logger: Logger): + # Contains all components (including subcomponents). + # Ports are components too. + self.components_by_name = {} # {'name': object, ...} + self.names_by_component = {} # {'name': object, ...} + + def add_component(name, component): + self.components_by_name[name] = component + self.names_by_component[component] = name + if hasattr(component, 'get_subcomponents'): + for subname, subcomponent in component.get_subcomponents(): + add_component(name + '.' + subname, subcomponent) + + for component_yaml in yaml['components']: + if component_yaml['type'] == 'odrive': + add_component(component_yaml['name'], ODriveComponent(component_yaml)) + elif component_yaml['type'] == 'generalpurpose': + add_component(component_yaml['name'], GeneralPurposeComponent(component_yaml)) + elif component_yaml['type'] == 'teensy': + add_component(component_yaml['name'], TeensyComponent(self, component_yaml)) + elif component_yaml['type'] == 'motor': + add_component(component_yaml['name'], MotorComponent(component_yaml)) + elif component_yaml['type'] == 'encoder': + add_component(component_yaml['name'], EncoderComponent(self, component_yaml)) + else: + logger.warn('test rig has unsupported component ' + component_yaml['type']) + continue + + # List of disjunct sets, where each set holds references of the mutually connected components + self.connections = [] + for connection_yaml in yaml['connections']: + self.connections.append(set(self.components_by_name[name] for name in connection_yaml)) + self.connections = list(disjoint_sets(self.connections)) + + # Dict for fast lookup of the connection sets for each port + self.net_by_component = {} + for s in self.connections: + for port in s: + self.net_by_component[port] = s + + def get_components(self, t: type): + """Returns a tuple (name, component) for all components that are of the specified type""" + return (comp for comp in self.names_by_component.keys() if isinstance(comp, t)) + + def get_component_name(self, component: Component): + if isinstance(component, ProxiedComponent): + return self.names_by_component[component.impl] + else: + return self.names_by_component[component] + + def get_directly_connected_components(self, component: Union[str, Component]): + """ + Returns all components that are directly connected to the specified + component, excluding the specified component itself. + """ + if isinstance(component, str): + component = self.components_by_name[component] + result = self.net_by_component.get(component, set([component])) + return [c for c in result if (c != component)] + + def get_connected_components(self, src: Union[dict, Tuple[Union[Component, str], bool]], comp_type: type = None): + """ + Returns all components that are either directly or indirectly (through a + Teensy) connected to the specified component(s). + + component: Either: + - A component object. + - A component name given as string. + - A tuple of the form (comp, dir) where comp is a component object + or name and dir specifies the data direction. + The direction is required if routing through a Teensy should be + considered. + - A dict {sumcomponent: val} where subcomponent is a string + such as 'tx' or 'rx' and val is of one of the forms described above. + + A type can be specified to filter the connected components. + """ + + if isinstance(src, dict): + component_list = [] + for name, subsrc in src.items(): + component_list.append([c for c in self.get_connected_components(subsrc) if self.get_component_name(c).endswith('.' + name)]) + + for combination in itertools.product(*component_list): + if len(set(c.parent for c in combination)) != 1: + continue # parent of the components don't match + proxied_dst = combination[0].parent + if comp_type and not isinstance(proxied_dst, comp_type): + continue # not the requested type + gpio_tuples = [c2 for c in combination for c2 in c.gpio_tuples if isinstance(c, ProxiedComponent)] + if len(gpio_tuples): + yield ProxiedComponent(proxied_dst, *gpio_tuples) + else: + yield proxied_dst + + else: + + if isinstance(src, tuple): + src, dir = src + else: + dir = None + + for dst in self.get_directly_connected_components(src): + if (not comp_type) or isinstance(dst, comp_type): + yield dst + + if (not dir is None) and isinstance(getattr(dst, 'parent', None), TeensyComponent): + teensy = dst.parent + for gpio2 in teensy.gpios: + for proxied_dst in self.get_directly_connected_components(gpio2): + if (not comp_type) or isinstance(proxied_dst, comp_type): + yield ProxiedComponent(proxied_dst, (teensy, dst if dir else gpio2, gpio2 if dir else dst, None)) # Helper functions ------------------------------------------------------------# -def request_state(axis_ctx: AxisTestContext, state, expect_success=True): +def request_state(axis_ctx: ODriveAxisComponent, state, expect_success=True): axis_ctx.handle.requested_state = state time.sleep(0.001) if expect_success: @@ -131,7 +442,7 @@ def request_state(axis_ctx: AxisTestContext, state, expect_success=True): test_assert_eq(axis_ctx.handle.error, AXIS_ERROR_INVALID_STATE) axis_ctx.handle.error = AXIS_ERROR_NONE # reset error -def get_errors(axis_ctx: AxisTestContext): +def get_errors(axis_ctx: ODriveAxisComponent): errors = [] if axis_ctx.handle.motor.error != 0: errors.append("motor failed with error 0x{:04X}".format(axis_ctx.handle.motor.error)) @@ -145,41 +456,12 @@ def get_errors(axis_ctx: AxisTestContext): errors.append("and by the way: axis reports no error even though there is one") return errors -def test_assert_no_error(axis_ctx: AxisTestContext): +def test_assert_no_error(axis_ctx: ODriveAxisComponent): errors = get_errors(axis_ctx) if len(errors) > 0: raise TestFailed("\n".join(errors)) -def yaml_to_test_objects(test_rig_yaml: dict, logger: Logger): - available_test_objects = {} - - def add_component(component): - available_test_objects[type(component)] = available_test_objects.get(type(component), []) - available_test_objects[type(component)].append(component) - - for component_yaml in test_rig_yaml['components']: - if component_yaml['type'] == 'odrive': - odrv_ctx = ODriveTestContext(component_yaml) - add_component(odrv_ctx) - for enc_ctx in odrv_ctx.encoders: - add_component(enc_ctx) - for axis_ctx in odrv_ctx.axes: - add_component(axis_ctx) - elif component_yaml['type'] == 'generalpurpose': - for (k, v) in [(k, v) for (k, v) in component_yaml.items() if k.startswith("can")]: - can_ctx = CANTestContext({'id': k, 'bus': v}) - add_component(can_ctx) - elif component_yaml['type'] == 'motor': - motor_ctx = MotorTestContext(component_yaml) - add_component(motor_ctx) - else: - logger.warn('test rig has unsupported component ' + component_yaml['type']) - continue - - - return available_test_objects - -def run_shell(command_line, logger, timeout=None): +def run_shell(command_line, logger, env=None, timeout=None): """ Runs a shell command in the current directory """ @@ -192,60 +474,95 @@ def run_shell(command_line, logger, timeout=None): cmd = shlex.split(command_line) result = subprocess.run(cmd, timeout=timeout, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) + stderr=subprocess.STDOUT, + env=env) if result.returncode != 0: logger.error(result.stdout.decode(sys.stdout.encoding)) raise TestFailed("command {} failed".format(command_line)) -def program_teensy(hex_file_path, program_gpio: int, logger: Logger): - """ - Programs the specified hex file onto the Teensy. - To reset the Teensy, a GPIO of the local system must be connected to the - Teensy's "Program" pin. This pin must first be manually made available to - user space: - - echo 26 | sudo tee /sys/class/gpio/export - sudo chmod a+rw /sys/class/gpio/gpio26/* - echo out > /sys/class/gpio/gpio26/direction +def select_params(param_options): + params = [] - """ + # Select parameters from the resource list + # (this could be arbitrarily complex to improve parallelization of the tests) + for param in param_options: + if is_list_like(param): + if len(param) == 0: + return None + else: + selection = param[0] + if is_list_like(selection): + params = params + list(selection) + else: + params.append(selection) + else: + params.append(param) + return params - # Put Teensy into program mode by pulling it's program pin down - with open("/sys/class/gpio/gpio{}/direction".format(program_gpio), "w") as fp: - fp.write("out") - with open("/sys/class/gpio/gpio{}/value".format(program_gpio), "w") as gpio: - gpio.write("0") - time.sleep(0.1) - with open("/sys/class/gpio/gpio{}/value".format(program_gpio), "w") as gpio: - gpio.write("1") - - run_shell(["teensy_loader_cli", "-mmcu=imxrt1062", "-w", hex_file_path], logger, timeout = 5) - time.sleep(0.5) # give it some time to boot +def run(tests): + if not isinstance(tests, list): + tests = [tests] -def run(test_cases): - if not isinstance(test_cases, list): - test_cases = [test_cases] + for test in tests: + # The result of get_test_cases can be described in ABNF grammar: + # test-case-list = *arglist + # arglist = *flexible-arg + # flexible-arg = component / *argvariant + # argvariant = component / arglist + # + # If for a particular test-case, the components are not given plainly + # but in some selectable form, the test driver will select exactly one + # of those options. + # In other words, it will bring arglist from the form *flexible-arg + # into the form *component before calling the test. + # + # All of the provided test-cases are executed. If none is provided, + # a warning is reported. A warning is also reported if for a particular + # test case no component combination can be resolved. - for test_case in test_cases: - # Compile a list of list of potential objects that might be compatible with this - # test - possible_parameters = [] - sig = signature(test_case.is_compatible) - for param_name in sig.parameters: - param_type = sig.parameters[param_name].annotation - possible_parameters.append(available_test_objects[param_type]) + test_cases = list(test.get_test_cases(testrig)) - # For each combination, check if the test is compatible with these objects - for param_combination in itertools.product(*possible_parameters): - if not test_case.is_compatible(*param_combination): + if len(test_cases) == 0: + logger.warn('no resources are available to conduct the test {}'.format(type(test).__name__)) + continue + + for test_case in test_cases: + params = select_params(test_case) + if params is None: + logger.warn('no resources are available to conduct the test {}'.format(type(test).__name__)) continue - - for param in param_combination: - param.make_available(logger) - logger.notify('* running {} on {}...'.format(type(test_case).__name__, - [str(p) for p in param_combination])) - test_case.run_test(*param_combination, logger) + logger.notify('* preparing {} with {}...'.format(type(test).__name__, + [(testrig.get_component_name(p) if isinstance(p, Component) else str(p)) for p in params])) + + teensies = set() + for param in params: + if isinstance(param, ProxiedComponent): + param.prepare() + for teensy, _, _, _ in param.gpio_tuples: + teensies.add(teensy) + + for teensy in teensies: + teensy.commit_routing_config(logger) + + # prepare all components + teensies = set() + for param in params: + if isinstance(param, ProxiedComponent): + continue + if hasattr(param, 'prepare'): + param.prepare(logger) + + logger.notify('* running {} on {}...'.format(type(test).__name__, + [(testrig.get_component_name(p) if isinstance(p, Component) else str(p)) for p in params])) + + # Resolve routed components + for i, param in enumerate(params): + if isinstance(param, ProxiedComponent): + params[i] = param.impl + + test.run_test(*params, logger) + logger.success('All tests passed!') @@ -269,7 +586,7 @@ args = parser.parse_args() test_rig_yaml = yaml.load(args.test_rig_yaml, Loader=yaml.BaseLoader) logger = Logger() -available_test_objects = yaml_to_test_objects(test_rig_yaml, logger) +testrig = TestRig(test_rig_yaml, logger) if args.setup_host: @@ -285,3 +602,22 @@ if args.setup_host: export_gpio(26) # connected to Teensy Program pin os.chmod("/dev/ttyS0", stat.S_IROTH | stat.S_IWOTH) + + # This breaks the retarded teensy loader that shows up on every compile + if not os.path.isfile('/usr/share/arduino/hardware/tools/teensy_post_compile_old'): + os.rename('/usr/share/arduino/hardware/tools/teensy_post_compile', '/usr/share/arduino/hardware/tools/teensy_post_compile_old') + with open('/usr/share/arduino/hardware/tools/teensy_post_compile', 'w') as scr: + scr.write('#!/bin/bash\n') + scr.write('if [ "$ARDUINO_COMPILE_DESTINATION" != "" ]; then\n') + scr.write(' cp -r ${2#-path=}/*.ino.hex ${ARDUINO_COMPILE_DESTINATION}\n') + scr.write('fi\n') + os.chmod('/usr/share/arduino/hardware/tools/teensy_post_compile', stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) + + # Bring up CAN interface(s) + for intf in testrig.get_components(CanInterfaceComponent): + name = intf.yaml['interface'] + run_shell('ip link set dev {} down'.format(intf)) + run_shell('ip link set dev {} type can bitrate 250000'.format(intf)) + run_shell('ip link set dev {} type can loopback off'.format(intf)) + run_shell('ip link set dev {} up'.format(intf)) + diff --git a/tools/odrive/tests/uart_ascii_test.py b/tools/odrive/tests/uart_ascii_test.py index a4e78a49..71c8bd7d 100644 --- a/tools/odrive/tests/uart_ascii_test.py +++ b/tools/odrive/tests/uart_ascii_test.py @@ -5,13 +5,12 @@ import struct import time import os import io -import serial import functools import operator from fibre.utils import Logger from odrive.enums import * -from test_runner import ODriveTestContext, test_assert_eq, test_assert_no_error, program_teensy +from test_runner import * def append_checksum(command): @@ -32,28 +31,29 @@ def reset_state(ser): ser.flushInput() # discard response class TestUartAscii(): - def is_compatible(self, odrive: ODriveTestContext): - return True + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + ports = list(testrig.get_connected_components({ + 'rx': (odrive.gpio1, True), + 'tx': (odrive.gpio2, False) + }, SerialPortComponent)) + yield (odrive, ports) - def run_test(self, odrive: ODriveTestContext, logger: Logger): + def run_test(self, odrive: ODriveComponent, port: SerialPortComponent, logger: Logger): """ Tests the most important functions of the ASCII protocol. """ - # Disable noise - with open("/sys/class/gpio/gpio{}/direction".format(20), "w") as fp: - fp.write("out") - with open("/sys/class/gpio/gpio{}/value".format(20), "w") as gpio: - gpio.write("0") - - hexfile = 'uart_pass_through.ino.hex' - program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) - time.sleep(1.0) + if (odrive.handle.config.gpio1_pwm_mapping.endpoint != (0,0)) or (odrive.handle.config.gpio2_pwm_mapping.endpoint != (0,0)): + logger.debug('UART pins in use. Reconfiguring...') + odrive.handle.config.gpio1_pwm_mapping.endpoint = None + odrive.handle.config.gpio2_pwm_mapping.endpoint = None + odrive.save_config_and_reboot() odrive.handle.axis0.config.enable_step_dir = False odrive.handle.config.enable_uart = True - with serial.Serial('/dev/ttyS0', 115200, timeout=1) as ser: + with port.open(115200) as ser: # reset port to known state reset_state(ser) @@ -159,84 +159,24 @@ class TestUartAscii(): # TODO: test cases for 't', 'ss', 'se', 'sr' commands - -class TestUartNoise(): - def is_compatible(self, odrive: ODriveTestContext): - return True - - def run_test(self, odrive: ODriveTestContext, logger: Logger): - """ - Tests if the UART can handle invalid signals. - """ - - # Disable noise - with open("/sys/class/gpio/gpio{}/direction".format(20), "w") as fp: - fp.write("out") - with open("/sys/class/gpio/gpio{}/value".format(20), "w") as gpio: - gpio.write("0") - - hexfile = 'uart_pass_through.ino.hex' - program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) - time.sleep(1.0) - - odrive.handle.axis0.config.enable_step_dir = False - odrive.handle.config.enable_uart = True - - with serial.Serial('/dev/ttyS0', 115200, timeout=1) as ser: - # reset port to known state - reset_state(ser) - - # Enable square wave of ~1.6MHz on the ODrive's RX line - with open("/sys/class/gpio/gpio{}/value".format(20), "w") as gpio: - gpio.write("1") - - time.sleep(0.1) - reset_state(ser) - - # Read an attribute (should fail because the command is not passed through) - ser.write(b'r vbus_voltage\n') - test_assert_eq(ser.readline(), b'') - - # Disable square wave - with open("/sys/class/gpio/gpio{}/value".format(20), "w") as gpio: - gpio.write("0") - - # Give receiver some time to recover - time.sleep(0.1) - - # reset port to known state - reset_state(ser) - - # Try again - ser.write(b'r vbus_voltage\n') - response = float(ser.readline().strip()) - test_assert_eq(response, odrive.handle.vbus_voltage, accuracy=0.1) - - - class TestUartBurnIn(): - def is_compatible(self, odrive: ODriveTestContext): - return True + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + ports = list(testrig.get_connected_components({ + 'rx': (odrive.gpio1, True), + 'tx': (odrive.gpio2, False) + }, SerialPortComponent)) + yield (odrive, ports) - def run_test(self, odrive: ODriveTestContext, logger: Logger): + def run_test(self, odrive: ODriveComponent, port: SerialPortComponent, logger: Logger): """ Tests if the ASCII protocol can handle 64kB of random data being thrown at it. """ - # Disable noise - with open("/sys/class/gpio/gpio{}/direction".format(20), "w") as fp: - fp.write("out") - with open("/sys/class/gpio/gpio{}/value".format(20), "w") as gpio: - gpio.write("0") - - hexfile = 'uart_pass_through.ino.hex' - program_teensy(os.path.join(os.path.dirname(__file__), hexfile), 26, logger) - time.sleep(1.0) - odrive.handle.axis0.config.enable_step_dir = False odrive.handle.config.enable_uart = True - with serial.Serial('/dev/ttyS0', 115200, timeout=1) as ser: + with port.open(115200) as ser: with open('/dev/random', 'rb') as rand: buf = rand.read(65536) ser.write(buf) @@ -250,9 +190,81 @@ class TestUartBurnIn(): test_assert_eq(response, odrive.handle.vbus_voltage, accuracy=0.1) +class TestUartNoise(): + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + # For every ODrive, find a connected serial port which has a teensy + # in between, so that we can inject noise, + + ports = list(testrig.get_connected_components({ + 'rx': (odrive.gpio1, True), + 'tx': (odrive.gpio2, False) + }, SerialPortComponent)) + + # Hack the bus objects to enable noise_enable functionality on the TX line. + + def get_noise_gpio(bus): + teensy = bus.gpio_tuples[1][0] + for teensy_gpio in teensy.gpios: + for other_gpio in testrig.get_directly_connected_components(teensy_gpio): + if isinstance(other_gpio, LinuxGpioComponent): + return teensy_gpio, other_gpio + return None + + for idx, bus in enumerate(ports): + noise_gpio_on_teensy, noise_gpio_on_rpi = get_noise_gpio(bus) + assert(noise_gpio_on_rpi) + t, i, o, _ = bus.gpio_tuples[1] + bus.gpio_tuples[1] = (t, i, o, noise_gpio_on_teensy) + ports[idx] = (bus, noise_gpio_on_rpi) + + yield (odrive, ports) + + def run_test(self, odrive: ODriveComponent, port: SerialPortComponent, noise_enable: LinuxGpioComponent, logger: Logger): + """ + Tests if the UART can handle invalid signals. + """ + noise_enable.config(output=True) + noise_enable.write(False) + time.sleep(0.1) + + odrive.handle.axis0.config.enable_step_dir = False + odrive.handle.config.enable_uart = True + + with port.open(115200) as ser: + # reset port to known state + reset_state(ser) + + # Enable square wave of ~1.6MHz on the ODrive's RX line + noise_enable.write(True) + + time.sleep(0.1) + reset_state(ser) + + time.sleep(1.0) + + # Read an attribute (should fail because the command is not passed through) + ser.write(b'r vbus_voltage\n') + test_assert_eq(ser.readline(), b'') + + # Disable square wave + noise_enable.write(False) + + # Give receiver some time to recover + time.sleep(0.1) + + # reset port to known state + reset_state(ser) + + # Try again + ser.write(b'r vbus_voltage\n') + response = float(ser.readline().strip()) + test_assert_eq(response, odrive.handle.vbus_voltage, accuracy=0.1) + + if __name__ == '__main__': test_runner.run([ TestUartAscii(), - TestUartNoise(), TestUartBurnIn(), + TestUartNoise(), ]) diff --git a/tools/odrive/tests/uart_pass_through.ino.hex b/tools/odrive/tests/uart_pass_through.ino.hex deleted file mode 100644 index 8175c58e..00000000 --- a/tools/odrive/tests/uart_pass_through.ino.hex +++ /dev/null @@ -1,786 +0,0 @@ -:0200000460009A -:100000004643464200000156000000000101020084 -:1000100000000000000000000000000000000000E0 -:1000200000000000000000000000000000000000D0 -:1000300000000000000000000000000000000000C0 -:1000400000000000010403000000000000000000A8 -:100050000000200000000000000000000000000080 -:100060000000000000000000000000000000000090 -:100070000000000000000000000000000000000080 -:10008000EB04180A063204260000000000000000FD -:10009000050404240000000000000000000000002F -:1000A0000000000000000000000000000000000050 -:1000B0000604000000000000000000000000000036 -:1000C0000000000000000000000000000000000030 -:1000D00020041808000000000000000000000000DC -:1000E0000000000000000000000000000000000010 -:1000F0000000000000000000000000000000000000 -:10010000D8041808000000000000000000000000F3 -:100110000204180804200000000000000000000095 -:1001200000000000000000000000000000000000CF -:10013000600400000000000000000000000000005B -:1001400000000000000000000000000000000000AF -:10015000000000000000000000000000000000009F -:10016000000000000000000000000000000000008F -:10017000000000000000000000000000000000007F -:10018000000000000000000000000000000000006F -:10019000000000000000000000000000000000005F -:1001A000000000000000000000000000000000004F -:1001B000000000000000000000000000000000003F -:1001C000000100000010000001000000000000001D -:1001D000000001000000000000000000000000001E -:1001E000000000000000000000000000000000000F -:1001F00000000000000000000000000000000000FF -:10020000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE -:10021000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEE -:10022000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDE -:10023000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCE -:10024000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBE -:10025000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAE -:10026000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9E -:10027000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8E -:10028000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7E -:10029000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6E -:1002A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5E -:1002B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4E -:1002C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3E -:1002D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2E -:1002E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1E -:1002F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0E -:10030000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD -:10031000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED -:10032000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDD -:10033000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCD -:10034000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBD -:10035000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAD -:10036000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9D -:10037000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8D -:10038000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7D -:10039000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6D -:1003A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D -:1003B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4D -:1003C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3D -:1003D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2D -:1003E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1D -:1003F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0D -:10040000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC -:10041000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC -:10042000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDC -:10043000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCC -:10044000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBC -:10045000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAC -:10046000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9C -:10047000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8C -:10048000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7C -:10049000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6C -:1004A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5C -:1004B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4C -:1004C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3C -:1004D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2C -:1004E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1C -:1004F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0C -:10050000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB -:10051000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEB -:10052000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDB -:10053000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCB -:10054000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBB -:10055000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAB -:10056000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9B -:10057000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8B -:10058000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7B -:10059000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6B -:1005A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5B -:1005B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4B -:1005C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3B -:1005D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2B -:1005E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1B -:1005F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0B -:10060000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA -:10061000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEA -:10062000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDA -:10063000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCA -:10064000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBA -:10065000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAA -:10066000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9A -:10067000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8A -:10068000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7A -:10069000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6A -:1006A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5A -:1006B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4A -:1006C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3A -:1006D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2A -:1006E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1A -:1006F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0A -:10070000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9 -:10071000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9 -:10072000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD9 -:10073000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC9 -:10074000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB9 -:10075000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA9 -:10076000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF99 -:10077000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF89 -:10078000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF79 -:10079000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF69 -:1007A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF59 -:1007B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF49 -:1007C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF39 -:1007D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF29 -:1007E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF19 -:1007F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF09 -:10080000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8 -:10081000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8 -:10082000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD8 -:10083000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC8 -:10084000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB8 -:10085000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA8 -:10086000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF98 -:10087000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF88 -:10088000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF78 -:10089000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF68 -:1008A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF58 -:1008B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF48 -:1008C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF38 -:1008D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF28 -:1008E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF18 -:1008F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF08 -:10090000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7 -:10091000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE7 -:10092000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD7 -:10093000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7 -:10094000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB7 -:10095000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA7 -:10096000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF97 -:10097000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF87 -:10098000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF77 -:10099000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF67 -:1009A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF57 -:1009B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF47 -:1009C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF37 -:1009D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF27 -:1009E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF17 -:1009F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF07 -:100A0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6 -:100A1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE6 -:100A2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD6 -:100A3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC6 -:100A4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB6 -:100A5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA6 -:100A6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF96 -:100A7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF86 -:100A8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF76 -:100A9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF66 -:100AA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF56 -:100AB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF46 -:100AC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF36 -:100AD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF26 -:100AE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF16 -:100AF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF06 -:100B0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5 -:100B1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5 -:100B2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD5 -:100B3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC5 -:100B4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB5 -:100B5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA5 -:100B6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF95 -:100B7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF85 -:100B8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF75 -:100B9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF65 -:100BA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF55 -:100BB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF45 -:100BC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF35 -:100BD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF25 -:100BE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF15 -:100BF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF05 -:100C0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4 -:100C1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE4 -:100C2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD4 -:100C3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC4 -:100C4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB4 -:100C5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA4 -:100C6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF94 -:100C7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF84 -:100C8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF74 -:100C9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF64 -:100CA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF54 -:100CB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF44 -:100CC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF34 -:100CD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF24 -:100CE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF14 -:100CF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF04 -:100D0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3 -:100D1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE3 -:100D2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD3 -:100D3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC3 -:100D4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB3 -:100D5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA3 -:100D6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF93 -:100D7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF83 -:100D8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF73 -:100D9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF63 -:100DA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF53 -:100DB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF43 -:100DC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF33 -:100DD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF23 -:100DE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF13 -:100DF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF03 -:100E0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2 -:100E1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE2 -:100E2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD2 -:100E3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2 -:100E4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB2 -:100E5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA2 -:100E6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF92 -:100E7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF82 -:100E8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF72 -:100E9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF62 -:100EA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF52 -:100EB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF42 -:100EC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF32 -:100ED000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF22 -:100EE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF12 -:100EF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF02 -:100F0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1 -:100F1000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE1 -:100F2000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD1 -:100F3000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC1 -:100F4000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB1 -:100F5000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA1 -:100F6000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF91 -:100F7000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF81 -:100F8000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF71 -:100F9000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF61 -:100FA000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF51 -:100FB000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF41 -:100FC000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF31 -:100FD000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF21 -:100FE000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF11 -:100FF000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF01 -:10100000D10020402C100060000000000000000013 -:1010100020100060001000600000000000000000D0 -:1010200000000060E030000000000000000001202F -:1010300035100060764B0720764C4FF42A01764A33 -:101040005C64186499639546744A75498A420FD066 -:10105000744B9A420CD2D4430846234423F0030332 -:1010600004330B4450F8041B984242F8041BF9D196 -:101070006D4A6E498A420FD06D4B9A420CD2D443CE -:101080000846234423F0030304330B4450F8041BA5 -:10109000984242F8041BF9D1664A674B9A420BD238 -:1010A000D04311460024034423F0030304331344C4 -:1010B00041F8044B8B42FBD1604A4FF47001604B06 -:1010C000116003F530715F4A43F8042F9942FBD158 -:1010D0005D4B80215D4A03F8011B9342FBD15C4CC0 -:1010E0004FF080375B4E4FF0FF32DFF8A8A10025AC -:1010F000DFF8A491DFF8A481574B4549C3F800A05D -:10110000C4F80471C4F80091C4F8F470C4F8F08015 -:10111000F36923F07F0343F04003F361736A23F024 -:101120007F0343F0400373628A66CA660A674A67B0 -:1011300000F0B6F8494A6320494B4A49106003223F -:101140001D60CAF8381043F8082C4749474A4848F8 -:10115000C3F8082D0B68474A43F08073CAF83C0077 -:1011600045480B601368454943F001031360036869 -:101170000B6000F0E5F8C4F804714148C4F8009130 -:10118000C4F8F470C4F8F08000F056FA00BF00BF55 -:1011900000BF00BFF16E3B4A41F440513A4BF1664B -:1011A0001560C2F80851C2F81851C2F82851C2F8A7 -:1011B00038519A6BD20708D442F615623349596503 -:1011C0001A659A6B42F001029A632F4A304C936879 -:1011D00043F00113936000F013FA2368132BFCD93A -:1011E00000F05CF900F0D0F900F012FA00F0DAF843 -:1011F00000F00AFA2368B3F5967FFBD300F020FADB -:1012000000F012FA00F0F4F900F0F6F9FAE700BF86 -:1012100000C00A40ABAAAAAA008007200000000074 -:1012200050160060D016000000000020242D006041 -:10123000C0030020C0030020C032002088ED00E081 -:10124000FC0F00201102000000E400E0A0E400E038 -:1012500000800D4000C00F4008ED00E014E000E009 -:1012600018E000E0890D0000FCED00E00000202007 -:101270008D0E0000001000E0041000E0840D00203E -:101280000046C3230040084000400D400000C05607 -:10129000880D0020001000201B1018200C0D1113C9 -:1012A000F0B5194A0021194B4FF0100E18480124CF -:1012B000184E194D0160194FC2F800E01E6015600C -:1012C000174E184D1F601660174F1D60174E184DB2 -:1012D00017601E60174F1560174E184D1F6016607F -:1012E0001D600460BFF34F8FBFF36F8FC3F8B01161 -:1012F000BFF34F8FBFF36F8F52F8883C43F44033F6 -:1013000042F8883CF0BD00BF9CED00E0A0ED00E09D -:1013100094ED00E0250008031100200021000207E1 -:1013200012000020250008131300202027000B13B3 -:101330001400004033001013150000602F000B074D -:10134000F0B4174A40274FF480314FF480564FF4E1 -:1013500000554FF4404443F24200136913F0020F6A -:1013600006D0946151619061136913F0020FF8D1B6 -:1013700013F4005F01D15561EFE713F4805F01D1F1 -:101380005661EAE7002BE8DA13F4803F01D091615F -:10139000E3E75B0601D45761DFE7F0BC704700BFAD -:1013A00000800D40364A03203649F3EE096A13687F -:1013B00023F00103F0B51360C2F89000D1F8E030DB -:1013C000DFF8D4E0DCB2C3F30B202F4FC0EB135394 -:1013D00007EE904AA4F15501CEF80040B8EEE77A46 -:1013E00003EB830407EE900A03FB01F13B6003EB80 -:1013F0008313B8EEE75A07EE901A091B77EE666A78 -:10140000B8EE677A214D07EE901A0B44C5ED006ADD -:10141000F8EE677A1E4EC7EE265A1E4930601068F5 -:1014200087EEA66A07EE903AF8EE677A87EEA67A1C -:1014300075EE855A76EE056AFCEEE55AFCEEE66A34 -:1014400015EE903A40EA035316EE900A77EE057ACD -:10145000136001EA0041D2F81031FCEEE77A0B4349 -:10146000C2F8103117EE903AD2F81011C3F30B0303 -:101470000B43C2F81031136843F002031360F0BD50 -:1014800080810D4000441F40F0030020EC03002049 -:10149000F40300200000FF0FE8030020304B40F66B -:1014A000617270B5C3F8202140F2044500F0B8F82D -:1014B0002C492D48D1F880202C4C42F003022C4BB3 -:1014C000C1F88020C0F86051226813401BB9D0F8E1 -:1014D000A8319A071AD0244B4FF00041234A516398 -:1014E0001A46D3F8401141F00201C3F84011D2F876 -:1014F00040319B07FBD44FF400301E491B4B4FF08B -:101500000042086019209A6300F092F81A4D0022F8 -:10151000164B4FF08041144C0A26996328461A60F6 -:101520001146C4F8A8614FF4207200F075F84FF42A -:1015300081064FF4800040F24313104A10492E6098 -:101540002864C4F85851C2F80412C4F848310D4A4E -:101550004FF4003101231160C4F8403170BD00BF69 -:1015600000800D4000C00F4000002E4000900D4054 -:10157000001C1E008CE200E0003000200010002063 -:10158000ED0300000CE100E0114B1249D86E0A4651 -:1015900040F4403030B4D86640F2B765D86EA0242D -:1015A00040F44070D8664D648C64936C1B06FCD488 -:1015B000094B40F2B760A021064A58649964936CC5 -:1015C00013F08003FBD1054A137030BC704700BF95 -:1015D00000C00F4000400C4000800C40810D0020F6 -:1015E000F8B500BFF8BC08BC9E467047FFFFFFFF80 -:1015F0005FF800F0B10000005FF800F0AD0E0000F1 -:101600005FF800F0290200005FF800F02D020000F2 -:101610005FF800F0F11200005FF800F02D160000F6 -:101620005FF800F0ED0A00005FF800F0AD00000088 -:101630005FF800F0E11100005FF800F04D0F0000CE -:101640005FF800F0DD150000450000001501000006 -:10165000000000000000000000000000000000008A -:10166000000000000000000000000000000000007A -:1016700010B5054C237833B9044B13B10448AFF3CC -:1016800000800123237010BDC00300200000000073 -:10169000D4160000084B10B51BB108490848AFF339 -:1016A00000800848036803B910BD074B002BFBD02E -:1016B000BDE81040184700BF00000000C403002030 -:1016C000D4160000C00300200000000008B5084B3D -:1016D0000121187800F084F8064B0121187800F0F9 -:1016E0007FF8054B00211878BDE8084000F078B875 -:1016F000DC030020F0020020EC020020FFF7E6BF30 -:10170000124B70B5187800F065F8114B114C054676 -:10171000187800F05FF823780F4A064683F001033B -:101720001078237000F056F80C4B50B931461878F9 -:1017300000F04AF80A4B29461878BDE8704000F0DE -:1017400043B82178187800F03FF8F3E7E40300206D -:10175000F8020020E0030020EC020020DC0300205F -:10176000F0020020044A054B106805491A68054B31 -:1017700008601A60704700BFFC020020F4020020DD -:10178000E4030020DC0300200001074B1A181B585B -:10179000D2685868104202D011B9C3F88820704747 -:1017A000C3F88420704700BF000000200001054BF3 -:1017B0001A5819189268CB681A4214BF01200020E9 -:1017C000704700BF00000020272801D8FFF7DCBFCA -:1017D000704700BF272801D8FFF7E8BF00207047F7 -:1017E00027281CD800011A4A012902EB00031058CF -:1017F00010B415D0042913D0DC68426822EA040230 -:1018000042609A68E9B102291ED003295B685DF83D -:10181000044B0CBF0F491049116015221A60704724 -:10182000DA680129446842EA040242609A6808D0F2 -:1018300040F6380111605B6815225DF8044B1A60B0 -:10184000704738211160F6E704491160F3E700BFE3 -:1018500000000020383001003800010038F001009D -:1018600004207146084203D0EFF3098000F008B865 -:10187000EFF3088000F004B8704700BF704700BF66 -:101880001B4B05211B4A382030B5C2F848110821EE -:10189000C2F8380383B05A68174C0A4317485A6095 -:1018A000C3F88410C3F888102368834202D914480F -:1018B00000F074FE0E4D08240020124A1249C5F8AB -:1018C00084400190019B934205D8019B0133019311 -:1018D000019B9342F9D9C5F888400190019B8B4246 -:1018E000EDD8019B01330193019B8B42F9D9E6E7C7 -:1018F00000C01B4000801F40B4030020FF45C323ED -:1019000000A3E1113F420F003F548900836B30B4C4 -:101910001BB1536843F40043536072B6446B9CB1EF -:10192000104B2260D3F8B0410C4217D1D3F840419C -:1019300044F48044C3F84041D3F8B851D3F840414F -:101940006404F3D5294209D10023064C8260C360A8 -:10195000D4F8B0311943C4F8B0110263426362B6DF -:1019600030BC704700002E4038B50546036B06E0DA -:10197000AA6B1C6890476A6B2346944208D01846AD -:1019800033B1012B04D05A681206F1D52B6338BD50 -:1019900000232B636B6338BDF0B5F1B9224C2349AA -:1019A0000020234B01228025A1600A601A464D6069 -:1019B000E060D3F8BC41886044F001141D4DC3F8C9 -:1019C000BC41D3F8B01141F00101C3F8B011286057 -:1019D000D2F8B031002BFBD1F0BD0904164B144DE9 -:1019E0000126114C41F0800100221E60596000F573 -:1019F000805CE26400F5005EA36400F54057D5F812 -:101A0000B01100F580462A46986041F48031C3F851 -:101A10000CC0C3F810E05F619E61C5F8B011D2F848 -:101A2000B031002BFBD1BAE700300020202000208D -:101A300000002E4004040020002000202DE9F04F7B -:101A4000BB4C83B0D4F8448118F0010FC4F8448132 -:101A50005ED0D4F8AC31002B55D04FF00119DFF82F -:101A6000F0B22646B34FCA46B96AFA6AC4F8AC3136 -:101A7000D4F8403143F40053C4F84031D4F8403135 -:101A80009D04F5D5D6F8403123F40053C6F8403113 -:101A9000C6F8B4A1D4F8B43113F00113FAD188B266 -:101AA00040F28165CBF80030A84200F29680B0F594 -:101AB000D06F80F0D881B0F5817F00F0578100F2BF -:101AC000D580802800F02681822840F0C78092B21D -:101AD00002F07F01072900F2C18089009648974DE6 -:101AE0000844016810062B706B7040F1E581C90352 -:101AF00001D501232B7002219048FFF74DFFD4F848 -:101B0000AC31002BB0D18A4AD2F8BC31002B44D181 -:101B100018F0400F18D0864BD3F8AC111A46C3F812 -:101B2000AC11D3F8BC11C3F8BC11D2F8B041804B52 -:101B3000002CFAD14FF0FF32C3F8B421D3F884312E -:101B400000F074FB7E4B1C6018F0807F03D07D4B4F -:101B50001B6803B1984718F0007F03D07A4B1B68CD -:101B600003B1984718F0040F02D0714BD3F88431B9 -:101B70006F4BD3F8482112060CD518F0800F09D00E -:101B800072490A78002A00F08E81531EDBB20B7076 -:101B900003B9FBBE03B0BDE8F08F6D49C2F8BC319C -:101BA0000868034240F0CC81654A14681C40AFD0FD -:101BB000630700F1E281670300F1EF81260700F17E -:101BC000E881250300F1E181E00600F1DA81E1021C -:101BD0009ED56048FFF7C8FE9AE742F22105A84269 -:101BE00038D06FD8B0F5086F00F02F81B0F5106FC6 -:101BF00034D1C1F30741584A584811705849594ADD -:101C00000193C6F8C801C6F8CC11C6F8D02100F07F -:101C10000FFB554A019B80210120FB64106051603D -:101C2000D6F8BC11936041F00111BA64C6F8BC113A -:101C3000D6F8B02142F48032C6F8B021444A13608D -:101C4000D4F8B031002BFBD1D4F8AC31002B7FF4A9 -:101C50000BAF58E7100C072800F03281C4F8C09190 -:101C6000D4F8AC31002B7FF4FFAE4CE740F20235E4 -:101C7000A84200F0B780B0F5A06FEFD13A4A8020BB -:101C80000125FB6450601560D6F8BC01936040F0FC -:101C90000113BA64C6F8BC31D6F8B03143F48033CE -:101CA000C6F8B031D4F8B031002BFBD10B0C5B0679 -:101CB00043F08073C6F85431D4F8AC31002B7FF474 -:101CC000D3AE20E742F22123984200F0828042F214 -:101CD00021339842C2D1244B012180221960002176 -:101CE0005A60D4F8BC21BB6442F001129960F964D7 -:101CF000C4F8BC21D4F8B03143F48033C4F8B03117 -:101D0000D4F8B031002BFBD1D4F8AC31002B7FF4E8 -:101D1000ABAEF8E6094A0221104613705370FFF784 -:101D20003BFED4F8AC31002B7FF49EAEEBE600BF57 -:101D300000002E4000300020C0012E40180400207A -:101D4000FC030020F8030020000400200804002009 -:101D50000404002040320020280400200200CC00AF -:101D6000C80002000200C8002020002092B202F049 -:101D70007F03072B3FF672AF12F0800F4FEA830309 -:101D8000884A4FF001011A44136814BF23F48033CA -:101D900023F0010313608022834B196000215A60F5 -:101DA000D4F8BC21BB6442F001129960F964C4F814 -:101DB000BC21D4F8B03143F48033C4F8B031D4F846 -:101DC000B031002BFBD1D4F8AC31002B7FF44CAEFA -:101DD00099E6764BC1F30741754A1868754B106058 -:101DE000197078E792B202F07F03072B3FF636AF07 -:101DF00012F0800F4FEA83036A4A4FF001011A4440 -:101E0000136814BF43F4803343F00103136080224E -:101E1000654B196000215A60D4F8BC21BB6442F0C4 -:101E200001129960F964C4F8BC21D4F8B03143F4CC -:101E30008033C4F8B031D4F8B031002BFBD1D4F8E2 -:101E4000AC31002B7FF410AE5DE65B4A01215B4BA9 -:101E5000127818461A70FFF79FFDD4F8AC31002BAA -:101E60007FF402AE4FE6564B586800283FF4F6AEBA -:101E7000090C1FFA82FE04E00C33586800283FF476 -:101E8000EDAE1D888D42F7D15D887545F4D1090A04 -:101E9000120C03290CBF01781989914228BF114601 -:101EA000FFF77AFD2BE6D3F8482122F08002C3F831 -:101EB000482103B0BDE8F08FCA077FF51CAE18E6D5 -:101EC000404D0120FB6029603F4B4049186059603C -:101ED0003F49D6F8B0016A6001F5005E40F00102AA -:101EE000A1F5005001F58055BB609860A1F58050C8 -:101EF000D860C6F8B02119615D61C3F818E0D4F864 -:101F0000B031002BFBD1284A012048F28001FB644C -:101F100010604FF480305160D6F8BC11936041F0EE -:101F20000113BA64C6F8BC31D6F8B0310343C6F821 -:101F3000B031CBF80000D4F8B031002BFBD1DEE596 -:101F4000204C42F22100002524880D6084427FF459 -:101F50002BAE2049204C03C90D0C86282060A1809F -:101F6000A5717FF421AED2F8481150241B4841F0EE -:101F70008001C2F84811047016E61948FFF7F4FC16 -:101F800019E61848FFF7F0FC21E61748FFF7ECFCCC -:101F90001AE61648FFF7E8FC13E61548FFF7E4FCDD -:101FA0000CE600BFC0012E4020200020880D00203C -:101FB00088320020600D0020280400201804002032 -:101FC0008002002010040020002000208000070074 -:101FD000202400202004002080320020080400205B -:101FE0000031002000320020C0310020803100206C -:101FF00040310020002AA0F102022DE9F04714BF71 -:1020000000274FF00057022A01D9BDE8F0874FEAB8 -:10201000C01ADFF840900D4604460AEB090600217D -:1020200040229846304600F029FE012047EA05414B -:102030004AF80910C6F83880B060B8F1000FE4D053 -:10204000034BA0401C6820431860BDE8F08700BF28 -:10205000FC03002000300020002AA0F102022DE93C -:10206000F04714BF00274FF00057022A01D9BDE8FE -:10207000F0874022C501DFF84490884615440021CE -:1020800004461E4605EB090A504600F0F7FD012202 -:1020900047EA084145F80910CAF83860CAF808202C -:1020A000002EE4D004F11000034B82401868024374 -:1020B0001A60BDE8F08700BFFC030020003000205C -:1020C00012048160C36142F08002F0B442600127D3 -:1020D00001F5805601F5005501F5405401F58042A7 -:1020E0000760C660056144618261F0BC704700BF53 -:1020F000831E022B00D9704730B4064B00F1100448 -:1021000001250A4603EBC01005FA04F130BCFFF7C5 -:10211000FDBB00BF40300020831E022B00D970475A -:1021200010B4054B01240A4604FA00F103EBC01079 -:102130005DF8044BFFF7EABB00300020124A134B56 -:10214000D2F8200220F07F40984210B584B002D827 -:1021500000EB800040000E4C01A90A2200F0C8FBF1 -:1021600001A90023204611F8012B01333AB10A2BB3 -:1021700020F8022FF7D11623237004B010BD5B00A6 -:10218000DBB2237004B010BD00441F407F9698005E -:10219000980300204368C269C3F30E43054930B475 -:1021A000C3F14003044C002521F8123024F81250EA -:1021B00030BC7047F80B0020F00B0020F8B5154B31 -:1021C0001B783BB903F0FF04134B1B7813B1134D7D -:1021D0002A8802B9F8BD124F2346124EC2F580720A -:1021E0003978114806EB411600EB01213046FFF724 -:1021F00067FF31460420FFF77BFF3B780133DBB2FA -:10220000062B98BF3B704FF0000388BF3C702B80BB -:10221000F8BD00BF2C0B002028040020600C00201B -:102220002D0B0020800C00202C040020704700BFE4 -:102230000021E0222048F8B50C46204E204D00F049 -:102240001DFD204F2146204B6022347028461F4E32 -:102250001C8000F013FD23462246102102203C6022 -:10226000BC803460B480FFF7F7FE2246184B402153 -:102270000320FFF7BFFE2346224640210420FFF73C -:10228000EBFE2346402228461249FFF719FF294654 -:102290000320FFF741FF104B4A2210491048086005 -:1022A000C3F88440C3F88020D3F8482142F08072FC -:1022B000C3F84821F8BD00BF800C00202D0B002082 -:1022C000000C0020F80B0020600C0020F00B002018 -:1022D000450B0000300B002000002E40F8030020CA -:1022E0006D0B0000024A034B10881B88C01A704710 -:1022F000F80B0020F00B002010B4EFF3108272B640 -:10230000437F33B9017F012908D0032910D001236D -:10231000437702B962B65DF8044B7047114C2168EF -:10232000A1B1114943610B68086083615861EEE710 -:102330000E4C216881B10E4943610B6808608361CE -:1023400058610C4B4FF080511960E0E7064B41613A -:10235000816120601860DAE7054B4161816120608E -:102360001860EEE7740D0020700D0020640D002051 -:10237000680D002004ED00E010B4047F4160022CE1 -:10238000C26003D05DF8044BFFF7B6BF83685DF809 -:10239000044B184770B5EFF3108172B60C4C2368EC -:1023A0008BB10C4E00255A6922607AB1956101B952 -:1023B00062B65D7718469B689847EFF3108172B656 -:1023C0002368002BEFD101B962B670BD3260EEE731 -:1023D000640D0020680D0020FFF7DCBF184A30B400 -:1023E0001468002C28D0036821688B420FD2CB1AC6 -:1023F00000218460C1602360E0601060022330BC73 -:10240000037570470360144611688B4208D3A268B5 -:102410005B1A002AF6D18260C4600360A060EDE719 -:10242000D568CB1A82600222C560E060C16888600E -:102430002360027530BC70478460C4601060DDE7C3 -:102440006C0D0020F8B5224E34682CB32368002BA5 -:102450003AD11D461F4F04E03468ECB12368002BCD -:1024600032D1A36803B1DD6020693360036825754C -:102470001B68BB4221D1037F4560022BC46020D082 -:10248000FFF73AFF6368002BE6D023602046FFF792 -:10249000A5FF3468002CE1D1EFF3108372B60E4A29 -:1024A00000211068116003B962B628B18468FFF793 -:1024B00095FF20460028F9D1F8BD22460021984713 -:1024C000E0E783689847DDE7013B2360E4E700BF6E -:1024D0006C0D0020290D00007C0D0020044A054BE6 -:1024E0001168054A1960136801331360FFF7AABF2A -:1024F000041000E0840D0020880D002070B5214CF0 -:10250000237883B9204B01221B7822701BBB1F4B01 -:102510001B78002B29D11E4B00211A68217012B1A3 -:10252000EFF3058202B170BDEFF3108072B61A6846 -:10253000F2B1184C2178D9B90126556926701D6071 -:10254000D5B1A96100B962B60025936810465577E8 -:102550009847257070BDFFF7C5FE0028D7D000F062 -:102560005FFB0A4B1B78002BD5D000F045FBD2E770 -:102570000028D8D162B670BD074B1D600028E3D19A -:10258000E1E700BF800D0020B8030020A80D002067 -:10259000740D0020780D0020700D00208C4A8D4BAA -:1025A00090422DE9F0438C4D5C699969EF681DD923 -:1025B0008A4B984240F20181894B40F22766DFF84E -:1025C00060E20344874D1A0AAEFB0232D30903EBE3 -:1025D000830303EB830202F2E243B34228BF334694 -:1025E000A3F54873A5FB0336F60804E07E4EB0421F -:1025F00094BF06260E26774A07F01F0ED2F88030C9 -:10260000B64543F0C003C2F880300AD2724B27F0BF -:102610001F071A463743DF601368002BFCDA07F008 -:102620001F0E14F000732ED1704D714AD5F810C0F2 -:1026300015460CEA0202AA420ABF4FF0C0534FF4FB -:102640008052002284EA030515F0605F06D024F072 -:10265000605403F060535F4D1C436C6181EA0203D8 -:1026600013F4405F08D05B4B21F4405111431A46EC -:102670009961936C1D07FCD444F00074554A546171 -:10268000936C9906FCD401215A4D0A4601FB02F3D2 -:1026900000FB03F3AB4209D8072A00F28480013221 -:1026A00001FB02F300FB03F3AB42F5D95248534D53 -:1026B0001844A5FB0030030D6C2B79D8352B7ED840 -:1026C000DFF8608136234E48DFF820C14D4DDCF83D -:1026D0000090B0FBF2F009EA05054545B0FBF1F0CA -:1026E0000BD043F400534FF480586546CCF800807B -:1026F000CCF800302B68002BFCDADFF8D8C0013AA8 -:10270000DCF8103003F00703934207D002F0070211 -:102710006546CCF81020AB6CDB03FCD40139890290 -:1027200084EA010313F4E05F0AD02A4B24F4E05456 -:1027300001F4E0511A460C435C61936C9907FCD498 -:10274000314B32490344DB09A1FB0331090B042956 -:1027500028BF04214B1E1B0284EA030212F4407FAF -:1027600006D024F4407403F440731A4A1C435461A5 -:10277000184B24F000741A465C61936C9B06FCD4E1 -:10278000B0FBF1F1224A7645224B1060196008D265 -:10279000114B27F01F071A463743DF601368002BE1 -:1027A000FCDABDE8F083042980D8013101226DE70D -:1027B000DFF874806C23184886E712261BE7174859 -:1027C000DFF8688000FB03F043EA08087CE700BFFD -:1027D00000A4781F00C00F40000008400046C3233B -:1027E00000BA3CDC1F85EB5100366E0100800D40C5 -:1027F00040300080FFB19F26808D5B00819F5E1678 -:1028000000B29F267F3001807FD1F0089F10E50045 -:10281000B4030020B003002000643F4D001BB7004C -:1028200023B24C00362000806C2000800020008005 -:10283000002852D02DE9F04F814683B0274C01206B -:10284000274D284E54E8003F2A68316844E80003C9 -:10285000002BF7D1244F4FF47A7E2448D7F800C0DC -:10286000BB4607F1C6470368C1EB0C0107F5DE174D -:1028700007F67F67A7FB03C3BA4601279B0CB1FB92 -:10288000F3F30EFB023854E8003F2A68316844E84D -:102890000073002BF7D1DBF800C04FF47A7E036899 -:1028A0000EFB02F2C1EB0C01AAFB033EC8EB0203D4 -:1028B0004FEA9E42B1FBF2F1CA18B2F57A7F07D314 -:1028C000B9F1010908F57A78DDD103B0BDE8F08FE0 -:1028D0000190FFF713FE0198D5E770478C32002076 -:1028E000880D0020840D0020041000E0B4030020B7 -:1028F000F0B44E1E0025374600E00135B0FBF2F380 -:1029000002FB130000F13704092800F13000E4B2A3 -:1029100098BFC4B2184607F8014F002BEDD14A19F1 -:1029200053704DB1013316F8014F1778E81A37701C -:10293000834202F80149F5DB0846F0BC704700BF4E -:10294000A4484FF00F0CA44B826F42F47F02F0B505 -:1029500082670025D0F880204FF470469F4C4FF4DA -:10296000604E29464FF4806714432A46C0F88040E1 -:10297000A3F88C6148F2B826A3F88EC1A3F8905151 -:10298000B3F8880180B240F0F000A3F8880101EBB1 -:102990004100914B0131002540011C4604290344AC -:1029A000A3F804E0DF805A841A865A805A81DE81B7 -:1029B0005A82DA825A83DA83E9D1B4F888014FF077 -:1029C0000F0C874B4FF4704680B229464FF4604796 -:1029D0002A4640EA0C004FF4806EA4F88801B4F84F -:1029E000880180B240F47060A4F88801A3F88C617B -:1029F00048F2B826A3F88EC1A3F89051B3F8880125 -:102A000080B240F0F000A3F8880101EB4100744B64 -:102A10000131002540011C46042903449F80A3F88E -:102A200006E05A841A865A805A81DE815A82DA82F6 -:102A30005A83DA83E9D1B4F888014FF00F0C694B5F -:102A40004FF4704680B229464FF460472A4640EA68 -:102A50000C004FF4806EA4F88801B4F8880180B2AD -:102A600040F47060A4F88801A3F88C6148F2B8269D -:102A7000A3F88EC1A3F89051B3F8880180B240F05A -:102A8000F000A3F8880101EB4100564B013100250D -:102A900040011C46042903449F80A3F806E05A84A1 -:102AA0001A865A805A81DE815A82DA825A83DA8300 -:102AB000E9D1B4F888014FF00F0C4B4B4FF470473D -:102AC00080B229464FF460462A4640EA0C004FF493 -:102AD000806EA4F88801B4F8880180B240F4706078 -:102AE000A4F88801A3F88C7148F2B827A3F88EC126 -:102AF000A3F89051B3F8880180B240F0F000A3F839 -:102B0000880101EB4100384B013140011C4604298A -:102B100003449E80A3F806E05A841A865A805A819C -:102B2000DF815A82DA825A83DA83EAD1B4F88831B3 -:102B30000F27002241F201069BB245F6C05E114606 -:102B400043F226053B43A4F88831B4F888319BB2A0 -:102B500043F47063A4F888315001244B01320344DC -:102B6000042A99815981DF819E82A3F806E01980A9 -:102B700019829D81F0D100220F2741F2010645F60E -:102B8000C055114643F226045001194B013203444B -:102B9000042A99815981DF819E82DD801980198202 -:102BA0009C81F1D100220F2741F2010645F6C05564 -:102BB000114643F2260450010E4B01320344042A0D -:102BC00099815981DF819E82DD80198019829C81E3 -:102BD000F1D1F0BD00C00F4000C03D40000003FC3B -:102BE00000003E4000403E4000803E4000C01D408E -:102BF00000001E4000401E4038B5074B1C784CB109 -:102C0000064D55F8043F002BFBD09847631E13F088 -:102C1000FF04F6D138BD00BFA80D0020880D0020AC -:102C2000014B00221A707047B803002070B50F4E98 -:102C30000F4D761BB61018BF002405D0013455F88F -:102C4000043B9847A642F9D10A4E0B4D761B00F083 -:102C500063F8B61018BF002406D0013455F8043BC1 -:102C60009847A642F9D170BD70BD00BF48160060FC -:102C700048160060501600604816006070B4840763 -:102C800046D0541E002A41D0CDB2034602E0621E57 -:102C9000E4B3144603F8015B9A07F8D1032C2ED94C -:102CA000CDB245EA05250F2C45EA054519D903F1B2 -:102CB00010022646103E0F2E42F8105C42F80C5CC3 -:102CC00042F8085C42F8045C02F11002F2D8A4F168 -:102CD000100222F00F0204F00F041032032C1344F0 -:102CE0000DD91E462246043A032A46F8045BFAD858 -:102CF000221F22F003020432134404F003042CB117 -:102D0000C9B21C4403F8011BA342FBD170BC70473D -:102D100014460346C2E700BF5FF800F0E11500600B -:042D2000F8B500BF43 -:102D240000000042C8801F40B8821F400800000015 -:102D340000000042C4801F40B4821F400400000011 -:102D440000C0004224801F4014821F401000000075 -:102D540000C0004228801F4018821F40200000004D -:102D640000C000422C801F401C821F404000000015 -:102D740000C0004234801F4024821F400001000034 -:102D84000040004264811F4054831F40000400003F -:102D94000040004280811F4070831F4000000200F9 -:102DA400004000427C811F406C831F4000000100F2 -:102DB4000040004268811F4058831F400008000003 -:102DC400004000423C811F402C831F400100000052 -:102DD4000040004244811F4034831F40040000002F -:102DE4000040004240811F4030831F400200000029 -:102DF4000040004248811F4038831F400800000003 -:102E04000000004204811F40F4821F4000000400BF -:102E14000000004208811F40F8821F4000000800A3 -:102E24000000004218811F4008831F4000008000FA -:102E34000000004214811F4004831F400000400032 -:102E44000000004200811F40F0821F400000020089 -:102E540000000042FC801F40EC821F400000010083 -:102E64000000004224811F4014831F40000000041E -:102E74000000004228811F4018831F400000000802 -:102E8400000000421C811F400C831F400000000111 -:102E94000000004220811F4010831F4000000002F8 -:102EA40000000042EC801F40DC821F400010000044 -:102EB40000000042F0801F40E0821F40002000001C -:102EC4000000004234811F4024831F400000004062 -:102ED4000000004238811F4028831F40000000800A -:102EE4000080004294801F4084821F400000040040 -:102EF40000C0004290801F4080821F40000000807C -:102F040000800042A8801F4098821F40000080007B -:102F140000800042A4801F4094821F4000004000B3 -:102F2400004000426C811F405C831F400010000081 -:102F340000C0004230801F4020821F4080000000FB -:102F440000800042C8811F40B8831F4000800000F9 -:102F540000800042C4811F40B4831F400040000031 -:102F640000800042C0811F40B0831F400020000049 -:102F740000800042BC811F40AC831F400010000051 -:102F840000800042D0811F40C0831F400000020027 -:102F940000800042CC811F40BC831F400000010020 -:102FA4000001000068030020120000000006000079 -:102FB400000300200A000000000200002403002097 -:102FC4004300000000070000240300204300000029 -:102FD4000003000094030020000000000103090422 -:102FE4000C03002000000000020309047C030020FD -:102FF40000000000030309049803002000000000FF -:1030040000000000000000000000000004000000B8 -:103014000C000000090000000B0000000A00000082 -:103024000A060002020000400100000018035400D8 -:10303400650065006E007300790064007500690026 -:103044006E006F0009024300020100C0320904004F -:103054000001020201000524001001052401010100 -:10306400042402060524060001070582031000104B -:1030740009040100020A00000007050302400000E1 -:103084000705840240000000120100020200004013 -:10309400C0168304790201020301000016035500DF -:1030A4005300420020005300650072006900610073 -:1030B4006C000000040309040C030000000000007D -:1030C40000000000000000000000000000000000FC -:1030D4000029DE07007B9A170100000000000000B1 -:040000056000100087 -:00000001FF diff --git a/tools/test-rig-rpi.yaml b/tools/test-rig-rpi.yaml index 7078aad2..ed4f7f7d 100644 --- a/tools/test-rig-rpi.yaml +++ b/tools/test-rig-rpi.yaml @@ -8,15 +8,24 @@ components: name: rpi ssh: odrv net: homenet - can0: main_canbus - uart0: /dev/serial/by-id/[not-yet-used] + components: + - type: uart + name: uart0 + port: /dev/ttyS0 + connected-to: main_uart + - type: can + name: can0 + interface: can0 + connected-to: odrive.can + - {type: gpio, num: 20} # need to specify GPIOs explicitly for the generalpurpose type + - {type: gpio, num: 26} - type: programmer name: The Blue STLink/v2 id: '\x53\x3f\x75\x06\x49\x3f\x49\x51\x44\x54\x19\x3f' - type: odrive - name: ODrive + name: odrive board-version: v3.6-58V serial-number: "20703595524B" brake-resistance: 0.47 @@ -40,15 +49,35 @@ components: max-voltage: 40 - type: encoder - name: real_encoder_0 - cpr: 8192 - max-rpm: 7000 - - - type: encoder - name: real_encoder_1 + name: real_encoder cpr: 8192 max-rpm: 7000 - type: teensy name: teensy - + +connections: + - ['odrive.can', 'rpi.can0'] + - ['teensy.program', 'rpi.gpio26'] + - ['teensy.gpio11', 'rpi.uart0.tx'] + - ['teensy.gpio12', 'rpi.uart0.rx'] + - ['teensy.gpio10', 'odrive.gpio1'] + - ['teensy.gpio9', 'odrive.gpio2'] + - ['teensy.gpio8', 'odrive.gpio3'] + - ['teensy.gpio7', 'odrive.gpio4'] + - ['teensy.gpio15', 'odrive.gpio5'] + - ['teensy.gpio16', 'odrive.gpio6'] + - ['teensy.gpio17', 'odrive.gpio7'] + - ['teensy.gpio18', 'odrive.gpio8'] + - ['teensy.gpio4', 'rpi.gpio20'] + - ['teensy.gpio23', 'odrive.encoder0.z'] + - ['teensy.gpio22', 'odrive.encoder0.a'] + - ['teensy.gpio21', 'odrive.encoder0.b'] + - ['teensy.gpio20', 'odrive.encoder1.z'] + - ['teensy.gpio19', 'odrive.encoder1.a'] + - ['teensy.gpio18', 'odrive.encoder1.b'] + - ['teensy.gpio0', 'real_encoder.z'] + - ['teensy.gpio1', 'real_encoder.a'] + - ['teensy.gpio2', 'real_encoder.b'] + - ['odrive.axis0', 'D5065-270KV_0'] + - ['D5065-270KV_0', 'real_encoder'] From 237a50bfc94ba1c0dd51e73506fd04000a43305c Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 21 Apr 2020 12:25:18 +0200 Subject: [PATCH 322/549] add step/dir input tests --- Firmware/MotorControl/axis.cpp | 3 +- Firmware/MotorControl/axis.hpp | 8 ++- tools/odrive/tests/step_dir_test.py | 92 ++++++++++++++++++++++++ tools/odrive/tests/test_runner.py | 107 ++++++++++++++++------------ tools/test-rig-rpi.yaml | 18 ++--- 5 files changed, 171 insertions(+), 57 deletions(-) create mode 100644 tools/odrive/tests/step_dir_test.py diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 5635c70c..0e6f64dc 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -338,7 +338,7 @@ bool Axis::run_closed_loop_control_loop() { return true; }); - set_step_dir_active(false); + set_step_dir_active(config_.enable_step_dir && config_.step_dir_always_on); return check_for_errors(); } @@ -428,6 +428,7 @@ bool Axis::run_idle_loop() { // run_control_loop ignores missed modulation timing updates // if and only if we're in AXIS_STATE_IDLE safety_critical_disarm_motor_pwm(motor_); + set_step_dir_active(config_.enable_step_dir && config_.step_dir_always_on); run_control_loop([this]() { return true; }); diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 50e72d00..9f99f33a 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -68,8 +68,14 @@ public: bool startup_closed_loop_control = false; // ' + str(o.num)) for t, i, o, n in self.gpio_tuples) + ')' + def __eq__(self, obj): + return isinstance(obj, ProxiedComponent) and (self.impl == obj.impl) # and (self.gpio_tuples == obj.gpio_tuples) + def prepare(self): for teensy, gpio_in, gpio_out, gpio_noise_enable in self.gpio_tuples: teensy.add_route(gpio_in, gpio_out, gpio_noise_enable) @@ -480,24 +488,26 @@ def run_shell(command_line, logger, env=None, timeout=None): logger.error(result.stdout.decode(sys.stdout.encoding)) raise TestFailed("command {} failed".format(command_line)) -def select_params(param_options): - params = [] +def get_combinations(param_options): + if len(param_options) > 0: + param = param_options[0] + if not is_list_like(param): + param = [param] + + for part1, part2 in itertools.product(param, get_combinations(param_options[1:]) if (len(param_options) > 1) else [()]): + if not isinstance(part1, tuple): + part1 = (part1,) + yield part1 + part2 + +def select_params(param_options): # Select parameters from the resource list # (this could be arbitrarily complex to improve parallelization of the tests) - for param in param_options: - if is_list_like(param): - if len(param) == 0: - return None - else: - selection = param[0] - if is_list_like(selection): - params = params + list(selection) - else: - params.append(selection) - else: - params.append(param) - return params + for combination in get_combinations(param_options): + if all_unique(combination): + return list(combination) + + return None def run(tests): if not isinstance(tests, list): @@ -590,34 +600,37 @@ testrig = TestRig(test_rig_yaml, logger) if args.setup_host: - def export_gpio(gpio): - if not os.path.isdir("/sys/class/gpio/gpio{}".format(gpio)): + for gpio in testrig.get_components(LinuxGpioComponent): + num = gpio.num + logger.debug('exporting GPIO ' + str(num) + ' to user space...') + if not os.path.isdir("/sys/class/gpio/gpio{}".format(num)): with open("/sys/class/gpio/export", "w") as fp: - fp.write(str(gpio)) - os.chmod("/sys/class/gpio/gpio{}/value".format(gpio), stat.S_IROTH | stat.S_IWOTH) - os.chmod("/sys/class/gpio/gpio{}/direction".format(gpio), stat.S_IROTH | stat.S_IWOTH) + fp.write(str(num)) + os.chmod("/sys/class/gpio/gpio{}/value".format(num), stat.S_IROTH | stat.S_IWOTH) + os.chmod("/sys/class/gpio/gpio{}/direction".format(num), stat.S_IROTH | stat.S_IWOTH) - # TODO: read configuration from yaml file - export_gpio(20) # connected to Teensy GPIO - export_gpio(26) # connected to Teensy Program pin + for port in testrig.get_components(SerialPortComponent): + logger.debug('changing permissions on ' + port.yaml['port'] + '...') + os.chmod(port.yaml['port'], stat.S_IROTH | stat.S_IWOTH) - os.chmod("/dev/ttyS0", stat.S_IROTH | stat.S_IWOTH) - - # This breaks the retarded teensy loader that shows up on every compile - if not os.path.isfile('/usr/share/arduino/hardware/tools/teensy_post_compile_old'): - os.rename('/usr/share/arduino/hardware/tools/teensy_post_compile', '/usr/share/arduino/hardware/tools/teensy_post_compile_old') - with open('/usr/share/arduino/hardware/tools/teensy_post_compile', 'w') as scr: - scr.write('#!/bin/bash\n') - scr.write('if [ "$ARDUINO_COMPILE_DESTINATION" != "" ]; then\n') - scr.write(' cp -r ${2#-path=}/*.ino.hex ${ARDUINO_COMPILE_DESTINATION}\n') - scr.write('fi\n') - os.chmod('/usr/share/arduino/hardware/tools/teensy_post_compile', stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) + if len(list(testrig.get_components(TeensyComponent))): + # This breaks the annoying teensy loader that shows up on every compile + logger.debug('modifying teensyduino installation...') + if not os.path.isfile('/usr/share/arduino/hardware/tools/teensy_post_compile_old'): + os.rename('/usr/share/arduino/hardware/tools/teensy_post_compile', '/usr/share/arduino/hardware/tools/teensy_post_compile_old') + with open('/usr/share/arduino/hardware/tools/teensy_post_compile', 'w') as scr: + scr.write('#!/bin/bash\n') + scr.write('if [ "$ARDUINO_COMPILE_DESTINATION" != "" ]; then\n') + scr.write(' cp -r ${2#-path=}/*.ino.hex ${ARDUINO_COMPILE_DESTINATION}\n') + scr.write('fi\n') + os.chmod('/usr/share/arduino/hardware/tools/teensy_post_compile', stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) # Bring up CAN interface(s) for intf in testrig.get_components(CanInterfaceComponent): name = intf.yaml['interface'] - run_shell('ip link set dev {} down'.format(intf)) - run_shell('ip link set dev {} type can bitrate 250000'.format(intf)) - run_shell('ip link set dev {} type can loopback off'.format(intf)) - run_shell('ip link set dev {} up'.format(intf)) + logger.debug('bringing up {}...'.format(name)) + run_shell('ip link set dev {} down'.format(name), logger) + run_shell('ip link set dev {} type can bitrate 250000'.format(name), logger) + run_shell('ip link set dev {} type can loopback off'.format(name), logger) + run_shell('ip link set dev {} up'.format(name), logger) diff --git a/tools/test-rig-rpi.yaml b/tools/test-rig-rpi.yaml index ed4f7f7d..3321263f 100644 --- a/tools/test-rig-rpi.yaml +++ b/tools/test-rig-rpi.yaml @@ -17,12 +17,13 @@ components: name: can0 interface: can0 connected-to: odrive.can - - {type: gpio, num: 20} # need to specify GPIOs explicitly for the generalpurpose type + - {type: gpio, num: 19} # need to specify GPIOs explicitly for the generalpurpose type + - {type: gpio, num: 20} - {type: gpio, num: 26} - - type: programmer - name: The Blue STLink/v2 - id: '\x53\x3f\x75\x06\x49\x3f\x49\x51\x44\x54\x19\x3f' +# - type: programmer +# name: The Blue STLink/v2 +# id: '\x53\x3f\x75\x06\x49\x3f\x49\x51\x44\x54\x19\x3f' - type: odrive name: odrive @@ -65,11 +66,12 @@ connections: - ['teensy.gpio9', 'odrive.gpio2'] - ['teensy.gpio8', 'odrive.gpio3'] - ['teensy.gpio7', 'odrive.gpio4'] - - ['teensy.gpio15', 'odrive.gpio5'] - - ['teensy.gpio16', 'odrive.gpio6'] - - ['teensy.gpio17', 'odrive.gpio7'] - - ['teensy.gpio18', 'odrive.gpio8'] + - ['teensy.gpio14', 'odrive.gpio5'] + - ['teensy.gpio15', 'odrive.gpio6'] + - ['teensy.gpio16', 'odrive.gpio7'] + - ['teensy.gpio17', 'odrive.gpio8'] - ['teensy.gpio4', 'rpi.gpio20'] + - ['teensy.gpio5', 'rpi.gpio19'] - ['teensy.gpio23', 'odrive.encoder0.z'] - ['teensy.gpio22', 'odrive.encoder0.a'] - ['teensy.gpio21', 'odrive.encoder0.b'] From 574190e433dba1c0568db3a30a87eab2a73eb4c7 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 21 Apr 2020 20:39:10 -0400 Subject: [PATCH 323/549] Change trap_traj timer to be more robust --- Firmware/MotorControl/controller.cpp | 11 +++++------ Firmware/MotorControl/controller.hpp | 1 - Firmware/MotorControl/trapTraj.hpp | 2 ++ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index c88fe4a7..498e03e5 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -54,7 +54,7 @@ void Controller::move_to_pos(float goal_point) { axis_->trap_.config_.vel_limit, axis_->trap_.config_.accel_limit, axis_->trap_.config_.decel_limit); - traj_start_loop_count_ = axis_->loop_counter_; + axis_->trap_.t_ = 0.0f; trajectory_done_ = false; } @@ -198,10 +198,8 @@ bool Controller::update(float* current_setpoint_output) { // Avoid updating uninitialized trajectory if (trajectory_done_) break; - // Note: uint32_t loop count delta is OK across overflow - // Beware of negative deltas, as they will not be well behaved due to uint! - float t = (axis_->loop_counter_ - traj_start_loop_count_) * current_meas_period; - if (t > axis_->trap_.Tf_) { + + if (axis_->trap_.t_ > axis_->trap_.Tf_) { // Drop into position control mode when done to avoid problems on loop counter delta overflow config_.control_mode = CTRL_MODE_POSITION_CONTROL; pos_setpoint_ = input_pos_; @@ -209,10 +207,11 @@ bool Controller::update(float* current_setpoint_output) { current_setpoint_ = 0.0f; trajectory_done_ = true; } else { - TrapezoidalTrajectory::Step_t traj_step = axis_->trap_.eval(t); + TrapezoidalTrajectory::Step_t traj_step = axis_->trap_.eval(axis_->trap_.t_); pos_setpoint_ = traj_step.Y; vel_setpoint_ = traj_step.Yd; current_setpoint_ = traj_step.Ydd * config_.inertia; + axis_->trap_.t_ += current_meas_period; } anticogging_pos = pos_setpoint_; // FF the position setpoint instead of the pos_estimate } break; diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index afbb4edd..56a60020 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -117,7 +117,6 @@ public: bool input_pos_updated_ = false; - uint32_t traj_start_loop_count_ = 0; bool trajectory_done_ = true; bool anticogging_valid_ = false; diff --git a/Firmware/MotorControl/trapTraj.hpp b/Firmware/MotorControl/trapTraj.hpp index 6c343c42..fd142da5 100644 --- a/Firmware/MotorControl/trapTraj.hpp +++ b/Firmware/MotorControl/trapTraj.hpp @@ -47,6 +47,8 @@ public: float Tf_; float yAccel_; + + float t_; }; #endif \ No newline at end of file From a5d991e712c26d7957ad16b9f3d54224fc5159bc Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 21 Apr 2020 20:39:39 -0400 Subject: [PATCH 324/549] Clarify current_setpoint calculation --- Firmware/MotorControl/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 498e03e5..3b450277 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -160,7 +160,7 @@ bool Controller::update(float* current_setpoint_output) { float step = std::clamp(full_step, -max_step_size, max_step_size); vel_setpoint_ += step; - current_setpoint_ = step / current_meas_period * config_.inertia; + current_setpoint_ = (step / current_meas_period) * config_.inertia; } break; case INPUT_MODE_CURRENT_RAMP: { float max_step_size = std::abs(current_meas_period * config_.current_ramp_rate); From c2a5ce821a167aabc0ecfd5542b34fdac4e6ec3c Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 21 Apr 2020 20:39:48 -0400 Subject: [PATCH 325/549] Add input_modes.md --- docs/commands.md | 15 +++++++ docs/input_modes.md | 106 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 docs/input_modes.md diff --git a/docs/commands.md b/docs/commands.md index 2d26586a..c9769e8c 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -60,6 +60,21 @@ Possible values are: * `CTRL_MODE_CURRENT_CONTROL` * `CTRL_MODE_VOLTAGE_CONTROL` - this one is not normally used. +### Input Mode +The default input mode is `INPUT_MODE_PASSTHROUGH`. +Modes can be selected by changing `.controller.config.input_mode`. +Possible values are: +* `INPUT_MODE_INACTIVE` +* `INPUT_MODE_PASSTHROUGH` +* `INPUT_MODE_VEL_RAMP` +* `INPUT_MODE_POS_FILTER` +* `INPUT_MODE_MIX_CHANNELS` +* `INPUT_MODE_TRAP_TRAJ` +* `INPUT_MODE_CURRENT_RAMP` +* `INPUT_MODE_MIRROR` + +For more information, see [input_modes](input_modes.md). + # Control Commands * `.controller.input_pos = ` * `.controller.input_vel = ` diff --git a/docs/input_modes.md b/docs/input_modes.md new file mode 100644 index 00000000..ccb88206 --- /dev/null +++ b/docs/input_modes.md @@ -0,0 +1,106 @@ +# Input Modes +As of version ###, ODrive now intercepts the incoming commands and can apply filters to them. The old protocol values `pos_setpoint`, `vel_setpoint`, and `current_setpoint` are still used internally by the closed-loop cascade control, but the user cannot write to them directly. This allows us to condense the number of ways the ODrive accepts motion commands. The new commands are: + +* `.controller.config.input_mode` +* `.controller.input_pos` +* `.controller.input_vel` +* `.controller.input_current` + +The Input Modes currently valid are: +* `INPUT_MODE_INACTIVE` +* `INPUT_MODE_PASSTHROUGH` +* `INPUT_MODE_VEL_RAMP` +* `INPUT_MODE_POS_FILTER` +* `INPUT_MODE_MIX_CHANNELS` +* `INPUT_MODE_TRAP_TRAJ` +* `INPUT_MODE_CURRENT_RAMP` +* `INPUT_MODE_MIRROR` + +--- + +## INPUT_MODE_INACTIVE +Disable inputs. Setpoints retain their last value. + +## INPUT_MODE_PASSTHROUGH +Pass `input_xxx` through to `xxx_setpoint` directly. + +### Valid Inputs: +* `input_pos` +* `input_vel` +* `input_current` + +### Valid Control modes: +* `CTRL_MODE_VOLTAGE_CONTROL` +* `CTRL_MODE_CURRENT_CONTROL` +* `CTRL_MODE_VELOCITY_CONTROL` +* `CTRL_MODE_POSITION_CONTROL` + +## INPUT_MODE_VEL_RAMP +Ramps a velocity command from the current value to the target value. + +### Configuration Values: +* `.controller.config.vel_ramp_rate` [cpr/sec] +* `.controller.config.inertia` [A/(count/s^2))] + +### Valid inputs: +* `input_vel` + +### Valid Control Modes: +* `CTRL_MODE_VELOCITY_CONTROL` + +## INPUT_MODE_POS_FILTER +Implements a 2nd order position tracking filter. Inteded for use with step/dir interface, but can also be used with position-only commands. + +### Configuration Values: +* `.controller.config.input_filter_bandwidth` +* `.controller.config.inertia` + +### Valid inputs: +* `input_pos` + +### Valid Control modes: +* `CTRL_MODE_POSITION_CONTROL` + +## INPUT_MODE_MIX_CHANNELS +Not Implemented. + + +## INPUT_MODE_TRAP_TRAJ +Implementes an online trapezoidal trajectory planner. + +### Configuration Values: +* `.trap_traj.config.vel_limit` +* `.trap_traj.config.accel_limit` +* `.trap_traj.config.decel_limit` +* `.controller.config.inertia` + +### Valid Inputs: +* `input_pos` + +### Valid Control Modes: +* `CTRL_MODE_POSITION_CONTROL` + +## INPUT_MODE_CURRENT_RAMP +Ramp a current command from the current value to the target value. + +### Configuration Values: +* `.controller.config.current_ramp_rate` + +### Valid Inputs: +* `input_current` + +### Valid Control Modes: +* `CTRL_MODE_CURRENT_CONTROL` + +## INPUT_MODE_MIRROR +Implements "electronic mirroring". This is like electronic camming, but you can only mirror exactly the movements of the other motor, according to a fixed ratio + +### Configuration Values +* `.controller.config.axis_to_mirror` +* `.controller.config.mirror_ratio` + +### Valid Inputs +* None. Inputs are taken directly from the other axis encoder estimates + +### Valid Control modes +* `CTRL_MODE_POSITION_CONTROL` From f4f4be0d6dfdcad5f883a6937833007ae77323fc Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 21 Apr 2020 20:46:38 -0400 Subject: [PATCH 326/549] Replace select instances of pos_setpoint by input_pos in docs --- docs/ascii-protocol.md | 2 +- docs/getting-started.md | 6 +++--- docs/interfaces.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/ascii-protocol.md b/docs/ascii-protocol.md index a0757e2f..5e1dc236 100644 --- a/docs/ascii-protocol.md +++ b/docs/ascii-protocol.md @@ -131,7 +131,7 @@ Not all parameters can be accessed via the ASCII protocol but at least all param ``` * `property` name of the property, as seen in ODrive Tool * `value` text representation of the value to be written - * Example: `w axis0.controller.pos_setpoint -123.456` + * Example: `w axis0.controller.input_pos -123.456` #### System commands: * `ss` - Save config diff --git a/docs/getting-started.md b/docs/getting-started.md index df3a8ad9..623e85da 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -247,7 +247,7 @@ Let's get motor 0 up and running. The procedure for motor 1 is exactly the same, 2. Type `odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL` Enter. From now on the ODrive will try to hold the motor's position. If you try to turn it by hand, it will fight you gently. That is unless you bump up `odrv0.axis0.motor.config.current_lim`, in which case it will fight you more fiercely. If the motor begins to vibrate either immediately or after being disturbed you will need to [lower the controller gains](control.md). -3. Send the motor a new position setpoint. `odrv0.axis0.controller.pos_setpoint = 10000` Enter. The units are in encoder counts. +3. Send the motor a new position setpoint. `odrv0.axis0.controller.input_pos = 10000` Enter. The units are in encoder counts. 4. At this point you will probably want to [Properly tune](control.md) the motor controller in order to maximize system performance. ## Other control modes @@ -328,9 +328,9 @@ You can also execute a move with the [appropriate ascii command](ascii-protocol. To enable Circular position control, set `axis.controller.config.setpoints_in_cpr = True` This mode is useful for continuos incremental position movement. For example a robot rolling indefinitely, or an extruder motor or conveyor belt moving with controlled increments indefinitely. -In the regular position mode, the `pos_setpoint` would grow to a very large value and would lose precision due to floating point rounding. +In the regular position mode, the `input_pos` would grow to a very large value and would lose precision due to floating point rounding. -In this mode, the controller will try to track the position within only one turn of the motor. Specifically, `pos_setpoint` is expected in the range `[0, cpr-1]`, where `cpr` is the number of encoder counts in one revolution. If the `pos_setpoint` is incremented to outside this range (say via step/dir input), it is automatically wrapped around into the correct value. +In this mode, the controller will try to track the position within only one turn of the motor. Specifically, `input_pos` is expected in the range `[0, cpr-1]`, where `cpr` is the number of encoder counts in one revolution. If the `input_pos` is incremented to outside this range (say via step/dir input), it is automatically wrapped around into the correct value. Note that in this mode `encoder.pos_cpr` is used for feedback in stead of `encoder.pos_estimate`. If you try to increment the axis with a large step in one go that exceeds `cpr/2` steps, the motor will go to the same angle around the wrong way. This is also the case if there is a large disturbance. If you have an application where you would like to handle larger steps, you can use a virtual CPR that is an integer times larger than your encoder's actual CPR. Set `encoder.config.cpr = N * your_enc_cpr`, where N is some integer. Choose N to give you an appropriate circular space for your application. diff --git a/docs/interfaces.md b/docs/interfaces.md index ca9db588..13dccec4 100644 --- a/docs/interfaces.md +++ b/docs/interfaces.md @@ -115,13 +115,13 @@ Some GPIO pins can be used for PWM input, if they are not allocated to other fun Any of the numerical parameters that are writable from the ODrive Tool can be hooked up to a PWM input. As an example, we'll configure GPIO4 to control the angle of axis 0. We want the axis to move within a range of -1500 to 1500 encoder counts. -1. Make sure you're able control the axis 0 angle by writing to `odrv0.axis0.controller.pos_setpoint`. If you need help with this follow the [getting started guide](getting-started.md). +1. Make sure you're able control the axis 0 angle by writing to `odrv0.axis0.controller.input_pos`. If you need help with this follow the [getting started guide](getting-started.md). 2. If you want to control your ODrive with the PWM input without using anything else to activate the ODrive, you can configure the ODrive such that axis 0 automatically goes operational at startup. See [here](commands.md#startup-procedure) for more information. 3. In ODrive Tool, configure the PWM input mapping ``` In [1]: odrv0.config.gpio4_pwm_mapping.min = -1500 In [2]: odrv0.config.gpio4_pwm_mapping.max = 1500 - In [3]: odrv0.config.gpio4_pwm_mapping.endpoint = odrv0.axis0.controller._remote_attributes['pos_setpoint'] + In [3]: odrv0.config.gpio4_pwm_mapping.endpoint = odrv0.axis0.controller._remote_attributes['input_pos'] ``` Note: you can disable the input by setting `odrv0.config.gpio4_pwm_mapping.endpoint = None` 4. Save the configuration and reboot From 9b2df645f9760debfa08572c41cfee0e19bc2e78 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 21 Apr 2020 20:49:18 -0400 Subject: [PATCH 327/549] Replace select instances of vel_setpoint by input_vel in docs --- docs/commands.md | 4 ++-- docs/encoders.md | 2 +- docs/getting-started.md | 2 +- docs/hoverboard.md | 20 ++++++++++---------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index c9769e8c..ae5e9e8f 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -111,7 +111,7 @@ All variables that are part of a `[...].config` object can be saved to non-volat The ODrive can run without encoder/hall feedback, but there is a minimum speed, usually around a few hunderd RPM. However the units of this mode is different from when using an encoder. Velocities are not measured in counts/s, instead it is electrical rad/s. This also applies to the gains. For example, `vel_gain` is in units of `A / (rad/s)` instead of `A / (count/s)`. -To give an example, suppose you have a motor with 7 pole pairs, and you want to spin it at 3000 RPM. Then you would set the `vel_setpoint` to `3000 * 2*pi/60 * 7 = 2199 rad/s electrical`. +To give an example, suppose you have a motor with 7 pole pairs, and you want to spin it at 3000 RPM. Then you would set the `input_vel` to `3000 * 2*pi/60 * 7 = 2199 rad/s electrical`. Below are some suggested starting parameters that you can use. Note that you _must_ set the `pm_flux_linkage` correctly for sensorless mode to work. @@ -119,7 +119,7 @@ Below are some suggested starting parameters that you can use. Note that you _mu odrv0.axis0.controller.config.vel_gain = 0.01 odrv0.axis0.controller.config.vel_integrator_gain = 0.05 odrv0.axis0.controller.config.control_mode = 2 -odrv0.axis0.controller.vel_setpoint = 400 +odrv0.axis0.controller.input_vel = 400 odrv0.axis0.motor.config.direction = 1 odrv0.axis0.sensorless_estimator.config.pm_flux_linkage = 5.51328895422 / ( * ) ``` diff --git a/docs/encoders.md b/docs/encoders.md index cb038850..d0b60bdf 100644 --- a/docs/encoders.md +++ b/docs/encoders.md @@ -70,7 +70,7 @@ If calibration works, congratulations. Now try: * `.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL` -* `.controller.set_vel_setpoint(3000,0) ` +* `.controller.input_vel = 3000` let it loop a few times and then set: * `.requested_state = AXIS_STATE_IDLE` diff --git a/docs/getting-started.md b/docs/getting-started.md index 623e85da..fa532abf 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -337,7 +337,7 @@ If you try to increment the axis with a large step in one go that exceeds `cpr/2 ### Velocity control Set `axis.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL`.
    -You can now control the velocity with `axis.controller.vel_setpoint = 5000` [count/s]. +You can now control the velocity with `axis.controller.input_vel = 5000` [count/s]. ### Ramped velocity control Set `axis.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL`.
    diff --git a/docs/hoverboard.md b/docs/hoverboard.md index 730391fd..91b50764 100644 --- a/docs/hoverboard.md +++ b/docs/hoverboard.md @@ -112,9 +112,9 @@ The ODrive starts in idle (we will look at changing this later) so we can enable odrv0.save_configuration() odrv0.reboot() odrv0.axis0.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL -odrv0.axis0.controller.vel_setpoint = 120 +odrv0.axis0.controller.input_vel = 120 # Your motor should spin here -odrv0.axis0.controller.vel_setpoint = 0 +odrv0.axis0.controller.input_vel = 0 odrv0.axis0.requested_state = AXIS_STATE_IDLE ``` @@ -128,31 +128,31 @@ We also have to reboot to activate the PWM input. ```txt odrv0.config.gpio3_pwm_mapping.min = -200 odrv0.config.gpio3_pwm_mapping.max = 200 -odrv0.config.gpio3_pwm_mapping.endpoint = odrv0.axis0.controller._remote_attributes['vel_setpoint'] +odrv0.config.gpio3_pwm_mapping.endpoint = odrv0.axis0.controller._remote_attributes['input_vel'] odrv0.config.gpio4_pwm_mapping.min = -200 odrv0.config.gpio4_pwm_mapping.max = 200 -odrv0.config.gpio4_pwm_mapping.endpoint = odrv0.axis1.controller._remote_attributes['vel_setpoint'] +odrv0.config.gpio4_pwm_mapping.endpoint = odrv0.axis1.controller._remote_attributes['input_vel'] odrv0.save_configuration() odrv0.reboot() ``` -Now we can check that the sticks are writing to the velocity setpoint. Move the stick, print `vel_setpoint`, move to a different position, check again. +Now we can check that the sticks are writing to the velocity setpoint. Move the stick, print `input_vel`, move to a different position, check again. ```txt -In [1]: odrv0.axis1.controller.vel_setpoint +In [1]: odrv0.axis1.controller.input_vel Out[1]: 0.1904754638671875 -In [2]: odrv0.axis1.controller.vel_setpoint +In [2]: odrv0.axis1.controller.input_vel Out[2]: 0.1904754638671875 -In [3]: odrv0.axis1.controller.vel_setpoint +In [3]: odrv0.axis1.controller.input_vel Out[3]: 28.152389526367188 -In [4]: odrv0.axis1.controller.vel_setpoint +In [4]: odrv0.axis1.controller.input_vel Out[4]: 61.21905517578125 -In [5]: odrv0.axis1.controller.vel_setpoint +In [5]: odrv0.axis1.controller.input_vel Out[5]: -52.990474700927734 ``` From 36c7b4dbd7287cec83bd85f79908e8c8ef52da07 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 21 Apr 2020 20:49:26 -0400 Subject: [PATCH 328/549] Replace current_setpoint by input_current in docs --- docs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index fa532abf..146ed201 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -347,7 +347,7 @@ You can now control the velocity with `axis.controller.input_vel = 5000` [count/ ### Current control Set `axis.controller.config.control_mode = CTRL_MODE_CURRENT_CONTROL`.
    -You can now control the current with `axis.controller.current_setpoint = 3` [A]. +You can now control the current with `axis.controller.input_current = 3` [A]. Note: If you exceed `vel_limit` in current control mode, the current is reduced. To disable this, set `axis.controller.enable_current_vel_limit = False`. From af5ee564b3f37365a37f8db6fd47ab52e7a5168b Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 21 Apr 2020 20:58:18 -0400 Subject: [PATCH 329/549] Add some flashy media to input_modes.md --- docs/control.md | 2 +- docs/input_modes.md | 7 +++++++ docs/test.md | 1 - 3 files changed, 8 insertions(+), 2 deletions(-) delete mode 100644 docs/test.md diff --git a/docs/control.md b/docs/control.md index 8af90a91..267049fd 100644 --- a/docs/control.md +++ b/docs/control.md @@ -2,7 +2,7 @@ The motor controller is a cascaded style position, velocity and current control loop, as per the diagram below. When the control mode is set to position control, the whole loop runs. When running in velocity control mode, the position control part is removed and the velocity command is fed directly in to the second stage input. In current control mode, only the current controller is used. -![Cascaded pos vel I loops](https://github.com/madcowswe/ODrive/blob/master/docs/controller_with_ff.png?raw=true) +![Cascaded pos vel I loops](controller_with_ff.png) Each stage of the control loop is a variation on a [PID controller](https://en.wikipedia.org/wiki/PID_controller). A PID controller is a mathematical model that can be adapted to control a wide variety of systems. This flexibility is essential as it allows the ODrive to be used to control all kinds of mechanical systems. diff --git a/docs/input_modes.md b/docs/input_modes.md index ccb88206..32d7df86 100644 --- a/docs/input_modes.md +++ b/docs/input_modes.md @@ -51,6 +51,9 @@ Ramps a velocity command from the current value to the target value. ## INPUT_MODE_POS_FILTER Implements a 2nd order position tracking filter. Inteded for use with step/dir interface, but can also be used with position-only commands. +![POS Filter Response](secondOrderResponse.png) +Result of a step command from 1000 to 0 + ### Configuration Values: * `.controller.config.input_filter_bandwidth` * `.controller.config.inertia` @@ -68,6 +71,8 @@ Not Implemented. ## INPUT_MODE_TRAP_TRAJ Implementes an online trapezoidal trajectory planner. +![Trapezoidal Planner Response](TrapTrajPosVel.png) + ### Configuration Values: * `.trap_traj.config.vel_limit` * `.trap_traj.config.accel_limit` @@ -95,6 +100,8 @@ Ramp a current command from the current value to the target value. ## INPUT_MODE_MIRROR Implements "electronic mirroring". This is like electronic camming, but you can only mirror exactly the movements of the other motor, according to a fixed ratio +[![](http://img.youtube.com/vi/D4_vBtyVVzM/0.jpg)](http://www.youtube.com/watch?v=D4_vBtyVVzM "Example Mirroring Video") + ### Configuration Values * `.controller.config.axis_to_mirror` * `.controller.config.mirror_ratio` diff --git a/docs/test.md b/docs/test.md deleted file mode 100644 index 9daeafb9..00000000 --- a/docs/test.md +++ /dev/null @@ -1 +0,0 @@ -test From 7e2434f7d84b1085b106e320a429d192c84900ec Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 21 Apr 2020 20:11:33 +0200 Subject: [PATCH 330/549] add analog input tests (still require manual intervention to enable low pass filter) --- tools/odrive/tests/analog_input_test.py | 133 ++++++++++++++++++++++++ tools/odrive/tests/calibration_test.py | 3 - tools/odrive/tests/encoder_test.py | 3 - tools/odrive/tests/pwm_input_test.py | 6 +- tools/odrive/tests/test_runner.py | 51 +++++++-- tools/test-rig-rpi.yaml | 10 +- 6 files changed, 184 insertions(+), 22 deletions(-) create mode 100644 tools/odrive/tests/analog_input_test.py diff --git a/tools/odrive/tests/analog_input_test.py b/tools/odrive/tests/analog_input_test.py new file mode 100644 index 00000000..6a3c73e7 --- /dev/null +++ b/tools/odrive/tests/analog_input_test.py @@ -0,0 +1,133 @@ + +import test_runner + +import time +import math +import os +import numpy as np +import scipy.optimize + +from odrive.enums import errors +from test_runner import * + + +teensy_code_template = """ +void setup() { + analogWriteResolution(10); + // base clock of the PWM timer is 150MHz (on Teensy 4.0) + int freq = 150000000/1024; // ~146.5kHz PWM frequency + analogWriteFrequency({analog_out}, freq); + + // for filtering, assuming we have a 150 Ohm resistor, we need a capacitor of + // 1/(150000000/1024)*2*pi/150 = 2.85954744646751e-07 F, that's ~0.33uF + + //pinMode({lpf_enable}, OUTPUT); +} + +int i = 0; +void loop() { + i++; + i = i & 0x3ff; + if (digitalRead({analog_reset})) + i = 0; + analogWrite({analog_out}, i); + delay(1); +} +""" + + +def fit_sawtooth(data, min_val, max_val, period, range): + """ + Returns the average absolute error and the number of outliers + """ + func = lambda x, a: np.mod((max_val - min_val) / a * (x - a/2) - min_val, max_val - min_val) + min_val + params = scipy.optimize.curve_fit(func, data[:,0], data[:,1], [period])[0] + diffs = data[:,1] - func(data[:,0], *params) + return np.abs(diffs).mean(), np.count_nonzero((diffs > range) | (diffs < -range)) + + +class TestAnalogInput(): + """ + Verifies the Analog input. + + The Teensy generates a PWM signal with a duty cycle that follows a sawtooth signal + with a period of 1 second. The signal should be connected to the ODrive's + analog input through a low-pass-filter. + + ___ ___ + Teensy PWM ----|___|-------o---------|___|----- ODrive Analog Input + 150 Ohm | 150 Ohm + === + | 330nF + | + GND + + """ + + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + for odrive_gpio_num, odrive_gpio in [(2, odrive.gpio3), (3, odrive.gpio4)]: + analog_out_options = [] + lpf_gpio = [gpio for lpf in testrig.get_connected_components(odrive_gpio, LowPassFilterComponent) + for gpio in testrig.get_connected_components(lpf.en, LinuxGpioComponent)] + for teensy_gpio in testrig.get_connected_components(odrive_gpio, TeensyGpio): + teensy = teensy_gpio.parent + analog_reset_options = [] + for gpio in teensy.gpios: + for local_gpio in testrig.get_connected_components(gpio, LinuxGpioComponent): + analog_reset_options.append((gpio, local_gpio)) + analog_out_options.append((teensy, teensy_gpio, analog_reset_options)) + yield (odrive, lpf_gpio, odrive_gpio_num, analog_out_options) + + + def run_test(self, odrive: ODriveComponent, lpf_enable: LinuxGpioComponent, analog_in_num: int, teensy: TeensyComponent, teensy_analog_out: Component, teensy_analog_reset: Component, analog_reset_gpio: LinuxGpioComponent, logger: Logger): + code = teensy_code_template.replace("{analog_out}", str(teensy_analog_out.num)).replace("{analog_reset}", str(teensy_analog_reset.num)) #.replace("lpf_enable", str(lpf_enable.num)) + teensy.compile_and_program(code) + analog_reset_gpio.config(output=True) + analog_reset_gpio.write(True) + lpf_enable.config(output=True) + lpf_enable.write(False) + + logger.debug("Set up analog input...") + + min_val = -20000 + max_val = 20000 + + analog_mapping = [ + None, #odrive.handle.config.gpio1_analog_mapping, + None, #odrive.handle.config.gpio2_analog_mapping, + odrive.handle.config.gpio3_analog_mapping, + odrive.handle.config.gpio4_analog_mapping, + None, #odrive.handle.config.gpio5_analog_mapping, + ][analog_in_num] + + odrive.unuse_gpios() + analog_mapping.endpoint = odrive.handle.axis0.controller._remote_attributes['input_pos'] + analog_mapping.min = min_val + analog_mapping.max = max_val + odrive.save_config_and_reboot() + + + logger.debug("Log test_property...") + log_x = [] + log_y = [] + start = time.monotonic() + analog_reset_gpio.write(False) + while time.monotonic() - start < 5.0: + log_x.append(time.monotonic() - start) + log_y.append(odrive.handle.axis0.controller.input_pos) + + + # Expect mean error to be at most 2% (of the full scale). + # Expect there to be less than 1% outliers, where an outlier is anything that is more than 5% (of full scale) away from the expected value. + full_range = abs(max_val - min_val) + data = np.array([log_x, log_y]).transpose() + mean_error, n_outliers = fit_sawtooth(data, min_val, max_val, 1.05, full_range * 0.05) + + test_assert_eq(mean_error, 0, range = full_range * 0.02) + test_assert_eq(n_outliers, 0, range = len(log_x) * 0.01) + + + +if __name__ == '__main__': + test_runner.run(TestAnalogInput()) diff --git a/tools/odrive/tests/calibration_test.py b/tools/odrive/tests/calibration_test.py index 595b3b92..3cd60740 100644 --- a/tools/odrive/tests/calibration_test.py +++ b/tools/odrive/tests/calibration_test.py @@ -9,9 +9,6 @@ from fibre.utils import Logger from test_runner import * from odrive.enums import * -def modpm(val, range): - return ((val + (range / 2)) % range) - (range / 2) - class TestMotorCalibration(): """ diff --git a/tools/odrive/tests/encoder_test.py b/tools/odrive/tests/encoder_test.py index 016f39da..d1ffa061 100644 --- a/tools/odrive/tests/encoder_test.py +++ b/tools/odrive/tests/encoder_test.py @@ -37,9 +37,6 @@ void loop() { """ -def modpm(val, range): - return ((val + (range / 2)) % range) - (range / 2) - class TestIncrementalEncoder(): def get_test_cases(self, testrig: TestRig): diff --git a/tools/odrive/tests/pwm_input_test.py b/tools/odrive/tests/pwm_input_test.py index fc603eb3..e1dbddd9 100644 --- a/tools/odrive/tests/pwm_input_test.py +++ b/tools/odrive/tests/pwm_input_test.py @@ -37,10 +37,6 @@ void loop() { """ - -def modpm(val, range): - return ((val + (range / 2)) % range) - (range / 2) - class TestPwmInput(): """ Verifies the PWM input. @@ -125,7 +121,7 @@ class TestPwmInput(): test_assert_eq(min_val, with_min, range = step_size) test_assert_eq(max_val, with_max, range = step_size) - def run_test(self, odrive: ODriveComponent, teensy: TeensyComponent, teensy_gpio1: int, teensy_gpio2: int, teensy_gpio3: int, teensy_gpio4: int, logger: Logger): + def run_test(self, odrive: ODriveComponent, teensy: TeensyComponent, teensy_gpio1: Component, teensy_gpio2: Component, teensy_gpio3: Component, teensy_gpio4: Component, logger: Logger): # TODO: test each GPIO separately setup_code = "\n".join(" pinMode(" + str(gpio.num) + ", OUTPUT);" for gpio in [teensy_gpio1, teensy_gpio2, teensy_gpio3, teensy_gpio4]) diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index d76b32ae..82298f3f 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -65,6 +65,9 @@ def all_unique(lst): seen = list() return not any(i in seen or seen.append(i) for i in lst) +def modpm(val, range): + return ((val + (range / 2)) % range) - (range / 2) + # Test Components -------------------------------------------------------------# class Component(object): @@ -100,7 +103,8 @@ class ODriveComponent(Component): logger.debug('waiting for {} ({})'.format(self.yaml['name'], self.yaml['serial-number'])) self.handle = odrive.find_any( - path="usb", serial_number=self.yaml['serial-number'], timeout=30)#, printer=print) + path="usb", serial_number=self.yaml['serial-number'], timeout=60)#, printer=print) + assert(self.handle) #for axis_idx, axis_ctx in enumerate(self.axes): # axis_ctx.handle = self.handle.__dict__['axis{}'.format(axis_idx)] for encoder_idx, encoder_ctx in enumerate(self.encoders): @@ -109,6 +113,17 @@ class ODriveComponent(Component): for axis_idx, axis_ctx in enumerate(self.axes): axis_ctx.handle = self.handle.__dict__['axis{}'.format(axis_idx)] + def unuse_gpios(self): + self.handle.config.enable_uart = False + self.handle.axis0.config.enable_step_dir = False + self.handle.axis1.config.enable_step_dir = False + self.handle.config.gpio1_pwm_mapping.endpoint = None + self.handle.config.gpio2_pwm_mapping.endpoint = None + self.handle.config.gpio3_pwm_mapping.endpoint = None + self.handle.config.gpio4_pwm_mapping.endpoint = None + self.handle.config.gpio3_analog_mapping.endpoint = None + self.handle.config.gpio4_analog_mapping.endpoint = None + def save_config_and_reboot(self): self.handle.save_configuration() try: @@ -302,6 +317,15 @@ class TeensyComponent(Component): self.compile(code_fp.name, hex_fp.name) self.program(hex_fp.name, logger) +class LowPassFilterComponent(Component): + def __init__(self, parent: Component): + Component.__init__(self, parent) + self.en = Component(self) + + def get_subcomponents(self): + yield 'en', self.en + + class ProxiedComponent(Component): def __init__(self, impl, *gpio_tuples): """ @@ -348,6 +372,8 @@ class TestRig(): add_component(component_yaml['name'], MotorComponent(component_yaml)) elif component_yaml['type'] == 'encoder': add_component(component_yaml['name'], EncoderComponent(self, component_yaml)) + elif component_yaml['type'] == 'lpf': + add_component(component_yaml['name'], LowPassFilterComponent(self)) else: logger.warn('test rig has unsupported component ' + component_yaml['type']) continue @@ -490,15 +516,20 @@ def run_shell(command_line, logger, env=None, timeout=None): def get_combinations(param_options): - if len(param_options) > 0: - param = param_options[0] - if not is_list_like(param): - param = [param] - - for part1, part2 in itertools.product(param, get_combinations(param_options[1:]) if (len(param_options) > 1) else [()]): - if not isinstance(part1, tuple): - part1 = (part1,) - yield part1 + part2 + if isinstance(param_options, tuple): + if len(param_options) > 0: + for part1, part2 in itertools.product( + get_combinations(param_options[0]), + get_combinations(param_options[1:]) if (len(param_options) > 1) else [()]): + assert(isinstance(part1, tuple)) + assert(isinstance(part2, tuple)) + yield part1 + part2 + elif is_list_like(param_options): + for item in param_options: + for c in get_combinations(item): + yield c + else: + yield (param_options,) def select_params(param_options): # Select parameters from the resource list diff --git a/tools/test-rig-rpi.yaml b/tools/test-rig-rpi.yaml index 3321263f..33588274 100644 --- a/tools/test-rig-rpi.yaml +++ b/tools/test-rig-rpi.yaml @@ -17,7 +17,9 @@ components: name: can0 interface: can0 connected-to: odrive.can - - {type: gpio, num: 19} # need to specify GPIOs explicitly for the generalpurpose type + # need to specify GPIOs explicitly for the generalpurpose type + - {type: gpio, num: 16} + - {type: gpio, num: 19} - {type: gpio, num: 20} - {type: gpio, num: 26} @@ -57,6 +59,9 @@ components: - type: teensy name: teensy + - {type: lpf, name: lpf0} + - {type: lpf, name: lpf1} + connections: - ['odrive.can', 'rpi.can0'] - ['teensy.program', 'rpi.gpio26'] @@ -83,3 +88,6 @@ connections: - ['teensy.gpio2', 'real_encoder.b'] - ['odrive.axis0', 'D5065-270KV_0'] - ['D5065-270KV_0', 'real_encoder'] + - ['odrive.gpio3', 'lpf0'] + - ['odrive.gpio4', 'lpf1'] + - ['lpf0.en', 'lpf1.en', 'rpi.gpio16'] From 71d640e562a275cbe633eb60e851e55d4a276e3f Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 23 Apr 2020 11:10:24 +0200 Subject: [PATCH 331/549] add sin/cos encoder test, improve sawtooth fitting --- tools/odrive/tests/analog_input_test.py | 34 ++--- tools/odrive/tests/encoder_test.py | 187 +++++++++++++++++------- tools/odrive/tests/pwm_input_test.py | 2 +- tools/odrive/tests/test_runner.py | 97 ++++++++++-- 4 files changed, 239 insertions(+), 81 deletions(-) diff --git a/tools/odrive/tests/analog_input_test.py b/tools/odrive/tests/analog_input_test.py index 6a3c73e7..d1f83774 100644 --- a/tools/odrive/tests/analog_input_test.py +++ b/tools/odrive/tests/analog_input_test.py @@ -5,7 +5,6 @@ import time import math import os import numpy as np -import scipy.optimize from odrive.enums import errors from test_runner import * @@ -36,16 +35,6 @@ void loop() { """ -def fit_sawtooth(data, min_val, max_val, period, range): - """ - Returns the average absolute error and the number of outliers - """ - func = lambda x, a: np.mod((max_val - min_val) / a * (x - a/2) - min_val, max_val - min_val) + min_val - params = scipy.optimize.curve_fit(func, data[:,0], data[:,1], [period])[0] - diffs = data[:,1] - func(data[:,0], *params) - return np.abs(diffs).mean(), np.count_nonzero((diffs > range) | (diffs < -range)) - - class TestAnalogInput(): """ Verifies the Analog input. @@ -92,6 +81,7 @@ class TestAnalogInput(): min_val = -20000 max_val = 20000 + period = 1.025 # period in teensy code is 1s, but due to tiny overhead it's a bit longer analog_mapping = [ None, #odrive.handle.config.gpio1_analog_mapping, @@ -108,24 +98,24 @@ class TestAnalogInput(): odrive.save_config_and_reboot() - logger.debug("Log test_property...") - log_x = [] - log_y = [] + logger.debug("Recording log...") + data = [] start = time.monotonic() analog_reset_gpio.write(False) while time.monotonic() - start < 5.0: - log_x.append(time.monotonic() - start) - log_y.append(odrive.handle.axis0.controller.input_pos) + data.append(( + time.monotonic() - start, + odrive.handle.axis0.controller.input_pos + )) + data = np.array(data) # Expect mean error to be at most 2% (of the full scale). - # Expect there to be less than 1% outliers, where an outlier is anything that is more than 5% (of full scale) away from the expected value. + # Expect there to be less than 2% outliers, where an outlier is anything that is more than 5% (of full scale) away from the expected value. full_range = abs(max_val - min_val) - data = np.array([log_x, log_y]).transpose() - mean_error, n_outliers = fit_sawtooth(data, min_val, max_val, 1.05, full_range * 0.05) - - test_assert_eq(mean_error, 0, range = full_range * 0.02) - test_assert_eq(n_outliers, 0, range = len(log_x) * 0.01) + slope, offset, fitted_curve = fit_sawtooth(data, min_val, max_val) + test_assert_eq(slope, (max_val - min_val) / period, accuracy=0.005) + test_curve_fit(data, fitted_curve, max_mean_err = full_range * 0.02, inlier_range = full_range * 0.05, max_outliers = len(data[:,0]) * 0.02) diff --git a/tools/odrive/tests/encoder_test.py b/tools/odrive/tests/encoder_test.py index d1ffa061..53335716 100644 --- a/tools/odrive/tests/encoder_test.py +++ b/tools/odrive/tests/encoder_test.py @@ -6,6 +6,7 @@ from math import pi import os from fibre.utils import Logger +from odrive.enums import * from test_runner import * @@ -36,8 +37,100 @@ void loop() { """ +teensy_code_template2 = """ +void setup() { + analogWriteResolution(10); + int freq = 150000000/1024; // ~146.5kHz PWM frequency + analogWriteFrequency({enc_sin}, freq); + analogWriteFrequency({enc_cos}, freq); +} -class TestIncrementalEncoder(): +int rpm = 60; +float pos = 0; + +void loop() { + pos += 0.001f * ((float)rpm / 60.0f); + if (pos > 1.0f) + pos -= 1.0f; + analogWrite({enc_sin}, (int)(512.0f + 512.0f * sin(2.0f * M_PI * pos))); + analogWrite({enc_cos}, (int)(512.0f + 512.0f * cos(2.0f * M_PI * pos))); + delay(1); +} +""" + + +class TestEncoderBase(): + """ + Base class for encoder tests. + TODO: incremental encoder doesn't use this yet. + + All encoder tests expect the encoder to run at a constant velocity. + This can be achieved by generating an encoder signal with a Teensy. + + During 5 seconds, several variables are recorded and then compared against + the expected waveform. This is either a straight line, a sawtooth function + or a constant. + """ + + def run_generic_encoder_test(self, encoder, true_cpr, true_rps): + encoder.config.cpr = true_cpr + true_cps = true_cpr * true_rps + + logger.debug("Recording log...") + data = [] + start = time.monotonic() + encoder.set_linear_count(0) # prevent numerical errors + while time.monotonic() - start < 5.0: + data.append(( + time.monotonic() - start, + encoder.shadow_count, + encoder.count_in_cpr, + encoder.phase, + encoder.pos_estimate, + encoder.pos_cpr, + encoder.vel_estimate, + )) + + data = np.array(data) + + short_period = (abs(1 / true_rps) < 5.0) + reverse = (true_rps < 0) + + # encoder.shadow_count + slope, offset, fitted_curve = fit_line(data[:,(0,1)]) + test_assert_eq(slope, true_cps, accuracy=0.005) + test_curve_fit(data[:,(0,1)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.01, max_outliers = len(data[:,0]) * 0.02) + + # encoder.count_in_cpr + slope, offset, fitted_curve = fit_sawtooth(data[:,(0,2)], true_cpr if reverse else 0, 0 if reverse else true_cpr) + test_assert_eq(slope, true_cps, accuracy=0.005) + test_curve_fit(data[:,(0,2)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.01, max_outliers = len(data[:,0]) * 0.02) + + # encoder.phase + slope, offset, fitted_curve = fit_sawtooth(data[:,(0,3)], -pi, pi, sigma=5) + test_assert_eq(slope / 7, 2*pi*abs(true_rps), accuracy=0.01) + test_curve_fit(data[:,(0,3)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.01, max_outliers = len(data[:,0]) * 0.02) + + # encoder.pos_estimate + slope, offset, fitted_curve = fit_line(data[:,(0,4)]) + test_assert_eq(slope, true_cps, accuracy=0.005) + test_curve_fit(data[:,(0,4)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.01, max_outliers = len(data[:,0]) * 0.02) + + # encoder.pos_cpr + slope, offset, fitted_curve = fit_sawtooth(data[:,(0,5)], true_cpr if reverse else 0, 0 if reverse else true_cpr) + test_assert_eq(slope, true_cps, accuracy=0.005) + test_curve_fit(data[:,(0,5)], fitted_curve, max_mean_err = true_cpr * 0.05, inlier_range = true_cpr * 0.05, max_outliers = len(data[:,0]) * 0.02) + + # encoder.vel_estimate + slope, offset, fitted_curve = fit_line(data[:,(0,6)]) + test_assert_eq(slope, 0.0, range = true_cpr * abs(true_rps) * 0.005) + test_assert_eq(offset, true_cpr * true_rps, accuracy = 0.005) + test_curve_fit(data[:,(0,6)], fitted_curve, max_mean_err = true_cpr * 0.05, inlier_range = true_cpr * 0.05, max_outliers = len(data[:,0]) * 0.02) + + + + +class TestIncrementalEncoder(TestEncoderBase): def get_test_cases(self, testrig: TestRig): for odrive in testrig.get_components(ODriveComponent): @@ -50,41 +143,13 @@ class TestIncrementalEncoder(): ] valid_combinations = [ - [combination[0].parent] + list(combination) + (combination[0].parent,) + tuple(combination) for combination in itertools.product(*gpio_conns) if ((len(set(c.parent for c in combination)) == 1) and isinstance(combination[0].parent, TeensyComponent)) ] yield (encoder, valid_combinations) - def run_delta_test(self, encoder, true_cps, with_cpr): - encoder.config.cpr = with_cpr - - for i in range(100): - now = time.monotonic() - new_shadow_count = encoder.shadow_count - new_count_in_cpr = encoder.count_in_cpr - new_phase = encoder.phase - new_pos_estimate = encoder.pos_estimate - new_pos_cpr = encoder.pos_cpr - - if i > 0: - dt = now - before - test_assert_eq((new_shadow_count - last_shadow_count) / dt, true_cps, accuracy = 0.05) - test_assert_eq(modpm(new_count_in_cpr - last_count_in_cpr, with_cpr) / dt, true_cps, accuracy = 0.3) - #test_assert_eq(modpm(new_phase - last_phase, 2*pi) / dt, 2*pi*true_rps, accuracy = 0.1) - test_assert_eq((new_pos_estimate - last_pos_estimate) / dt, true_cps, accuracy = 0.3) - test_assert_eq(modpm(new_pos_cpr - last_pos_cpr, with_cpr) / dt, true_cps, accuracy = 0.3) - test_assert_eq(encoder.vel_estimate, true_cps, accuracy = 0.05) - - before = now - last_shadow_count = new_shadow_count - last_count_in_cpr = new_count_in_cpr - last_phase = new_phase - last_pos_estimate = new_pos_estimate - last_pos_cpr = new_pos_cpr - - time.sleep(0.01) def run_test(self, enc: EncoderComponent, teensy: TeensyComponent, teensy_gpio_a: int, teensy_gpio_b: int, logger: Logger): true_cps = 8192*-0.5 # counts per second generated by the virtual encoder @@ -92,32 +157,56 @@ class TestIncrementalEncoder(): code = teensy_code_template.replace("{enc_a}", str(teensy_gpio_a.num)).replace("{enc_b}", str(teensy_gpio_b.num)) teensy.compile_and_program(code) - time.sleep(1.0) # wait for PLLs to stabilize + if enc.handle.config.mode != ENCODER_MODE_INCREMENTAL: + enc.handle.config.mode = ENCODER_MODE_INCREMENTAL + enc.parent.save_config_and_reboot() + else: + time.sleep(1.0) # wait for PLLs to stabilize encoder = enc.handle - # The true encoder count and PLL output should be roughly the same. - # At 8192 CPR and 0.5 RPM, the delta because of sequential reading is - # around 3.25 counts. The exact value depends on the connection. - # The tracking error of the PLL is below 1 count. - - #logger.debug("check if count_in_cpr == pos_cpr") - #configured_cpr = 8192 - #encoder.config.cpr = configured_cpr - #expected_delta = true_cps/1200 - #for _ in range(1000): - # first = enc.handle.axis0.encoder.count_in_cpr - # second = enc.handle.axis0.encoder.pos_cpr - # test_assert_eq(modpm(second - first, configured_cpr), expected_delta, range=abs(true_cps/500)) - # time.sleep(0.001) - logger.debug("check if variables move at the correct velocity (8192 CPR)...") - self.run_delta_test(encoder, true_cps, 8192) + self.run_generic_encoder_test(enc.handle, 8192, true_cps / 8192) logger.debug("check if variables move at the correct velocity (65536 CPR)...") - self.run_delta_test(encoder, true_cps, 65536) + self.run_generic_encoder_test(enc.handle, 65536, true_cps / 65536) encoder.config.cpr = 8192 +class TestSinCosEncoder(TestEncoderBase): + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + gpio_conns = [ + testrig.get_directly_connected_components(odrive.gpio3), + testrig.get_directly_connected_components(odrive.gpio4), + ] + + valid_combinations = [ + (combination[0].parent,) + tuple(combination) + for combination in itertools.product(*gpio_conns) + if ((len(set(c.parent for c in combination)) == 1) and isinstance(combination[0].parent, TeensyComponent)) + ] + + yield (odrive.encoders[0], valid_combinations) + + + def run_test(self, enc: EncoderComponent, teensy: TeensyComponent, teensy_gpio_sin: TeensyGpio, teensy_gpio_cos: TeensyGpio, logger: Logger): + code = teensy_code_template2.replace("{enc_sin}", str(teensy_gpio_sin.num)).replace("{enc_cos}", str(teensy_gpio_cos.num)) + teensy.compile_and_program(code) + + if enc.handle.config.mode != ENCODER_MODE_SINCOS: + enc.parent.unuse_gpios() + enc.handle.config.mode = ENCODER_MODE_SINCOS + enc.parent.save_config_and_reboot() + else: + time.sleep(1.0) # wait for PLLs to stabilize + + self.run_generic_encoder_test(enc.handle, 6283, 1.0) + + + if __name__ == '__main__': - test_runner.run(TestIncrementalEncoder()) + test_runner.run([ + TestIncrementalEncoder(), + TestSinCosEncoder(), + ]) diff --git a/tools/odrive/tests/pwm_input_test.py b/tools/odrive/tests/pwm_input_test.py index e1dbddd9..e601bd72 100644 --- a/tools/odrive/tests/pwm_input_test.py +++ b/tools/odrive/tests/pwm_input_test.py @@ -61,7 +61,7 @@ class TestPwmInput(): ] valid_combinations = [ - [combination[0].parent] + list(combination) + (combination[0].parent,) + tuple(combination) for combination in itertools.product(*gpio_conns) if ((len(set(c.parent for c in combination)) == 1) and isinstance(combination[0].parent, TeensyComponent)) ] diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index 82298f3f..c3787641 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -17,6 +17,11 @@ import tempfile import io from typing import Union, Tuple +# needed for curve fitting +import numpy as np +import scipy.optimize +import scipy.ndimage.filters + # Assert utils ----------------------------------------------------------------# @@ -68,6 +73,79 @@ def all_unique(lst): def modpm(val, range): return ((val + (range / 2)) % range) - (range / 2) +def fit_line(data): + func = lambda x, a, b: x*a + b + slope, offset = scipy.optimize.curve_fit(func, data[:,0], data[:,1], [1.0, 0])[0] + return slope, offset, func(data[:,0], slope, offset) + +def fit_sawtooth(data, min_val, max_val, sigma=10): + """ + Fits the data to a sawtooth function. + Returns the average absolute error and the number of outliers. + The sample data must span at least one full period. + data is expected to contain one row (t, y) for each sample. + """ + + # Sawtooth function with free parameters for period and x-shift + func = lambda x, a, b: np.mod(a * x + b, max_val - min_val) + min_val + + # Fit period and x-shift + mid_point = (min_val + max_val) / 2 + filtered_data = scipy.ndimage.filters.gaussian_filter(data[:,1], sigma=sigma) + if max_val > min_val: + zero_crossings = data[np.where((filtered_data[:-1] > mid_point) & (filtered_data[1:] < mid_point))[0], 0] + else: + zero_crossings = data[np.where((filtered_data[:-1] < mid_point) & (filtered_data[1:] > mid_point))[0], 0] + + + if len(zero_crossings) == 0: + # No zero-crossing - fit simple line + slope, offset, _ = fit_line(data) + + elif len(zero_crossings) == 1: + # One zero-crossing - fit line based on the longer half + z_index = np.where(data[:,0] > zero_crossings[0])[0][0] + if z_index > len(data[:,0]): + slope, offset, _ = fit_line(data[:z_index]) + else: + slope, offset, _ = fit_line(data[z_index:]) + + else: + # Two or more zero-crossings - determine period based on average distance between zero-crossings + + period = (zero_crossings[1:] - zero_crossings[:-1]).mean() + slope = (max_val - min_val) / period + + #shift = scipy.optimize.curve_fit(lambda x, b: func(x, period, b), data[:,0], data[:,1], [0.0])[0][0] + if np.std(np.mod(zero_crossings, period)) < np.std(np.mod(zero_crossings + period/2, period)): + shift = np.mean(np.mod(zero_crossings, period)) + else: + shift = np.mean(np.mod(zero_crossings + period/2, period)) - period/2 + offset = -slope * shift + + return slope, offset, func(data[:,0], slope, offset) + +def test_curve_fit(data, fitted_curve, max_mean_err, inlier_range, max_outliers): + def save(): + import json + filename = '/tmp/log.json' + print('saving data to ' + filename) + with open(filename, 'w+') as fp: + json.dump(np.concatenate([data, np.array([fitted_curve]).transpose()], 1).tolist(), fp, indent=2) + + diffs = data[:,1] - fitted_curve + + mean_err = np.abs(diffs).mean() + if mean_err > max_mean_err: + save() + raise TestFailed("curve fit has too large mean error: {} > {}".format(mean_err, max_mean_err)) + + outliers = np.count_nonzero((diffs > inlier_range) | (diffs < -inlier_range)) + if outliers > max_outliers: + save() + raise TestFailed("curve fit has too many outliers (err > {}): {} > {}".format(inlier_range, outliers, max_outliers)) + + # Test Components -------------------------------------------------------------# class Component(object): @@ -307,15 +385,16 @@ class TeensyComponent(Component): time.sleep(0.5) # give it some time to boot def compile_and_program(self, code: str): - with io.TextIOWrapper(tempfile.NamedTemporaryFile(suffix='.ino')) as code_fp: - code_fp.write(code) - code_fp.flush() - code_fp.seek(0) - print('Writing code to teensy: ') - print(code_fp.read()) - with tempfile.NamedTemporaryFile(suffix='.hex') as hex_fp: - self.compile(code_fp.name, hex_fp.name) - self.program(hex_fp.name, logger) + with tempfile.TemporaryDirectory() as temp_dir: + with open(os.path.join(temp_dir, 'code.ino'), 'w+') as code_fp: + code_fp.write(code) + code_fp.flush() + code_fp.seek(0) + print('Writing code to teensy: ') + print(code_fp.read()) + with tempfile.NamedTemporaryFile(suffix='.hex') as hex_fp: + self.compile(code_fp.name, hex_fp.name) + self.program(hex_fp.name, logger) class LowPassFilterComponent(Component): def __init__(self, parent: Component): From c45ef8934b80e30d5689712dca515af68c3afa1f Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 23 Apr 2020 14:35:49 +0200 Subject: [PATCH 332/549] Change wrap_pm to be based on fmodf Before this commit, bad user input could cause the device to hang, that is, no longer respond on USB (other side effects are likely, though not tested for). An example of such an input is: odrv0.axis0.encoder.config.cpr = 10 odrv0.axis0.encoder.config.offset = 20000 --- CHANGELOG.md | 2 ++ Firmware/MotorControl/utils.hpp | 26 ++++++++++++++------------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1918483f..13d5fa31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ Please add a note of your changes below this heading if you make a Pull Request. * Some Encoder settings have been made read-only * Cleaned up VSCode C/C++ Configuration settings on Windows with recursive includePath * Now compiling with C++17 +* Fixed a firmware hang that could occur from unlikely but possible user input + # Releases ## [0.4.11] - 2019-07-25 ### Added diff --git a/Firmware/MotorControl/utils.hpp b/Firmware/MotorControl/utils.hpp index 574aa800..224f3716 100644 --- a/Firmware/MotorControl/utils.hpp +++ b/Firmware/MotorControl/utils.hpp @@ -69,18 +69,6 @@ static const float one_by_sqrt3 = 0.57735026919f; static const float two_by_sqrt3 = 1.15470053838f; static const float sqrt3_by_2 = 0.86602540378f; -//beware of inserting large values! -static inline float wrap_pm(float x, float pm_range) { - while (x >= pm_range) x -= (2.0f * pm_range); - while (x < -pm_range) x += (2.0f * pm_range); - return x; -} - -//beware of inserting large angles! -static inline float wrap_pm_pi(float theta) { - return wrap_pm(theta, M_PI); -} - // like fmodf, but always positive static inline float fmodf_pos(float x, float y) { float out = fmodf(x, y); @@ -89,6 +77,20 @@ static inline float fmodf_pos(float x, float y) { return out; } +/** + * @brief Similar to modulo operator, except that the output range is centered + * around zero. + * The returned value is always in the range [-pm_range, pm_range). + */ +static inline float wrap_pm(float x, float pm_range) { + return fmodf_pos(x + pm_range, 2.0f * pm_range) - pm_range; +} + +//beware of inserting large angles! +static inline float wrap_pm_pi(float theta) { + return wrap_pm(theta, M_PI); +} + // Compute rising edge timings (0.0 - 1.0) as a function of alpha-beta // as per the magnitude invariant clarke transform // The magnitude of the alpha-beta vector may not be larger than sqrt(3)/2 From 826c21267c82d02567cf344c07ec097ea59ede3f Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 23 Apr 2020 16:26:22 +0200 Subject: [PATCH 333/549] add hall effect encoder test --- tools/odrive/tests/encoder_test.py | 97 ++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 13 deletions(-) diff --git a/tools/odrive/tests/encoder_test.py b/tools/odrive/tests/encoder_test.py index 53335716..723432a6 100644 --- a/tools/odrive/tests/encoder_test.py +++ b/tools/odrive/tests/encoder_test.py @@ -34,7 +34,6 @@ void loop() { delayMicroseconds(microseconds_per_count); } } - """ teensy_code_template2 = """ @@ -58,6 +57,34 @@ void loop() { } """ +teensy_code_template3 = """ +void setup() { + pinMode({hall_a}, OUTPUT); + pinMode({hall_b}, OUTPUT); + pinMode({hall_c}, OUTPUT); + digitalWrite({hall_a}, HIGH); +} + +int cpr = 90; // 15 pole-pairs. Value suggested in hoverboard.md +int rpm = 60; +int microseconds_per_count = (1000000 * 60 / cpr / rpm); + +void loop() { + digitalWrite({hall_b}, HIGH); + delayMicroseconds(microseconds_per_count); + digitalWrite({hall_a}, LOW); + delayMicroseconds(microseconds_per_count); + digitalWrite({hall_c}, HIGH); + delayMicroseconds(microseconds_per_count); + digitalWrite({hall_b}, LOW); + delayMicroseconds(microseconds_per_count); + digitalWrite({hall_a}, HIGH); + delayMicroseconds(microseconds_per_count); + digitalWrite({hall_c}, LOW); + delayMicroseconds(microseconds_per_count); +} +""" + class TestEncoderBase(): """ @@ -99,22 +126,22 @@ class TestEncoderBase(): # encoder.shadow_count slope, offset, fitted_curve = fit_line(data[:,(0,1)]) test_assert_eq(slope, true_cps, accuracy=0.005) - test_curve_fit(data[:,(0,1)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.01, max_outliers = len(data[:,0]) * 0.02) + test_curve_fit(data[:,(0,1)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.02, max_outliers = len(data[:,0]) * 0.02) # encoder.count_in_cpr slope, offset, fitted_curve = fit_sawtooth(data[:,(0,2)], true_cpr if reverse else 0, 0 if reverse else true_cpr) test_assert_eq(slope, true_cps, accuracy=0.005) - test_curve_fit(data[:,(0,2)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.01, max_outliers = len(data[:,0]) * 0.02) + test_curve_fit(data[:,(0,2)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.02, max_outliers = len(data[:,0]) * 0.02) # encoder.phase - slope, offset, fitted_curve = fit_sawtooth(data[:,(0,3)], -pi, pi, sigma=5) - test_assert_eq(slope / 7, 2*pi*abs(true_rps), accuracy=0.01) - test_curve_fit(data[:,(0,3)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.01, max_outliers = len(data[:,0]) * 0.02) + slope, offset, fitted_curve = fit_sawtooth(data[:,(0,3)], pi if reverse else -pi, -pi if reverse else pi, sigma=5) + test_assert_eq(slope / 7, 2*pi*true_rps, accuracy=0.01) + test_curve_fit(data[:,(0,3)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.02, max_outliers = len(data[:,0]) * 0.02) # encoder.pos_estimate slope, offset, fitted_curve = fit_line(data[:,(0,4)]) test_assert_eq(slope, true_cps, accuracy=0.005) - test_curve_fit(data[:,(0,4)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.01, max_outliers = len(data[:,0]) * 0.02) + test_curve_fit(data[:,(0,4)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.02, max_outliers = len(data[:,0]) * 0.02) # encoder.pos_cpr slope, offset, fitted_curve = fit_sawtooth(data[:,(0,5)], true_cpr if reverse else 0, 0 if reverse else true_cpr) @@ -123,8 +150,8 @@ class TestEncoderBase(): # encoder.vel_estimate slope, offset, fitted_curve = fit_line(data[:,(0,6)]) - test_assert_eq(slope, 0.0, range = true_cpr * abs(true_rps) * 0.005) - test_assert_eq(offset, true_cpr * true_rps, accuracy = 0.005) + test_assert_eq(slope, 0.0, range = true_cpr * abs(true_rps) * 0.01) + test_assert_eq(offset, true_cpr * true_rps, accuracy = 0.01) test_curve_fit(data[:,(0,6)], fitted_curve, max_mean_err = true_cpr * 0.05, inlier_range = true_cpr * 0.05, max_outliers = len(data[:,0]) * 0.02) @@ -163,13 +190,13 @@ class TestIncrementalEncoder(TestEncoderBase): else: time.sleep(1.0) # wait for PLLs to stabilize - encoder = enc.handle + enc.handle.config.bandwidth = 1000 - logger.debug("check if variables move at the correct velocity (8192 CPR)...") + logger.debug("testing with 8192 CPR...") self.run_generic_encoder_test(enc.handle, 8192, true_cps / 8192) - logger.debug("check if variables move at the correct velocity (65536 CPR)...") + logger.debug("testing with 65536 CPR...") self.run_generic_encoder_test(enc.handle, 65536, true_cps / 65536) - encoder.config.cpr = 8192 + enc.handle.config.cpr = 8192 @@ -201,12 +228,56 @@ class TestSinCosEncoder(TestEncoderBase): else: time.sleep(1.0) # wait for PLLs to stabilize + enc.handle.config.bandwidth = 100 + self.run_generic_encoder_test(enc.handle, 6283, 1.0) +class TestHallEffectEncoder(TestEncoderBase): + + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + for encoder in odrive.encoders: + # Find the Teensy that is connected to the encoder pins and the corresponding Teensy GPIOs + + gpio_conns = [ + testrig.get_directly_connected_components(encoder.a), + testrig.get_directly_connected_components(encoder.b), + testrig.get_directly_connected_components(encoder.z), + ] + + valid_combinations = [ + (combination[0].parent,) + tuple(combination) + for combination in itertools.product(*gpio_conns) + if ((len(set(c.parent for c in combination)) == 1) and isinstance(combination[0].parent, TeensyComponent)) + ] + + yield (encoder, valid_combinations) + + + def run_test(self, enc: EncoderComponent, teensy: TeensyComponent, teensy_gpio_a: int, teensy_gpio_b: int, teensy_gpio_c: int, logger: Logger): + true_cpr = 90 + true_rps = -1.0 + + code = teensy_code_template3.replace("{hall_a}", str(teensy_gpio_a.num)).replace("{hall_b}", str(teensy_gpio_b.num)).replace("{hall_c}", str(teensy_gpio_c.num)) + teensy.compile_and_program(code) + + if enc.handle.config.mode != ENCODER_MODE_HALL: + enc.handle.config.mode = ENCODER_MODE_HALL + enc.parent.save_config_and_reboot() + else: + time.sleep(1.0) # wait for PLLs to stabilize + + enc.handle.config.bandwidth = 100 + + self.run_generic_encoder_test(enc.handle, true_cpr, true_rps) + enc.handle.config.cpr = 8192 + + if __name__ == '__main__': test_runner.run([ TestIncrementalEncoder(), TestSinCosEncoder(), + TestHallEffectEncoder(), ]) From 1b9f44211478eb2d47013cc9849674aa9ec5a47a Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 23 Apr 2020 17:44:07 +0200 Subject: [PATCH 334/549] clarify ascii doc --- docs/ascii-protocol.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/ascii-protocol.md b/docs/ascii-protocol.md index 5e1dc236..b82863e0 100644 --- a/docs/ascii-protocol.md +++ b/docs/ascii-protocol.md @@ -5,11 +5,13 @@ * **Via USB:** * **Windows:** Use the Zadig utility to set the ODrive's driver to "usbser". Windows will then make the device available as COM port. You can use [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/) to manually send commands or open the COM port using your favorite programming language - * **Linux/macOS:** Run `/dev/tty*` to list all serial ports. The ODrive will show up as `/dev/ttyACM0` on Linux and `/dev/tty.usbmodem[...]` on macOS. Once you know the name, you can use `screen /dev/ttyACM0` (with the correct name) to send commands manually or open the device using your favorite programming language. Serial ports on Unix can be opened, written to and read from like a normal file. + * **Linux/macOS:** Run `/dev/tty*` to list all serial ports. The ODrive will show up as `/dev/ttyACM0` (or similar) on Linux and `/dev/tty.usbmodem[...]` on macOS. Once you know the name, you can use `screen /dev/ttyACM0` (with the correct name) to send commands manually or open the device using your favorite programming language. Serial ports on Unix can be opened, written to and read from like a normal file. * **Via UART:** Connect the ODrive's TX (GPIO1) to your host's RX. Connect your ODrive's RX (GPIO2) to your host's TX. The logic level of the ODrive is 3.3V. * **Arduino:** You can use the [ODrive Arduino library](https://github.com/madcowswe/ODrive/tree/master/Arduino/ODriveArduino) to talk to the ODrive. * **Windows/Linux/macOS:** You can use an FTDI USB-UART cable to connect to the ODrive. +The ODrive does not echo commands. That means that when you type commands into a program like `screen`, the characters you type won't show up in the console. + ## Command format The ASCII protocol is human-readable and line-oriented, with each line having the following format: @@ -18,7 +20,7 @@ The ASCII protocol is human-readable and line-oriented, with each line having th command *42 ; comment [new line character] ``` - * `*42` stands for a GCode compatible checksum and can be omitted. If and only if a checksum is provided, the device will also include a checksum in the response, if any. + * `*42` stands for a GCode compatible checksum and can be omitted. If and only if a checksum is provided, the device will also include a checksum in the response, if any. The checksum is calculated as the bitwise xor of all characters before the asterisk (`*`).
    Example of a valid checksum: `r vbus_voltage *93`. * comments are supported for GCode compatibility * the command is interpreted once the new-line character is encountered From 0f3baf816f57f2ae5788ede686e62ebdc375522f Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 1 Apr 2019 18:40:32 -0700 Subject: [PATCH 335/549] accept 0xffffffff offset for json crc --- Firmware/fibre/cpp/protocol.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Firmware/fibre/cpp/protocol.cpp b/Firmware/fibre/cpp/protocol.cpp index 23a2e0a5..f3ffecf3 100644 --- a/Firmware/fibre/cpp/protocol.cpp +++ b/Firmware/fibre/cpp/protocol.cpp @@ -140,15 +140,21 @@ void JSONDescriptorEndpoint::handle(const uint8_t* input, size_t input_length, S return; uint32_t offset = 0; read_le(&offset, input); - NullStreamSink output_with_offset = NullStreamSink(offset, *output); - size_t id = 0; - write_string("[", &output_with_offset); - json_file_endpoint_.write_json(id, &output_with_offset); - id += decltype(json_file_endpoint_)::endpoint_count; - write_string(",", &output_with_offset); - application_endpoints_->write_json(id, &output_with_offset); - write_string("]", &output_with_offset); + // If the offset is special value 0xFFFFFFFF, send back the JSON crc instead + if (offset == 0xffffffff) { + default_readwrite_endpoint_handler(&json_crc_, nullptr, 0, output); + } else { + NullStreamSink output_with_offset = NullStreamSink(offset, *output); + + size_t id = 0; + write_string("[", &output_with_offset); + json_file_endpoint_.write_json(id, &output_with_offset); + id += decltype(json_file_endpoint_)::endpoint_count; + write_string(",", &output_with_offset); + application_endpoints_->write_json(id, &output_with_offset); + write_string("]", &output_with_offset); + } } int BidirectionalPacketBasedChannel::process_packet(const uint8_t* buffer, size_t length) { From f6eacd7ae4a84ead5b4341a1c35f84ffb6cfe049 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 1 Apr 2019 19:13:15 -0700 Subject: [PATCH 336/549] implement fetching of json crc on discovery --- Firmware/fibre/python/fibre/discovery.py | 25 ++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Firmware/fibre/python/fibre/discovery.py b/Firmware/fibre/python/fibre/discovery.py index 6039d7e0..58cda567 100644 --- a/Firmware/fibre/python/fibre/discovery.py +++ b/Firmware/fibre/python/fibre/discovery.py @@ -7,6 +7,7 @@ import json import time import threading import traceback +import struct import fibre.protocol import fibre.utils import fibre.remote_object @@ -64,12 +65,24 @@ def find_all(path, serial_number, """ try: logger.debug("Connecting to device on " + channel._name) - try: - json_bytes = channel.remote_endpoint_read_buffer(0) - except (TimeoutError, ChannelBrokenException): - logger.debug("no response - probably incompatible") - return - json_crc16 = fibre.protocol.calc_crc16(fibre.protocol.PROTOCOL_VERSION, json_bytes) + + # Fetching the json crc to check cache + json_crc16 = channel.remote_endpoint_operation(0, struct.pack("> 8) & 0xff)) + + #TODO check cache using json_crc16 + cache_miss = True + + if (cache_miss): + # Download the JSON data + try: + json_bytes = channel.remote_endpoint_read_buffer(0) + except (TimeoutError, ChannelBrokenException): + logger.debug("no response - probably incompatible") + return + json_crc16 = fibre.protocol.calc_crc16(fibre.protocol.PROTOCOL_VERSION, json_bytes) + channel._interface_definition_crc = json_crc16 try: json_string = json_bytes.decode("ascii") From 1e9e6479418605fb1e0af7cd998981980b327598 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 1 Apr 2019 19:49:26 -0700 Subject: [PATCH 337/549] backwards compatible with old firmware --- Firmware/fibre/python/fibre/discovery.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Firmware/fibre/python/fibre/discovery.py b/Firmware/fibre/python/fibre/discovery.py index 58cda567..1e6ee6f7 100644 --- a/Firmware/fibre/python/fibre/discovery.py +++ b/Firmware/fibre/python/fibre/discovery.py @@ -67,12 +67,17 @@ def find_all(path, serial_number, logger.debug("Connecting to device on " + channel._name) # Fetching the json crc to check cache - json_crc16 = channel.remote_endpoint_operation(0, struct.pack("> 8) & 0xff)) + cache_miss = True + try: + json_crc16 = channel.remote_endpoint_operation(0, struct.pack("> 8) & 0xff)) + except Exception as error: + logger.debug("Error fetching JSON CRC, falling back to downloading full JSON") + #logger.debug(traceback.format_exc()) #TODO check cache using json_crc16 - cache_miss = True + # Hence set cache_miss = False if (cache_miss): # Download the JSON data From f7eadfeb3bc867ebfa71641b1d6ad004f60de75a Mon Sep 17 00:00:00 2001 From: Adam Munich Date: Mon, 1 Apr 2019 22:02:05 -0700 Subject: [PATCH 338/549] Update discovery.py fxd --- Firmware/fibre/python/fibre/discovery.py | 44 +++++++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/Firmware/fibre/python/fibre/discovery.py b/Firmware/fibre/python/fibre/discovery.py index 1e6ee6f7..224d0dc6 100644 --- a/Firmware/fibre/python/fibre/discovery.py +++ b/Firmware/fibre/python/fibre/discovery.py @@ -13,6 +13,8 @@ import fibre.utils import fibre.remote_object from fibre.utils import Event, Logger from fibre.protocol import ChannelBrokenException, TimeoutError +import tempfile +import os # Load all installed transport layers @@ -42,6 +44,7 @@ try: except ImportError: pass + def noprint(text): pass @@ -55,7 +58,6 @@ def find_all(path, serial_number, the callback for each Fibre node that is found. This function is non-blocking. """ - def did_discover_channel(channel): """ Inits an object from a given channel and then calls did_discover_object_callback @@ -66,8 +68,12 @@ def find_all(path, serial_number, try: logger.debug("Connecting to device on " + channel._name) - # Fetching the json crc to check cache + temp_dir = tempfile.gettempdir() + cache_miss = True + json_crc16 = 0 + + # Fetching the json crc to check cache try: json_crc16 = channel.remote_endpoint_operation(0, struct.pack("> 8) & 0xff)) + try: json_data = json.loads(json_string) except json.decoder.JSONDecodeError as error: logger.debug("device responded on endpoint 0 with something that is not JSON: " + str(error)) return + json_data = {"name": "fibre_node", "members": json_data} obj = fibre.remote_object.RemoteObject(json_data, None, channel, logger) @@ -111,7 +142,10 @@ def find_all(path, serial_number, if serial_number != None and device_serial_number != serial_number: logger.debug("Ignoring device with serial number {}".format(device_serial_number)) return + did_discover_object_callback(obj) + + except Exception: logger.debug("Unexpected exception after discovering channel: " + traceback.format_exc()) From 4fc9f7e3fee92309dbbed3cf2cbff8ce2cd00287 Mon Sep 17 00:00:00 2001 From: Adam Munich Date: Wed, 3 Apr 2019 15:48:45 -0700 Subject: [PATCH 339/549] Update discovery.py fixd --- Firmware/fibre/python/fibre/discovery.py | 71 ++++++++++++------------ 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/Firmware/fibre/python/fibre/discovery.py b/Firmware/fibre/python/fibre/discovery.py index 224d0dc6..3489ab3a 100644 --- a/Firmware/fibre/python/fibre/discovery.py +++ b/Firmware/fibre/python/fibre/discovery.py @@ -71,49 +71,50 @@ def find_all(path, serial_number, temp_dir = tempfile.gettempdir() cache_miss = True - json_crc16 = 0 # Fetching the json crc to check cache try: json_crc16 = channel.remote_endpoint_operation(0, struct.pack("> 8) & 0xff)) + + cache_path = temp_dir + '/fibre_schema_cache_' + str(json_crc16) + + try: + json_cache = open(cache_path, 'r+') + logger.debug("Found cache file with crc") + cache_miss = False + + except: + logger.debug("Cache miss, there is no cache file") + cache_miss = True + + # Download the JSON data + if(cache_miss == True): + try: + logger.debug("Getting json schema from USB... this is slow...") + json_bytes = channel.remote_endpoint_read_buffer(0) + json_cache = open(cache_path, 'w+') + json_cache.write(json_bytes.decode("ascii")) + logger.debug("saved json_bytes to file") + + except (TimeoutError, ChannelBrokenException): + logger.debug("no response - probably incompatible") + return + else: + try: + json_bytes = json_cache.read().encode("ascii") + logger.debug("loaded json_bytes from " + cache_path) + + except (TimeoutError, ChannelBrokenException): + logger.debug("could not read cache file") + return + except Exception as error: logger.debug("Error fetching JSON CRC, falling back to downloading full JSON") - #logger.debug(traceback.format_exc()) - - cache_path = temp_dir + '/fibre_schema_cache_' + str(json_crc16) - - try: - json_cache = open(cache_path, 'r+') - logger.debug("Found cache file with crc") - cache_miss = False - except: - logger.debug("Cache miss, there is no cache file") - cache_miss = True - - if (cache_miss): - # Download the JSON data - try: - logger.debug("Getting json schema from USB... this is slow...") - json_bytes = channel.remote_endpoint_read_buffer(0) - json_cache = open(cache_path, 'w+') - json_cache.write(json_bytes.decode("ascii")) - logger.debug("saved json_bytes to file") - - except (TimeoutError, ChannelBrokenException): - logger.debug("no response - probably incompatible") - return - else: - try: - json_bytes = json_cache.read().encode("ascii") - logger.debug("loaded json_bytes from " + cache_path) - - except (TimeoutError, ChannelBrokenException): - logger.debug("could not read cache file") - return - - json_crc16 = fibre.protocol.calc_crc16(fibre.protocol.PROTOCOL_VERSION, json_bytes) + logger.debug("Getting json schema from USB... this is slow...") + json_bytes = channel.remote_endpoint_read_buffer(0) + json_crc16 = fibre.protocol.calc_crc16(fibre.protocol.PROTOCOL_VERSION, json_bytes) channel._interface_definition_crc = json_crc16 From 3b4d2a5d34ac2a31a3f8ed391a8f430eff2ac46e Mon Sep 17 00:00:00 2001 From: Adam Munich Date: Wed, 3 Apr 2019 16:17:11 -0700 Subject: [PATCH 340/549] Update discovery.py store string directly --- Firmware/fibre/python/fibre/discovery.py | 32 +++++++++++++----------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/Firmware/fibre/python/fibre/discovery.py b/Firmware/fibre/python/fibre/discovery.py index 3489ab3a..007c93fc 100644 --- a/Firmware/fibre/python/fibre/discovery.py +++ b/Firmware/fibre/python/fibre/discovery.py @@ -92,37 +92,41 @@ def find_all(path, serial_number, # Download the JSON data if(cache_miss == True): try: - logger.debug("Getting json schema from USB... this is slow...") + logger.debug("Getting JSON schema from USB... this is slow...") json_bytes = channel.remote_endpoint_read_buffer(0) + try: + json_string = json_bytes.decode("ascii") + except UnicodeDecodeError: + logger.debug("Device responded on endpoint 0 with something that is not ASCII") + return json_cache = open(cache_path, 'w+') - json_cache.write(json_bytes.decode("ascii")) - logger.debug("saved json_bytes to file") + json_cache.write(json_string) + logger.debug("Saved JSON to cache file " + cache_path) except (TimeoutError, ChannelBrokenException): - logger.debug("no response - probably incompatible") + logger.debug("No response - probably incompatible") return else: try: - json_bytes = json_cache.read().encode("ascii") - logger.debug("loaded json_bytes from " + cache_path) + json_string = json_cache.read() + logger.debug("Loaded JSON from cache file " + cache_path) except (TimeoutError, ChannelBrokenException): - logger.debug("could not read cache file") + logger.debug("Could not read cache file " + cache_path) return except Exception as error: logger.debug("Error fetching JSON CRC, falling back to downloading full JSON") logger.debug("Getting json schema from USB... this is slow...") json_bytes = channel.remote_endpoint_read_buffer(0) + try: + json_string = json_bytes.decode("ascii") + except UnicodeDecodeError: + logger.debug("Device responded on endpoint 0 with something that is not ASCII") + return json_crc16 = fibre.protocol.calc_crc16(fibre.protocol.PROTOCOL_VERSION, json_bytes) channel._interface_definition_crc = json_crc16 - - try: - json_string = json_bytes.decode("ascii") - except UnicodeDecodeError: - logger.debug("device responded on endpoint 0 with something that is not ASCII") - return logger.debug("JSON: " + json_string.replace('{"name"', '\n{"name"')) logger.debug("JSON checksum: 0x{:02X} 0x{:02X}".format(json_crc16 & 0xff, (json_crc16 >> 8) & 0xff)) @@ -130,7 +134,7 @@ def find_all(path, serial_number, try: json_data = json.loads(json_string) except json.decoder.JSONDecodeError as error: - logger.debug("device responded on endpoint 0 with something that is not JSON: " + str(error)) + logger.debug("Device responded on endpoint 0 with something that is not JSON: " + str(error)) return json_data = {"name": "fibre_node", "members": json_data} From 1445d3531a4b8fa15907c025781abcdc4a9fe182 Mon Sep 17 00:00:00 2001 From: Ben Wang Date: Mon, 3 Jun 2019 12:13:27 -0400 Subject: [PATCH 341/549] refactor fiber cache --- Firmware/fibre/python/fibre/discovery.py | 105 ++++++++++++----------- Firmware/fibre/python/setup.py | 4 +- 2 files changed, 57 insertions(+), 52 deletions(-) diff --git a/Firmware/fibre/python/fibre/discovery.py b/Firmware/fibre/python/fibre/discovery.py index 007c93fc..ea323d95 100644 --- a/Firmware/fibre/python/fibre/discovery.py +++ b/Firmware/fibre/python/fibre/discovery.py @@ -13,7 +13,7 @@ import fibre.utils import fibre.remote_object from fibre.utils import Event, Logger from fibre.protocol import ChannelBrokenException, TimeoutError -import tempfile +import appdirs import os # Load all installed transport layers @@ -54,6 +54,32 @@ def find_all(path, serial_number, channel_termination_token, logger): """ + Load a json file from disk, return None if it does not exist or is invalid json + """ + def load_json_file(name): + try: + file = open(name, "r+") + except: + logger.debug(f"Failed to open json file {name}") + return None + + try: + file_str = file.read() + except: + logger.debug(f"Failed to read json file {name}") + return None + + try: + json_data = json.loads(file_str) + except: + logger.debug(f"Failed to deserialize json file {name}") + return None + + file.close() + + return json_data + + """ Starts scanning for Fibre nodes that match the specified path spec and calls the callback for each Fibre node that is found. This function is non-blocking. @@ -68,75 +94,52 @@ def find_all(path, serial_number, try: logger.debug("Connecting to device on " + channel._name) - temp_dir = tempfile.gettempdir() + temp_dir = appdirs.user_cache_dir("odrivetool") + try: + os.mkdir(temp_dir) + except FileExistsError: + pass - cache_miss = True + cache_path = "" # Fetching the json crc to check cache try: json_crc16 = channel.remote_endpoint_operation(0, struct.pack("> 8) & 0xff)) + cache_path = temp_dir + '/fibre_schema_cache_' + str(json_crc16) + except: + logger.debug("Failed to get JSON checksum") - cache_path = temp_dir + '/fibre_schema_cache_' + str(json_crc16) - - try: - json_cache = open(cache_path, 'r+') - logger.debug("Found cache file with crc") - cache_miss = False - - except: - logger.debug("Cache miss, there is no cache file") - cache_miss = True - - # Download the JSON data - if(cache_miss == True): - try: - logger.debug("Getting JSON schema from USB... this is slow...") - json_bytes = channel.remote_endpoint_read_buffer(0) - try: - json_string = json_bytes.decode("ascii") - except UnicodeDecodeError: - logger.debug("Device responded on endpoint 0 with something that is not ASCII") - return - json_cache = open(cache_path, 'w+') - json_cache.write(json_string) - logger.debug("Saved JSON to cache file " + cache_path) - - except (TimeoutError, ChannelBrokenException): - logger.debug("No response - probably incompatible") - return - else: - try: - json_string = json_cache.read() - logger.debug("Loaded JSON from cache file " + cache_path) - - except (TimeoutError, ChannelBrokenException): - logger.debug("Could not read cache file " + cache_path) - return - - except Exception as error: - logger.debug("Error fetching JSON CRC, falling back to downloading full JSON") - logger.debug("Getting json schema from USB... this is slow...") + if cache_path == "" or load_json_file(cache_path) is None: + # Downloading json data + logger.info("Downloading json data from ODrive... (this might take a while)") json_bytes = channel.remote_endpoint_read_buffer(0) try: json_string = json_bytes.decode("ascii") except UnicodeDecodeError: logger.debug("Device responded on endpoint 0 with something that is not ASCII") - return + raise UnicodeDecodeError + json_crc16 = fibre.protocol.calc_crc16(fibre.protocol.PROTOCOL_VERSION, json_bytes) + cache_path = temp_dir + '/fibre_schema_cache_' + str(json_crc16) + + logger.debug(f"Opening json cache file {cache_path}") + with open(cache_path, 'w+') as json_cache: + json_cache.write(json_string) + logger.debug("Saved JSON to cache file " + cache_path) + json_data = load_json_file(cache_path) + else: + with open(cache_path, "rb") as f: + json_crc16 = fibre.protocol.calc_crc16(fibre.protocol.PROTOCOL_VERSION, f.read()) + json_data = load_json_file(cache_path) + channel._interface_definition_crc = json_crc16 - logger.debug("JSON: " + json_string.replace('{"name"', '\n{"name"')) + logger.debug("JSON: " + str(json_data).replace('{"name"', '\n{"name"')) logger.debug("JSON checksum: 0x{:02X} 0x{:02X}".format(json_crc16 & 0xff, (json_crc16 >> 8) & 0xff)) - try: - json_data = json.loads(json_string) - except json.decoder.JSONDecodeError as error: - logger.debug("Device responded on endpoint 0 with something that is not JSON: " + str(error)) - return - json_data = {"name": "fibre_node", "members": json_data} obj = fibre.remote_object.RemoteObject(json_data, None, channel, logger) diff --git a/Firmware/fibre/python/setup.py b/Firmware/fibre/python/setup.py index 85f266f3..9edccf6b 100644 --- a/Firmware/fibre/python/setup.py +++ b/Firmware/fibre/python/setup.py @@ -76,7 +76,9 @@ setup( license='MIT', url = 'https://github.com/samuelsadok/fibre', keywords = ['communication', 'transport-layer', 'rpc'], - install_requires = [], + install_requires = [ + 'appdirs', # Used to find caching directory + ], #package_data={'': ['version.txt']}, classifiers = [], ) From 2fd20eb931ba5748f0c1ff8ad8e5c4b42160ebea Mon Sep 17 00:00:00 2001 From: Adam Munich Date: Wed, 19 Jun 2019 16:17:38 -0700 Subject: [PATCH 342/549] Update protocol.cpp --- Firmware/fibre/cpp/protocol.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Firmware/fibre/cpp/protocol.cpp b/Firmware/fibre/cpp/protocol.cpp index f3ffecf3..857bb6bc 100644 --- a/Firmware/fibre/cpp/protocol.cpp +++ b/Firmware/fibre/cpp/protocol.cpp @@ -1,4 +1,3 @@ - /* Includes ------------------------------------------------------------------*/ #include @@ -13,9 +12,10 @@ /* Global constant data ------------------------------------------------------*/ /* Global variables ----------------------------------------------------------*/ -Endpoint** endpoint_list_ = nullptr; // initialized by calling fibre_publish -size_t n_endpoints_ = 0; // initialized by calling fibre_publish -uint16_t json_crc_; // initialized by calling fibre_publish +Endpoint** endpoint_list_ = nullptr; // initialized by calling fibre_publish +size_t n_endpoints_ = 0; // initialized by calling fibre_publish +uint16_t json_crc_; // initialized by calling fibre_publish +uint32_t json_fibre_cache_entropy_; // initialized by calling fibre_publish JSONDescriptorEndpoint json_file_endpoint_ = JSONDescriptorEndpoint(); EndpointProvider* application_endpoints_; @@ -143,7 +143,8 @@ void JSONDescriptorEndpoint::handle(const uint8_t* input, size_t input_length, S // If the offset is special value 0xFFFFFFFF, send back the JSON crc instead if (offset == 0xffffffff) { - default_readwrite_endpoint_handler(&json_crc_, nullptr, 0, output); + //default_readwrite_endpoint_handler(&json_crc_, nullptr, 0, output); + default_readwrite_endpoint_handler(&json_fibre_cache_entropy_, nullptr, 0, output); } else { NullStreamSink output_with_offset = NullStreamSink(offset, *output); @@ -187,8 +188,8 @@ int BidirectionalPacketBasedChannel::process_packet(const uint8_t* buffer, size_ // Verify packet trailer. The expected trailer value depends on the selected endpoint. // For endpoint 0 this is just the protocol version, for all other endpoints it's a // CRC over the entire JSON descriptor tree (this may change in future versions). - uint16_t expected_trailer = endpoint_id ? json_crc_ : PROTOCOL_VERSION; - uint16_t actual_trailer = buffer[length - 2] | (buffer[length - 1] << 8); + uint32_t expected_trailer = endpoint_id ? json_crc_ : PROTOCOL_VERSION; + uint32_t actual_trailer = buffer[length - 2] | (buffer[length - 1] << 8); if (expected_trailer != actual_trailer) { LOG_FIBRE("trailer mismatch for endpoint %d: expected %04x, got %04x\r\n", endpoint_id, expected_trailer, actual_trailer); return -1; From c93400aacf583d1e9e4e008158b01759d8db5930 Mon Sep 17 00:00:00 2001 From: Adam Munich Date: Wed, 19 Jun 2019 16:18:27 -0700 Subject: [PATCH 343/549] Update protocol.hpp --- Firmware/fibre/cpp/include/fibre/protocol.hpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index 016d7064..bfe774db 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -1,3 +1,5 @@ +#pragma GCC optimize ("O3") + /* see protocol.md for the protocol specification */ @@ -1098,6 +1100,7 @@ public: extern Endpoint** endpoint_list_; extern size_t n_endpoints_; extern uint16_t json_crc_; +extern uint32_t json_fibre_cache_entropy_; extern JSONDescriptorEndpoint json_file_endpoint_; extern EndpointProvider* application_endpoints_; @@ -1124,12 +1127,16 @@ int fibre_publish(T& application_objects) { // Calculate the CRC16 of the JSON file. // The init value is the protocol version. CRC16Calculator crc16_calculator(PROTOCOL_VERSION); + uint8_t offset[4] = { 0 }; json_file_endpoint_.handle(offset, sizeof(offset), &crc16_calculator); json_crc_ = crc16_calculator.get_crc16(); + // Add entropy for fibre cache + json_file_endpoint_.handle(offset, sizeof(offset), &crc16_calculator); + json_fibre_cache_entropy_ = (uint32_t) crc16_calculator.get_crc16(); + json_fibre_cache_entropy_ += json_crc_ << 16; + return 0; } - - #endif From 0e1ee833d2d133353f0d20c1429d0cc1e2c9d8e6 Mon Sep 17 00:00:00 2001 From: Adam Munich Date: Wed, 19 Jun 2019 17:56:14 -0700 Subject: [PATCH 344/549] Update discovery.py added entropy --- Firmware/fibre/python/fibre/discovery.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Firmware/fibre/python/fibre/discovery.py b/Firmware/fibre/python/fibre/discovery.py index ea323d95..347fae85 100644 --- a/Firmware/fibre/python/fibre/discovery.py +++ b/Firmware/fibre/python/fibre/discovery.py @@ -104,10 +104,12 @@ def find_all(path, serial_number, # Fetching the json crc to check cache try: - json_crc16 = channel.remote_endpoint_operation(0, struct.pack("> 8) & 0xff)) - cache_path = temp_dir + '/fibre_schema_cache_' + str(json_crc16) + json_fibre_cache_entropy = channel.remote_endpoint_operation(0, struct.pack("> 16 + + logger.debug("Device reported JSON entropy: {:08d}".format(json_fibre_cache_entropy)) + cache_path = temp_dir + '/fibre_schema_cache_' + str(json_fibre_cache_entropy) except: logger.debug("Failed to get JSON checksum") @@ -122,23 +124,25 @@ def find_all(path, serial_number, raise UnicodeDecodeError json_crc16 = fibre.protocol.calc_crc16(fibre.protocol.PROTOCOL_VERSION, json_bytes) - - cache_path = temp_dir + '/fibre_schema_cache_' + str(json_crc16) - - logger.debug(f"Opening json cache file {cache_path}") + json_fibre_cache_entropy = fibre.protocol.calc_crc16(json_crc16, json_bytes) | (json_crc16 << 16) + cache_path = temp_dir + '/fibre_schema_cache_' + str(json_fibre_cache_entropy) + logger.debug(f"Creating new JSON cache file {cache_path}") with open(cache_path, 'w+') as json_cache: json_cache.write(json_string) logger.debug("Saved JSON to cache file " + cache_path) json_data = load_json_file(cache_path) else: with open(cache_path, "rb") as f: + logger.debug(f"Loaded JSON from cache file {cache_path}") json_crc16 = fibre.protocol.calc_crc16(fibre.protocol.PROTOCOL_VERSION, f.read()) + f.seek(0) + json_fibre_cache_entropy = fibre.protocol.calc_crc16(json_crc16, f.read()) | (json_crc16 << 16) json_data = load_json_file(cache_path) channel._interface_definition_crc = json_crc16 - logger.debug("JSON: " + str(json_data).replace('{"name"', '\n{"name"')) - logger.debug("JSON checksum: 0x{:02X} 0x{:02X}".format(json_crc16 & 0xff, (json_crc16 >> 8) & 0xff)) + logger.debug("JSON: " + str(json_data).replace("{'name'", "\n{'name'")) + logger.debug("Local cache JSON entropy: {:08d}".format(json_fibre_cache_entropy)) json_data = {"name": "fibre_node", "members": json_data} obj = fibre.remote_object.RemoteObject(json_data, None, channel, logger) From 5e3d8693c9278c4a2dc7e758c20338d16aeacf7a Mon Sep 17 00:00:00 2001 From: Adam Munich Date: Wed, 19 Jun 2019 17:57:18 -0700 Subject: [PATCH 345/549] Update shell.py fix broken exceptionhandler --- Firmware/fibre/python/fibre/shell.py | 32 +++++++++++++++++++--------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/Firmware/fibre/python/fibre/shell.py b/Firmware/fibre/python/fibre/shell.py index d5e24d85..c5a7257a 100644 --- a/Firmware/fibre/python/fibre/shell.py +++ b/Firmware/fibre/python/fibre/shell.py @@ -80,11 +80,23 @@ def launch_shell(args, # If IPython is installed, embed IPython shell, otherwise embed regular shell if use_ipython: - help = lambda: print_help(args, len(discovered_devices) > 0) # Override help function # pylint: disable=W0612 - locals()['__name__'] = globals()['__name__'] # to fix broken "%run -i script.py" + # Override help function # pylint: disable=W0612 + help = lambda: print_help(args, len(discovered_devices) > 0) + # to fix broken "%run -i script.py" + locals()['__name__'] = globals()['__name__'] console = IPython.terminal.embed.InteractiveShellEmbed(banner1='') - console.runcode = console.run_code # hack to make IPython look like the regular console + + # hack to make IPython look like the regular console + console.runcode = console.run_cell interact = console + + # Catch ChannelBrokenException (since disconnect is not always an error) + default_exception_hook = console._showtraceback + def filtered_exception_hook(ex_class, ex, trace): + if(ex_class.__module__+'.'+ex_class.__name__ != 'fibre.protocol.ChannelBrokenException'): + default_exception_hook(ex_class,ex,trace) + + console._showtraceback = filtered_exception_hook else: # Enable tab complete if possible try: @@ -100,13 +112,13 @@ def launch_shell(args, console = code.InteractiveConsole(locals=interactive_variables) interact = lambda: console.interact(banner='') - # install hook to hide ChannelBrokenException - console.runcode('import sys') - console.runcode('superexcepthook = sys.excepthook') - console.runcode('def newexcepthook(ex_class,ex,trace):\n' - ' if ex_class.__module__ + "." + ex_class.__name__ != "fibre.ChannelBrokenException":\n' - ' superexcepthook(ex_class,ex,trace)') - console.runcode('sys.excepthook=newexcepthook') + # Catch ChannelBrokenException (since disconnect is not alway an error) + console.runcode("import sys") + console.runcode("default_exception_hook = sys.excepthook") + console.runcode("def filtered_exception_hook(ex_class, ex, trace):\n" + " if ex_class.__module__ + '.' + ex_class.__name__ != 'fibre.protocol.ChannelBrokenException':\n" + " default_exception_hook(ex_class,ex,trace)") + console.runcode("sys.excepthook=filtered_exception_hook") # Launch shell From ff544bfc1983ca9a3e842a373eab6d9df845741a Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 24 Apr 2020 10:19:20 +0200 Subject: [PATCH 346/549] simplify JSON caching logic, make JSON cache ID opaque, remove spurious changes --- Firmware/fibre/cpp/include/fibre/protocol.hpp | 10 +-- Firmware/fibre/cpp/protocol.cpp | 12 +-- Firmware/fibre/python/fibre/discovery.py | 87 +++++++------------ 3 files changed, 42 insertions(+), 67 deletions(-) diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index bfe774db..883ba544 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -1,5 +1,3 @@ -#pragma GCC optimize ("O3") - /* see protocol.md for the protocol specification */ @@ -1100,7 +1098,7 @@ public: extern Endpoint** endpoint_list_; extern size_t n_endpoints_; extern uint16_t json_crc_; -extern uint32_t json_fibre_cache_entropy_; +extern uint32_t json_version_id_; // exposed to hosts to facilitate cache lookup extern JSONDescriptorEndpoint json_file_endpoint_; extern EndpointProvider* application_endpoints_; @@ -1134,9 +1132,11 @@ int fibre_publish(T& application_objects) { // Add entropy for fibre cache json_file_endpoint_.handle(offset, sizeof(offset), &crc16_calculator); - json_fibre_cache_entropy_ = (uint32_t) crc16_calculator.get_crc16(); - json_fibre_cache_entropy_ += json_crc_ << 16; + json_version_id_ = (uint32_t) crc16_calculator.get_crc16(); + json_version_id_ += json_crc_ << 16; return 0; } + + #endif diff --git a/Firmware/fibre/cpp/protocol.cpp b/Firmware/fibre/cpp/protocol.cpp index 857bb6bc..d5af8a0b 100644 --- a/Firmware/fibre/cpp/protocol.cpp +++ b/Firmware/fibre/cpp/protocol.cpp @@ -1,3 +1,4 @@ + /* Includes ------------------------------------------------------------------*/ #include @@ -15,7 +16,7 @@ Endpoint** endpoint_list_ = nullptr; // initialized by calling fibre_publish size_t n_endpoints_ = 0; // initialized by calling fibre_publish uint16_t json_crc_; // initialized by calling fibre_publish -uint32_t json_fibre_cache_entropy_; // initialized by calling fibre_publish +uint32_t json_version_id_; // initialized by calling fibre_publish JSONDescriptorEndpoint json_file_endpoint_ = JSONDescriptorEndpoint(); EndpointProvider* application_endpoints_; @@ -141,10 +142,9 @@ void JSONDescriptorEndpoint::handle(const uint8_t* input, size_t input_length, S uint32_t offset = 0; read_le(&offset, input); - // If the offset is special value 0xFFFFFFFF, send back the JSON crc instead + // If the offset is special value 0xFFFFFFFF, send back the JSON version ID instead if (offset == 0xffffffff) { - //default_readwrite_endpoint_handler(&json_crc_, nullptr, 0, output); - default_readwrite_endpoint_handler(&json_fibre_cache_entropy_, nullptr, 0, output); + default_readwrite_endpoint_handler(&json_version_id_, nullptr, 0, output); } else { NullStreamSink output_with_offset = NullStreamSink(offset, *output); @@ -188,8 +188,8 @@ int BidirectionalPacketBasedChannel::process_packet(const uint8_t* buffer, size_ // Verify packet trailer. The expected trailer value depends on the selected endpoint. // For endpoint 0 this is just the protocol version, for all other endpoints it's a // CRC over the entire JSON descriptor tree (this may change in future versions). - uint32_t expected_trailer = endpoint_id ? json_crc_ : PROTOCOL_VERSION; - uint32_t actual_trailer = buffer[length - 2] | (buffer[length - 1] << 8); + uint16_t expected_trailer = endpoint_id ? json_crc_ : PROTOCOL_VERSION; + uint16_t actual_trailer = buffer[length - 2] | (buffer[length - 1] << 8); if (expected_trailer != actual_trailer) { LOG_FIBRE("trailer mismatch for endpoint %d: expected %04x, got %04x\r\n", endpoint_id, expected_trailer, actual_trailer); return -1; diff --git a/Firmware/fibre/python/fibre/discovery.py b/Firmware/fibre/python/fibre/discovery.py index 347fae85..12836f11 100644 --- a/Firmware/fibre/python/fibre/discovery.py +++ b/Firmware/fibre/python/fibre/discovery.py @@ -54,36 +54,11 @@ def find_all(path, serial_number, channel_termination_token, logger): """ - Load a json file from disk, return None if it does not exist or is invalid json - """ - def load_json_file(name): - try: - file = open(name, "r+") - except: - logger.debug(f"Failed to open json file {name}") - return None - - try: - file_str = file.read() - except: - logger.debug(f"Failed to read json file {name}") - return None - - try: - json_data = json.loads(file_str) - except: - logger.debug(f"Failed to deserialize json file {name}") - return None - - file.close() - - return json_data - - """ Starts scanning for Fibre nodes that match the specified path spec and calls the callback for each Fibre node that is found. This function is non-blocking. """ + def did_discover_channel(channel): """ Inits an object from a given channel and then calls did_discover_object_callback @@ -94,26 +69,32 @@ def find_all(path, serial_number, try: logger.debug("Connecting to device on " + channel._name) - temp_dir = appdirs.user_cache_dir("odrivetool") + cache_dir = appdirs.user_cache_dir("odrivetool") + cache_path = None + + # Fetch the json version tag to check cache (only supported on firmware v0.5 or later) try: - os.mkdir(temp_dir) - except FileExistsError: - pass + json_version_tag = channel.remote_endpoint_operation(0, struct.pack("> 16 - - logger.debug("Device reported JSON entropy: {:08d}".format(json_fibre_cache_entropy)) - cache_path = temp_dir + '/fibre_schema_cache_' + str(json_fibre_cache_entropy) + logger.debug("Device reported JSON version ID: {:08d}".format(json_version_tag)) + cache_path = os.path.join(cache_dir, 'fibre_schema_cache_{:08d}'.format(json_version_tag)) except: logger.debug("Failed to get JSON checksum") - if cache_path == "" or load_json_file(cache_path) is None: + # Check cache + json_data = None + try: + if not cache_path is None: + with open(cache_path, 'rb') as fp: + json_crc16 = fibre.protocol.calc_crc16(fibre.protocol.PROTOCOL_VERSION, fp.read()) + fp.seek(0) + json_data = json.load(fp) + except: + logger.debug(f"Failed load JSON cache file {cache_path}") + + # Fallback to loading JSON from device + if json_data is None: # Downloading json data logger.info("Downloading json data from ODrive... (this might take a while)") json_bytes = channel.remote_endpoint_read_buffer(0) @@ -124,25 +105,19 @@ def find_all(path, serial_number, raise UnicodeDecodeError json_crc16 = fibre.protocol.calc_crc16(fibre.protocol.PROTOCOL_VERSION, json_bytes) - json_fibre_cache_entropy = fibre.protocol.calc_crc16(json_crc16, json_bytes) | (json_crc16 << 16) - cache_path = temp_dir + '/fibre_schema_cache_' + str(json_fibre_cache_entropy) - logger.debug(f"Creating new JSON cache file {cache_path}") - with open(cache_path, 'w+') as json_cache: - json_cache.write(json_string) - logger.debug("Saved JSON to cache file " + cache_path) - json_data = load_json_file(cache_path) - else: - with open(cache_path, "rb") as f: - logger.debug(f"Loaded JSON from cache file {cache_path}") - json_crc16 = fibre.protocol.calc_crc16(fibre.protocol.PROTOCOL_VERSION, f.read()) - f.seek(0) - json_fibre_cache_entropy = fibre.protocol.calc_crc16(json_crc16, f.read()) | (json_crc16 << 16) - json_data = load_json_file(cache_path) + json_data = json.loads(json_string) + + # Save JSON to cache + if not cache_path is None: + logger.debug(f"Creating new JSON cache file {cache_path}") + os.makedirs(cache_dir, exist_ok=True) + with open(cache_path, 'w+') as json_cache: + json_cache.write(json_string) + logger.debug(f"Saved JSON to cache file {cache_path}") channel._interface_definition_crc = json_crc16 logger.debug("JSON: " + str(json_data).replace("{'name'", "\n{'name'")) - logger.debug("Local cache JSON entropy: {:08d}".format(json_fibre_cache_entropy)) json_data = {"name": "fibre_node", "members": json_data} obj = fibre.remote_object.RemoteObject(json_data, None, channel, logger) From d3a864785d2e6c8ed6177566f7b69ef966ae4568 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 24 Apr 2020 11:13:11 +0200 Subject: [PATCH 347/549] remove spurious line change --- Firmware/fibre/python/fibre/discovery.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Firmware/fibre/python/fibre/discovery.py b/Firmware/fibre/python/fibre/discovery.py index 12836f11..4407537b 100644 --- a/Firmware/fibre/python/fibre/discovery.py +++ b/Firmware/fibre/python/fibre/discovery.py @@ -44,7 +44,6 @@ try: except ImportError: pass - def noprint(text): pass From 78b48f1a9b6cd8ce528e44b3f35f79540392c752 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 24 Apr 2020 11:21:49 +0200 Subject: [PATCH 348/549] amend changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13d5fa31..52cffece 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,8 @@ Please add a note of your changes below this heading if you make a Pull Request. * Cleaned up VSCode C/C++ Configuration settings on Windows with recursive includePath * Now compiling with C++17 * Fixed a firmware hang that could occur from unlikely but possible user input +* Added JSON caching to Fibre. This drastically reduces the time odrivetool needs to connect to an ODrive (except for the first time or after firmware updates). +* Fix IPython `RuntimeWarning` that would occur every time `odrivetool` was started. # Releases ## [0.4.11] - 2019-07-25 From 9ef3d0c6a16d9868d5492e7aab482a3ac055b044 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 25 Apr 2020 22:38:53 -0400 Subject: [PATCH 349/549] Store the result of get_inverter_temp to a member variable --- Firmware/MotorControl/motor.cpp | 6 +++--- Firmware/MotorControl/motor.hpp | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 38b3155b..39115713 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -150,8 +150,7 @@ float Motor::get_inverter_temp() { return horner_fma(normalized_voltage, thermistor_poly_coeffs, thermistor_num_coeffs); } -bool Motor::update_thermal_limits() { - float fet_temp = get_inverter_temp(); +bool Motor::update_thermal_limits(float fet_temp) { float temp_margin = config_.inverter_temp_limit_upper - fet_temp; float derating_range = config_.inverter_temp_limit_upper - config_.inverter_temp_limit_lower; thermal_current_lim_ = config_.current_lim * (temp_margin / derating_range); @@ -170,7 +169,8 @@ bool Motor::do_checks() { set_error(ERROR_DRV_FAULT); return false; } - if (!update_thermal_limits()) { + inverter_temp_ = get_inverter_temp(); + if (!update_thermal_limits(inverter_temp_)) { //error already set in function return false; } diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 3714ea9a..5d493437 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -125,7 +125,7 @@ public: void set_error(Error_t error); bool do_checks(); float get_inverter_temp(); - bool update_thermal_limits(); + bool update_thermal_limits(float fet_temp); float effective_current_lim(); void log_timing(TimingLog_t log_idx); float phase_current_from_adcval(uint32_t ADCValue); @@ -187,6 +187,7 @@ public: DRV8301_FaultType_e drv_fault_ = DRV8301_FaultType_NoFault; DRV_SPI_8301_Vars_t gate_driver_regs_; //Local view of DRV registers (initialized by DRV8301_setup) float thermal_current_lim_ = 10.0f; //[A] + float inverter_temp_ = 20.0f; // Communication protocol definitions auto make_protocol_definitions() { @@ -200,7 +201,7 @@ public: make_protocol_property("DC_calib_phC", &DC_calib_.phC), make_protocol_property("phase_current_rev_gain", &phase_current_rev_gain_), make_protocol_ro_property("thermal_current_lim", &thermal_current_lim_), - make_protocol_function("get_inverter_temp", *this, &Motor::get_inverter_temp), + make_protocol_ro_property("inverter_temp", &inverter_temp_), make_protocol_object("current_control", make_protocol_property("p_gain", ¤t_control_.p_gain), make_protocol_property("i_gain", ¤t_control_.i_gain), From 9c86808bcafbb340195f7a236233a59845d6126e Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 26 Apr 2020 00:16:52 -0400 Subject: [PATCH 350/549] Changelog updates --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52cffece..3369efc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * Added support for Flylint VSCode Extension for static code analysis * Using an STM32F405 .svd file allows CortexDebug to view registers during debugging * Added scripts for building via docker. +* Brake resistor logic now attempts to clamp voltage according to `odrv.config.nominal_voltage` ### Changed * Changed ratiometric `motor.config.current_lim_tolerance` to absolute `motor.config.current_lim_margin` @@ -35,6 +36,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * Fixed a firmware hang that could occur from unlikely but possible user input * Added JSON caching to Fibre. This drastically reduces the time odrivetool needs to connect to an ODrive (except for the first time or after firmware updates). * Fix IPython `RuntimeWarning` that would occur every time `odrivetool` was started. +* Change `motor.get_inverter_temp()` to use a property which was already being sampled at `motor.inverter_temp` # Releases ## [0.4.11] - 2019-07-25 From e9aa6b867cd9e9a7c3be9fd6fdc3827facd6d0b3 Mon Sep 17 00:00:00 2001 From: camrbuss Date: Sat, 25 Apr 2020 22:20:50 -0600 Subject: [PATCH 351/549] Ubunutu 20 documentation --- docs/developer-guide.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 36790201..5c85d398 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -50,7 +50,7 @@ $ tup --version # should be 0.7.5 or later $ python --version # should be 3.7 or later ``` -#### Linux (Ubuntu) +#### Linux (Ubuntu < 20.04) ```bash sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa sudo apt-get update @@ -59,6 +59,13 @@ sudo apt-get install openocd sudo add-apt-repository ppa:jonathonf/tup && sudo apt-get update && sudo apt-get install tup ``` +#### Linux (Ubuntu >= 20.04) +```bash +sudo apt install gcc-arm-embedded +sudo apt install openocd +sudo apt install tup +``` + #### Arch Linux ```bash sudo pacman -S arm-none-eabi-gcc arm-none-eabi-binutils @@ -119,6 +126,8 @@ You can also modify the compile-time defaults for all `.config` parameters. You 2. Connect the ODrive via USB and power it up. 3. Flash the firmware using [odrivetool dfu](odrivetool#device-firmware-update). +If you get `/bin/sh: 1: python: not found` while running `make`, change the tup file command to use `python3` + ### Flashing using an STLink/v2 programmer * Connect `GND`, `SWD`, and `SWC` on connector J2 to the programmer. Note: Always plug in `GND` first! From baa6e4b5dc3e3903d48c634d7e183b70b83962df Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 26 Apr 2020 00:25:19 -0400 Subject: [PATCH 352/549] Set default configuration of endstops to configuration 4 --- Firmware/MotorControl/endstop.hpp | 2 +- docs/endstops.md | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Firmware/MotorControl/endstop.hpp b/Firmware/MotorControl/endstop.hpp index 7c7fa8b4..6983fa6c 100644 --- a/Firmware/MotorControl/endstop.hpp +++ b/Firmware/MotorControl/endstop.hpp @@ -10,7 +10,7 @@ class Endstop { uint16_t gpio_num = 0; bool enabled = false; bool is_active_high = false; - bool pullup = false; + bool pullup = true; }; explicit Endstop(Endstop::Config_t& config); diff --git a/docs/endstops.md b/docs/endstops.md index 87455825..fb165965 100644 --- a/docs/endstops.md +++ b/docs/endstops.md @@ -12,10 +12,11 @@ Each axis supports two endstops: `min_endstop` and `max_endstop`. For each ends Name | Type | Default --- | -- | -- gpio_num | int | 0 -enabled | boolean | False -offset | int | 0 -debounce_ms | float | 100.0 -is_active_high | boolean | False +offset | float | 0.0 +debounce_ms | float | 50.0 +enabled | boolean | false +is_active_high | boolean | false +pullup | boolean | true ### gpio_num The GPIO pin number, according to the silkscreen labels on ODrive. Set with these commands: @@ -53,6 +54,9 @@ This is how you configure the endstop to be either "NPN" or "PNP". An "NPN" con 3D printer endstops (like those that come with a RAMPS 1.4) are typically configuration **4**. +### pullup +Match the pullup value to the configuration. If `true`, it enables the GPIO pullup resistor. If `false`, it enables the GPIO pull*down* resistor. + ![Endstop configuration](Endstop_configuration.png) From 8cd673363c3943c3527f7018a1d5a1698b820094 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 24 Apr 2020 16:48:03 +0200 Subject: [PATCH 353/549] Reboot on `erase_configuration()`. This avoids unexpected behavior of a subsequent `save_configuration()` call, since the configuration is only erased from NVM, not from RAM. --- CHANGELOG.md | 1 + Firmware/MotorControl/main.cpp | 7 +++++++ docs/commands.md | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52cffece..318a2a3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * Fixed a firmware hang that could occur from unlikely but possible user input * Added JSON caching to Fibre. This drastically reduces the time odrivetool needs to connect to an ODrive (except for the first time or after firmware updates). * Fix IPython `RuntimeWarning` that would occur every time `odrivetool` was started. +* Reboot on `erase_configuration()`. This avoids unexpected behavior of a subsequent `save_configuration()` call, since the configuration is only erased from NVM, not from RAM. # Releases ## [0.4.11] - 2019-07-25 diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index f3c7d4b4..71b6425e 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -95,6 +95,13 @@ extern "C" int load_configuration(void) { void erase_configuration(void) { NVM_erase(); + + // FIXME: this reboot is a workaround because we don't want the next save_configuration + // to write back the old configuration from RAM to NVM. The proper action would + // be to reset the values in RAM to default. However right now that's not + // practical because several startup actions depend on the config. The + // other problem is that the stack overflows if we reset to default here. + NVIC_SystemReset(); } void enter_dfu_mode() { diff --git a/docs/commands.md b/docs/commands.md index ae5e9e8f..33f7eefc 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -99,7 +99,7 @@ Using the motor current and the known KV of your motor you can estimate the moto All variables that are part of a `[...].config` object can be saved to non-volatile memory on the ODrive so they persist after you remove power. The relevant commands are: * `.save_configuration()`: Stores the configuration to persistent memory on the ODrive. - * `.erase_configuration()`: Resets the configuration variables to their factory defaults. This only has an effect after a reboot. A side effect of this command is that motor control stops (in case it was running) and the USB communication breaks out temporarily. This is because erasing flash pages hangs the microcontroller for several seconds. + * `.erase_configuration()`: Resets the configuration variables to their factory defaults. This also reboots the device. ### Diagnostics From 7004e93506580fdc0ec4609a0671890bf0fe549d Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 24 Apr 2020 18:39:31 +0200 Subject: [PATCH 354/549] add closed loop control tests --- tools/odrive/tests/closed_loop_test.py | 153 +++++++++++++++++++++++++ tools/odrive/tests/test_runner.py | 35 ++++-- 2 files changed, 179 insertions(+), 9 deletions(-) create mode 100644 tools/odrive/tests/closed_loop_test.py diff --git a/tools/odrive/tests/closed_loop_test.py b/tools/odrive/tests/closed_loop_test.py new file mode 100644 index 00000000..2b9cff42 --- /dev/null +++ b/tools/odrive/tests/closed_loop_test.py @@ -0,0 +1,153 @@ + +import test_runner + +import time +from math import pi +import os + +from fibre.utils import Logger +from test_runner import * +from odrive.enums import * + + +class TestClosedLoopControl(): + """ + """ + + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + for num in range(2): + encoders = testrig.get_connected_components({ + 'a': (odrive.encoders[num].a, False), + 'b': (odrive.encoders[num].b, False) + }, EncoderComponent) + motors = testrig.get_connected_components(odrive.axes[num], MotorComponent) + + for motor, encoder in itertools.product(motors, encoders): + if encoder.impl in testrig.get_connected_components(motor): + yield (odrive.axes[num], motor, encoder) + + def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, enc_ctx: EncoderComponent, logger: Logger): + axis = axis_ctx.handle + time.sleep(1.0) # wait for PLLs to stabilize + + # Make sure there are no funny configurations active + logger.debug('Setting up clean configuration...') + axis_ctx.parent.erase_config_and_reboot() + + # Set motor calibration values + axis_ctx.handle.motor.config.phase_resistance = float(motor_ctx.yaml['phase-resistance']) + axis_ctx.handle.motor.config.phase_inductance = float(motor_ctx.yaml['phase-inductance']) + axis_ctx.handle.motor.config.pre_calibrated = True + + # Set calibration settings + axis_ctx.handle.motor.config.direction = 0 + axis_ctx.handle.encoder.config.use_index = False + axis_ctx.handle.encoder.config.calib_scan_omega = 12.566 # 2 electrical revolutions per second + axis_ctx.handle.encoder.config.calib_scan_distance = 50.265 # 8 revolutions + axis_ctx.handle.encoder.config.bandwidth = 1000 + + + axis_ctx.handle.clear_errors() + + logger.debug('Calibrating encoder offset...') + request_state(axis_ctx, AXIS_STATE_ENCODER_OFFSET_CALIBRATION) + + time.sleep(9) # actual calibration takes 8 seconds + + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_no_error(axis_ctx) + + nominal_rps = 1.0 + nominal_vel = float(enc_ctx.yaml['cpr']) * nominal_rps + logger.debug(f'Testing closed loop velocity control at {nominal_rps} rounds/s...') + + axis_ctx.handle.controller.config.control_mode = CTRL_MODE_VELOCITY_CONTROL + axis_ctx.handle.controller.config.input_mode = INPUT_MODE_PASSTHROUGH + axis_ctx.handle.controller.input_vel = 0 + + request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + axis_ctx.handle.controller.input_vel = nominal_vel + + data = record_log(lambda: [axis_ctx.handle.encoder.vel_estimate, axis_ctx.handle.encoder.pos_estimate], duration=5.0) + + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) + test_assert_no_error(axis_ctx) + request_state(axis_ctx, AXIS_STATE_IDLE) + + # encoder.vel_estimate + slope, offset, fitted_curve = fit_line(data[:,(0,1)]) + test_assert_eq(slope, 0.0, range = nominal_vel * 0.02) + test_assert_eq(offset, nominal_vel, accuracy = 0.05) + test_curve_fit(data[:,(0,1)], fitted_curve, max_mean_err = nominal_vel * 0.3, inlier_range = nominal_vel * 0.5, max_outliers = len(data[:,0]) * 0.1) + + # encoder.pos_estimate + slope, offset, fitted_curve = fit_line(data[:,(0,2)]) + test_assert_eq(slope, nominal_vel, accuracy = 0.01) + test_curve_fit(data[:,(0,2)], fitted_curve, max_mean_err = nominal_vel * 0.01, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) + + + logger.debug(f'Testing closed loop position control...') + + axis_ctx.handle.controller.config.control_mode = CTRL_MODE_POSITION_CONTROL + axis_ctx.handle.controller.input_pos = 0 + axis_ctx.handle.controller.config.vel_limit = float(enc_ctx.yaml['cpr']) * 5.0 # max 5 rps + axis_ctx.handle.encoder.set_linear_count(0) + + request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + + # Test small position changes + axis_ctx.handle.controller.input_pos = 5000 + time.sleep(0.3) + test_assert_no_error(axis_ctx) + test_assert_eq(axis_ctx.handle.encoder.pos_estimate, 5000, range=2000) # large range needed because of cogging torque + axis_ctx.handle.controller.input_pos = -5000 + time.sleep(0.3) + test_assert_no_error(axis_ctx) + test_assert_eq(axis_ctx.handle.encoder.pos_estimate, -5000, range=2000) + + axis_ctx.handle.controller.input_pos = 0 + time.sleep(0.3) + + nominal_vel = float(enc_ctx.yaml['cpr']) * 5.0 + axis_ctx.handle.controller.input_pos = nominal_vel * 2.0 # 10 turns (takes 2 seconds) + + # Test large position change with bounded velocity + data = record_log(lambda: [axis_ctx.handle.encoder.vel_estimate, axis_ctx.handle.encoder.pos_estimate], duration=4.0) + + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) + test_assert_no_error(axis_ctx) + request_state(axis_ctx, AXIS_STATE_IDLE) + + data_motion = data[data[:,0] < 1.9] + data_still = data[data[:,0] > 2.1] + + # encoder.vel_estimate + slope, offset, fitted_curve = fit_line(data_motion[:,(0,1)]) + test_assert_eq(slope, 0.0, range = nominal_vel * 0.05) + test_assert_eq(offset, nominal_vel, accuracy = 0.05) + test_curve_fit(data_motion[:,(0,1)], fitted_curve, max_mean_err = nominal_vel * 0.05, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) + + # encoder.pos_estimate + slope, offset, fitted_curve = fit_line(data_motion[:,(0,2)]) + test_assert_eq(slope, nominal_vel, accuracy = 0.01) + test_curve_fit(data_motion[:,(0,2)], fitted_curve, max_mean_err = nominal_vel * 0.01, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) + + # encoder.vel_estimate + slope, offset, fitted_curve = fit_line(data_still[:,(0,1)]) + test_assert_eq(slope, 0.0, range = nominal_vel * 0.05) + test_assert_eq(offset, 0.0, range = nominal_vel * 0.05) + test_curve_fit(data_still[:,(0,1)], fitted_curve, max_mean_err = nominal_vel * 0.05, inlier_range = nominal_vel * 0.1, max_outliers = len(data[:,0]) * 0.01) + + # encoder.pos_estimate + slope, offset, fitted_curve = fit_line(data_still[:,(0,2)]) + test_assert_eq(slope, 0.0, range = nominal_vel * 0.05) + test_assert_eq(offset, nominal_vel*2, range = nominal_vel * 0.02) + test_curve_fit(data_still[:,(0,2)], fitted_curve, max_mean_err = nominal_vel * 0.01, inlier_range = nominal_vel * 0.01, max_outliers = len(data[:,0]) * 0.01) + + + +if __name__ == '__main__': + test_runner.run([ + TestClosedLoopControl() + ]) diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index c3787641..2a394aca 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -73,6 +73,21 @@ def all_unique(lst): def modpm(val, range): return ((val + (range / 2)) % range) - (range / 2) +def record_log(data_getter, duration=5.0): + logger.debug(f"Recording log for {duration}s...") + data = [] + start = time.monotonic() + while time.monotonic() - start < duration: + data.append((time.monotonic() - start,) + tuple(data_getter())) + return np.array(data) + +def save_log(data): + import json + filename = '/tmp/log.json' + with open(filename, 'w+') as fp: + json.dump(data.tolist(), fp, indent=2) + print(f'data saved to {filename}') + def fit_line(data): func = lambda x, a, b: x*a + b slope, offset = scipy.optimize.curve_fit(func, data[:,0], data[:,1], [1.0, 0])[0] @@ -126,23 +141,16 @@ def fit_sawtooth(data, min_val, max_val, sigma=10): return slope, offset, func(data[:,0], slope, offset) def test_curve_fit(data, fitted_curve, max_mean_err, inlier_range, max_outliers): - def save(): - import json - filename = '/tmp/log.json' - print('saving data to ' + filename) - with open(filename, 'w+') as fp: - json.dump(np.concatenate([data, np.array([fitted_curve]).transpose()], 1).tolist(), fp, indent=2) - diffs = data[:,1] - fitted_curve mean_err = np.abs(diffs).mean() if mean_err > max_mean_err: - save() + save_log(np.concatenate([data, np.array([fitted_curve]).transpose()], 1)) raise TestFailed("curve fit has too large mean error: {} > {}".format(mean_err, max_mean_err)) outliers = np.count_nonzero((diffs > inlier_range) | (diffs < -inlier_range)) if outliers > max_outliers: - save() + save_log(np.concatenate([data, np.array([fitted_curve]).transpose()], 1)) raise TestFailed("curve fit has too many outliers (err > {}): {} > {}".format(inlier_range, outliers, max_outliers)) @@ -212,6 +220,15 @@ class ODriveComponent(Component): time.sleep(2) self.prepare(logger) + def erase_config_and_reboot(self): + try: + self.handle.erase_configuration() + except fibre.ChannelBrokenException: + pass # this is expected + self.handle = None + time.sleep(2) + self.prepare(logger) + class MotorComponent(Component): def __init__(self, yaml: dict): self.yaml = yaml From 744baf79ef5ba36c202a98ee6def6f812b2b13a7 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 24 Apr 2020 19:50:40 +0200 Subject: [PATCH 355/549] overhaul all tests so they pass --- tools/odrive/tests/analog_input_test.py | 12 +-- tools/odrive/tests/calibration_test.py | 15 ++- tools/odrive/tests/can_test.py | 7 ++ tools/odrive/tests/encoder_test.py | 23 ++-- tools/odrive/tests/pwm_input_test.py | 136 ++++++------------------ tools/odrive/tests/uart_ascii_test.py | 11 +- 6 files changed, 61 insertions(+), 143 deletions(-) diff --git a/tools/odrive/tests/analog_input_test.py b/tools/odrive/tests/analog_input_test.py index d1f83774..2110d6f5 100644 --- a/tools/odrive/tests/analog_input_test.py +++ b/tools/odrive/tests/analog_input_test.py @@ -97,18 +97,8 @@ class TestAnalogInput(): analog_mapping.max = max_val odrive.save_config_and_reboot() - - logger.debug("Recording log...") - data = [] - start = time.monotonic() analog_reset_gpio.write(False) - while time.monotonic() - start < 5.0: - data.append(( - time.monotonic() - start, - odrive.handle.axis0.controller.input_pos - )) - - data = np.array(data) + data = record_log(lambda: [odrive.handle.axis0.controller.input_pos], duration=5.0) # Expect mean error to be at most 2% (of the full scale). # Expect there to be less than 2% outliers, where an outlier is anything that is more than 5% (of full scale) away from the expected value. diff --git a/tools/odrive/tests/calibration_test.py b/tools/odrive/tests/calibration_test.py index 3cd60740..6d65bde0 100644 --- a/tools/odrive/tests/calibration_test.py +++ b/tools/odrive/tests/calibration_test.py @@ -25,6 +25,11 @@ class TestMotorCalibration(): def run_test(self, axis_ctx: ODriveAxisComponent, motor_ctx: MotorComponent, logger: Logger): # reset old calibration values + + if axis_ctx.handle.encoder.config.mode != ENCODER_MODE_INCREMENTAL: + axis_ctx.handle.encoder.config.mode = ENCODER_MODE_INCREMENTAL + axis_ctx.parent.save_config_and_reboot() + axis_ctx.handle.motor.config.phase_resistance = 0.0 axis_ctx.handle.motor.config.phase_inductance = 0.0 axis_ctx.handle.motor.config.pre_calibrated = False @@ -219,11 +224,11 @@ class TestEncoderIndexSearch(): test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) test_assert_no_error(axis_ctx) - test_assert_eq(axis_ctx.handle.encoder.shadow_count, 0.0, range=20) - test_assert_eq(modpm(axis_ctx.handle.encoder.count_in_cpr, cpr), 0.0, range=20) - test_assert_eq(axis_ctx.handle.encoder.pos_estimate, 0.0, range=20) - test_assert_eq(axis_ctx.handle.encoder.pos_cpr, 0.0, range=20) - test_assert_eq(axis_ctx.handle.encoder.pos_abs, 0.0, range=20) + test_assert_eq(axis_ctx.handle.encoder.shadow_count, 0.0, range=50) + test_assert_eq(modpm(axis_ctx.handle.encoder.count_in_cpr, cpr), 0.0, range=50) + test_assert_eq(axis_ctx.handle.encoder.pos_estimate, 0.0, range=50) + test_assert_eq(modpm(axis_ctx.handle.encoder.pos_cpr, cpr), 0.0, range=50) + test_assert_eq(axis_ctx.handle.encoder.pos_abs, 0.0, range=50) if __name__ == '__main__': diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index 7db9c5a6..ff51078c 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -87,6 +87,10 @@ class TestSimpleCAN(): yield (odrive, list(can_interfaces)) def run_test(self, odrive: ODriveComponent, canbus: CanInterfaceComponent, logger: Logger): + + # make sure no gpio input is overwriting our values + odrive.unuse_gpios() + node_id = 0 axis = odrive.handle.axis0 axis.config.can_node_id = node_id @@ -135,6 +139,9 @@ class TestSimpleCAN(): test_assert_eq(axis.controller.config.control_mode, 3) test_assert_eq(axis.controller.config.input_mode, 1) + axis.controller.input_pos = 1234 + axis.controller.input_vel = 1234 + axis.controller.input_current = 1234 my_cmd('set_input_pos', input_pos=1, vel_ff=2, cur_ff=3) fence() test_assert_eq(axis.controller.input_pos, 1.0, range=0.1) diff --git a/tools/odrive/tests/encoder_test.py b/tools/odrive/tests/encoder_test.py index 723432a6..d3f66949 100644 --- a/tools/odrive/tests/encoder_test.py +++ b/tools/odrive/tests/encoder_test.py @@ -103,20 +103,15 @@ class TestEncoderBase(): encoder.config.cpr = true_cpr true_cps = true_cpr * true_rps - logger.debug("Recording log...") - data = [] - start = time.monotonic() encoder.set_linear_count(0) # prevent numerical errors - while time.monotonic() - start < 5.0: - data.append(( - time.monotonic() - start, - encoder.shadow_count, - encoder.count_in_cpr, - encoder.phase, - encoder.pos_estimate, - encoder.pos_cpr, - encoder.vel_estimate, - )) + data = record_log(lambda: [ + encoder.shadow_count, + encoder.count_in_cpr, + encoder.phase, + encoder.pos_estimate, + encoder.pos_cpr, + encoder.vel_estimate, + ], duration=5.0) data = np.array(data) @@ -151,7 +146,7 @@ class TestEncoderBase(): # encoder.vel_estimate slope, offset, fitted_curve = fit_line(data[:,(0,6)]) test_assert_eq(slope, 0.0, range = true_cpr * abs(true_rps) * 0.01) - test_assert_eq(offset, true_cpr * true_rps, accuracy = 0.01) + test_assert_eq(offset, true_cpr * true_rps, accuracy = 0.02) test_curve_fit(data[:,(0,6)], fitted_curve, max_mean_err = true_cpr * 0.05, inlier_range = true_cpr * 0.05, max_outliers = len(data[:,0]) * 0.02) diff --git a/tools/odrive/tests/pwm_input_test.py b/tools/odrive/tests/pwm_input_test.py index e601bd72..a37cc155 100644 --- a/tools/odrive/tests/pwm_input_test.py +++ b/tools/odrive/tests/pwm_input_test.py @@ -14,16 +14,16 @@ float position = 0; // between 0 and 1 float velocity = 1; // [position per second] void setup() { -{setup_code} + pinMode({pwm_gpio}, OUTPUT); } // the loop routine runs over and over again forever: void loop() { int high_microseconds = 1000 + (int)(position * 1000.0f); -{set_high_code} + digitalWrite({pwm_gpio}, HIGH); delayMicroseconds(high_microseconds); -{set_low_code} + digitalWrite({pwm_gpio}, LOW); // Wait for a total of 20ms. // delayMicroseconds() only works well for values <= 16383 @@ -44,121 +44,45 @@ class TestPwmInput(): The Teensy generates a PWM signal that goes from 0% (1ms high) to 100% (2ms high) in 1 second and then resumes at 0%. - This test takes about 1min. - Note: this test is currently only written for ODrive 3.6 (or similar GPIO layout). """ def get_test_cases(self, testrig: TestRig): for odrive in testrig.get_components(ODriveComponent): - # Find the Teensy that is connected to gpios 1-4 of the ODrive and the corresponding Teensy GPIOs + # Run a separate test for each PWM-capable GPIO. Use different min/max settings for each test. + yield (odrive, 1, -50, 200, list(testrig.get_connected_components(odrive.gpio1, TeensyGpio))) + yield (odrive, 2, 20, 400, list(testrig.get_connected_components(odrive.gpio2, TeensyGpio))) + yield (odrive, 3, -1000, 0, list(testrig.get_connected_components(odrive.gpio3, TeensyGpio))) + yield (odrive, 4, -20000, 20000, list(testrig.get_connected_components(odrive.gpio4, TeensyGpio))) - gpio_conns = [ - testrig.get_directly_connected_components(odrive.gpio1), - testrig.get_directly_connected_components(odrive.gpio2), - testrig.get_directly_connected_components(odrive.gpio3), - testrig.get_directly_connected_components(odrive.gpio4) - ] - - valid_combinations = [ - (combination[0].parent,) + tuple(combination) - for combination in itertools.product(*gpio_conns) - if ((len(set(c.parent for c in combination)) == 1) and isinstance(combination[0].parent, TeensyComponent)) - ] - - yield (odrive, valid_combinations) - - def run_delta_test(self, attr, with_min, with_max, timeout = 5.0): - rounds_per_s = 1.0 - units_per_s = (with_max - with_min) * rounds_per_s - step_size = units_per_s * 0.02 # 20ms per step - min_val = math.inf - max_val = -math.inf - cumulative_delta = 0 - - rate = units_per_s - rate_gain = 1 / 0.5 # 200ms time constant - - # Could do something fancy like fit a piecewise linear function - - start = time.monotonic() - i = 0 - while True: - now = time.monotonic() - new_val = attr.get_value() - min_val = min(min_val, new_val) - max_val = max(max_val, new_val) - - if i > 0: - dt = now - before - delta = modpm(new_val - last_val, with_max - with_min) - cumulative_delta += delta - - # low pass filter rate - rate += (delta / dt - rate) * min(rate_gain * dt, 1) - #print("rate", rate) - - # After 500ms verify the rate - if now - start > 0.5: - # 40% seems like a very large range. With a smaller range - # the test tends to fail. May want to investigate if this - # is only because of the non-realtimeness of the tester - # of if it's an actual problem. Looks fine on software oscilloscope. - test_assert_eq(rate, units_per_s, accuracy=0.4) - - before = now - last_val = new_val - - if (now - start > timeout): - break - time.sleep(0.005) # PWM time resolution is 20ms, so let's read a bit slower. - i += 1 - - # Check the total incement during this time - test_assert_eq(cumulative_delta, timeout * units_per_s, accuracy=0.1) - - # Check that the minimum and maximum values were observed - test_assert_eq(min_val, with_min, range = step_size) - test_assert_eq(max_val, with_max, range = step_size) - - def run_test(self, odrive: ODriveComponent, teensy: TeensyComponent, teensy_gpio1: Component, teensy_gpio2: Component, teensy_gpio3: Component, teensy_gpio4: Component, logger: Logger): - # TODO: test each GPIO separately - - setup_code = "\n".join(" pinMode(" + str(gpio.num) + ", OUTPUT);" for gpio in [teensy_gpio1, teensy_gpio2, teensy_gpio3, teensy_gpio4]) - set_high_code = "\n".join(" digitalWrite(" + str(gpio.num) + ", HIGH);" for gpio in [teensy_gpio1, teensy_gpio2, teensy_gpio3, teensy_gpio4]) - set_low_code = "\n".join(" digitalWrite(" + str(gpio.num) + ", LOW);" for gpio in [teensy_gpio1, teensy_gpio2, teensy_gpio3, teensy_gpio4]) - - code = teensy_code_template.replace("{setup_code}", setup_code).replace("{set_high_code}", set_high_code).replace("{set_low_code}", set_low_code) + def run_test(self, odrive: ODriveComponent, odrive_gpio_num: int, min_val: float, max_val: float, teensy_gpio: Component, logger: Logger): + teensy = teensy_gpio.parent + code = teensy_code_template.replace("{pwm_gpio}", str(teensy_gpio.num)) teensy.compile_and_program(code) - time.sleep(1.0) # wait for PLLs to stabilize - logger.debug("Set up PWM input...") - odrive.handle.erase_configuration() - odrive.handle.config.enable_uart = False - odrive.handle.config.gpio1_pwm_mapping.endpoint = odrive.handle.axis0.controller._remote_attributes['input_pos'] - odrive.handle.config.gpio1_pwm_mapping.min = -50 - odrive.handle.config.gpio1_pwm_mapping.max = 200 - odrive.handle.config.gpio2_pwm_mapping.endpoint = odrive.handle.axis1.controller._remote_attributes['input_pos'] - odrive.handle.config.gpio2_pwm_mapping.min = 20 - odrive.handle.config.gpio2_pwm_mapping.max = 400 - odrive.handle.config.gpio3_pwm_mapping.endpoint = odrive.handle.axis0.controller._remote_attributes['input_vel'] - odrive.handle.config.gpio3_pwm_mapping.min = -1000 - odrive.handle.config.gpio3_pwm_mapping.max = 0 - odrive.handle.config.gpio4_pwm_mapping.endpoint = odrive.handle.axis1.controller._remote_attributes['input_vel'] - odrive.handle.config.gpio4_pwm_mapping.min = -20000 - odrive.handle.config.gpio4_pwm_mapping.max = 20000 + odrive.unuse_gpios() + + pwm_mapping = [ + odrive.handle.config.gpio1_pwm_mapping, + odrive.handle.config.gpio2_pwm_mapping, + odrive.handle.config.gpio3_pwm_mapping, + odrive.handle.config.gpio4_pwm_mapping + ][odrive_gpio_num - 1] + + pwm_mapping.endpoint = odrive.handle.axis0.controller._remote_attributes['input_pos'] + pwm_mapping.min = min_val + pwm_mapping.max = max_val odrive.save_config_and_reboot() + + data = record_log(lambda: [odrive.handle.axis0.controller.input_pos], duration=5.0) + + full_scale = max_val - min_val + slope, offset, fitted_curve = fit_sawtooth(data, min_val, max_val) + test_assert_eq(slope, full_scale / 1.0, accuracy=0.001) + test_curve_fit(data, fitted_curve, max_mean_err = full_scale * 0.02, inlier_range = full_scale * 0.03, max_outliers = len(data[:,0]) * 0.01) - logger.debug("Check if PWM on GPIO1 works...") - self.run_delta_test(odrive.handle.axis0.controller._remote_attributes['input_pos'], -50, 200) - logger.debug("Check if PWM on GPIO2 works...") - self.run_delta_test(odrive.handle.axis1.controller._remote_attributes['input_pos'], 20, 400) - logger.debug("Check if PWM on GPIO3 works...") - self.run_delta_test(odrive.handle.axis0.controller._remote_attributes['input_vel'], -1000, 0) - logger.debug("Check if PWM on GPIO4 works...") - self.run_delta_test(odrive.handle.axis1.controller._remote_attributes['input_vel'], -20000, 20000) if __name__ == '__main__': diff --git a/tools/odrive/tests/uart_ascii_test.py b/tools/odrive/tests/uart_ascii_test.py index 71c8bd7d..fbf00f62 100644 --- a/tools/odrive/tests/uart_ascii_test.py +++ b/tools/odrive/tests/uart_ascii_test.py @@ -44,13 +44,10 @@ class TestUartAscii(): Tests the most important functions of the ASCII protocol. """ - if (odrive.handle.config.gpio1_pwm_mapping.endpoint != (0,0)) or (odrive.handle.config.gpio2_pwm_mapping.endpoint != (0,0)): - logger.debug('UART pins in use. Reconfiguring...') - odrive.handle.config.gpio1_pwm_mapping.endpoint = None - odrive.handle.config.gpio2_pwm_mapping.endpoint = None - odrive.save_config_and_reboot() - - odrive.handle.axis0.config.enable_step_dir = False + logger.debug('Enabling UART...') + # GPIOs might be in use by something other than UART and some components + # might be configured so that they would fail in the later test. + odrive.erase_config_and_reboot() odrive.handle.config.enable_uart = True with port.open(115200) as ser: From b3dc36f97c6c7abcfa369dc161092c62ade1dcbf Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 27 Apr 2020 17:38:17 +0200 Subject: [PATCH 356/549] extend CAN test: add watchdog and heartbeat tests --- tools/odrive/tests/can_test.py | 68 ++++++++++++++++++++++----- tools/odrive/tests/test_runner.py | 24 ++++++++++ tools/odrive/tests/uart_ascii_test.py | 20 +------- 3 files changed, 81 insertions(+), 31 deletions(-) diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index ff51078c..e69a8bd9 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -13,7 +13,7 @@ from test_runner import * # Each argument is described as tuple (name, format, scale). # Struct format codes: https://docs.python.org/2/library/struct.html command_set = { - 'heartbeat': (0x001, [('error', 'I', 1), ('current_state', 'I', 1)]), # untested + 'heartbeat': (0x001, [('error', 'I', 1), ('current_state', 'I', 1)]), # tested 'estop': (0x002, []), # tested 'get_motor_error': (0x003, [('motor_error', 'I', 1)]), # untested 'get_encoder_error': (0x004, [('encoder_error', 'I', 1)]), # untested @@ -21,8 +21,8 @@ command_set = { 'set_node_id': (0x006, [('node_id', 'H', 1)]), # tested 'set_requested_state': (0x007, [('requested_state', 'I', 1)]), # tested # 0x008 not yet implemented - 'get_encoder_estimates': (0x009, [('encoder_pos_estimate', 'f', 1), ('encoder_vel_estimate', 'f', 1)]), # untested - 'get_encoder_count': (0x00a, [('encoder_shadow_count', 'i', 1), ('encoder_count', 'i', 1)]), # untested + 'get_encoder_estimates': (0x009, [('encoder_pos_estimate', 'f', 1), ('encoder_vel_estimate', 'f', 1)]), # partially tested + 'get_encoder_count': (0x00a, [('encoder_shadow_count', 'i', 1), ('encoder_count', 'i', 1)]), # partially tested 'set_controller_modes': (0x00b, [('control_mode', 'i', 1), ('input_mode', 'i', 1)]), # tested 'set_input_pos': (0x00c, [('input_pos', 'i', 1), ('vel_ff', 'h', 0.1), ('cur_ff', 'h', 0.01)]), # tested 'set_input_vel': (0x00d, [('input_vel', 'i', 0.01), ('cur_ff', 'h', 0.01)]), # tested @@ -34,7 +34,7 @@ command_set = { 'set_traj_a_per_css': (0x013, [('a_per_css', 'f', 1)]), # tested 'get_iq': (0x014, [('iq_setpoint', 'f', 1), ('iq_measured', 'f', 1)]), # untested 'get_sensorless_estimates': (0x015, [('sensorless_pos_estimate', 'f', 1), ('sensorless_vel_estimate', 'f', 1)]), # untested - 'reboot': (0x016, []), # untested + 'reboot': (0x016, []), # tested 'get_vbus_voltage': (0x017, [('vbus_voltage', 'f', 1)]), # tested 'clear_errors': (0x018, []), # partially tested } @@ -52,7 +52,12 @@ def command(bus, node_id_, cmd_name, **kwargs): msg = can.Message(arbitration_id=((node_id_ << 5) | cmd_id), data=data) bus.send(msg) -async def request(bus, node_id, cmd_name, timeout = 1.0): +async def record_messages(bus, node_id, cmd_name, timeout = 5.0): + """ + Returns an async generator that yields a dictionary for each CAN message that + is received, provided that the CAN ID matches the expected value. + """ + cmd_spec = command_set[cmd_name] cmd_id = cmd_spec[0] fmt = '<' + ''.join([f for (n, f, s) in cmd_spec[1]]) # all little endian @@ -61,23 +66,37 @@ async def request(bus, node_id, cmd_name, timeout = 1.0): notifier = can.Notifier(bus, [reader], timeout = timeout, loop = asyncio.get_event_loop()) try: - msg = can.Message(arbitration_id=((node_id << 5) | cmd_id), data=[], is_remote_frame=True) - bus.send(msg) - # The timeout in can.Notifier only triggers if no new messages are received at all, # so we need a second monitoring method. start = time.monotonic() while True: msg = await reader.get_message() if ((msg.arbitration_id == ((node_id << 5) | cmd_id)) and not msg.is_remote_frame): - break + fields = struct.unpack(fmt, msg.data[:(struct.calcsize(fmt))]) + res = {n: (fields[i] * s) for (i, (n, f, s)) in enumerate(cmd_spec[1])} + res['t'] = time.monotonic() + yield res if (time.monotonic() - start) > timeout: - raise TimeoutError() + break finally: notifier.stop() - fields = struct.unpack(fmt, msg.data[:(struct.calcsize(fmt))]) - return {n: (fields[i] * s) for (i, (n, f, s)) in enumerate(cmd_spec[1])} +async def request(bus, node_id, cmd_name, timeout = 1.0): + cmd_spec = command_set[cmd_name] + cmd_id = cmd_spec[0] + + msg_generator = record_messages(bus, node_id, cmd_name, timeout) + + msg = can.Message(arbitration_id=((node_id << 5) | cmd_id), data=[], is_remote_frame=True) + bus.send(msg) + + async for msg in msg_generator: + return msg + + raise TimeoutError() + +async def get_all(async_iterator): + return [x async for x in async_iterator] class TestSimpleCAN(): @@ -111,6 +130,10 @@ class TestSimpleCAN(): fence() test_assert_eq(axis.config.can_node_id, node_id) + axis.encoder.set_linear_count(123) + test_assert_eq(my_req('get_encoder_estimates')['encoder_pos_estimate'], 123.0, accuracy=0.01) + test_assert_eq(my_req('get_encoder_count')['encoder_shadow_count'], 123.0, accuracy=0.01) + my_cmd('clear_errors') fence() test_assert_eq(axis.error, 0) @@ -176,6 +199,27 @@ class TestSimpleCAN(): fence() test_assert_eq(axis.controller.config.inertia, 55.086, range=0.0001) + # any CAN cmd will feed the watchdog + test_watchdog(axis, lambda: my_cmd('set_input_current', input_current=0.0), logger) + + logger.debug('testing heartbeat...') + # note that this will include the heartbeats that were received during the + # watchdog test (which takes 4.8s). + heartbeats = asyncio.run(get_all(record_messages(canbus.handle, node_id, 'heartbeat', timeout = 1.0))) + test_assert_eq(len(heartbeats), 5.8 / 0.1, accuracy=0.05) + test_assert_eq([msg['error'] for msg in heartbeats[0:35]], [0] * 35) # before watchdog expiry + test_assert_eq([msg['error'] for msg in heartbeats[-10:]], [errors.axis.ERROR_WATCHDOG_TIMER_EXPIRED] * 10) # after watchdog expiry + test_assert_eq([msg['current_state'] for msg in heartbeats], [1] * len(heartbeats)) + + logger.debug('testing reboot...') + my_cmd('reboot') + time.sleep(0.5) + if len(odrive.handle._remote_attributes) != 0: + raise TestFailed("device didn't seem to reboot") + odrive.handle = None + time.sleep(2.0) + odrive.prepare(logger) + if __name__ == '__main__': test_runner.run(TestSimpleCAN()) diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index 2a394aca..ea863d97 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -6,6 +6,7 @@ sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) import stat import odrive +from odrive.enums import * import fibre from fibre import Logger, Event import argparse @@ -153,6 +154,29 @@ def test_curve_fit(data, fitted_curve, max_mean_err, inlier_range, max_outliers) save_log(np.concatenate([data, np.array([fitted_curve]).transpose()], 1)) raise TestFailed("curve fit has too many outliers (err > {}): {} > {}".format(inlier_range, outliers, max_outliers)) +def test_watchdog(axis, feed_func, logger: Logger): + """ + Tests the watchdog of one axis, using the provided function to feed the watchdog. + This test assumes that the testing host has no more than 300ms random delays. + """ + start = time.monotonic() + axis.config.enable_watchdog = False + axis.error = 0 + axis.config.watchdog_timeout = 1.0 + axis.watchdog_feed() + axis.config.enable_watchdog = True + test_assert_eq(axis.error, 0) + for _ in range(5): # keep the watchdog alive for 3.5 seconds + time.sleep(0.7) + logger.debug('feeding watchdog at {}s'.format(time.monotonic() - start)) + feed_func() + err = axis.error + logger.debug('checking error at {}s'.format(time.monotonic() - start)) + test_assert_eq(err, 0) + + logger.debug('letting watchdog expire...') + time.sleep(1.3) # let the watchdog expire + test_assert_eq(axis.error, errors.axis.ERROR_WATCHDOG_TIMER_EXPIRED) # Test Components -------------------------------------------------------------# diff --git a/tools/odrive/tests/uart_ascii_test.py b/tools/odrive/tests/uart_ascii_test.py index fbf00f62..86b2f4ab 100644 --- a/tools/odrive/tests/uart_ascii_test.py +++ b/tools/odrive/tests/uart_ascii_test.py @@ -131,25 +131,7 @@ class TestUartAscii(): test_assert_eq(float(response.split()[0]), odrive.handle.axis0.encoder.pos_estimate, accuracy=0.001) test_assert_eq(float(response.split()[1]), odrive.handle.axis0.encoder.vel_estimate, accuracy=0.001) - - # Test watchdog (assumes that the testing host has no more than 300ms random delays) - start = time.monotonic() - odrive.handle.axis0.config.enable_watchdog = False - odrive.handle.axis0.error = 0 - odrive.handle.axis0.config.watchdog_timeout = 1.0 - odrive.handle.axis0.watchdog_feed() - odrive.handle.axis0.config.enable_watchdog = True - test_assert_eq(odrive.handle.axis0.error, 0) - for _ in range(5): # keep the watchdog alive for 3.5 seconds - time.sleep(0.7) - print('feeding watchdog at {}s'.format(time.monotonic() - start)) - ser.write(b'u 0\n') - err = odrive.handle.axis0.error - print('checking error at {}s'.format(time.monotonic() - start)) - test_assert_eq(err, 0) - - time.sleep(1.3) # let the watchdog expire - test_assert_eq(odrive.handle.axis0.error, errors.axis.ERROR_WATCHDOG_TIMER_EXPIRED) + test_watchdog(odrive.handle.axis0, lambda: ser.write(b'u 0\n'), logger) test_assert_eq(ser.readline(), b'') # check if the device remained silent during the test From 3d2d0f0e1ad90dbfc75b0f1f57a85d984977d7c1 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 27 Apr 2020 15:27:43 -0400 Subject: [PATCH 357/549] Clean up and modernize axis task_chain --- .gitignore | 2 ++ Firmware/MotorControl/axis.cpp | 9 ++++++--- Firmware/MotorControl/axis.hpp | 4 ++-- Firmware/Tests/test_can.cpp | 1 - Firmware/Tests/test_rotate.cpp | 32 ++++++++++++++++++++++++++++++++ Firmware/Tests/test_runner.cpp | 6 +----- Firmware/Tests/test_timer.cpp | 1 - Firmware/Tupfile.lua | 5 +++-- 8 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 Firmware/Tests/test_rotate.cpp diff --git a/.gitignore b/.gitignore index 106877fa..f29e35c0 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,5 @@ ODrive\.creator\.user ODrive\.files ODrive\.includes + +Firmware/Tests/bin/ diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 0e6f64dc..53b9925d 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -551,9 +551,12 @@ void Axis::run_state_machine_loop() { } // If the state failed, go to idle, else advance task chain - if (!status) + if (!status) { + std::fill(task_chain_.begin(), task_chain_.end(), AXIS_STATE_UNDEFINED); current_state_ = AXIS_STATE_IDLE; - else - memmove(task_chain_, task_chain_ + 1, sizeof(task_chain_) - sizeof(task_chain_[0])); + } else { + std::rotate(task_chain_.begin(), task_chain_.begin() + 1, task_chain_.end()); + task_chain_.back() = AXIS_STATE_UNDEFINED; + } } } diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 9f99f33a..c5a87a67 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -253,8 +253,8 @@ public: uint16_t dir_pin_; State_t requested_state_ = AXIS_STATE_STARTUP_SEQUENCE; - State_t task_chain_[10] = { AXIS_STATE_UNDEFINED }; - State_t& current_state_ = task_chain_[0]; + std::array task_chain_ = { AXIS_STATE_UNDEFINED }; + State_t& current_state_ = task_chain_.front(); uint32_t loop_counter_ = 0; LockinState_t lockin_state_ = LOCKIN_STATE_INACTIVE; Homing_t homing_; diff --git a/Firmware/Tests/test_can.cpp b/Firmware/Tests/test_can.cpp index 29f96a79..ef2ffecf 100644 --- a/Firmware/Tests/test_can.cpp +++ b/Firmware/Tests/test_can.cpp @@ -1,5 +1,4 @@ -#define DOCTEST_IMPLEMENT #include #include #include diff --git a/Firmware/Tests/test_rotate.cpp b/Firmware/Tests/test_rotate.cpp new file mode 100644 index 00000000..b77376cc --- /dev/null +++ b/Firmware/Tests/test_rotate.cpp @@ -0,0 +1,32 @@ +#include + +#include +#include +#include + +TEST_CASE("Rotate Axis State"){ + std::array testArr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + const int& currentVal = testArr.front(); + CHECK(currentVal == testArr[0]); + + std::rotate(testArr.begin(), testArr.begin() + 1, testArr.end()); + CHECK(currentVal == testArr[0]); + CHECK(currentVal == 1); + + CHECK(testArr.back() == 0); + + std::rotate(testArr.begin(), testArr.begin() + 1, testArr.end()); + CHECK(currentVal == testArr[0]); + CHECK(currentVal == 2); + CHECK(testArr.back() == 1); + + +} + +TEST_CASE("Fill Test"){ + std::array testArr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + std::fill(testArr.begin(), testArr.end(), 6); + for(const auto& val : testArr){ + CHECK(val == 6); + } +} \ No newline at end of file diff --git a/Firmware/Tests/test_runner.cpp b/Firmware/Tests/test_runner.cpp index e2314477..e111405f 100644 --- a/Firmware/Tests/test_runner.cpp +++ b/Firmware/Tests/test_runner.cpp @@ -16,10 +16,6 @@ using std::cout; using std::endl; - - - - TEST_SUITE("delta_enc") { // Modulo (as opposed to remainder), per https://stackoverflow.com/a/19288271 int mod(int dividend, int divisor) { @@ -131,7 +127,7 @@ TEST_SUITE("vel_ramp") { return v & 1; } - TEST_CASE("Blah") { + TEST_CASE("Equivalence") { float vel_setpoint = 0.0f; float vel_ramp_rate = 8000; float input_vel = 0.0f; diff --git a/Firmware/Tests/test_timer.cpp b/Firmware/Tests/test_timer.cpp index 4b3d81ac..89487f93 100644 --- a/Firmware/Tests/test_timer.cpp +++ b/Firmware/Tests/test_timer.cpp @@ -1,4 +1,3 @@ -#define DOCTEST_IMPLEMENT #include #include "MotorControl/timer.hpp" #include diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 8b66326a..4577bde1 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -191,6 +191,7 @@ build{ if tup.getconfig('DOCTEST') == 'true' then TEST_INCLUDES = '-I. -I./MotorControl -I./fibre/cpp/include -I./Drivers/DRV8301 -I./doctest' - tup.frule{inputs='Tests/*.cpp', command='g++ -O3 -std=gnu++17 '..TEST_INCLUDES..' %f -o %o', outputs='Tests/test_runner.exe'} + tup.foreach_rule('Tests/*.cpp', 'g++ -O3 -std=c++17 '..TEST_INCLUDES..' -c %f -o %o', 'Tests/bin/%B.o') + tup.frule{inputs='Tests/bin/*.o', command='g++ %f -o %o', outputs='Tests/test_runner.exe'} tup.frule{inputs='Tests/test_runner.exe', command='%f'} -end \ No newline at end of file +end From 4b58aeb637b7d5eab218dee6d464b8f745426691 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 29 Apr 2020 14:49:29 +0200 Subject: [PATCH 358/549] add SPI encoder tests (AMS and CUI protocol) --- tools/odrive/enums.py | 2 +- tools/odrive/tests/encoder_test.py | 401 ++++++++++++++++++++++------- tools/odrive/tests/test_runner.py | 6 + tools/test-rig-rpi.yaml | 19 +- 4 files changed, 333 insertions(+), 95 deletions(-) diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 5a3f1b76..1292e48b 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -91,6 +91,6 @@ INPUT_MODE_MIRROR = 7 ENCODER_MODE_INCREMENTAL = 0x00 ENCODER_MODE_HALL = 0x01 ENCODER_MODE_SINCOS = 0x02 -#ENCODER_MODE_SPI_ABS_CUI = 0x100 # currently not functional +ENCODER_MODE_SPI_ABS_CUI = 0x100 ENCODER_MODE_SPI_ABS_AMS = 0x101 ENCODER_MODE_SPI_ABS_AEAT = 0x102 diff --git a/tools/odrive/tests/encoder_test.py b/tools/odrive/tests/encoder_test.py index d3f66949..b9af8ed6 100644 --- a/tools/odrive/tests/encoder_test.py +++ b/tools/odrive/tests/encoder_test.py @@ -10,82 +10,6 @@ from odrive.enums import * from test_runner import * -teensy_code_template = """ -void setup() { - pinMode({enc_a}, OUTPUT); - pinMode({enc_b}, OUTPUT); -} - -int cpr = 8192; -int rpm = 30; - -// the loop routine runs over and over again forever: -void loop() { - int microseconds_per_count = (1000000 * 60 / cpr / rpm); - - for (;;) { - digitalWrite({enc_a}, HIGH); - delayMicroseconds(microseconds_per_count); - digitalWrite({enc_b}, HIGH); - delayMicroseconds(microseconds_per_count); - digitalWrite({enc_a}, LOW); - delayMicroseconds(microseconds_per_count); - digitalWrite({enc_b}, LOW); - delayMicroseconds(microseconds_per_count); - } -} -""" - -teensy_code_template2 = """ -void setup() { - analogWriteResolution(10); - int freq = 150000000/1024; // ~146.5kHz PWM frequency - analogWriteFrequency({enc_sin}, freq); - analogWriteFrequency({enc_cos}, freq); -} - -int rpm = 60; -float pos = 0; - -void loop() { - pos += 0.001f * ((float)rpm / 60.0f); - if (pos > 1.0f) - pos -= 1.0f; - analogWrite({enc_sin}, (int)(512.0f + 512.0f * sin(2.0f * M_PI * pos))); - analogWrite({enc_cos}, (int)(512.0f + 512.0f * cos(2.0f * M_PI * pos))); - delay(1); -} -""" - -teensy_code_template3 = """ -void setup() { - pinMode({hall_a}, OUTPUT); - pinMode({hall_b}, OUTPUT); - pinMode({hall_c}, OUTPUT); - digitalWrite({hall_a}, HIGH); -} - -int cpr = 90; // 15 pole-pairs. Value suggested in hoverboard.md -int rpm = 60; -int microseconds_per_count = (1000000 * 60 / cpr / rpm); - -void loop() { - digitalWrite({hall_b}, HIGH); - delayMicroseconds(microseconds_per_count); - digitalWrite({hall_a}, LOW); - delayMicroseconds(microseconds_per_count); - digitalWrite({hall_c}, HIGH); - delayMicroseconds(microseconds_per_count); - digitalWrite({hall_b}, LOW); - delayMicroseconds(microseconds_per_count); - digitalWrite({hall_a}, HIGH); - delayMicroseconds(microseconds_per_count); - digitalWrite({hall_c}, LOW); - delayMicroseconds(microseconds_per_count); -} -""" - - class TestEncoderBase(): """ Base class for encoder tests. @@ -112,8 +36,6 @@ class TestEncoderBase(): encoder.pos_cpr, encoder.vel_estimate, ], duration=5.0) - - data = np.array(data) short_period = (abs(1 / true_rps) < 5.0) reverse = (true_rps < 0) @@ -130,7 +52,7 @@ class TestEncoderBase(): # encoder.phase slope, offset, fitted_curve = fit_sawtooth(data[:,(0,3)], pi if reverse else -pi, -pi if reverse else pi, sigma=5) - test_assert_eq(slope / 7, 2*pi*true_rps, accuracy=0.01) + test_assert_eq(slope / 7, 2*pi*true_rps, accuracy=0.05) test_curve_fit(data[:,(0,3)], fitted_curve, max_mean_err = true_cpr * 0.01, inlier_range = true_cpr * 0.02, max_outliers = len(data[:,0]) * 0.02) # encoder.pos_estimate @@ -151,6 +73,31 @@ class TestEncoderBase(): +teensy_incremental_encoder_emulation_code = """ +void setup() { + pinMode({enc_a}, OUTPUT); + pinMode({enc_b}, OUTPUT); +} + +int cpr = 8192; +int rpm = 30; + +// the loop routine runs over and over again forever: +void loop() { + int microseconds_per_count = (1000000 * 60 / cpr / rpm); + + for (;;) { + digitalWrite({enc_a}, HIGH); + delayMicroseconds(microseconds_per_count); + digitalWrite({enc_b}, HIGH); + delayMicroseconds(microseconds_per_count); + digitalWrite({enc_a}, LOW); + delayMicroseconds(microseconds_per_count); + digitalWrite({enc_b}, LOW); + delayMicroseconds(microseconds_per_count); + } +} +""" class TestIncrementalEncoder(TestEncoderBase): @@ -173,10 +120,10 @@ class TestIncrementalEncoder(TestEncoderBase): yield (encoder, valid_combinations) - def run_test(self, enc: EncoderComponent, teensy: TeensyComponent, teensy_gpio_a: int, teensy_gpio_b: int, logger: Logger): + def run_test(self, enc: EncoderComponent, teensy: TeensyComponent, teensy_gpio_a: TeensyGpio, teensy_gpio_b: TeensyGpio, logger: Logger): true_cps = 8192*-0.5 # counts per second generated by the virtual encoder - code = teensy_code_template.replace("{enc_a}", str(teensy_gpio_a.num)).replace("{enc_b}", str(teensy_gpio_b.num)) + code = teensy_incremental_encoder_emulation_code.replace("{enc_a}", str(teensy_gpio_a.num)).replace("{enc_b}", str(teensy_gpio_b.num)) teensy.compile_and_program(code) if enc.handle.config.mode != ENCODER_MODE_INCREMENTAL: @@ -195,6 +142,27 @@ class TestIncrementalEncoder(TestEncoderBase): +teensy_sin_cos_encoder_emulation_code = """ +void setup() { + analogWriteResolution(10); + int freq = 150000000/1024; // ~146.5kHz PWM frequency + analogWriteFrequency({enc_sin}, freq); + analogWriteFrequency({enc_cos}, freq); +} + +float rps = 1.0f; +float pos = 0; + +void loop() { + pos += 0.001f * rps; + if (pos > 1.0f) + pos -= 1.0f; + analogWrite({enc_sin}, (int)(512.0f + 512.0f * sin(2.0f * M_PI * pos))); + analogWrite({enc_cos}, (int)(512.0f + 512.0f * cos(2.0f * M_PI * pos))); + delay(1); +} +""" + class TestSinCosEncoder(TestEncoderBase): def get_test_cases(self, testrig: TestRig): for odrive in testrig.get_components(ODriveComponent): @@ -213,7 +181,7 @@ class TestSinCosEncoder(TestEncoderBase): def run_test(self, enc: EncoderComponent, teensy: TeensyComponent, teensy_gpio_sin: TeensyGpio, teensy_gpio_cos: TeensyGpio, logger: Logger): - code = teensy_code_template2.replace("{enc_sin}", str(teensy_gpio_sin.num)).replace("{enc_cos}", str(teensy_gpio_cos.num)) + code = teensy_sin_cos_encoder_emulation_code.replace("{enc_sin}", str(teensy_gpio_sin.num)).replace("{enc_cos}", str(teensy_gpio_cos.num)) teensy.compile_and_program(code) if enc.handle.config.mode != ENCODER_MODE_SINCOS: @@ -229,6 +197,34 @@ class TestSinCosEncoder(TestEncoderBase): +teensy_hall_effect_encoder_emulation_code = """ +void setup() { + pinMode({hall_a}, OUTPUT); + pinMode({hall_b}, OUTPUT); + pinMode({hall_c}, OUTPUT); + digitalWrite({hall_a}, HIGH); +} + +int cpr = 90; // 15 pole-pairs. Value suggested in hoverboard.md +float rps = 1.0f; +int us_per_count = (1000000.0f / cpr / rps); + +void loop() { + digitalWrite({hall_b}, HIGH); + delayMicroseconds(us_per_count); + digitalWrite({hall_a}, LOW); + delayMicroseconds(us_per_count); + digitalWrite({hall_c}, HIGH); + delayMicroseconds(us_per_count); + digitalWrite({hall_b}, LOW); + delayMicroseconds(us_per_count); + digitalWrite({hall_a}, HIGH); + delayMicroseconds(us_per_count); + digitalWrite({hall_c}, LOW); + delayMicroseconds(us_per_count); +} +""" + class TestHallEffectEncoder(TestEncoderBase): def get_test_cases(self, testrig: TestRig): @@ -251,11 +247,11 @@ class TestHallEffectEncoder(TestEncoderBase): yield (encoder, valid_combinations) - def run_test(self, enc: EncoderComponent, teensy: TeensyComponent, teensy_gpio_a: int, teensy_gpio_b: int, teensy_gpio_c: int, logger: Logger): + def run_test(self, enc: EncoderComponent, teensy: TeensyComponent, teensy_gpio_a: TeensyGpio, teensy_gpio_b: TeensyGpio, teensy_gpio_c: TeensyGpio, logger: Logger): true_cpr = 90 true_rps = -1.0 - code = teensy_code_template3.replace("{hall_a}", str(teensy_gpio_a.num)).replace("{hall_b}", str(teensy_gpio_b.num)).replace("{hall_c}", str(teensy_gpio_c.num)) + code = teensy_hall_effect_encoder_emulation_code.replace("{hall_a}", str(teensy_gpio_a.num)).replace("{hall_b}", str(teensy_gpio_b.num)).replace("{hall_c}", str(teensy_gpio_c.num)) teensy.compile_and_program(code) if enc.handle.config.mode != ENCODER_MODE_HALL: @@ -270,9 +266,242 @@ class TestHallEffectEncoder(TestEncoderBase): enc.handle.config.cpr = 8192 + +# This encoder emulation mimics the specification given in the following datasheets: +# +# With {mode} == ENCODER_MODE_SPI_ABS_CUI: +# AMT23xx: https://www.cuidevices.com/product/resource/amt23.pdf +# +# With {mode} == ENCODER_MODE_SPI_ABS_AMS: +# AS5047P: https://ams.com/documents/20143/36005/AS5047P_DS000324_2-00.pdf/a7d44138-51f1-2f6e-c8b6-2577b369ace8 +# AS5048A/AS5048B: https://ams.com/documents/20143/36005/AS5048_DS000298_4-00.pdf/910aef1f-6cd3-cbda-9d09-41f152104832 +# => Only the read command on address 0x3fff is currently implemented. + +teensy_spi_encoder_emulation_code = """ +#define ENCODER_MODE_SPI_ABS_CUI 0x100 +#define ENCODER_MODE_SPI_ABS_AMS 0x101 +#define ENCODER_MODE_SPI_ABS_AEAT 0x102 + +static float rps = 1.0f; +static uint32_t cpr = 16384; +static uint32_t us_per_revolution = (uint32_t)(1000000.0f / rps); +static uint16_t spi_txd = 0; // first output word: NOP +static uint32_t zerotime = 0; + +void setup() { + pinMode({ncs}, INPUT_PULLUP); +} + +uint16_t get_pos_now() { + uint32_t time = micros(); + return ((uint64_t)((time - zerotime) % us_per_revolution)) * cpr / us_per_revolution; +} + + +#if {mode} == ENCODER_MODE_SPI_ABS_AMS + +uint8_t ams_parity(uint16_t v) { + v ^= v >> 8; + v ^= v >> 4; + v ^= v >> 2; + v ^= v >> 1; + return v & 1; +} + +uint16_t handle_command(uint16_t cmd) { + const uint16_t ERROR_RESPONSE = 0xc000; // error flag and parity bit set + + if (ams_parity(cmd)) { + return ERROR_RESPONSE; + } + + if (!(cmd & 14)) { // write not supported + return ERROR_RESPONSE; + } + + uint16_t addr = cmd & 0x3fff; + uint16_t data; + + switch (addr) { + case 0x3fff: data = get_pos_now(); break; + default: return ERROR_RESPONSE; + } + + return data | (ams_parity(data) << 15); +} + +#endif + +#if {mode} == ENCODER_MODE_SPI_ABS_CUI + +uint8_t cui_parity(uint16_t v) { + v ^= v >> 8; + v ^= v >> 4; + v ^= v >> 2; + return ~v & 3; +} + +uint16_t handle_command(uint16_t cmd) { + (void) cmd; // input not used on CUI + + // Test the cui_parity function itself with the example given in the datasheet + if ((0x21AB | (cui_parity(0x21AB) << 14)) != 0x61AB) { + return 0x0000; + } + + uint16_t data = get_pos_now(); + return data | (cui_parity(data) << 14); +} + +#endif + + +void loop() { + while (digitalReadFast({reset})) { + zerotime = micros(); + } + + if (!digitalReadFast({ncs})) { + static uint16_t spi_rxd = 0; + + pinMode({miso}, OUTPUT); + + for (;;) { + while (!digitalReadFast({sck})) + if (digitalReadFast({ncs})) + goto cs_deasserted; + + // Rising edge: Push output bit + + bool output_bit = spi_txd & 0x8000; + digitalWriteFast({miso}, output_bit); + spi_txd <<= 1; + + while (digitalReadFast({sck})) + if (digitalReadFast({ncs})) + goto cs_deasserted; + + // Falling edge: Sample input bit (only in AMS mode) + +#if {mode} == ENCODER_MODE_SPI_ABS_AMS + bool input_bit = digitalReadFast({mosi}); + spi_rxd <<= 1; + if (input_bit) { + spi_rxd |= 1; + } else { + spi_rxd &= ~1; + } +#endif + } + +cs_deasserted: + // chip deselected: Process command + pinMode({miso}, INPUT); + + spi_txd = handle_command(spi_rxd); + } +} +""" + +class TestSpiEncoder(TestEncoderBase): + def __init__(self, mode: int): + self.mode = mode + + def get_test_cases(self, testrig: TestRig): + for odrive in testrig.get_components(ODriveComponent): + for encoder in odrive.encoders: + odrive_ncs_gpio = odrive.gpio7 # this GPIO choice is completely arbitrary + gpio_conns = [ + testrig.get_connected_components(odrive.sck, TeensyGpio), + testrig.get_connected_components(odrive.miso, TeensyGpio), + testrig.get_connected_components(odrive.mosi, TeensyGpio), + testrig.get_connected_components(odrive_ncs_gpio, TeensyGpio), + ] + + valid_combinations = [] + for combination in itertools.product(*gpio_conns): + if (len(set(c.parent for c in combination)) != 1): + continue + teensy = combination[0].parent + reset_pin_options = [] + for gpio in teensy.gpios: + for local_gpio in testrig.get_connected_components(gpio, LinuxGpioComponent): + reset_pin_options.append((gpio, local_gpio)) + valid_combinations.append((teensy, *combination, reset_pin_options)) + + yield (encoder, 7, valid_combinations) + + + def run_test(self, enc: EncoderComponent, odrive_ncs_gpio: int, teensy: TeensyComponent, teensy_gpio_sck: TeensyGpio, teensy_gpio_miso: TeensyGpio, teensy_gpio_mosi: TeensyGpio, teensy_gpio_ncs: TeensyGpio, teensy_gpio_reset: TeensyGpio, reset_gpio: LinuxGpioComponent, logger: Logger): + true_cpr = 16384 + true_rps = 1.0 + + reset_gpio.config(output=True) # hold encoder and disable its SPI + reset_gpio.write(True) + + code = (teensy_spi_encoder_emulation_code + .replace("{sck}", str(teensy_gpio_sck.num)) + .replace("{miso}", str(teensy_gpio_miso.num)) + .replace("{mosi}", str(teensy_gpio_mosi.num)) + .replace("{ncs}", str(teensy_gpio_ncs.num)) + .replace("{reset}", str(teensy_gpio_reset.num)) + .replace("{mode}", str(self.mode))) + teensy.compile_and_program(code) + + logger.debug(f'Configuring absolute encoder in mode 0x{self.mode:x}...') + enc.handle.config.mode = self.mode + enc.handle.config.abs_spi_cs_gpio_pin = odrive_ncs_gpio + enc.handle.config.cpr = true_cpr + enc.parent.save_config_and_reboot() + + time.sleep(1.0) + + logger.debug('Testing absolute readings and SPI errors...') + + # Encoder is still disabled - expect recurring error + enc.handle.error = 0 + time.sleep(0.002) + # This fails from time to time because the pull-up on the ODrive only manages + # to pull MISO to 1.8V, leaving it in the undefined range. + test_assert_eq(enc.handle.error, errors.encoder.ERROR_ABS_SPI_COM_FAIL) + + # Enable encoder and expect error to go away + reset_gpio.write(False) + release_time = time.monotonic() + enc.handle.error = 0 + time.sleep(0.002) + test_assert_eq(enc.handle.error, 0) + + # Check absolute position after 1.5s + time.sleep(1.5) + true_delta_t = time.monotonic() - release_time + test_assert_eq(enc.handle.pos_abs, (true_delta_t * true_rps * true_cpr) % true_cpr, range = true_cpr*0.001) + + test_assert_eq(enc.handle.error, 0) + reset_gpio.write(True) + time.sleep(0.002) + test_assert_eq(enc.handle.error, errors.encoder.ERROR_ABS_SPI_COM_FAIL) + reset_gpio.write(False) + release_time = time.monotonic() + enc.handle.error = 0 + time.sleep(0.002) + test_assert_eq(enc.handle.error, 0) + + # Check absolute position after 1.5s + time.sleep(1.5) + true_delta_t = time.monotonic() - release_time + test_assert_eq(enc.handle.pos_abs, (true_delta_t * true_rps * true_cpr) % true_cpr, range = true_cpr*0.001) + + self.run_generic_encoder_test(enc.handle, true_cpr, true_rps) + enc.handle.config.cpr = 8192 + + + if __name__ == '__main__': test_runner.run([ - TestIncrementalEncoder(), - TestSinCosEncoder(), + #TestIncrementalEncoder(), + #TestSinCosEncoder(), TestHallEffectEncoder(), + TestSpiEncoder(ENCODER_MODE_SPI_ABS_AMS), + TestSpiEncoder(ENCODER_MODE_SPI_ABS_CUI), ]) diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index ea863d97..56b9e663 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -194,6 +194,9 @@ class ODriveComponent(Component): for i in range(1,9): self.__setattr__('gpio' + str(i), Component(self)) self.can = Component(self) + self.sck = Component(self) + self.miso = Component(self) + self.mosi = Component(self) def get_subcomponents(self): for enc_ctx in self.encoders: @@ -203,6 +206,9 @@ class ODriveComponent(Component): for i in range(1,9): yield ('gpio' + str(i)), getattr(self, 'gpio' + str(i)) yield 'can', self.can + yield 'spi.sck', self.sck + yield 'spi.miso', self.miso + yield 'spi.mosi', self.mosi def prepare(self, logger: Logger): """ diff --git a/tools/test-rig-rpi.yaml b/tools/test-rig-rpi.yaml index 33588274..4fb0b4e3 100644 --- a/tools/test-rig-rpi.yaml +++ b/tools/test-rig-rpi.yaml @@ -65,18 +65,18 @@ components: connections: - ['odrive.can', 'rpi.can0'] - ['teensy.program', 'rpi.gpio26'] - - ['teensy.gpio11', 'rpi.uart0.tx'] - - ['teensy.gpio12', 'rpi.uart0.rx'] - - ['teensy.gpio10', 'odrive.gpio1'] - - ['teensy.gpio9', 'odrive.gpio2'] - - ['teensy.gpio8', 'odrive.gpio3'] - - ['teensy.gpio7', 'odrive.gpio4'] + - ['teensy.gpio12', 'rpi.uart0.tx'] + - ['teensy.gpio13', 'rpi.uart0.rx'] + - ['teensy.gpio11', 'odrive.gpio1'] + - ['teensy.gpio10', 'odrive.gpio2'] + - ['teensy.gpio9', 'odrive.gpio3'] + - ['teensy.gpio8', 'odrive.gpio4'] - ['teensy.gpio14', 'odrive.gpio5'] - ['teensy.gpio15', 'odrive.gpio6'] - ['teensy.gpio16', 'odrive.gpio7'] - ['teensy.gpio17', 'odrive.gpio8'] - - ['teensy.gpio4', 'rpi.gpio20'] - - ['teensy.gpio5', 'rpi.gpio19'] + - ['teensy.gpio6', 'rpi.gpio20'] + - ['teensy.gpio7', 'rpi.gpio19'] - ['teensy.gpio23', 'odrive.encoder0.z'] - ['teensy.gpio22', 'odrive.encoder0.a'] - ['teensy.gpio21', 'odrive.encoder0.b'] @@ -86,6 +86,9 @@ connections: - ['teensy.gpio0', 'real_encoder.z'] - ['teensy.gpio1', 'real_encoder.a'] - ['teensy.gpio2', 'real_encoder.b'] + - ['teensy.gpio3', 'odrive.spi.mosi'] + - ['teensy.gpio4', 'odrive.spi.miso'] + - ['teensy.gpio5', 'odrive.spi.sck'] - ['odrive.axis0', 'D5065-270KV_0'] - ['D5065-270KV_0', 'real_encoder'] - ['odrive.gpio3', 'lpf0'] From 4cfbb2c3e4b50bb6a0ddd5f32e559f2441ec3e5c Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 29 Apr 2020 16:18:43 +0200 Subject: [PATCH 359/549] fix various issues with SPI encoder implementation - Remove logic that would inhibit error detection until the first value is received. This logic posed a risk when no encoder is connected at all. - check error flag in AMS mode - Make CUI mode work. Only tested with emulated CUI encoder. - only go to ready-state if a successful value was received _and_ the offset was already pre-calibrated - enable pull-up on MISO so that a disconnected AMS encoder can be detected - update encoder documentation --- Firmware/Board/v3/Src/spi.c | 2 +- Firmware/MotorControl/encoder.cpp | 53 ++++++++++++++++++++----------- Firmware/MotorControl/encoder.hpp | 11 +++---- docs/encoders.md | 41 ++++++++++++++---------- 4 files changed, 65 insertions(+), 42 deletions(-) diff --git a/Firmware/Board/v3/Src/spi.c b/Firmware/Board/v3/Src/spi.c index d66515f9..fe2ab4ea 100644 --- a/Firmware/Board/v3/Src/spi.c +++ b/Firmware/Board/v3/Src/spi.c @@ -103,7 +103,7 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle) */ GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Pull = GPIO_PULLUP; // required for disconnect detection on SPI encoders GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF6_SPI3; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 47b7aa95..dcb1bbf9 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -310,6 +310,7 @@ void Encoder::sample_now() { case MODE_SPI_ABS_AMS: case MODE_SPI_ABS_CUI: + case MODE_SPI_ABS_AEAT: { // Do nothing } break; @@ -351,12 +352,12 @@ bool Encoder::abs_spi_start_transaction(){ return false; } HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_RESET); - HAL_SPI_TransmitReceive_DMA(hw_config_.spi,(uint8_t*)abs_spi_dma_tx_,(uint8_t*)abs_spi_dma_rx_,1); + HAL_SPI_TransmitReceive_DMA(hw_config_.spi, (uint8_t*)abs_spi_dma_tx_, (uint8_t*)abs_spi_dma_rx_, 1); } return true; } -uint8_t parity(uint16_t v){ +uint8_t ams_parity(uint16_t v) { v ^= v >> 8; v ^= v >> 4; v ^= v >> 2; @@ -364,29 +365,48 @@ uint8_t parity(uint16_t v){ return v & 1; } +uint8_t cui_parity(uint16_t v) { + v ^= v >> 8; + v ^= v >> 4; + v ^= v >> 2; + return ~v & 3; +} + void Encoder::abs_spi_cb(){ HAL_GPIO_WritePin(abs_spi_cs_port_, abs_spi_cs_pin_, GPIO_PIN_SET); + + uint16_t pos; + switch (mode_) { case MODE_SPI_ABS_AMS: { - uint8_t parity_calc, parity_bit; - auto rawVal = abs_spi_dma_rx_[0]; - parity_calc = parity(rawVal & 0x7FFF); - parity_bit = rawVal >> 15; - - if (parity_calc == parity_bit) { - pos_abs_ = rawVal & 0x3FFF; - abs_spi_pos_updated_ = true; + uint16_t rawVal = abs_spi_dma_rx_[0]; + // check if parity is correct (even) and error flag clear + if (ams_parity(rawVal) || ((rawVal >> 14) & 1)) { + return; } + pos = rawVal & 0x3fff; } break; - case MODE_SPI_ABS_AEAT: { - pos_abs_ = abs_spi_dma_rx_[0]; - abs_spi_pos_updated_ = true; + + case MODE_SPI_ABS_CUI: { + uint16_t rawVal = abs_spi_dma_rx_[0]; + // check if parity is correct + if (cui_parity(rawVal)) { + return; + } + pos = rawVal & 0x3fff; } break; + default: { set_error(ERROR_UNSUPPORTED_ENCODER_MODE); + return; } break; } - is_ready_ = true; + + pos_abs_ = pos; + abs_spi_pos_updated_ = true; + if (config_.pre_calibrated) { + is_ready_ = true; + } } void Encoder::abs_spi_cs_pin_init(){ @@ -448,7 +468,7 @@ bool Encoder::update() { case MODE_SPI_ABS_AMS: case MODE_SPI_ABS_CUI: case MODE_SPI_ABS_AEAT: { - if (abs_spi_pos_updated_ == false && abs_spi_pos_init_once_) { + if (abs_spi_pos_updated_ == false) { // Low pass filter the error spi_error_rate_ += current_meas_period * (1.0f - spi_error_rate_); if (spi_error_rate_ > 0.005f) @@ -464,9 +484,6 @@ bool Encoder::update() { if (delta_enc > config_.cpr/2) { delta_enc -= config_.cpr; } - if (!abs_spi_pos_init_once_ && delta_enc != 0) { - abs_spi_pos_init_once_ = true; - } }break; default: { diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 842d70d9..872bf810 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -24,9 +24,9 @@ public: MODE_INCREMENTAL, MODE_HALL, MODE_SINCOS, - MODE_SPI_ABS_CUI = 0x100, - MODE_SPI_ABS_AMS = 0x101, - MODE_SPI_ABS_AEAT = 0x102, + MODE_SPI_ABS_CUI = 0x100, //!< compatible with CUI AMT23xx + MODE_SPI_ABS_AMS = 0x101, //!< compatible with AMS AS5047P, AS5048A/AS5048B (no daisy chain support) + MODE_SPI_ABS_AEAT = 0x102, //!< not yet implemented }; const uint32_t MODE_FLAG_ABS = 0x100; @@ -110,10 +110,9 @@ public: bool abs_spi_start_transaction(); void abs_spi_cb(); void abs_spi_cs_pin_init(); - uint16_t abs_spi_dma_tx_[2] = {0xFFFF, 0x0000}; - uint16_t abs_spi_dma_rx_[2]; + uint16_t abs_spi_dma_tx_[1] = {0xFFFF}; + uint16_t abs_spi_dma_rx_[1]; bool abs_spi_pos_updated_ = false; - bool abs_spi_pos_init_once_ = false; Mode_t mode_ = MODE_INCREMENTAL; GPIO_TypeDef* abs_spi_cs_port_; uint16_t abs_spi_cs_pin_; diff --git a/docs/encoders.md b/docs/encoders.md index d0b60bdf..ae0c1263 100644 --- a/docs/encoders.md +++ b/docs/encoders.md @@ -121,7 +121,7 @@ Connect to the I pin, see if you get a pulse on a complete rotation. Sometimes t If you are using SPI, have a lot at the signal on the CLK, and CS pins. There are many examples on the net for how these should behave. ## Encoder Noise -Noise is found in all circuits, life is just about figuring out if it is preventing your system from working. Lots of users have no problems with noise interfering with their odrive operation, others will tell you "_I've been using the same encoder as you with no problems_". Power to 'em, that may be true, but it doesn't mean it will work for you. If you are concerned about noise, there are several possible sources: +Noise is found in all circuits, life is just about figuring out if it is preventing your system from working. Lots of users have no problems with noise interfering with their ODrive operation, others will tell you "_I've been using the same encoder as you with no problems_". Power to 'em, that may be true, but it doesn't mean it will work for you. If you are concerned about noise, there are several possible sources: * Importantly, encoder wires may be too close to motor wires, avoid overlap as much as possible * Long wires between encoder and ODrive @@ -135,26 +135,33 @@ If you are using an encoder with an index signal, another problem that has been * when performing an index_search, the motor does not return to the same position each time. One easy step that _might_ fix the noise on the Z input has been to solder a 22nF-47nF capacitor to the Z pin and the GND pin on the underside of the ODrive board. -## AS5047/AS5048 Encoders -The AS5047/AS5048 encoders are Hall Effect/Magnetic sensors that can serve as rotary encoders for the ODrive. -The AS5047 has 3 independent output interfaces: SPI, ABI, and PWM. -The AS5048 has 4 independent output interfaces: SPI, ABI, I2C, and PWM. +## SPI Encoders -Both chips come with evaluation boards that can simplify mounted the chips to your motor. For our purposes if you are using an evaluation board you should select the settings for 3.3v, and tie MOSI high to 3.3v. +Apart from (incremental) quadrature encoders, ODrive also supports absolute SPI encoders (since firmware v0.5). These are usually based on are Hall Effect/Magnetic sensors and measure an absolute angle. This means you don't need to repeat the encoder calibration after every ODrive reboot. Currently, the following modes are supported: -If you are having calibration problems - make sure your magnet is centered on the axis of rotation on the motor, some users report this has a significant impact on calibration. Also make sure your magnet height is within range of the spec sheet. + * **CUI protocol**: Compatible with the AMT23xx family (AMT232A, AMT232B, AMT233A, AMT233B). + * **AMS protocol**: Compatible with AS5047P and AS5048A/AS5048B. -#### Using ABI. -You can use ABI with the AS5047/AS5048 with the default ODrive firmware. For your wiring, connect A, B, 3.3v, GND to the labeled pins on the odrive -The acronym I and Z mean the same thing, connect those as well if you are using an index signal. +Some of these chips come with evaluation boards that can simplify mounting the chips to your motor. For our purposes if you are using an evaluation board you should select the settings for 3.3v. -#### Using SPI. -TobinHall has written a [branch](https://github.com/TobinHall/ODrive/tree/Non-Blocking_Absolute_SPI) that supports the SPI option on the AS5047/AS5048. Use his build to flash firmware on your ODrive and connect MISO, SCK, and CS to the labeled pins on the odrive +1. Connect the encoder to the ODrive's SPI interface: + + - The encoder's SCK, MISO (aka "DATA" on CUI encoders), GND and 3.3V should connect to the ODrive pins with the same label. + - The encoder's MOSI should be tied to 3.3V (AMS encoders only. CUI encoders don't have this pin.) + - The encoder's Chip Select (aka nCS/CSn) can be connected to any of the ODrive's GPIOs (caution: GPIOs 1 and 2 are usually used by UART). -Tie MOSI to 3.3v, connect to the SCK, CLK, MISO, GND and 3.2v pins on the ODrive. (note for SPI users, the acronym SCK and CLK mean the same thing, the acronym CSn and CS mean the same thing.) +2. In `odrivetool`, run: -Add these commands to your calibration / startup script: -* `.encoder.config.abs_spi_cs_gpio_pin = 4` or which ever GPIO pin you choose -* `.encoder.config.mode = 257` -* `.axis0.encoder.config.cpr = 2**14` + .encoder.config.abs_spi_cs_gpio_pin = 4 # or which ever GPIO pin you choose + .encoder.config.mode = ENCODER_MODE_SPI_ABS_CUI # or ENCODER_MODE_SPI_ABS_AMS + .encoder.config.cpr = 2**14 # or 2**12 for AMT232A and AMT233A + .save_configuration() + .reboot() + +3. Run the [offset calibration](#encoder-without-index-signal) and then save the calibration with `.save_configuration()`. + The next time you reboot, the encoder should be immediately ready. + +Sometimes the encoder takes longer than the ODrive to start, in which case you need to clear the errors after every restart. + +If you are having calibration problems - make sure your magnet is centered on the axis of rotation on the motor, some users report this has a significant impact on calibration. Also make sure your magnet height is within range of the spec sheet. From f84ff36c747b07c3044b1025a83ab07e400b4327 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 30 Apr 2020 11:44:14 +0200 Subject: [PATCH 360/549] only enable vbus_voltage clamping if dc_bus_overvoltage_trip_level > nominal_voltage --- Firmware/MotorControl/low_level.cpp | 5 ++++- Firmware/MotorControl/odrive_main.h | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 14a59b4d..689bb277 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -594,7 +594,10 @@ void update_brake_current() { // Don't start braking until -Ibus > regen_current_allowed float brake_current = -Ibus_sum - board_config.max_regen_current; float brake_duty = brake_current * std::abs(board_config.brake_resistance) / vbus_voltage; - brake_duty += std::max((vbus_voltage - board_config.nominal_voltage) / (board_config.dc_bus_overvoltage_trip_level / 0.9f - board_config.nominal_voltage), 0.0f); + + if (board_config.nominal_voltage < board_config.dc_bus_overvoltage_trip_level) { + brake_duty += std::max((vbus_voltage - board_config.nominal_voltage) / (board_config.dc_bus_overvoltage_trip_level / 0.9f - board_config.nominal_voltage), 0.0f); + } // Clamp the duty cycle brake_duty = std::clamp(brake_duty, 0.0f, 0.9f); diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index fe14446f..a505b3e1 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -95,7 +95,12 @@ struct BoardConfig_t { // Date: Fri, 1 May 2020 11:36:07 +0200 Subject: [PATCH 361/549] update developer documentation --- docs/configuring-vscode.md | 11 ++++++----- docs/developer-guide.md | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/configuring-vscode.md b/docs/configuring-vscode.md index 69edf772..7eb70178 100644 --- a/docs/configuring-vscode.md +++ b/docs/configuring-vscode.md @@ -23,12 +23,12 @@ Before doing the VSCode setup, make sure you've installed all of your [prerequis You should now be ready to compile and test the ODrive project. ## Building the Firmware -* Tasks -> Run Build Task +* Terminal -> Run Build Task (Ctrl+Shift+B) A terminal window will open with your native shell. VSCode is configured to run the command `make -j4` in this terminal. ## Flashing the Firmware -* Tasks -> Run Task -> flash +* Terminal -> Run Task -> flash A terminal window will open with your native shell. VSCode is configured to run the command `make flash` in this terminal. @@ -42,12 +42,13 @@ Note: If developing on Windows, you should have `arm-none-eabi-gdb` and `openOCD * Make sure you have the Firmware folder as your active folder * Set `CONFIG_DEBUG=true` in the tup.config file * Flash the board with the newest code (starting debug session doesn't do this) - * Debug -> Start Debugging (or press F5) + * In the _Run_ tab (Ctrl+Shift+D), select "Debug ODrive (Firmware)" + * Press _Start Debugging_ (or press F5) * The processor will reset and halt. * Set your breakpoints. Note: you can only set breakpoints when the processor is halted, if you set them during run mode, they won't get applied. - * Run (F5) + * _Continue_ (F5) * Stepping over/in/out, restarting, and changing breakpoints can be done by first pressing the "pause" (F6) button at the top the screen. - * When done debugging, simply stop (Shift+F5) the debugger. It will kill your openOCD process too. + * When done debugging, simply stop (Shift+F5) the debugger. It will kill your openOCD process too. ## Cleaning the Build This sometimes needs to be done if you change branches. diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 6f351070..dc20b208 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -59,7 +59,7 @@ sudo apt-get install openocd sudo add-apt-repository ppa:jonathonf/tup && sudo apt-get update && sudo apt-get install tup ``` -#### Arch Linux +#### Linux (Arch Linux) ```bash sudo pacman -S arm-none-eabi-gcc arm-none-eabi-binutils sudo pacman -S arm-none-eabi-gdb @@ -128,7 +128,7 @@ If the flashing worked, you can connect to the board using the [odrivetool](gett

    ## Testing -The script `tools/run_tests.py` runs a sequence of automated tests for several firmware features as well as high power burn-in tests. Some tests only need one ODrive and one motor/encoder pair while other tests need a back-to-back test rig such as [this one](https://cad.onshape.com/documents/026bda35ad5dff4d73c1d37f/w/ae302174f402737e1fdb3783/e/5ca143a6e5e24daf1fe8e434). In any case, to run the tests you need to provide a YAML file that lists the parameters of your test setup. An example can be found at [`tools/test-rig-parallel.yaml`](tools/test-rig-parallel.yaml`). The programmer serial number can be found by running `Firmware/find_programmer.sh` (make sure it has the latest formware from STM). +The script `tools/run_tests.py` runs a sequence of automated tests for several firmware features as well as high power burn-in tests. Some tests only need one ODrive and one motor/encoder pair while other tests need a back-to-back test rig such as [this one](https://cad.onshape.com/documents/026bda35ad5dff4d73c1d37f/w/ae302174f402737e1fdb3783/e/5ca143a6e5e24daf1fe8e434). In any case, to run the tests you need to provide a YAML file that lists the parameters of your test setup. An example can be found at [`tools/test-rig-parallel.yaml`](tools/test-rig-parallel.yaml`). The programmer serial number can be found by running `Firmware/find_programmer.sh` (make sure it has the latest firmware from STM).