diff --git a/.travis.yml b/.travis.yml index bad65bb3..531bce18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,10 +3,10 @@ language: c sudo: false -addons: - apt: - packages: - libc6-i386 +addons: + apt: + packages: + libc6-i386 cache: directories: @@ -28,4 +28,4 @@ script: - cd Firmware - mkdir -p build - tup generate ./build.sh - - bash -x ./build.sh + - bash -xe ./build.sh diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index 691314ac..b337bc20 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -4,136 +4,114 @@ "name": "Win32", "includePath": [ "${workspaceRoot}", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/include", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS", - "${workspaceRoot}/Drivers/DRV8301", - "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Core/Inc", - "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc", - "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc", - "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy", - "${workspaceRoot}/Drivers/CMSIS/Device/ST/STM32F4xx/Include", - "${workspaceRoot}/Drivers/CMSIS/Include", - "${workspaceRoot}/Inc", "${workspaceRoot}/MotorControl", - "C:/Program Files (x86)/GNU Tools ARM Embedded/6 2017-q1-update/arm-none-eabi/include/c++/6.3.1", - "C:/Program Files (x86)/GNU Tools ARM Embedded/6 2017-q1-update/arm-none-eabi/include/c++/6.3.1/arm-none-eabi", - "C:/Program Files (x86)/GNU Tools ARM Embedded/6 2017-q1-update/lib/gcc/arm-none-eabi/6.3.1/include", - "C:/Program Files (x86)/GNU Tools ARM Embedded/6 2017-q1-update/arm-none-eabi/include" + "${workspaceRoot}/Board/v3.3/Inc", + "${workspaceRoot}/Board/v3.3/Drivers/CMSIS/Include", + "${workspaceRoot}/Board/v3.3/Drivers/CMSIS/Device/ST/STM32F4xx/Include", + "${workspaceRoot}/Board/v3.3/Drivers/DRV8301", + "${workspaceRoot}/Board/v3.3/Drivers/STM32F4xx_HAL_Driver/Inc", + "${workspaceRoot}/Board/v3.3/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy", + "${workspaceRoot}/Board/v3.3/Middlewares/ST/STM32_USB_Device_Library/Core/Inc", + "${workspaceRoot}/Board/v3.3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc", + "${workspaceRoot}/Board/v3.3/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS", + "${workspaceRoot}/Board/v3.3/Middlewares/Third_Party/FreeRTOS/Source/include", + "${workspaceRoot}/Board/v3.3/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F", + "c:/program files (x86)/gnu tools arm embedded/6 2017-q1-update/arm-none-eabi/include/c++/6.3.1", + "c:/program files (x86)/gnu tools arm embedded/6 2017-q1-update/arm-none-eabi/include/c++/6.3.1/arm-none-eabi", + "c:/program files (x86)/gnu tools arm embedded/6 2017-q1-update/arm-none-eabi/include/c++/6.3.1/backward", + "c:/program files (x86)/gnu tools arm embedded/6 2017-q1-update/lib/gcc/arm-none-eabi/6.3.1/include", + "c:/program files (x86)/gnu tools arm embedded/6 2017-q1-update/lib/gcc/arm-none-eabi/6.3.1/include-fixed", + "c:/program files (x86)/gnu tools arm embedded/6 2017-q1-update/arm-none-eabi/include" ], "defines": [ - "_DEBUG", - "UNICODE" + "STM32F405xx", + "USE_HAL_DRIVER", + "__weak=\"__attribute__((weak))\"", + "__packed=\"__attribute__((__packed__))\"", + "__GNUC__" ], "intelliSenseMode": "clang-x64", "browse": { "path": [ "${workspaceRoot}", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/include", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS", - "${workspaceRoot}/Drivers/DRV8301", - "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Core/Inc", - "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc", - "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc", - "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy", - "${workspaceRoot}/Drivers/CMSIS/Device/ST/STM32F4xx/Include", - "${workspaceRoot}/Drivers/CMSIS/Include", - "${workspaceRoot}/Inc", - "${workspaceRoot}/MotorControl" + "C:/Program Files (x86)/GNU Tools ARM Embedded" ], - "limitSymbolsToIncludedHeaders": true, - "databaseFilename": "" + "limitSymbolsToIncludedHeaders": true } }, - { - "name": "Linux", - "includePath": [ - "${workspaceRoot}", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/include", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS", - "${workspaceRoot}/Drivers/DRV8301", - "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Core/Inc", - "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc", - "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc", - "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy", - "${workspaceRoot}/Drivers/CMSIS/Device/ST/STM32F4xx/Include", - "${workspaceRoot}/Drivers/CMSIS/Include", - "${workspaceRoot}/Inc", - "${workspaceRoot}/MotorControl", - "/usr/lib/gcc/arm-none-eabi/4.9.3/include", - "/usr/lib/arm-none-eabi/include" - ], - "defines": [ - "_DEBUG", - "UNICODE" - ], - "intelliSenseMode": "clang-x64", - "browse": { - "path": [ + { + "name": "Linux", + "includePath": [ + "${workspaceRoot}", + "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F", + "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/include", + "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS", + "${workspaceRoot}/Drivers/DRV8301", + "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Core/Inc", + "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc", + "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc", + "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy", + "${workspaceRoot}/Drivers/CMSIS/Device/ST/STM32F4xx/Include", + "${workspaceRoot}/Drivers/CMSIS/Include", + "${workspaceRoot}/Inc", + "${workspaceRoot}/MotorControl", + "/usr/lib/gcc/arm-none-eabi/4.9.3/include", + "/usr/lib/arm-none-eabi/include" + ], + "defines": [ + "STM32F405xx", + "USE_HAL_DRIVER", + "__weak=\"__attribute__((weak))\"", + "__packed=\"__attribute__((__packed__))\"", + "__GNUC__" + ], + "intelliSenseMode": "clang-x64", + "browse": { + "path": [ "${workspaceRoot}", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/include", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS", - "${workspaceRoot}/Drivers/DRV8301", - "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Core/Inc", - "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc", - "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc", - "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy", - "${workspaceRoot}/Drivers/CMSIS/Device/ST/STM32F4xx/Include", - "${workspaceRoot}/Drivers/CMSIS/Include", - "${workspaceRoot}/Inc", - "${workspaceRoot}/MotorControl" - ], - "limitSymbolsToIncludedHeaders": true, - "databaseFilename": "" - } - }, - { - "name": "MacOS", - "includePath": [ - "${workspaceRoot}", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/include", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS", - "${workspaceRoot}/Drivers/DRV8301", - "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Core/Inc", - "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc", - "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc", - "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy", - "${workspaceRoot}/Drivers/CMSIS/Device/ST/STM32F4xx/Include", - "${workspaceRoot}/Drivers/CMSIS/Include", - "${workspaceRoot}/Inc", - "${workspaceRoot}/MotorControl", - "/usr/local/Caskroom/gcc-arm-embedded/6-2017-q2-update/gcc-arm-none-eabi-6-2017-q2-update/arm-none-eabi/include", - "/usr/local/Caskroom/gcc-arm-embedded/6-2017-q2-update/gcc-arm-none-eabi-6-2017-q2-update/arm-none-eabi/include/c++/6.3.1", - "/usr/local/Caskroom/gcc-arm-embedded/6-2017-q2-update/gcc-arm-none-eabi-6-2017-q2-update/arm-none-eabi/include/c++/6.3.1/arm-none-eabi", - "/usr/local/Caskroom/gcc-arm-embedded/6-2017-q2-update/gcc-arm-none-eabi-6-2017-q2-update/lib/gcc/arm-none-eabi/6.3.1/include" - ], - "defines": [ - "_DEBUG", - "UNICODE" - ], - "intelliSenseMode": "clang-x64", - "browse": { - "path": [ + "/usr/lib/gcc/arm-none-eabi/4.9.3/include", + "/usr/lib/arm-none-eabi/include" + ], + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "" + } + }, + { + "name": "Mac", + "includePath": [ + "${workspaceRoot}", + "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F", + "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/include", + "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS", + "${workspaceRoot}/Drivers/DRV8301", + "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Core/Inc", + "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc", + "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc", + "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy", + "${workspaceRoot}/Drivers/CMSIS/Device/ST/STM32F4xx/Include", + "${workspaceRoot}/Drivers/CMSIS/Include", + "${workspaceRoot}/Inc", + "${workspaceRoot}/MotorControl", + "/usr/local/Caskroom/gcc-arm-embedded/6-2017-q2-update/gcc-arm-none-eabi-6-2017-q2-update/arm-none-eabi/include", + "/usr/local/Caskroom/gcc-arm-embedded/6-2017-q2-update/gcc-arm-none-eabi-6-2017-q2-update/arm-none-eabi/include/c++/6.3.1", + "/usr/local/Caskroom/gcc-arm-embedded/6-2017-q2-update/gcc-arm-none-eabi-6-2017-q2-update/arm-none-eabi/include/c++/6.3.1/arm-none-eabi", + "/usr/local/Caskroom/gcc-arm-embedded/6-2017-q2-update/gcc-arm-none-eabi-6-2017-q2-update/lib/gcc/arm-none-eabi/6.3.1/include" + ], + "defines": [ + "STM32F405xx", + "USE_HAL_DRIVER", + "__weak=\"__attribute__((weak))\"", + "__packed=\"__attribute__((__packed__))\"", + "__GNUC__" + ], + "intelliSenseMode": "clang-x64", + "browse": { + "path": [ "${workspaceRoot}", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/include", - "${workspaceRoot}/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS", - "${workspaceRoot}/Drivers/DRV8301", - "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Core/Inc", - "${workspaceRoot}/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc", - "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc", - "${workspaceRoot}/Drivers/STM32F4xx_HAL_Driver/Inc/Legacy", - "${workspaceRoot}/Drivers/CMSIS/Device/ST/STM32F4xx/Include", - "${workspaceRoot}/Drivers/CMSIS/Include", - "${workspaceRoot}/Inc", - "${workspaceRoot}/MotorControl" - ], - "limitSymbolsToIncludedHeaders": true, - "databaseFilename": "" + "/usr/local/Caskroom/gcc-arm-embedded/" + ], + "limitSymbolsToIncludedHeaders": true, + "databaseFilename": "" } } ], diff --git a/Firmware/.vscode/settings.json b/Firmware/.vscode/settings.json index 8f45c82a..63c1463b 100644 --- a/Firmware/.vscode/settings.json +++ b/Firmware/.vscode/settings.json @@ -1,5 +1,7 @@ { "C_Cpp.clang_format_style": "{ BasedOnStyle: Google, IndentWidth: 4, ColumnLimit: 0 }", + "C_Cpp.intelliSenseEngine": "Default", + "C_Cpp.intelliSenseEngineFallback": "Disabled", "files.exclude": { "build": true }, diff --git a/Firmware/.vscode/tasks.json b/Firmware/.vscode/tasks.json index 33956da9..2376b650 100644 --- a/Firmware/.vscode/tasks.json +++ b/Firmware/.vscode/tasks.json @@ -6,7 +6,7 @@ { "taskName": "build", "type": "shell", - "command": "make -j4", + "command": "make", "group": { "kind": "build", "isDefault": true diff --git a/Firmware/Board/v3.3/STM32F405RGTx_FLASH.ld b/Firmware/Board/v3.3/STM32F405RGTx_FLASH.ld index 2685ffab..3b96fa83 100644 --- a/Firmware/Board/v3.3/STM32F405RGTx_FLASH.ld +++ b/Firmware/Board/v3.3/STM32F405RGTx_FLASH.ld @@ -43,7 +43,8 @@ MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K -FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 1024K +FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 768K +NVM (r) : ORIGIN = 0x80C0000, LENGTH = 256K } /* Define output sections */ diff --git a/Firmware/Board/v3.3/Src/freertos.c b/Firmware/Board/v3.3/Src/freertos.c index bd169893..a7698928 100644 --- a/Firmware/Board/v3.3/Src/freertos.c +++ b/Firmware/Board/v3.3/Src/freertos.c @@ -56,6 +56,7 @@ #include "low_level.h" #include "axis_c_interface.h" #include "commands.h" +#include "config.h" /* USER CODE END Includes */ /* Variables -----------------------------------------------------------------*/ @@ -141,6 +142,9 @@ void StartDefaultTask(void const * argument) /* USER CODE BEGIN StartDefaultTask */ + // Init and load persistent configuration + init_configuration(); + // Init communications init_communication(); diff --git a/Firmware/CHANGELOG.md b/Firmware/CHANGELOG.md index 052c5e59..1d588d23 100644 --- a/Firmware/CHANGELOG.md +++ b/Firmware/CHANGELOG.md @@ -2,10 +2,12 @@ Please add a note of your changes below this heading if you make a PR ### Added +* **Storing of configuration parameters to Non Volatile Memory** * Reporting error if your encoder CPR is incorrect * Reporting of DRV status/control registers and fault codes * DRV status read script * Microsecond delay function +* Ability to start anticogging calibration over USB protocol * Travis-CI ### Changed diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index bd000916..553c7190 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -8,6 +8,9 @@ extern "C" { #include "low_level.h" } +//TODO: Make it really clear where this is loaded. +AxisConfig axis_configs[2]; //TODO: get a constexpr for num motors + // C interface extern "C" { void axis_thread_entry(void const* temp_motor_ptr) { @@ -19,9 +22,7 @@ void axis_thread_entry(void const* temp_motor_ptr) { while (&motors[ax_number] != motor) ++ax_number; - static const AxisConfig default_config; - - Axis axis(default_config, ax_number, motor); + Axis axis(axis_configs[ax_number], ax_number, motor); axis.StateMachineLoop(); } } // extern "C" @@ -36,10 +37,11 @@ void Axis::SetupLegacyMappings() { exposed_bools[4 * axis_number_ + 2] = &do_calibration_; } -Axis::Axis(const AxisConfig& config, uint8_t axis_number, Motor_t* legacy_motor_ref) +Axis::Axis(AxisConfig& config, uint8_t axis_number, Motor_t* legacy_motor_ref) : axis_number_(axis_number), enable_control_(config.enable_control_at_start), do_calibration_(config.do_calibration_at_start), + config_(config), legacy_motor_ref_(legacy_motor_ref) { SetupLegacyMappings(); } diff --git a/Firmware/MotorControl/axis.h b/Firmware/MotorControl/axis.h index b3cdf543..455515b1 100644 --- a/Firmware/MotorControl/axis.h +++ b/Firmware/MotorControl/axis.h @@ -15,6 +15,7 @@ struct AxisConfig { bool enable_control_at_start = true; bool do_calibration_at_start = true; }; +extern AxisConfig axis_configs[]; class Axis { public: @@ -40,7 +41,7 @@ public: //step/dir handler // Object operation requires ptr to legacy object for now, TODO: get rid of this dep - Axis(const AxisConfig& config, uint8_t axis_number, Motor_t* legacy_motor_ref); + Axis(AxisConfig& config, uint8_t axis_number, Motor_t* legacy_motor_ref); // Infinite loop that does calibration and enters main control loop as appropriate void StateMachineLoop(); @@ -50,6 +51,8 @@ public: bool enable_control_; bool do_calibration_; + AxisConfig& config_; + Motor_t* legacy_motor_ref_; private: diff --git a/Firmware/MotorControl/commands.cpp b/Firmware/MotorControl/commands.cpp index 91178b54..7ef7e995 100644 --- a/Firmware/MotorControl/commands.cpp +++ b/Firmware/MotorControl/commands.cpp @@ -8,9 +8,11 @@ #include "commands.h" #include "low_level.h" +#include "axis.h" #include "protocol.hpp" #include "freertos_vars.h" #include "utils.h" +#include "config.h" #ifdef ENABLE_LEGACY_PROTOCOL #include "legacy_commands.h" @@ -94,42 +96,70 @@ void motors_1_set_current_setpoint_func(void) { set_current_setpoint(&motors[1], motors[1].set_current_setpoint_args.current_setpoint); } +void motors_run_anticogging_calibration_func() { + for (uint8_t i = 0; i < num_motors; i++) { + // Ensure the cogging map was correctly allocated earlier and that the motor is capable of calibrating + if (motors[i].anticogging.cogging_map != NULL && motors[i].error == ERROR_NO_ERROR) { + motors[i].anticogging.calib_anticogging = true; + } + } +} // This table specifies which fields and functions are exposed on the USB and UART ports. // TODO: Autogenerate this table. It will come up again very soon in the Arduino library. // clang-format off const Endpoint endpoints[] = { Endpoint::make_property("vbus_voltage", const_cast(&vbus_voltage)), - Endpoint::make_property("elec_rad_per_enc", const_cast(&elec_rad_per_enc)), Endpoint::make_property("UUID_0", (const uint32_t*)(ID_UNIQUE_ADDRESS + 0*4)), Endpoint::make_property("UUID_1", (const uint32_t*)(ID_UNIQUE_ADDRESS + 1*4)), Endpoint::make_property("UUID_2", (const uint32_t*)(ID_UNIQUE_ADDRESS + 2*4)), + Endpoint::make_function("run_anticogging_calibration", &motors_run_anticogging_calibration_func), + // No parameters, but still requires a close_tree() + Endpoint::close_tree(), + Endpoint::make_object("config"), + Endpoint::make_property("brake_resistance", &brake_resistance), + Endpoint::close_tree(), + Endpoint::make_object("axis0"), + Endpoint::make_object("config"), + Endpoint::make_property("enable_control_at_start", &axis_configs[0].enable_control_at_start), + Endpoint::make_property("do_calibration_at_start", &axis_configs[0].do_calibration_at_start), + Endpoint::close_tree(), + Endpoint::close_tree(), Endpoint::make_object("motor0"), - Endpoint::make_property("control_mode", reinterpret_cast(&motors[0].control_mode)), + Endpoint::make_object("config"), + Endpoint::make_property("control_mode", reinterpret_cast(&motors[0].control_mode)), + Endpoint::make_property("counts_per_step", &motors[0].counts_per_step), + Endpoint::make_property("pole_pairs", &motors[0].pole_pairs), + Endpoint::make_property("pos_gain", &motors[0].pos_gain), + Endpoint::make_property("vel_gain", &motors[0].vel_gain), + Endpoint::make_property("vel_integrator_gain", &motors[0].vel_integrator_gain), + Endpoint::make_property("vel_limit", &motors[0].vel_limit), + Endpoint::make_property("calibration_current", &motors[0].calibration_current), + Endpoint::make_property("resistance_calib_max_voltage", &motors[0].resistance_calib_max_voltage), + Endpoint::make_property("phase_inductance", &motors[0].phase_inductance), + Endpoint::make_property("phase_resistance", &motors[0].phase_resistance), + Endpoint::make_property("motor_type", reinterpret_cast(&motors[0].motor_type)), + Endpoint::make_property("rotor_mode", reinterpret_cast(&motors[0].rotor_mode)), + Endpoint::close_tree(), Endpoint::make_property("error", reinterpret_cast(&motors[0].error)), Endpoint::make_property("pos_setpoint", &motors[0].pos_setpoint), - Endpoint::make_property("pos_gain", &motors[0].pos_gain), Endpoint::make_property("vel_setpoint", &motors[0].vel_setpoint), - Endpoint::make_property("vel_gain", &motors[0].vel_gain), - Endpoint::make_property("vel_integrator_gain", &motors[0].vel_integrator_gain), Endpoint::make_property("vel_integrator_current", &motors[0].vel_integrator_current), - Endpoint::make_property("vel_limit", &motors[0].vel_limit), Endpoint::make_property("current_setpoint", &motors[0].current_setpoint), - Endpoint::make_property("calibration_current", &motors[0].calibration_current), - Endpoint::make_property("phase_inductance", const_cast(&motors[0].phase_inductance)), - Endpoint::make_property("phase_resistance", const_cast(&motors[0].phase_resistance)), Endpoint::make_property("current_meas_phB", const_cast(&motors[0].current_meas.phB)), Endpoint::make_property("current_meas_phC", const_cast(&motors[0].current_meas.phC)), Endpoint::make_property("DC_calib.phB", &motors[0].DC_calib.phB), Endpoint::make_property("DC_calib.phC", &motors[0].DC_calib.phC), Endpoint::make_property("shunt_conductance", &motors[0].shunt_conductance), Endpoint::make_property("phase_current_rev_gain", &motors[0].phase_current_rev_gain), - Endpoint::make_property("thread_ready", reinterpret_cast(&motors[0].thread_ready)), + Endpoint::make_property("thread_ready", &motors[0].thread_ready), Endpoint::make_property("control_deadline", &motors[0].control_deadline), Endpoint::make_property("last_cpu_time", &motors[0].last_cpu_time), Endpoint::make_property("loop_counter", &motors[0].loop_counter), Endpoint::make_object("current_control"), - Endpoint::make_property("current_lim", &motors[0].current_control.current_lim), + Endpoint::make_object("config"), + Endpoint::make_property("current_lim", &motors[0].current_control.current_lim), + Endpoint::close_tree(), Endpoint::make_property("p_gain", &motors[0].current_control.p_gain), Endpoint::make_property("i_gain", &motors[0].current_control.i_gain), Endpoint::make_property("v_current_control_integral_d", &motors[0].current_control.v_current_control_integral_d), @@ -146,6 +176,14 @@ const Endpoint endpoints[] = { Endpoint::make_property("ctrl_reg_2", (&motors[0].gate_driver_regs.Ctrl_Reg_2_Value)), Endpoint::close_tree(), Endpoint::make_object("encoder"), + Endpoint::make_object("config"), + Endpoint::make_property("use_index", &motors[0].encoder.use_index), + Endpoint::make_property("manually_calibrated", &motors[0].encoder.manually_calibrated), + Endpoint::make_property("idx_search_speed", &motors[0].encoder.idx_search_speed), + Endpoint::make_property("cpr", &motors[0].encoder.encoder_cpr), + Endpoint::make_property("offset", &motors[0].encoder.encoder_offset), + Endpoint::make_property("motor_dir", &motors[0].encoder.motor_dir), + Endpoint::close_tree(), Endpoint::make_property("phase", const_cast(&motors[0].encoder.phase)), Endpoint::make_property("pll_pos", &motors[0].encoder.pll_pos), Endpoint::make_property("pll_vel", &motors[0].encoder.pll_vel), @@ -167,33 +205,61 @@ const Endpoint endpoints[] = { Endpoint::make_function("set_current_setpoint", &motors_0_set_current_setpoint_func), Endpoint::make_property("current_setpoint", &motors[0].set_current_setpoint_args.current_setpoint), Endpoint::close_tree(), + Endpoint::make_object("timing_log"), + Endpoint::make_property("TIMING_LOG_GENERAL", &motors[0].timing_log[TIMING_LOG_GENERAL]), + Endpoint::make_property("TIMING_LOG_ADC_CB_M0_I", &motors[0].timing_log[TIMING_LOG_ADC_CB_M0_I]), + Endpoint::make_property("TIMING_LOG_ADC_CB_M0_DC", &motors[0].timing_log[TIMING_LOG_ADC_CB_M0_DC]), + Endpoint::make_property("TIMING_LOG_ADC_CB_M1_I", &motors[0].timing_log[TIMING_LOG_ADC_CB_M1_I]), + Endpoint::make_property("TIMING_LOG_ADC_CB_M1_DC", &motors[0].timing_log[TIMING_LOG_ADC_CB_M1_DC]), + Endpoint::make_property("TIMING_LOG_MEAS_R", &motors[0].timing_log[TIMING_LOG_MEAS_R]), + Endpoint::make_property("TIMING_LOG_MEAS_L", &motors[0].timing_log[TIMING_LOG_MEAS_L]), + Endpoint::make_property("TIMING_LOG_ENC_CALIB", &motors[0].timing_log[TIMING_LOG_ENC_CALIB]), + Endpoint::make_property("TIMING_LOG_IDX_SEARCH", &motors[0].timing_log[TIMING_LOG_IDX_SEARCH]), + Endpoint::make_property("TIMING_LOG_FOC_VOLTAGE", &motors[0].timing_log[TIMING_LOG_FOC_VOLTAGE]), + Endpoint::make_property("TIMING_LOG_FOC_CURRENT", &motors[0].timing_log[TIMING_LOG_FOC_CURRENT]), + Endpoint::close_tree(), Endpoint::close_tree(), // motor0 + Endpoint::make_object("axis1"), + Endpoint::make_object("config"), + Endpoint::make_property("enable_control_at_start", &axis_configs[1].enable_control_at_start), + Endpoint::make_property("do_calibration_at_start", &axis_configs[1].do_calibration_at_start), + Endpoint::close_tree(), + Endpoint::close_tree(), Endpoint::make_object("motor1"), - Endpoint::make_property("control_mode", reinterpret_cast(&motors[1].control_mode)), + Endpoint::make_object("config"), + Endpoint::make_property("control_mode", reinterpret_cast(&motors[1].control_mode)), + Endpoint::make_property("counts_per_step", &motors[1].counts_per_step), + Endpoint::make_property("pole_pairs", &motors[1].pole_pairs), + Endpoint::make_property("pos_gain", &motors[1].pos_gain), + Endpoint::make_property("vel_gain", &motors[1].vel_gain), + Endpoint::make_property("vel_integrator_gain", &motors[1].vel_integrator_gain), + Endpoint::make_property("vel_limit", &motors[1].vel_limit), + Endpoint::make_property("calibration_current", &motors[1].calibration_current), + Endpoint::make_property("resistance_calib_max_voltage", &motors[1].resistance_calib_max_voltage), + Endpoint::make_property("phase_inductance", &motors[1].phase_inductance), + Endpoint::make_property("phase_resistance", &motors[1].phase_resistance), + Endpoint::make_property("motor_type", reinterpret_cast(&motors[1].motor_type)), + Endpoint::make_property("rotor_mode", reinterpret_cast(&motors[1].rotor_mode)), + Endpoint::close_tree(), Endpoint::make_property("error", reinterpret_cast(&motors[1].error)), Endpoint::make_property("pos_setpoint", &motors[1].pos_setpoint), - Endpoint::make_property("pos_gain", &motors[1].pos_gain), Endpoint::make_property("vel_setpoint", &motors[1].vel_setpoint), - Endpoint::make_property("vel_gain", &motors[1].vel_gain), - Endpoint::make_property("vel_integrator_gain", &motors[1].vel_integrator_gain), Endpoint::make_property("vel_integrator_current", &motors[1].vel_integrator_current), - Endpoint::make_property("vel_limit", &motors[1].vel_limit), Endpoint::make_property("current_setpoint", &motors[1].current_setpoint), - Endpoint::make_property("calibration_current", &motors[1].calibration_current), - Endpoint::make_property("phase_inductance", const_cast(&motors[1].phase_inductance)), - Endpoint::make_property("phase_resistance", const_cast(&motors[1].phase_resistance)), Endpoint::make_property("current_meas_phB", const_cast(&motors[1].current_meas.phB)), Endpoint::make_property("current_meas_phC", const_cast(&motors[1].current_meas.phC)), Endpoint::make_property("DC_calib.phB", &motors[1].DC_calib.phB), Endpoint::make_property("DC_calib.phC", &motors[1].DC_calib.phC), Endpoint::make_property("shunt_conductance", &motors[1].shunt_conductance), Endpoint::make_property("phase_current_rev_gain", &motors[1].phase_current_rev_gain), - Endpoint::make_property("thread_ready", reinterpret_cast(&motors[1].thread_ready)), + Endpoint::make_property("thread_ready", &motors[1].thread_ready), Endpoint::make_property("control_deadline", &motors[1].control_deadline), Endpoint::make_property("last_cpu_time", &motors[1].last_cpu_time), Endpoint::make_property("loop_counter", &motors[1].loop_counter), Endpoint::make_object("current_control"), - Endpoint::make_property("current_lim", &motors[1].current_control.current_lim), + Endpoint::make_object("config"), + Endpoint::make_property("current_lim", &motors[1].current_control.current_lim), + Endpoint::close_tree(), Endpoint::make_property("p_gain", &motors[1].current_control.p_gain), Endpoint::make_property("i_gain", &motors[1].current_control.i_gain), Endpoint::make_property("v_current_control_integral_d", &motors[1].current_control.v_current_control_integral_d), @@ -210,6 +276,14 @@ const Endpoint endpoints[] = { Endpoint::make_property("ctrl_reg_2", (&motors[1].gate_driver_regs.Ctrl_Reg_2_Value)), Endpoint::close_tree(), Endpoint::make_object("encoder"), + Endpoint::make_object("config"), + Endpoint::make_property("use_index", &motors[1].encoder.use_index), + Endpoint::make_property("manually_calibrated", &motors[1].encoder.manually_calibrated), + Endpoint::make_property("idx_search_speed", &motors[1].encoder.idx_search_speed), + Endpoint::make_property("cpr", &motors[1].encoder.encoder_cpr), + Endpoint::make_property("offset", &motors[1].encoder.encoder_offset), + Endpoint::make_property("motor_dir", &motors[1].encoder.motor_dir), + Endpoint::close_tree(), Endpoint::make_property("phase", const_cast(&motors[1].encoder.phase)), Endpoint::make_property("pll_pos", &motors[1].encoder.pll_pos), Endpoint::make_property("pll_vel", &motors[1].encoder.pll_vel), @@ -231,7 +305,29 @@ const Endpoint endpoints[] = { Endpoint::make_function("set_current_setpoint", &motors_1_set_current_setpoint_func), Endpoint::make_property("current_setpoint", &motors[1].set_current_setpoint_args.current_setpoint), Endpoint::close_tree(), - Endpoint::close_tree() // motor1 + Endpoint::close_tree(), // motor1 + Endpoint::make_function("save_configuration", &save_configuration), + // no arguments + Endpoint::close_tree(), + Endpoint::make_function("erase_configuration", &erase_configuration), + // no arguments + Endpoint::close_tree(), + Endpoint::make_function("reboot", &NVIC_SystemReset), + // no arguments + Endpoint::close_tree(), + Endpoint::make_object("timing_log"), + Endpoint::make_property("TIMING_LOG_GENERAL", &motors[1].timing_log[TIMING_LOG_GENERAL]), + Endpoint::make_property("TIMING_LOG_ADC_CB_M0_I", &motors[1].timing_log[TIMING_LOG_ADC_CB_M0_I]), + Endpoint::make_property("TIMING_LOG_ADC_CB_M0_DC", &motors[1].timing_log[TIMING_LOG_ADC_CB_M0_DC]), + Endpoint::make_property("TIMING_LOG_ADC_CB_M1_I", &motors[1].timing_log[TIMING_LOG_ADC_CB_M1_I]), + Endpoint::make_property("TIMING_LOG_ADC_CB_M1_DC", &motors[1].timing_log[TIMING_LOG_ADC_CB_M1_DC]), + Endpoint::make_property("TIMING_LOG_MEAS_R", &motors[1].timing_log[TIMING_LOG_MEAS_R]), + Endpoint::make_property("TIMING_LOG_MEAS_L", &motors[1].timing_log[TIMING_LOG_MEAS_L]), + Endpoint::make_property("TIMING_LOG_ENC_CALIB", &motors[1].timing_log[TIMING_LOG_ENC_CALIB]), + Endpoint::make_property("TIMING_LOG_IDX_SEARCH", &motors[1].timing_log[TIMING_LOG_IDX_SEARCH]), + Endpoint::make_property("TIMING_LOG_FOC_VOLTAGE", &motors[1].timing_log[TIMING_LOG_FOC_VOLTAGE]), + Endpoint::make_property("TIMING_LOG_FOC_CURRENT", &motors[1].timing_log[TIMING_LOG_FOC_CURRENT]), + Endpoint::close_tree(), }; // clang-format on diff --git a/Firmware/MotorControl/config.cpp b/Firmware/MotorControl/config.cpp new file mode 100644 index 00000000..e61a8102 --- /dev/null +++ b/Firmware/MotorControl/config.cpp @@ -0,0 +1,247 @@ + +/* Includes ------------------------------------------------------------------*/ + +#include "config.h" + +#include +#include +#include + +#include "nvm.h" +#include "crc.hpp" +#include "low_level.h" +#include "axis.h" + +// IMPORTANT: if you change, reorder or otherwise modify any of the fields in +// the config structs, make sure to increment this number: +uint16_t config_version = 0x0001; + +/* Private defines -----------------------------------------------------------*/ +#define CRC16_INIT 0xabcd + +/* Private macros ------------------------------------------------------------*/ +/* Private typedef -----------------------------------------------------------*/ + +typedef struct { + Motor_control_mode_t control_mode; + float counts_per_step; + int32_t pole_pairs; + float pos_gain; + float vel_gain; + float vel_integrator_gain; + float vel_limit; + float calibration_current; + float resistance_calib_max_voltage; + float phase_inductance; + float phase_resistance; + Motor_type_t motor_type; + Rotor_mode_t rotor_mode; + float current_control_current_lim; + bool encoder_use_index; + bool encoder_manually_calibrated; + float encoder_idx_search_speed; + int32_t encoder_cpr; + int32_t encoder_offset; + int32_t encoder_motor_dir; +} MotorConfig_t; + +/* Global constant data ------------------------------------------------------*/ +/* Global variables ----------------------------------------------------------*/ +/* Private constant data -----------------------------------------------------*/ +/* Private variables ---------------------------------------------------------*/ +/* 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. +// The types of these objects are passed as template arguments to Config. +// +// Config has two template specializations to implement template recursion: +// - 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 +struct Config; + +template<> +struct Config<> { + static size_t get_size() { + return 0; + } + static int load_config(size_t offset, uint16_t* crc16) { + return 0; + } + static int store_config(size_t offset, uint16_t* crc16) { + return 0; + } +}; + +template +struct Config { + static size_t get_size() { + return sizeof(T) + Config::get_size(); + } + + // @brief Loads one or more consecutive objects from the NVM. + // During loading this function also calculates the CRC over the loaded data. + // @param offset: 0 means that the function should start reading at the beginning + // 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) { + 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)) + return -1; + *crc16 = calc_crc16(previous_crc16, (uint8_t *)val0, size); + if (Config::load_config(offset + size, crc16, vals...)) + return -1; + return 0; + } + + // @brief Stores one or more consecutive objects to the NVM. + // During storing this function also calculates the CRC over the stored data. + // @param offset: 0 means that the function should start writing at the beginning + // 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) { + size_t size = sizeof(T); + 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); + if (Config::store_config(offset + size, crc16, vals...)) + return -1; + return 0; + } + + // @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 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; + uint16_t crc16 = CRC16_INIT ^ config_version; + if (Config::load_config(0, &crc16, val0, vals..., &crc16)) + return -1; + if (crc16) + return -1; + return 0; + } + + // @brief Stores one or more consecutive objects to the NVM. In addition to the + // provided objects, a CRC of the data is stored. + // + // The CRC includes a version number and thus adds some protection against + // 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 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()) + return -1; + if (NVM_start_write(size)) + return -1; + uint16_t crc16 = 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)) + return -1; + if (NVM_commit()) + return -1; + return 0; + } +}; + +// This function is obviously stupid and should go away (make MotorConfig_t a member of Motor_t) +// TODO: make this go away as part of the C++ refactoring +void set_motor_config(const MotorConfig_t* config, Motor_t* motor) { + motor->control_mode = config->control_mode; + motor->counts_per_step = config->counts_per_step; + motor->pole_pairs = config->pole_pairs; + motor->pos_gain = config->pos_gain; + motor->vel_gain = config->vel_gain; + motor->vel_integrator_gain = config->vel_integrator_gain; + motor->vel_limit = config->vel_limit; + motor->calibration_current = config->calibration_current; + motor->resistance_calib_max_voltage = config->resistance_calib_max_voltage; + motor->phase_inductance = config->phase_inductance; + motor->phase_resistance = config->phase_resistance; + motor->motor_type = config->motor_type; + motor->rotor_mode = config->rotor_mode; + + motor->current_control.current_lim = config->current_control_current_lim; + + motor->encoder.use_index = config->encoder_use_index; + motor->encoder.manually_calibrated = config->encoder_manually_calibrated; + motor->encoder.idx_search_speed = config->encoder_idx_search_speed; + motor->encoder.encoder_cpr = config->encoder_cpr; + motor->encoder.encoder_offset = config->encoder_offset; + motor->encoder.motor_dir = config->encoder_motor_dir; +} + +// This function is obviously stupid and should go away (make MotorConfig_t a member of Motor_t) +// TODO: make this go away as part of the C++ refactoring +void get_motor_config(const Motor_t* motor, MotorConfig_t* config) { + config->control_mode = motor->control_mode; + config->counts_per_step = motor->counts_per_step; + config->pole_pairs = motor->pole_pairs; + config->pos_gain = motor->pos_gain; + config->vel_gain = motor->vel_gain; + config->vel_integrator_gain = motor->vel_integrator_gain; + config->vel_limit = motor->vel_limit; + config->calibration_current = motor->calibration_current; + config->resistance_calib_max_voltage = motor->resistance_calib_max_voltage; + config->phase_inductance = motor->phase_inductance; + config->phase_resistance = motor->phase_resistance; + config->motor_type = motor->motor_type; + config->rotor_mode = motor->rotor_mode; + + config->current_control_current_lim = motor->current_control.current_lim; + + config->encoder_use_index = motor->encoder.use_index; + config->encoder_manually_calibrated = motor->encoder.manually_calibrated; + config->encoder_idx_search_speed = motor->encoder.idx_search_speed; + config->encoder_cpr = motor->encoder.encoder_cpr; + config->encoder_offset = motor->encoder.encoder_offset; + config->encoder_motor_dir = motor->encoder.motor_dir; +} + + +void init_configuration(void) { + MotorConfig_t motor_config[2]; + //TODO: we really shouldn't be hardcoding like this + if (NVM_init() || Config::load_config(&motor_config[0], &motor_config[1], &axis_configs[0], &axis_configs[1], &brake_resistance)) { + //printf("no config found\r\n"); osDelay(5); + // load default config + // motor_config[0] = MotorConfig_t(); + // motor_config[1] = MotorConfig_t(); + + // Default config coming from flashed Motor_t + return; + } else { + //printf("load config successful\r\n"); osDelay(5); + } + + set_motor_config(&motor_config[0], &motors[0]); + set_motor_config(&motor_config[1], &motors[1]); +} + +void save_configuration(void) { + MotorConfig_t motor_config[2]; + get_motor_config(&motors[0], &motor_config[0]); + get_motor_config(&motors[1], &motor_config[1]); + //TODO: we really shouldn't be hardcoding like this + if (Config::store_config(&motor_config[0], &motor_config[1], &axis_configs[0], &axis_configs[1], &brake_resistance)) { + //printf("saving configuration failed\r\n"); osDelay(5); + } +} + +void erase_configuration(void) { + NVM_erase(); +} diff --git a/Firmware/MotorControl/config.h b/Firmware/MotorControl/config.h new file mode 100644 index 00000000..57fafcca --- /dev/null +++ b/Firmware/MotorControl/config.h @@ -0,0 +1,16 @@ +#ifndef __CONFIG_H +#define __CONFIG_H + +#ifdef __cplusplus +extern "C" { +#endif + +void init_configuration(void); +void save_configuration(void); +void erase_configuration(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __CONFIG_H */ diff --git a/Firmware/MotorControl/low_level.c b/Firmware/MotorControl/low_level.c index 88014cd9..c1e99742 100644 --- a/Firmware/MotorControl/low_level.c +++ b/Firmware/MotorControl/low_level.c @@ -33,11 +33,6 @@ // Arbitrary non-zero inital value to avoid division by zero if ADC reading is late float vbus_voltage = 12.0f; -// TODO stick parameter into struct -#define ENCODER_CPR (2048 * 4) // Default resolution of CUI-AMT102 encoder -#define POLE_PAIRS 7 // This value is correct for N5065 motors and Turnigy SK3 series. -const float elec_rad_per_enc = POLE_PAIRS * 2 * M_PI * (1.0f / (float)ENCODER_CPR); - #if HW_VERSION_MAJOR == 3 #if HW_VERSION_MINOR <= 3 #define SHUNT_RESISTANCE (675e-6f) @@ -59,7 +54,7 @@ Motor_t motors[] = { .enable_step_dir = false, //auto enabled after calibration .counts_per_step = 2.0f, .error = ERROR_NO_ERROR, - .drv_fault = DRV8301_FaultType_NoFault, + .pole_pairs = 7, // This value is correct for N5065 motors and Turnigy SK3 series. .pos_setpoint = 0.0f, .pos_gain = 20.0f, // [(counts/s) / counts] .vel_setpoint = 0.0f, @@ -125,9 +120,9 @@ Motor_t motors[] = { .encoder_timer = &htim3, .use_index = false, .index_found = false, - .calibrated = false, + .manually_calibrated = false, .idx_search_speed = 10.0f, // [rad/s electrical] - .encoder_cpr = ENCODER_CPR, + .encoder_cpr = (2048 * 4), // Default resolution of CUI-AMT102 encoder, .encoder_offset = 0, .encoder_state = 0, .motor_dir = 1, // 1 or -1 @@ -154,7 +149,6 @@ Motor_t motors[] = { .spin_up_target_vel = 400.0f, // [rad/s] }, .loop_counter = 0, - .timing_log_index = 0, .timing_log = {0}, .anticogging = { .index = 0, @@ -164,13 +158,14 @@ Motor_t motors[] = { .calib_pos_threshold = 1.0f, .calib_vel_threshold = 1.0f, }, + .drv_fault = DRV8301_FaultType_NoFault, }, { // M1 .control_mode = CTRL_MODE_POSITION_CONTROL, //see: Motor_control_mode_t .enable_step_dir = false, //auto enabled after calibration .counts_per_step = 2.0f, .error = ERROR_NO_ERROR, - .drv_fault = DRV8301_FaultType_NoFault, + .pole_pairs = 7, // This value is correct for N5065 motors and Turnigy SK3 series. .pos_setpoint = 0.0f, .pos_gain = 20.0f, // [(counts/s) / counts] .vel_setpoint = 0.0f, @@ -230,9 +225,9 @@ Motor_t motors[] = { .encoder_timer = &htim4, .use_index = false, .index_found = false, - .calibrated = false, + .manually_calibrated = false, .idx_search_speed = 10.0f, // [rad/s electrical] - .encoder_cpr = ENCODER_CPR, + .encoder_cpr = (2048 * 4), // Default resolution of CUI-AMT102 encoder, .encoder_offset = 0, .encoder_state = 0, .motor_dir = 1, // 1 or -1 @@ -259,7 +254,6 @@ Motor_t motors[] = { .spin_up_target_vel = 400.0f, // [rad/s] }, .loop_counter = 0, - .timing_log_index = 0, .timing_log = {0}, .anticogging = { .index = 0, @@ -268,11 +262,14 @@ Motor_t motors[] = { .calib_anticogging = false, .calib_pos_threshold = 1.0f, .calib_vel_threshold = 1.0f, - } + }, + .drv_fault = DRV8301_FaultType_NoFault, } }; const size_t num_motors = sizeof(motors) / sizeof(motors[0]); +float brake_resistance = 0.47f; // [ohm] + /* Private constant data -----------------------------------------------------*/ static const float one_by_sqrt3 = 0.57735026919f; static const float sqrt3_by_2 = 0.86602540378f; @@ -280,8 +277,6 @@ static const float current_meas_period = CURRENT_MEAS_PERIOD; static const int current_meas_hz = CURRENT_MEAS_HZ; /* Private variables ---------------------------------------------------------*/ -static float brake_resistance = 0.47f; // [ohm] - /* Function implementations --------------------------------------------------*/ //-------------------------------- @@ -319,7 +314,7 @@ void set_current_setpoint(Motor_t* motor, float current_setpoint) { // Utility //-------------------------------- -uint16_t check_timing(Motor_t* motor) { +uint16_t check_timing(Motor_t* motor, TimingLog_t log_idx) { TIM_HandleTypeDef* htim = motor->motor_timer; uint16_t timing = htim->Instance->CNT; bool down = htim->Instance->CR1 & TIM_CR1_DIR; @@ -328,10 +323,9 @@ uint16_t check_timing(Motor_t* motor) { timing = TIM_1_8_PERIOD_CLOCKS + delta; } - if (++(motor->timing_log_index) == TIMING_LOG_SIZE) { - motor->timing_log_index = 0; + if (log_idx < TIMING_LOG_SIZE) { + motor->timing_log[log_idx] = timing; } - motor->timing_log[motor->timing_log_index] = timing; return timing; } @@ -611,7 +605,7 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { motors[0].motor_timer->Instance->CCR3 = motors[0].next_timings[2]; } // Check the timing of the sequencing - check_timing(motor); + check_timing(motor, TIMING_LOG_ADC_CB_M1_DC); } else if (motor == &motors[0] && !counting_down) { // We are measuring M0 current here @@ -623,19 +617,19 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { motors[1].motor_timer->Instance->CCR3 = motors[1].next_timings[2]; } // Check the timing of the sequencing - check_timing(motor); + check_timing(motor, TIMING_LOG_ADC_CB_M0_I); } else if (motor == &motors[1] && !counting_down) { // We are measuring M1 current here current_meas_not_DC_CAL = true; // Check the timing of the sequencing - check_timing(motor); + check_timing(motor, TIMING_LOG_ADC_CB_M1_I); } else if (motor == &motors[0] && counting_down) { // We are measuring M0 DC_CAL here current_meas_not_DC_CAL = false; // Check the timing of the sequencing - check_timing(motor); + check_timing(motor, TIMING_LOG_ADC_CB_M0_DC); } else { global_fault(ERROR_PWM_SRC_FAIL); @@ -704,7 +698,7 @@ bool measure_phase_resistance(Motor_t* motor, float test_current, float max_volt queue_voltage_timings(motor, test_voltage, 0.0f); // Check we meet deadlines after queueing - motor->last_cpu_time = check_timing(motor); + motor->last_cpu_time = check_timing(motor, TIMING_LOG_MEAS_R); if (!(motor->last_cpu_time < motor->control_deadline)) { motor->error = ERROR_PHASE_RESISTANCE_TIMING; return false; @@ -743,7 +737,7 @@ bool measure_phase_inductance(Motor_t* motor, float voltage_low, float voltage_h queue_voltage_timings(motor, test_voltages[i], 0.0f); // Check we meet deadlines after queueing - motor->last_cpu_time = check_timing(motor); + motor->last_cpu_time = check_timing(motor, TIMING_LOG_MEAS_L); if (!(motor->last_cpu_time < motor->control_deadline)) { motor->error = ERROR_PHASE_INDUCTANCE_TIMING; return false; @@ -808,6 +802,8 @@ bool calib_enc_offset(Motor_t* motor, float voltage_magnitude) { encvaluesum += (int16_t)motor->encoder.encoder_timer->Instance->CNT; } + //TODO avoid recomputing elec_rad_per_enc every time + float elec_rad_per_enc = motor->pole_pairs * 2 * M_PI * (1.0f / (float)(motor->encoder.encoder_cpr)); float expected_encoder_delta = scan_range / elec_rad_per_enc; float actual_encoder_delta_abs = fabsf((int16_t)motor->encoder.encoder_timer->Instance->CNT-init_enc_val); if(fabsf(actual_encoder_delta_abs - expected_encoder_delta)/expected_encoder_delta > motor->encoder.encoder_calib_range) @@ -845,7 +841,6 @@ bool calib_enc_offset(Motor_t* motor, float voltage_magnitude) { int offset = encvaluesum / (num_steps * 2); motor->encoder.encoder_offset = offset; - motor->encoder.calibrated = true; return true; } @@ -874,7 +869,7 @@ bool motor_calibration(Motor_t* motor) { (float)(motor->encoder.motor_dir) * motor->encoder.idx_search_speed, enc_calibration_voltage)) return false; - if (!motor->encoder.calibrated) + if (!motor->encoder.manually_calibrated) if (!calib_enc_offset(motor, enc_calibration_voltage)) return false; } @@ -917,7 +912,7 @@ bool anti_cogging_calibration(Motor_t* motor) { fabsf(motor->encoder.pll_vel) < motor->anticogging.calib_vel_threshold) { motor->anticogging.cogging_map[motor->anticogging.index++] = motor->vel_integrator_current; } - if (motor->anticogging.index < ENCODER_CPR) { + if (motor->anticogging.index < motor->encoder.encoder_cpr) { set_pos_setpoint(motor, motor->anticogging.index, 0.0f, 0.0f); return false; } else { @@ -950,7 +945,7 @@ bool scan_for_enc_idx(Motor_t* motor, float omega, float voltage_magnitude) { queue_voltage_timings(motor, v_alpha, v_beta); // Check we meet deadlines after queueing - motor->last_cpu_time = check_timing(motor); + motor->last_cpu_time = check_timing(motor, TIMING_LOG_IDX_SEARCH); if (!(motor->last_cpu_time < motor->control_deadline)) { motor->error = ERROR_SCAN_MOTOR_TIMING; return false; @@ -975,9 +970,11 @@ void update_rotor(Motor_t* motor) { encoder->encoder_state += (int32_t)delta_enc; // compute electrical phase - int corrected_enc = encoder->encoder_state % ENCODER_CPR; + int corrected_enc = encoder->encoder_state % motor->encoder.encoder_cpr; corrected_enc -= encoder->encoder_offset; corrected_enc *= encoder->motor_dir; + //TODO avoid recomputing elec_rad_per_enc every time + float elec_rad_per_enc = motor->pole_pairs * 2 * M_PI * (1.0f / (float)(motor->encoder.encoder_cpr)); float ph = elec_rad_per_enc * (float)corrected_enc; // ph = fmodf(ph, 2*M_PI); encoder->phase = wrap_pm_pi(ph); @@ -1237,7 +1234,7 @@ bool FOC_voltage(Motor_t* motor, float v_d, float v_q) { queue_voltage_timings(motor, v_alpha, v_beta); // Check we meet deadlines after queueing - if (!(check_timing(motor) < motor->control_deadline)) { + if (!(check_timing(motor, TIMING_LOG_FOC_VOLTAGE) < motor->control_deadline)) { motor->error = ERROR_FOC_VOLTAGE_TIMING; return false; } @@ -1305,7 +1302,7 @@ bool FOC_current(Motor_t* motor, float Id_des, float Iq_des) { queue_modulation_timings(motor, mod_alpha, mod_beta); // Check we meet deadlines after queueing - motor->last_cpu_time = check_timing(motor); + motor->last_cpu_time = check_timing(motor, TIMING_LOG_FOC_CURRENT); if (!(motor->last_cpu_time < motor->control_deadline)) { motor->error = ERROR_FOC_TIMING; return false; @@ -1390,9 +1387,9 @@ void control_motor_loop(Motor_t* motor) { // 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 == ENCODER_CPR - 1) + // ensuring that we handle negative encoder positions properly (-1 == motor->encoder.encoder_cpr - 1) if (motor->anticogging.use_anticogging) { - Iq += motor->anticogging.cogging_map[mod(motor->encoder.pll_pos, ENCODER_CPR)]; + Iq += motor->anticogging.cogging_map[mod(motor->encoder.pll_pos, motor->encoder.encoder_cpr)]; } float v_err = vel_des - get_pll_vel(motor); diff --git a/Firmware/MotorControl/low_level.h b/Firmware/MotorControl/low_level.h index baad486e..e92774e4 100644 --- a/Firmware/MotorControl/low_level.h +++ b/Firmware/MotorControl/low_level.h @@ -57,16 +57,16 @@ typedef enum { // Note: these should be sorted from lowest level of control to // highest level of control, to allow "<" style comparisons. typedef enum { - CTRL_MODE_VOLTAGE_CONTROL, - CTRL_MODE_CURRENT_CONTROL, - CTRL_MODE_VELOCITY_CONTROL, - CTRL_MODE_POSITION_CONTROL + CTRL_MODE_VOLTAGE_CONTROL = 0, + CTRL_MODE_CURRENT_CONTROL = 1, + CTRL_MODE_VELOCITY_CONTROL = 2, + CTRL_MODE_POSITION_CONTROL = 3 } Motor_control_mode_t; typedef enum { - MOTOR_TYPE_HIGH_CURRENT, - // MOTOR_TYPE_LOW_CURRENT, //Not yet implemented - MOTOR_TYPE_GIMBAL + MOTOR_TYPE_HIGH_CURRENT = 0, + // MOTOR_TYPE_LOW_CURRENT = 1, //Not yet implemented + MOTOR_TYPE_GIMBAL = 2 } Motor_type_t; typedef struct { @@ -115,9 +115,9 @@ typedef struct { TIM_HandleTypeDef* encoder_timer; bool use_index; bool index_found; - bool calibrated; + bool manually_calibrated; float idx_search_speed; - int encoder_cpr; + int32_t encoder_cpr; int32_t encoder_offset; int32_t encoder_state; int32_t motor_dir; // 1/-1 for fwd/rev alignment to encoder. @@ -140,6 +140,7 @@ typedef struct { bool enable_step_dir; float counts_per_step; Error_t error; + int32_t pole_pairs; float pos_setpoint; float pos_gain; float vel_setpoint; @@ -174,7 +175,6 @@ typedef struct { Encoder_t encoder; Sensorless_t sensorless; uint32_t loop_counter; - int timing_log_index; uint16_t timing_log[TIMING_LOG_SIZE]; // Cache for remote procedure calls arguments struct { @@ -193,6 +193,20 @@ typedef struct { DRV8301_FaultType_e drv_fault; } Motor_t; +typedef enum { + TIMING_LOG_GENERAL, + TIMING_LOG_ADC_CB_M0_I, + TIMING_LOG_ADC_CB_M0_DC, + TIMING_LOG_ADC_CB_M1_I, + TIMING_LOG_ADC_CB_M1_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, +} TimingLog_t; + typedef struct{ int type; int index; @@ -203,6 +217,7 @@ extern const size_t num_motors; extern const float elec_rad_per_enc; /* Exported variables --------------------------------------------------------*/ extern float vbus_voltage; +extern float brake_resistance; extern Motor_t motors[]; /* Exported macro ------------------------------------------------------------*/ /* Exported functions --------------------------------------------------------*/ @@ -228,7 +243,7 @@ bool motor_calibration(Motor_t* motor); //// Old private: // Utility -uint16_t check_timing(Motor_t* motor); +uint16_t check_timing(Motor_t* motor, TimingLog_t log_idx); void global_fault(int error); float phase_current_from_adcval(Motor_t* motor, uint32_t ADCValue); // Initalisation diff --git a/Firmware/MotorControl/nvm.c b/Firmware/MotorControl/nvm.c new file mode 100644 index 00000000..e581beee --- /dev/null +++ b/Firmware/MotorControl/nvm.c @@ -0,0 +1,451 @@ +/* +* Flash-based Non-Volatile Memory (NVM) +* +* This file supports storing and loading persistent configuration based on +* the STM32 builtin flash memory. +* +* The STM32F405xx has 12 flash sectors of heterogeneous size. We use the last +* two sectors for configuration data. These pages have a size of 128kB each. +* Setting any bit in these sectors to 0 is always possible, but setting them +* to 1 requires erasing the whole sector. +* +* We consider each sector as an array of 64-bit fields except the first N bytes, which we +* instead use as an allocation block. The allocation block is a compact bit-field (2 bit per entry) +* that keeps track of the state of each field (erased, invalid, valid). +* +* One sector is always considered the valid (read) sector and the other one is the +* target for the next write access: they can be considered to be ping-pong or double buffred. +* +* When writing a block of data, instead of always erasing the whole writable sector the +* new data is appended in the erased area. This presumably increases flash life span. +* The writable sector is only erased if there is not enough space for the new data. +* +* On startup, if there is exactly one sector +* whose last non-erased value has the state "valid" that sector is considered +* the valid sector. In any other case the selection is undefined. +* +* +* To write a new block of data atomically we first mark all associated fields +* as "invalid" (in the allocation table) then write the data and then mark the +* fields as "valid" (in the direction of increasing address). +*/ + +#include "nvm.h" + +#include +#include +#include + +#if defined(STM32F405xx) + +// refer to page 75 of datasheet: +// http://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 +#define FLASH_SECTOR_10_BASE (const volatile uint8_t*)0x80C0000UL +#define FLASH_SECTOR_10_SIZE 0x20000UL +#define FLASH_SECTOR_11_BASE (const volatile uint8_t*)0x80E0000UL +#define FLASH_SECTOR_11_SIZE 0x20000UL + +#define HAL_FLASH_ClearError() __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGSERR | FLASH_FLAG_PGPERR) +#else +#error "unknown flash sector size" +#endif + +typedef enum { + VALID = 0, + INVALID = 1, + ERASED = 3 +} field_state_t; + +typedef struct { + size_t index; //!< next field to be written to (can be equal to n_data) + const uint32_t sector_id; //!< HAL ID of this sector + const size_t n_data; //!< number of 64-bit fields in this sector + const size_t n_reserved; //!< number of 64-bit fields in this sector that are reserved for the allocation table + const volatile uint8_t* const alloc_table; + const volatile uint64_t* const data; +} sector_t; + +sector_t sectors[] = { { + .sector_id = FLASH_SECTOR_10, + .n_data = FLASH_SECTOR_10_SIZE >> 3, + .n_reserved = (FLASH_SECTOR_10_SIZE >> 3) >> 5, + .alloc_table = FLASH_SECTOR_10_BASE, + .data = (uint64_t *)FLASH_SECTOR_10_BASE +}, { + .sector_id = FLASH_SECTOR_11, + .n_data = FLASH_SECTOR_11_SIZE >> 3, + .n_reserved = (FLASH_SECTOR_11_SIZE >> 3) >> 5, + .alloc_table = FLASH_SECTOR_11_BASE, + .data = (uint64_t *)FLASH_SECTOR_11_BASE +}}; + +uint8_t read_sector_; // 0 or 1 to indicate which sector to read from and which to write to +size_t n_staging_area_; // number of 64-bit values that were reserved using NVM_start_write +size_t n_valid_; // number of 64-bit fields that can be read + +// @brief Erases a flash sector. This sets all bits in the sector to 1. +// The sector's current index is reset to the minimum value (n_reserved). +// @returns 0 on success or a non-zero error code otherwise +int erase(sector_t *sector) { + FLASH_EraseInitTypeDef erase_struct = { + .TypeErase = FLASH_TYPEERASE_SECTORS, + .Banks = 0, // only used for mass erase + .Sector = sector->sector_id, + .NbSectors = 1, + .VoltageRange = FLASH_VOLTAGE_RANGE_3 + }; + HAL_FLASH_Unlock(); + HAL_FLASH_ClearError(); + uint32_t sector_error; + if (HAL_FLASHEx_Erase(&erase_struct, §or_error) != HAL_OK) + goto fail; + sector->index = sector->n_reserved; + + HAL_FLASH_Lock(); + return 0; +fail: + HAL_FLASH_Lock(); + //printf("erase failed: %u \r\n", HAL_FLASH_GetError()); + return HAL_FLASH_GetError(); // non-zero +} + + +// @brief Writes states into the allocation table. +// The write operation goes in the direction of increasing indices. +// @param state: 11: erased, 10: writing, 00: valid data +// @returns 0 on success or a non-zero error code otherwise +int set_allocation_state(sector_t *sector, size_t index, size_t count, field_state_t state) { + if (index < sector->n_reserved) + return -1; + if (index + count >= sector->n_data) + return -1; + + // expand state to state for 4 values + const uint8_t states = (state << 0) | (state << 2) | (state << 4) | (state << 6); + + // handle unaligned start + uint8_t mask = ~(0xff << ((index & 0x3) << 1)); + count += index & 0x3; + index -= index & 0x3; + + HAL_FLASH_Unlock(); + HAL_FLASH_ClearError(); + + // write states + for (; count >= 4; count -= 4, index += 4) { + if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, (uintptr_t)§or->alloc_table[index >> 2], states | mask) != HAL_OK) + goto fail; + mask = 0; + } + + // handle unaligned end + if (count) { + mask |= ~(0xff >> ((4 - count) << 1)); + if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, (uintptr_t)§or->alloc_table[index >> 2], states | mask) != HAL_OK) + goto fail; + } + + HAL_FLASH_Lock(); + return 0; +fail: + HAL_FLASH_Lock(); + return HAL_FLASH_GetError(); // non-zero +} + +// @brief Reads the allocation table from behind to determine how many fields match the +// reference state. +// @param sector: The sector on which to perform the search +// @param max_index: The maximum index that should be considered +// @param ref_state: The reference state +// @param state: Set to the first encountered state that is unequal to ref_state. +// Set to ref_state if all encountered states are equal to ref_state. +// @returns The smallest index that points to a field with ref_state. +// This value is at least sector->n_reserved and at most max_index. +size_t scan_allocation_table(sector_t *sector, size_t max_index, field_state_t ref_state, field_state_t *state) { + const uint8_t ref_states = (ref_state << 0) | (ref_state << 2) | (ref_state << 4) | (ref_state << 6); + size_t index = (((max_index + 3) >> 2) << 2); // start at the max index but round up to a multiple of 4 + size_t ignore = index - max_index; + uint8_t states = ref_states; + + //printf("scan from %08x to %08x for %02x\r\n", index, sector->n_reserved, ref_states); osDelay(5); + + // read 4 states at a time + for (; index >= (sector->n_reserved + 4); index -= 4) { + states = sector->alloc_table[(index - 1) >> 2]; + if (ignore) { // ignore the upper 1, 2 or 3 states if max_index was unaligned + uint8_t ignore_mask = ~(0xff >> (ignore << 1)); + states = (states & ~ignore_mask) | (ref_states & ignore_mask); + ignore = 0; + } + if (states != ref_states) + break; + } + + // once we encounterd a byte with any state mismatch determine which of the 4 states it is + for (; ((states >> 6) == (ref_states & 0x3)) && (index > sector->n_reserved); index--) { + states <<= 2; + } + + *state = states >> 6; + //printf("(it's %02x)\r\n", index); osDelay(5); + return index; +} + +// Loads the head of the NVM data. +// If this function fails subsequent calls to NVM functions (other than NVM_init or NVM_erase) +// cause undefined behavior. +// @returns 0 on success or a non-zero error code otherwise +int NVM_init(void) { + field_state_t sector0_state, sector1_state; + sectors[0].index = scan_allocation_table(§ors[0], sectors[0].n_data, + ERASED, §or0_state); + sectors[1].index = scan_allocation_table(§ors[1], sectors[1].n_data, + ERASED, §or1_state); + //printf("sector states: %02x, %02x\r\n", sector0_state, sector1_state); osDelay(5); + + // Select valid sector on a best effort basis + // (in unfortunate cases valid_sector might actually point + // to an invalid or erased sector) + read_sector_ = 0; + if (sector1_state == VALID) + read_sector_ = 1; + + // count the number of valid fields + sector_t *read_sector = §ors[read_sector_]; + uint8_t first_nonvalid_state; + size_t min_valid_index = scan_allocation_table(read_sector, read_sector->index, + VALID, &first_nonvalid_state); + n_valid_ = read_sector->index - min_valid_index; + + n_staging_area_ = 0; + + int status = 0; + /*// bring non-valid sectors into a known state + this is not absolutely required + if (sector0_state != VALID) + status |= erase(§ors[0]); + if (sector1_state != VALID) + status |= erase(§ors[1]); + */ + return status; +} + +// @brief Erases all data in the NVM. +// +// If this function fails subsequent calls to NVM functions (other than NVM_init or NVM_erase) +// cause undefined behavior. +// Caution: this function may take a long time (like 1 second) +// +// @returns 0 on success or a non-zero error code otherwise +int NVM_erase(void) { + read_sector_ = 0; + sectors[0].index = sectors[0].n_reserved; + sectors[1].index = sectors[1].n_reserved; + + int state = 0; + state |= erase(§ors[0]); + state |= erase(§ors[1]); + return state; +} + +// @brief Returns the maximum number of bytes that can be read using NVM_read. +// This holds until NVM_commit is called. +size_t NVM_get_max_read_length(void) { + return n_valid_ << 3; +} + +// @brief Returns the maximum length (in bytes) that can passed to NVM_start_write. +// This holds until NVM_commit is called. +size_t NVM_get_max_write_length(void) { + sector_t *target = §ors[1 - read_sector_]; + return (target->n_data - target->n_reserved) << 3; +} + +// @brief Reads from the latest committed block in the non-volatile memory. +// @param offset: offset in bytes (0 meaning the beginning of the valid area) +// @param data: buffer to write to +// @param length: length in bytes (if (offset + length) is out of range, the function fails) +// @returns 0 on success or a non-zero error code otherwise +int NVM_read(size_t offset, uint8_t *data, size_t length) { + if (offset + length > (n_valid_ << 3)) + return -1; + sector_t *read_sector = §ors[read_sector_]; + const uint8_t *src_ptr = ((const uint8_t *)&read_sector->data[read_sector->index - n_valid_]) + offset; + memcpy(data, src_ptr, length); + return 0; +} + +// @brief Starts an atomic write operation. +// +// The most recent valid NVM data is not modified or invalidated until NVM_commit is called. +// The length must be at most equal to the size indicated by NVM_get_max_write_length(). +// +// @param length: Length of the staging block that should be created +int NVM_start_write(size_t length) { + int status = 0; + sector_t *target = §ors[1 - read_sector_]; + + length = (length + 7) >> 3; // round to multiple of 64 bit + if (length > target->n_data - target->n_reserved) + return -1; + + // make room for the new data + if (length > target->n_data - target->index) + if ((status = erase(target))) + return status; + + // invalidate the fields we're about to write + status = set_allocation_state(target, target->index, length, INVALID); + if (status) + return status; + + n_staging_area_ = length; + return 0; +} + +// @brief Writes to the current data block that was opened with NVM_start_write. +// +// The operation fails if (offset + length) is larger than the length passed to NVM_start_write. +// The most recent valid NVM data is not modified or invalidated until NVM_commit is called. +// Warning: Writing different data to the same area multiple times during a single transaction +// will cause data corruption. +// +// @param offset: The offset in bytes, 0 being the beginning of the staging block. +// @param data: Pointer to the data that should be written +// @param length: Data length in bytes +int NVM_write(size_t offset, uint8_t *data, size_t length) { + if (offset + length > (n_staging_area_ << 3)) + return -1; + sector_t *target = §ors[1 - read_sector_]; + + HAL_FLASH_Unlock(); + HAL_FLASH_ClearError(); + + // handle unaligned start + for (; (offset & 0x3) && length; ++data, ++offset, --length) + if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, + ((uintptr_t)&target->data[target->index]) + offset, *data) != HAL_OK) + goto fail; + + // write 32-bit values (64-bit doesn't work) + for (; length >= 4; data += 4, offset += 4, length -=4) + if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, + ((uintptr_t)&target->data[target->index]) + offset, *(uint32_t*)data) != HAL_OK) + goto fail; + + // handle unaligned end + for (; length; ++data, ++offset, --length) + if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, + ((uintptr_t)&target->data[target->index]) + offset, *data) != HAL_OK) + goto fail; + + HAL_FLASH_Lock(); + return 0; +fail: + HAL_FLASH_Lock(); + return HAL_FLASH_GetError(); // non-zero +} + +// @brief Commits the new data to NVM atomically. +int NVM_commit(void) { + sector_t *read_sector = §ors[read_sector_]; + sector_t *write_sector = §ors[1 - read_sector_]; + + // mark the newly-written fields as valid + int status = set_allocation_state(write_sector, write_sector->index, n_staging_area_, VALID); + if (status) + return status; + + write_sector->index += n_staging_area_; + n_valid_ = n_staging_area_; + n_staging_area_ = 0; + read_sector_ = 1 - read_sector_; + + // invalidate the other sector + if (read_sector->index < read_sector->n_data) + status = set_allocation_state(read_sector, read_sector->index, 1, INVALID); + else + status = erase(read_sector); + + return status; +} + + +#include +/** @brief Call this at startup to test/demo the NVM driver + + Expected output when starting with a fully erased NVM + + [1st boot] + === NVM TEST === + NVM is empty + write 0x00, ..., 0x25 to NVM + new data committed to NVM + + [2nd boot] + === NVM TEST === + NVM contains 40 valid bytes: + 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f + 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f + 20 21 22 23 24 25 ff ff + write 0xbd, ..., 0xe2 to NVM + new data committed to NVM + + [3rd boot] + === NVM TEST === + NVM contains 40 valid bytes: + bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc + cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc + dd de df e0 e1 e2 ff ff + write 0xcb, ..., 0xf0 to NVM + new data committed to NVM +*/ +void NVM_demo(void) { + const size_t len = 38; + uint8_t data[len]; + int progress = 0; + uint8_t seed = 0; + + osDelay(100); + printf("=== NVM TEST ===\r\n"); osDelay(5); + //NVM_erase(); + if (progress++, NVM_init() != 0) + goto fail; + + // load bytes from NVM and print them + size_t available = NVM_get_max_read_length(); + if (available) { + printf("NVM contains %d valid bytes:\r\n", available); osDelay(5); + uint8_t buf[available]; + if (progress++, NVM_read(0, buf, available) != 0) + goto fail; + for (size_t pos = 0; pos < available; ++pos) { + seed += buf[pos]; + printf(" %02x", buf[pos]); + if ((((pos + 1) % 16) == 0) || ((pos + 1) == available)) + printf("\r\n"); + osDelay(2); + } + } else { + printf("NVM is empty\r\n"); osDelay(5); + } + + // store new bytes in NVM (data based on seed) + printf("write 0x%02x, ..., 0x%02x to NVM\r\n", seed, seed + len - 1); osDelay(5); + for (size_t i = 0; i < len; i++) + data[i] = seed++; + if (progress++, NVM_start_write(len) != 0) + goto fail; + if (progress++, NVM_write(0, data, len / 2)) + goto fail; + if (progress++, NVM_write(len / 2, &data[len / 2], len - (len / 2))) + goto fail; + if (progress++, NVM_commit()) + goto fail; + printf("new data committed to NVM\r\n"); osDelay(5); + + return; + +fail: + printf("NVM test failed at %d!\r\n", progress); +} diff --git a/Firmware/MotorControl/nvm.h b/Firmware/MotorControl/nvm.h new file mode 100644 index 00000000..efffa174 --- /dev/null +++ b/Firmware/MotorControl/nvm.h @@ -0,0 +1,33 @@ +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __NVML_H +#define __NVM_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Includes ------------------------------------------------------------------*/ +#include +#include + +/* Exported types ------------------------------------------------------------*/ +/* Exported constants --------------------------------------------------------*/ +/* Exported variables --------------------------------------------------------*/ +/* Exported macro ------------------------------------------------------------*/ +/* Exported functions --------------------------------------------------------*/ + +int NVM_init(void); +int NVM_erase(void); +size_t NVM_get_max_read_length(void); +size_t NVM_get_max_write_length(void); +int NVM_read(size_t offset, uint8_t *data, size_t length); +int NVM_start_write(size_t length); +int NVM_write(size_t offset, uint8_t *data, size_t length); +int NVM_commit(void); +void NVM_demo(void); + +#ifdef __cplusplus +} +#endif + +#endif //__NVM_H \ No newline at end of file diff --git a/Firmware/MotorControl/protocol.hpp b/Firmware/MotorControl/protocol.hpp index cde1e2ab..8b2e1edd 100644 --- a/Firmware/MotorControl/protocol.hpp +++ b/Firmware/MotorControl/protocol.hpp @@ -33,10 +33,16 @@ inline size_t write_le(T value, uint8_t* buffer); template inline size_t read_le(T* value, const uint8_t* buffer); +template<> +inline size_t write_le(bool value, uint8_t* buffer) { + buffer[0] = value ? 1 : 0; + return 1; +} + template<> inline size_t write_le(uint8_t value, uint8_t* buffer) { buffer[0] = value; - return 2; + return 1; } template<> @@ -71,10 +77,16 @@ inline size_t write_le(float value, uint8_t* buffer) { return write_le(*reinterpret_cast(&value), buffer); } +template<> +inline size_t read_le(bool* value, const uint8_t* buffer) { + *value = buffer[0]; + return 1; +} + template<> inline size_t read_le(uint8_t* value, const uint8_t* buffer) { *value = buffer[0]; - return 2; + return 1; } template<> @@ -358,6 +370,14 @@ template<> inline const char* get_default_json_modifier() { return "\"type\":\"uint8\",\"access\":\"rw\""; } +template<> +inline const char* get_default_json_modifier() { + return "\"type\":\"bool\",\"access\":\"r\""; +} +template<> +inline const char* get_default_json_modifier() { + return "\"type\":\"bool\",\"access\":\"rw\""; +} class Endpoint { public: diff --git a/Firmware/MotorControl/utils.h b/Firmware/MotorControl/utils.h index 004d00cf..bf329605 100644 --- a/Firmware/MotorControl/utils.h +++ b/Firmware/MotorControl/utils.h @@ -70,9 +70,10 @@ extern "C" { */ #define STM_ID_GetUUID(x) ((x >= 0 && x < 3) ? (*(uint32_t *)(ID_UNIQUE_ADDRESS + 4 * (x))) : 0) -#ifndef M_PI -#define M_PI 3.14159265358979323846f +#ifdef M_PI +#undef M_PI #endif +#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/README.md b/Firmware/README.md index 117d8485..daf025db 100644 --- a/Firmware/README.md +++ b/Firmware/README.md @@ -10,11 +10,11 @@ The project is under active development, so make sure to check the [Changelog](C -- [Configuring parameters](#configuring-parameters) +- [Configuring the build](#configuring-the-build) - [Compiling and downloading firmware](#compiling-and-downloading-firmware) -- [Setting up an IDE](#setting-up-an-ide) -- [Continuing without an IDE](#no-ide-instructions) +- [Setting up an IDE](#setting-up-an-ide) or [Continuing without an IDE](#no-ide-instructions) - [Communicating over USB or UART](#communicating-over-usb-or-uart) +- [Configuring parameters](#configuring-parameters) - [Encoder Calibration](#encoder-calibration) - [Checking for error codes](#checking-for-error-codes) - [Generating startup code](#generating-startup-code) @@ -22,8 +22,10 @@ The project is under active development, so make sure to check the [Changelog](C -## Configuring parameters -To correctly operate the ODrive, you need to supply some parameters. Some are mandatory, and if supplied incorrectly will cause the drive to malfunction. To get good performance you must also tune the drive. +

+## Configuring the build +To correctly operate the ODrive, you need to supply some parameters. Some are mandatory, and if supplied incorrectly will cause the drive to malfunction. +In this section we will set the compile-time parameters, later we will also set the [run time parameters](#configuring-parameters). The first thing to set is your board hardware version, located at the top of [Inc/main.h](Inc/main.h). If, for example, you are using the hardware: ODrive v3.2, then you should set it like this: ```C @@ -61,54 +63,6 @@ Note that UART is only supported on ODrive v3.3 and higher. - `UART_PROTOCOL_NONE`: Ignore UART communication - `USE_GPIO_MODE_STEP_DIR`: Step/direction control mode (use in conjunction with `UART_PROTOCOL_NONE`) -### Motor control parameters -The rest of all the parameters are at the top of the [MotorControl/low_level.c](MotorControl/low_level.c) file. Please note that many parameters occur twice, once for each motor. -In it's current state, the motor structs contain both tuning parameters, meant to be set by the developer, and static variables, meant to be modified by the software. Unfortunatly these are mixed together right now, but cleaning this up is a high priority task. - -It may be helpful to know that the entry point of each of the motor threads is `void axis_thread_entry` at the top of [MotorControl/axis.cpp](MotorControl/axis.cpp). This is like `main` for each motor, and is probably where you should start reading the code. - -### Mandatory parameters -You must set: -* `ENCODER_CPR`: Encoder Count Per Revolution (CPR). This is 4x the Pulse Per Revolution (PPR) value. -* `POLE_PAIRS`: This is the number of magnet poles in the rotor, **divided by two**. 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. -* `brake_resistance`: This is the resistance of the brake resistor. If you are not using it, you may set it to 0.0f. -* `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`). - -### Motor Modes -The firwmare currently supports two different types of motors, high-current motors, and Gimbal motors. If you're using a regular hobby brushless motor like [this](https://hobbyking.com/en_us/turnigy-aerodrive-sk3-5065-236kv-brushless-outrunner-motor.html) one, you should set `motor_mode` to `MOTOR_TYPE_HIGH_CURRENT`. For high-torque gimbal motors like [this](https://hobbyking.com/en_us/turnigy-hd-5208-brushless-gimbal-motor-bldc.html) one, you should choose `MOTOR_TYPE_GIMBAL`. - -**Further detail:** - -If 100's of mA of current noise is "small" for you, you can choose `MOTOR_TYPE_HIGH_CURRENT`. -If 100's of mA of current noise is "large" for you, and you do not intend to spin the motor very fast (omega * L << R), and the motor is fairly large resistance (1 ohm or larger), you can chose `MOTOR_TYPE_GIMBAL`. - -If 100's of mA current noise is "large" for you, and you intend to spin the motor fast, then you need to replace the shunt resistors on the ODrive. - -### Tuning parameters -The most important parameters are the limits: -* The current limit: `.current_lim = 75.0f, //[A] // Note: consistent with 40v/v gain`. The default current limit, for safety reasons, is set to 10A. This is quite weak, and good for making sure the drive is stable. Once you have tuned the drive, you can increase this to 75A to get some performance. Note that above 75A, you must change the current amplifier gains. - * Note: The motor current and the current drawn from the power supply is not the same in general. You should not look at the power supply current to see what is going on with the motor current. -* The velocity limit: `.vel_limit = 20000.0f, // [counts/s]`. The motor will be limited to this speed; again the default value is quite slow. -* You can change `.calibration_current` to the largest value you feel comfortable leaving running through the motor continously when the motor is stationary. - -The motion control gains are currently manually tuned: -* `.pos_gain = 20.0f, // [(counts/s) / counts]` -* `.vel_gain = 15.0f / 10000.0f, // [A/(counts/s)]` -* `.vel_integrator_gain = 10.0f / 10000.0f, // [A/(counts/s * s)]` - -An upcoming feature will enable automatic tuning. Until then, here is a rough tuning procedure: -* Set the integrator gain to 0 -* Make sure you have a stable system. If it is not, decrease all gains until you have one. -* Increase `vel_gain` by around 30% per iteration until the motor exhibits some vibration. -* Back down `vel_gain` to 50% of the vibrating value. -* Increase `pos_gain` by around 30% per iteration until you see some overshoot. -* Back down `pos_gain` until you do not have overshoot anymore. -* The integrator is not easily tuned, nor is it strictly required. Tune at your own discression. - -### Optional parameters -By default both motors are enabled, and the default control mode is position control. -If you want a different mode, you can change `.control_mode`. To disable a motor, set `.enable_control` and `.do_calibration` to false. -

## Compiling and downloading firmware ### Getting a programmer @@ -128,7 +82,7 @@ To compile the program, you first need to install the prerequisite tools: * Installing on Ubuntu: `sudo apt-get install openocd` * Installing on Arch Linux: build and install the [AUR package](https://aur.archlinux.org/packages/openocd/) * `tup`: Used as a build tool - * Installing on Ubuntu: `sudo apt-add-repository 'deb http://ppa.launchpad.net/anatol/tup/ubuntu precise main'; sudo apt-get update; sudo apt-get install tup` + * Installing on Ubuntu: `sudo add-apt-repository ppa:jonathonf/tup; sudo apt-get update; sudo apt-get install tup` * Installing on Arch Linux: `sudo pacman -S tup` * No additional USB CDC driver should be required on Linux. @@ -152,7 +106,6 @@ For working with the ODrive code you don't need an IDE, but the open-source IDE * [Configuring VSCode](configuring-vscode.md) * [Configuring Eclipse](configuring-eclipse.md) -

## No IDE Instructions After installing all of the above, open a Git Bash shell. Continue at section [Building the firmware](#building-the-firmware). @@ -176,7 +129,7 @@ If the flashing worked, you can start sending commands. If you want to do that n ## Communicating over USB or UART Warning: If testing USB or UART communication for the first time it is recommend that your motors are free to spin continuously and are not connected to a drivetrain with limited travel. ### From Linux/Windows/macOS -There are two example python scripts to help you get started with controlling the ODrive using python. One will drop you into an interactive shell to query settings, parameters, and variables, and let you send setpoints manually ([tools/explore_odrive.py](tools/explore_odrive.py)). The other is a demo application to show you how to control the ODrive programmatically ([tools/demo.py](tools/demo.py)). Below follows a step-by-step guide on how to run these. +There are two example python scripts to help you get started with controlling the ODrive using python. One will drop you into an interactive shell to query settings, parameters, and variables, and let you send setpoints manually ([tools/explore_odrive.py](../tools/explore_odrive.py)). The other is a demo application to show you how to control the ODrive programmatically ([tools/demo.py](../tools/demo.py)). Below follows a step-by-step guide on how to run these. * __Windows__: It is recommended to use a Unix style command prompt, such as Git Bash that comes with [Git for windows](https://git-scm.com/download/win). @@ -212,12 +165,79 @@ pip install pyusb pyserial ### Other platforms See the [protocol specification](protocol.md) or the [legacy protocol specification](legacy-protocol.md). +

+## Configuring parameters +The majority of the important parameters you would want to set after flashing the ODrive with firmware are configurable over the USB communication interface. These include some mandatory parameters that you must set for correct operation, as well as tuning and optional parameters. +To start the configuration session: + +* Launch `./tools/explore_odrive.py`. This will give you a command prompt where you can modify using simple assignments. +* Configure parameters of the `my_odrive.[...].config` objects. + * For example to adjust the position gain: `my_odrive.motor0.config.pos_gain = 30` Enter. + * The complete list of configurable parameters is: + * `my_odrive.motorN.config.*` + * `my_odrive.axisN.config.*` + * where N is a valid motor number (0 or 1). +* Save the configuration into non-volatile memory: `my_odrive.save_configuration()` Enter + * This will save the properties of all the `[...].config` objects and no other parameters. +* Reboot the drive: `my_odrive.reboot()` Enter + +Note that a firmware upgrade at this point will preserve the configuration if and only if the parameters of both firmware versions are identical. Should you need to reset the configuration, you can run `my_odrive.erase_configuration()`. + +__Developers__: Be aware that you can also modify the compile-time defaults for all of these parameters. Most of them you will find at the top of [MotorControl/low_level.c](MotorControl/low_level.c#L50). Note that the configuration parameters there are somewhat intertwined with runtime variables and hardware specific configuration that should not be changed. Also note that all parameters occur twice. + +### Mandatory parameters +You must set for every motor: +* `my_odrive.motorN.encoder.config.cpr`: Encoder Count Per Revolution (CPR). This is 4x the Pulse Per Revolution (PPR) value. +* `my_odrive.motorN.config.pole_pairs`: This is the number of magnet poles in the rotor, **divided by two**. 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. +* `my_odrive.config.brake_resistance` [Ohm]: This is the resistance of the brake resistor. If you are not using it, you may set it to 0.0f. +* `my_odrive.motorN.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`). + +#### Motor Modes +The firwmare currently supports two different types of motors, high-current motors, and Gimbal motors. If you're using a regular hobby brushless motor like [this](https://hobbyking.com/en_us/turnigy-aerodrive-sk3-5065-236kv-brushless-outrunner-motor.html) one, you should set `my_odrive.motorN.config.motor_type` to `MOTOR_TYPE_HIGH_CURRENT`. For high-torque gimbal motors like [this](https://hobbyking.com/en_us/turnigy-hd-5208-brushless-gimbal-motor-bldc.html) one, you should choose `MOTOR_TYPE_GIMBAL`. + +**Further detail:** +If 100's of mA of current noise is "small" for you, you can choose `MOTOR_TYPE_HIGH_CURRENT`. +If 100's of mA of current noise is "large" for you, and you do not intend to spin the motor very fast (omega * L << R), and the motor is fairly large resistance (1 ohm or larger), you can chose `MOTOR_TYPE_GIMBAL`. +If 100's of mA current noise is "large" for you, and you intend to spin the motor fast, then you need to replace the shunt resistors on the ODrive. + +### Tuning parameters +The most important parameters are the limits: +* The current limit: `my_odrive.motorN.current_control.config.current_lim` [A]. The default current limit, for safety reasons, is set to 10A. This is quite weak, and good for making sure the drive is stable. Once you have tuned the drive, you can increase this to 75A to get some performance. Note that above 75A, you must change the current amplifier gains. + * Note: The motor current and the current drawn from the power supply is not the same in general. You should not look at the power supply current to see what is going on with the motor current. +* The velocity limit: `my_odrive.motorN.config.vel_limit` [counts/s]. The motor will be limited to this speed; again the default value is quite slow. +* You can change `my_odrive.motorN.config.calibration_current` [A] to the largest value you feel comfortable leaving running through the motor continously when the motor is stationary. + +The motion control gains are currently manually tuned: +* `my_odrive.motorN.config.pos_gain = 20.0f` [(counts/s) / counts] +* `my_odrive.motorN.config.vel_gain = 15.0f / 10000.0f` [A/(counts/s)] +* `my_odrive.motorN.config.vel_integrator_gain = 10.0f / 10000.0f` [A/(counts/s * s)] + +An upcoming feature will enable automatic tuning. Until then, here is a rough tuning procedure: +* Set the integrator gain to 0 +* Make sure you have a stable system. If it is not, decrease all gains until you have one. +* Increase `vel_gain` by around 30% per iteration until the motor exhibits some vibration. +* Back down `vel_gain` to 50% of the vibrating value. +* Increase `pos_gain` by around 30% per iteration until you see some overshoot. +* Back down `pos_gain` until you do not have overshoot anymore. +* The integrator is not easily tuned, nor is it strictly required. Tune at your own discression. + +### Optional parameters +By default both motors are enabled, and the default control mode is position control. +If you want a different mode, you can change `my_odrive.motorN.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. + +To disable a motor at startup, set `my_odrive.axisN.config.enable_control` and `my_odrive.axisN.config.do_calibration` to `False`. +

## Encoder Calibration By default the encoder-to-motor calibration will run on every startup. During encoder calibration the rotor must be allowed to rotate without any biased load during startup. That means mass and weak friction loads are fine, but gravity or spring loads are not okay. ### Encoder with Index signal -If you have an encoder with an index (Z) signal, you may avoid having to do the calibration on every startup, and instead use the index signal to re-sync the encoder to a stored calibration. Bleow are the steps to do the one-time calibration and configuration. Note that you can follow these steps with one motor at a time, or all motors together, as you wish. +If you have an encoder with an index (Z) signal, you may avoid having to do the calibration on every startup, and instead use the index signal to re-sync the encoder to a stored calibration. Below are the steps to do the one-time calibration and configuration. Note that you can follow these steps with one motor at a time, or all motors together, as you wish. * Since you will only do this once, it is recommended that you mechanically disengage the motor from anything other than the encoder, so it can spin freely. * All the parameters we will be modifying are in the motor structs at the top of [MotorControl/low_level.c](MotorControl/low_level.c). @@ -262,6 +282,8 @@ The error nummber corresponds to the following: 19. `ERROR_SPIN_UP_TIMEOUT` 20. `ERROR_DRV_FAULT` 21. `ERROR_NOT_IMPLEMENTED_MOTOR_TYPE` +22. `ERROR_ENCODER_CPR_OUT_OF_RANGE` +23. `ERROR_DC_BUS_BROWNOUT` If you get an error code larger than this, it may be the case that someone added a code and forgot to update the documentation. In that case, please check [MotorControl/low_level.h](MotorControl/low_level.h) for the full enum. diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 4f1fa8c2..7adf79ac 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -74,10 +74,8 @@ FLAGS += '-mfpu=fpv4-sp-d16' FLAGS += '-mfloat-abi=hard' FLAGS += { '-Wall', '-fdata-sections', '-ffunction-sections'} -if tup.getconfig('DEBUG') == 'y' then - --C_FLAGS += '-g -gdwarf-2' - FLAGS += '-g -gdwarf-2' -end +FLAGS += '-g -gdwarf-2' + -- linker flags LDFLAGS += '-T'..boarddir..'/STM32F405RGTx_FLASH.ld' @@ -127,9 +125,11 @@ build{ 'MotorControl/utils.c', 'MotorControl/legacy_commands.c', 'MotorControl/low_level.c', + 'MotorControl/nvm.c', 'MotorControl/axis.cpp', 'MotorControl/commands.cpp', - 'MotorControl/protocol.cpp' + 'MotorControl/protocol.cpp', + 'MotorControl/config.cpp' }, includes={ 'Drivers/DRV8301', diff --git a/Firmware/configuring-vscode.md b/Firmware/configuring-vscode.md index ec9b0adb..add9ff45 100644 --- a/Firmware/configuring-vscode.md +++ b/Firmware/configuring-vscode.md @@ -19,7 +19,7 @@ Before doing the VSCode setup, make sure you've installed all of your [prerequis * Path Autocomplete * Auto Comment Blocks 1. Restart VSCode -1. Open the VSCode Workspace file, which is located in the root of the ODrive repository. It is called `VSCodeWorkspace.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). +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/Firmware/legacy-protocol.md b/Firmware/legacy-protocol.md index 5693a039..2898432f 100644 --- a/Firmware/legacy-protocol.md +++ b/Firmware/legacy-protocol.md @@ -52,7 +52,7 @@ s type index value ** `0` is float ** `1` is int ** `2` is bool -* `index` is the index in the corresponding [exposed variable table](MotorControl/commands.c). +* `index` is the index in the corresponding [exposed variable table](MotorControl/legacy_commands.c). For example * `g 0 12` will return the phase resistance of M0 diff --git a/VSCodeWorkspace.code-workspace b/ODrive_Workspace.code-workspace similarity index 100% rename from VSCodeWorkspace.code-workspace rename to ODrive_Workspace.code-workspace diff --git a/tools/explore_odrive.py b/tools/explore_odrive.py index 7afb8009..59492a30 100755 --- a/tools/explore_odrive.py +++ b/tools/explore_odrive.py @@ -7,6 +7,17 @@ import odrive.core import argparse import sys +# some enums described in the README +# TODO: transmit as part of the JSON +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 + # Parse arguments parser = argparse.ArgumentParser(description='Load an odrive object to play with in the IPython interactive shell.') diff --git a/tools/usbpermission b/tools/usbpermission new file mode 100755 index 00000000..5ea6d8e9 --- /dev/null +++ b/tools/usbpermission @@ -0,0 +1 @@ +sudo udevadm control --reload-rules && sudo service udev restart && sudo udevadm trigger