From 207426aa0f0f38902aed097a717ebd3830e5492c Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 4 Nov 2020 10:42:51 +0100 Subject: [PATCH] initial ODrive v4.0 support --- Firmware/.vscode/launch.json | 46 ++++- Firmware/Board/v3/Inc/board.h | 2 + Firmware/Drivers/DRV8353/drv8353.cpp | 195 ++++++++++++++++++++++ Firmware/Drivers/DRV8353/drv8353.hpp | 161 ++++++++++++++++++ Firmware/Drivers/STM32/stm32_nvm.c | 14 ++ Firmware/Drivers/STM32/stm32_system.h | 2 + Firmware/Drivers/status_led.cpp | 15 ++ Firmware/Drivers/status_led.hpp | 57 +++++++ Firmware/Drivers/ws2812.hpp | 86 ++++++++++ Firmware/Makefile | 3 + Firmware/MotorControl/low_level.cpp | 8 + Firmware/MotorControl/main.cpp | 51 ++++++ Firmware/MotorControl/utils.hpp | 3 +- Firmware/Tupfile.lua | 12 ++ Firmware/communication/ascii_protocol.cpp | 2 + Firmware/communication/interface_can.cpp | 8 +- Firmware/communication/interface_usb.cpp | 6 + Firmware/odrive-interface.yaml | 40 ++++- analysis/thermistors.py | 2 +- docs/developer-guide.md | 28 ++++ docs/pinout.md | 9 +- docs/resources.md | 56 +++++++ tools/odrive/tests/can_test.py | 9 +- tools/odrive/tests/test_runner.py | 28 +++- tools/odrive/utils.py | 26 +++ 25 files changed, 845 insertions(+), 24 deletions(-) create mode 100644 Firmware/Drivers/DRV8353/drv8353.cpp create mode 100644 Firmware/Drivers/DRV8353/drv8353.hpp create mode 100644 Firmware/Drivers/status_led.cpp create mode 100644 Firmware/Drivers/status_led.hpp create mode 100644 Firmware/Drivers/ws2812.hpp diff --git a/Firmware/.vscode/launch.json b/Firmware/.vscode/launch.json index cc8662d3..d277ce4c 100644 --- a/Firmware/.vscode/launch.json +++ b/Firmware/.vscode/launch.json @@ -9,7 +9,7 @@ "type": "cortex-debug", "servertype": "openocd", "request": "launch", - "name": "Debug ODrive - ST-Link", + "name": "Debug ODrive v3.x - ST-Link", "executable": "${workspaceRoot}/build/ODriveFirmware.elf", "configFiles": [ "interface/stlink-v2.cfg", @@ -23,7 +23,24 @@ "type": "cortex-debug", "servertype": "openocd", "request": "launch", - "name": "Debug ODrive - ST-Link - FreeRTOS", + "name": "Debug ODrive v4.x - ST-Link", + "executable": "${workspaceRoot}/build/ODriveFirmware.elf", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32f7x.cfg", + ], + "openOCDLaunchCommands": [ + "reset_config none separate" + ], + "svdFile": "${workspaceRoot}/Board/v4/STM32F7x.svd", + "cwd": "${workspaceRoot}" + }, + { + // For the Cortex-Debug extension + "type": "cortex-debug", + "servertype": "openocd", + "request": "launch", + "name": "Debug ODrive v3.x - ST-Link - FreeRTOS", "executable": "${workspaceRoot}/build/ODriveFirmware.elf", "rtos": "FreeRTOS", "configFiles": [ @@ -35,7 +52,7 @@ }, { // For the Cortex-Debug extension - // ssh -t odrv -L3333:localhost:3333 bash -c "\"openocd '-f' 'interface/stlink-v2.cfg' '-f' 'target/stm32f4x_stlink.cfg'\"" + // ssh -t odrv3 -L3333:localhost:3333 bash -c "\"openocd '-f' 'interface/stlink-v2.cfg' '-f' 'target/stm32f4x_stlink.cfg'\"" "type": "cortex-debug", "servertype": "external", "gdbTarget": "localhost:3333", @@ -43,7 +60,7 @@ "load" ], "request": "launch", - "name": "Debug ODrive via external server", + "name": "Debug ODrive v3.x - Remote", "executable": "${workspaceRoot}/build/ODriveFirmware.elf", "configFiles": [ "interface/stlink-v2.cfg", @@ -52,12 +69,31 @@ "svdFile": "${workspaceRoot}/Board/v3/STM32F40x.svd", "cwd": "${workspaceRoot}" }, + { + // For the Cortex-Debug extension + // ssh -t odrv4 -L3333:localhost:3333 bash -c "\"openocd '-f' 'interface/stlink.cfg' '-f' 'target/stm32f7x.cfg' -c 'reset_config none separate'\"" + "type": "cortex-debug", + "servertype": "external", + "gdbTarget": "localhost:3333", + "preLaunchCommands": [ + "load" + ], + "request": "launch", + "name": "Debug ODrive v4.x - Remote", + "executable": "${workspaceRoot}/build/ODriveFirmware.elf", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32f7x.cfg", + ], + "svdFile": "${workspaceRoot}/Board/v4/STM32F722.svd", + "cwd": "${workspaceRoot}" + }, { // For the Cortex-Debug extensions "type": "cortex-debug", "servertype": "bmp", "request": "launch", - "name": "Debug ODrive - Black Magic Probe", + "name": "Debug ODrive v3.x - Black Magic Probe", "executable": "${workspaceRoot}/build/ODriveFirmware.elf", "device": "STM32F4xx", "BMPGDBSerialPort": "${env:BMP_PORT}", diff --git a/Firmware/Board/v3/Inc/board.h b/Firmware/Board/v3/Inc/board.h index a6fbab36..db6adcf7 100644 --- a/Firmware/Board/v3/Inc/board.h +++ b/Firmware/Board/v3/Inc/board.h @@ -35,6 +35,8 @@ // consistent we just leave a gap in the counting scheme. #define GPIO_COUNT (17) +#define CAN_FREQ (2000000UL) + #if HW_VERSION_MINOR >= 5 && HW_VERSION_VOLTAGE >= 48 #define DEFAULT_BRAKE_RESISTANCE (2.0f) // [ohm] #else diff --git a/Firmware/Drivers/DRV8353/drv8353.cpp b/Firmware/Drivers/DRV8353/drv8353.cpp new file mode 100644 index 00000000..a1a06075 --- /dev/null +++ b/Firmware/Drivers/DRV8353/drv8353.cpp @@ -0,0 +1,195 @@ + +#include "drv8353.hpp" +#include "utils.hpp" +#include "cmsis_os.h" +#include "board.h" + +const SPI_InitTypeDef Drv8353::spi_config_ = { + .Mode = SPI_MODE_MASTER, + .Direction = SPI_DIRECTION_2LINES, + .DataSize = SPI_DATASIZE_16BIT, + .CLKPolarity = SPI_POLARITY_LOW, + .CLKPhase = SPI_PHASE_2EDGE, + .NSS = SPI_NSS_SOFT, + .BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16, + .FirstBit = SPI_FIRSTBIT_MSB, + .TIMode = SPI_TIMODE_DISABLE, + .CRCCalculation = SPI_CRCCALCULATION_DISABLE, + .CRCPolynomial = 10, +}; + +bool Drv8353::config(float requested_gain, float* actual_gain) { + // Calculate gain setting: Snap down to have equal or larger range as + // requested or largest possible range otherwise + + uint16_t gain_setting = 3; + float gain_choices[] = {5.0f, 10.0f, 20.0f, 40.0f}; + while (gain_setting && (gain_choices[gain_setting] > requested_gain)) { + gain_setting--; + } + + if (actual_gain) { + *actual_gain = gain_choices[gain_setting]; + } + + // For reference: + // Rds(on) of NTMFS5C628NL is ~3mOhm at 160A, 100°C and we have two in parallel + // Rshunt of ODrive v4 is 1mOhm + + RegisterFile new_config; + + new_config.driver_control = + (0b1 << 10) // overcurrent protection of any half bridge shuts down all half bridges + | (0b0 << 9) // enable Vcp and Vgls undervoltage lockout fault + | (0b0 << 8) // enable gate drive fault + | (0b1 << 7) // report overtemperature warning on nFAULT + | (0b00 << 5) // 6x PWM mode + | (0b0 << 4) // [applies to 1x PWM mode only] + | (0b0 << 3) // [applies to 1x PWM mode only] + | (0b0 << 2) // don't coast + | (0b0 << 1) // don't brake + | (0b0 << 0); // don't clear faults + + new_config.gate_drive_hs = + (0b011 << 8) // don't lock registers + | (0b1111 << 4) // 1A source current on high side FET drivers + | (0b1111 << 0); // 2A sink current on high side FET drivers + + new_config.gate_drive_ls = + (0b1 << 10) // clear overcurrent faults at next PWM input or t_retry (whichever comes first) - this has no effect since we use latched overcurrent fault mode + | (0b01 << 8) // 1000 ns peak gate current drive time + | (0b1111 << 4) // 1A source current on low side FET drivers + | (0b1111 << 0); // 2A sink current on low side FET drivers + + new_config.ocp_control = + (0b0 << 10) // retry time for Vds and shunt overcurrent protection: 8ms + | (0b01 << 8) // 100ns deadtime (we configure the STM timer to do 120ns deadtime as well) + | (0b00 << 6) // overcurrent causes a latching fault (no retry) + | (0b10 << 4) // overcurrent deglitch of 4us + | (0b0101 << 0); // Vds trip level 0.25 V (approx. ~133A per MOSFET at 100°C) + + new_config.csa_control = + (0b0 << 10) // measure current across SPx to SNx + | (0b1 << 9) // use Vref/2 as sense amplifier reference voltage + | (0b0 << 8) // measure Vds across SHx to SPx + | (gain_setting << 6) // select gain + | (0b0 << 5) // sense overcurrent fault enabled + | (0b000 << 2) // normal current sense operation on all three phases + | (0b00 << 0); // sense overcurrent protection at 0.25V sense input (corresponds to ~250A) + + bool regs_equal = (regs_.driver_control == new_config.driver_control) + && (regs_.gate_drive_hs == new_config.gate_drive_hs) + && (regs_.gate_drive_ls == new_config.gate_drive_ls) + && (regs_.ocp_control == new_config.ocp_control) + && (regs_.csa_control == new_config.csa_control); + + if (!regs_equal) { + regs_ = new_config; + state_ = kStateUninitialized; + enable_gpio_.write(false); + } + + return true; +} + +bool Drv8353::init() { + uint16_t val; + + if (state_ == kStateReady) { + return true; + } + + // Reset DRV chip. The enable pin also controls the SPI interface, not only + // the driver stages. + enable_gpio_.write(false); + delay_us(100); // t_rst, max = 40us + state_ = kStateUninitialized; // make is_ready() ignore transient errors before registers are set up + enable_gpio_.write(true); + osDelay(2); // t_wake, max = 1ms + + // Write current configuration + bool did_write_regs = write_reg(kRegNameDriverControl, regs_.driver_control) + && write_reg(kRegNameGateDriveHs, regs_.gate_drive_hs) + && write_reg(kRegNameGateDriveLs, regs_.gate_drive_ls) + && write_reg(kRegNameOcpControl, regs_.ocp_control) + && write_reg(kRegNameCsaControl, regs_.csa_control); + if (!did_write_regs) { + return false; + } + + // Wait for configuration to be applied + delay_us(100); + state_ = kStateStartupChecks; + + bool did_read_regs = read_reg(kRegNameDriverControl, &val) && (val == regs_.driver_control) + && read_reg(kRegNameGateDriveHs, &val) && (val == regs_.gate_drive_hs) + && read_reg(kRegNameGateDriveLs, &val) && (val == regs_.gate_drive_ls) + && read_reg(kRegNameOcpControl, &val) && (val == regs_.ocp_control) + && read_reg(kRegNameCsaControl, &val) && (val == regs_.csa_control); + if (!did_read_regs) { + return false; + } + + + if (get_error() != FaultType_NoFault) { + return false; + } + + // There could have been an nFAULT edge meanwhile. In this case we shouldn't + // consider the driver ready. + CRITICAL_SECTION() { + if (state_ == kStateStartupChecks) { + state_ = kStateReady; + } + } + + return state_ == kStateReady; +} + +void Drv8353::do_checks() { + if (state_ != kStateUninitialized && !nfault_gpio_.read()) { + state_ = kStateUninitialized; + } +} + +bool Drv8353::is_ready() { + return state_ == kStateReady; +} + +Drv8353::FaultType_e Drv8353::get_error() { + uint16_t fault1, fault2; + + if (!read_reg(kRegNameFaultStatus1, &fault1) || + !read_reg(kRegNameFaultStatus2, &fault2)) { + return (FaultType_e)0xffffffff; + } + + return (FaultType_e)((uint32_t)fault1 | ((uint32_t)fault2 << 16)); +} + +bool Drv8353::read_reg(const RegName_e regName, uint16_t* data) { + tx_buf_ = build_ctrl_word(DRV8353_CtrlMode_Read, regName, 0); + rx_buf_ = 0xffff; + if (!spi_arbiter_->transfer(spi_config_, ncs_gpio_, (uint8_t *)(&tx_buf_), (uint8_t *)(&rx_buf_), 1, 1000)) { + return false; + } + + delay_us(1); + + if (data) { + *data = rx_buf_ & 0x07FF; + } + + return true; +} + +bool Drv8353::write_reg(const RegName_e regName, const uint16_t data) { + // Do blocking write + tx_buf_ = build_ctrl_word(DRV8353_CtrlMode_Write, regName, data); + if (!spi_arbiter_->transfer(spi_config_, ncs_gpio_, (uint8_t *)(&tx_buf_), nullptr, 1, 1000)) { + return false; + } + delay_us(1); + + return true; +} diff --git a/Firmware/Drivers/DRV8353/drv8353.hpp b/Firmware/Drivers/DRV8353/drv8353.hpp new file mode 100644 index 00000000..b2ad72e6 --- /dev/null +++ b/Firmware/Drivers/DRV8353/drv8353.hpp @@ -0,0 +1,161 @@ +#ifndef __DRV8353_HPP +#define __DRV8353_HPP + +#include "stdbool.h" +#include "stdint.h" + +#include +#include +#include + + +class Drv8353 : public GateDriverBase, public OpAmpBase { +public: + typedef enum { + FaultType_NoFault = (0 << 0), + + // Fault Status Register 1 + FaultType_FAULT = (1 << 10), + FaultType_VDS_OCP = (1 << 9), + FaultType_GDF = (1 << 8), + FaultType_UVLO = (1 << 7), + FaultType_OTSD = (1 << 6), + FaultType_VDS_HA = (1 << 5), + FaultType_VDS_LA = (1 << 4), + FaultType_VDS_HB = (1 << 3), + FaultType_VDS_LB = (1 << 2), + FaultType_VDS_HC = (1 << 1), + FaultType_VDS_LC = (1 << 0), + + // Fault Status Register 2 + FaultType_SA_OC = (1 << 26), + FaultType_SB_OC = (1 << 25), + FaultType_SC_OC = (1 << 24), + FaultType_OTW = (1 << 23), + FaultType_GDUV = (1 << 22), + FaultType_VGS_HA = (1 << 21), + FaultType_VGS_LA = (1 << 20), + FaultType_VGS_HB = (1 << 19), + FaultType_VGS_LB = (1 << 18), + FaultType_VGS_HC = (1 << 17), + FaultType_VGS_LC = (1 << 16), + } FaultType_e; + + Drv8353(Stm32SpiArbiter* spi_arbiter, Stm32Gpio ncs_gpio, + Stm32Gpio enable_gpio, Stm32Gpio nfault_gpio) + : spi_arbiter_(spi_arbiter), ncs_gpio_(ncs_gpio), + enable_gpio_(enable_gpio), nfault_gpio_(nfault_gpio) {} + + /** + * @brief Prepares the gate driver's configuration. + * + * If the gate driver was in ready state and the new configuration is + * different from the old one then the gate driver will exit ready state. + * + * In any case changes to the configuration only take effect with a call to + * init(). + */ + bool config(float requested_gain, float* actual_gain); + + /** + * @brief Initializes the gate driver to the configuration prepared with + * config(). + * + * Returns true on success or false otherwise (e.g. if the gate driver is + * not connected or not powered or if config() was not yet called). + */ + bool init(); + + /** + * @brief Monitors the nFAULT pin. + * + * This must be run at an interval of <8ms from the moment the init() + * functions starts to run, otherwise it's possible that a temporary power + * loss is missed, leading to unwanted register values. + * In case of power loss the nFAULT pin can be low for as little as 8ms. + */ + void do_checks(); + + /** + * @brief Returns true if and only if the DRV8353 chip is in an initialized + * state and ready to do switching and current sensor opamp operation. + */ + bool is_ready() final; + + /** + * @brief This has no effect on this driver chip because the drive stages are + * always enabled while the chip is initialized + */ + bool set_enabled(bool enabled) final { return true; } + + FaultType_e get_error(); + + float get_midpoint() final { + return 0.5f; // [V] + } + + float get_max_output_swing() final { + return 1.35f / 1.65f; // +-1.35V, normalized from a scale of +-1.65V to +-0.5 + } + +private: + enum CtrlMode_e { + DRV8353_CtrlMode_Read = 1 << 15, //!< Read Mode + DRV8353_CtrlMode_Write = 0 << 15 //!< Write Mode + }; + + enum RegName_e { + kRegNameFaultStatus1 = (0 << 11), + kRegNameFaultStatus2 = (1 << 11), + kRegNameDriverControl = (2 << 11), + kRegNameGateDriveHs = (3 << 11), + kRegNameGateDriveLs = (4 << 11), + kRegNameOcpControl = (5 << 11), + kRegNameCsaControl = (6 << 11) + }; + + struct RegisterFile { + uint16_t driver_control; + uint16_t gate_drive_hs; + uint16_t gate_drive_ls; + uint16_t ocp_control; + uint16_t csa_control; + }; + + static inline uint16_t build_ctrl_word(const CtrlMode_e ctrlMode, + const RegName_e regName, + const uint16_t data) { + return ctrlMode | regName | (data & 0x07FF); + } + + /** @brief Reads data from a DRV8353 register */ + bool read_reg(const RegName_e regName, uint16_t* data); + + /** @brief Writes data to a DRV8353 register. There is no check if the write succeeded. */ + bool write_reg(const RegName_e regName, const uint16_t data); + + static const SPI_InitTypeDef spi_config_; + + // Configuration + Stm32SpiArbiter* spi_arbiter_; + Stm32Gpio ncs_gpio_; + Stm32Gpio enable_gpio_; + Stm32Gpio nfault_gpio_; + + RegisterFile regs_; //!< Current configuration. If is_ready_ is + //!< true then this can be considered consistent + //!< with the actual file on the DRV8353 chip. + + // We don't put these buffers on the stack because we place the stack in + // a RAM section which cannot be used by DMA. + uint16_t tx_buf_, rx_buf_; + + enum { + kStateUninitialized, + kStateStartupChecks, + kStateReady, + } state_ = kStateUninitialized; +}; + + +#endif // __DRV8353_HPP diff --git a/Firmware/Drivers/STM32/stm32_nvm.c b/Firmware/Drivers/STM32/stm32_nvm.c index 8d59f991..3d31ce18 100644 --- a/Firmware/Drivers/STM32/stm32_nvm.c +++ b/Firmware/Drivers/STM32/stm32_nvm.c @@ -48,6 +48,20 @@ #define FLASH_SECTOR_B_BASE (const volatile uint8_t*)0x80E0000UL #define FLASH_SECTOR_B_SIZE 0x20000UL +#elif defined(STM32F722xx) + +#include +#include + +// refer to page 68 of datasheet: +// https://www.st.com/resource/en/reference_manual/dm00305990-stm32f72xxx-and-stm32f73xxx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf +#define FLASH_SECTOR_A FLASH_SECTOR_1 +#define FLASH_SECTOR_A_BASE (const volatile uint8_t*)0x8004000UL +#define FLASH_SECTOR_A_SIZE 0x4000UL +#define FLASH_SECTOR_B FLASH_SECTOR_2 +#define FLASH_SECTOR_B_BASE (const volatile uint8_t*)0x8008000UL +#define FLASH_SECTOR_B_SIZE 0x4000UL + #else #error "unknown flash sector size" #endif diff --git a/Firmware/Drivers/STM32/stm32_system.h b/Firmware/Drivers/STM32/stm32_system.h index e065cb24..42727d53 100644 --- a/Firmware/Drivers/STM32/stm32_system.h +++ b/Firmware/Drivers/STM32/stm32_system.h @@ -3,6 +3,8 @@ #if defined(STM32F405xx) #include +#elif defined(STM32F722xx) +#include #else #error "unknown STM32 microcontroller" #endif diff --git a/Firmware/Drivers/status_led.cpp b/Firmware/Drivers/status_led.cpp new file mode 100644 index 00000000..e3942a5b --- /dev/null +++ b/Firmware/Drivers/status_led.cpp @@ -0,0 +1,15 @@ + +#include "status_led.hpp" +#include + +void I2sRgbLed::init() { + uint16_t init_buf[1] = {0}; + HAL_I2S_Transmit_DMA(&hi2s1, init_buf, 1); + while (hi2s1.State != HAL_I2S_STATE_READY); +} + +void I2sRgbLed::set_color(rgb_t color) { + rgb_t stripe[1] = {color}; + I2sWs2812Encoder::encode(stripe, 1, 0, i2s_buf_, kI2sBufLen); + HAL_I2S_Transmit_DMA(&hi2s1, i2s_buf_, kI2sBufLen); +} diff --git a/Firmware/Drivers/status_led.hpp b/Firmware/Drivers/status_led.hpp new file mode 100644 index 00000000..ea229957 --- /dev/null +++ b/Firmware/Drivers/status_led.hpp @@ -0,0 +1,57 @@ +#ifndef __STATUS_LED_HPP +#define __STATUS_LED_HPP + +#include +#include + +struct rgb_t { + rgb_t() { + val = 0; + } + rgb_t(uint8_t r, uint8_t g, uint8_t b) { + val = (r << 16) | (g << 8) | (b << 0); + } + rgb_t(uint32_t val) : val(val) {} + + uint8_t get_r() { return (val >> 16) & 0xff; } + uint8_t get_g() { return (val >> 8) & 0xff; } + uint8_t get_b() { return (val >> 0) & 0xff; } + + template + static rgb_t mix(rgb_t color0, rgb_t color1, uint32_t ratio) { + uint32_t ratio1 = (ratio >= max_val) ? (max_val - 1) : ratio; + uint32_t ratio0 = max_val - ratio1; + return rgb_t{ + (uint8_t)(((uint32_t)color0.get_r() * ratio0 + (uint32_t)color1.get_r() * ratio1) / max_val), + (uint8_t)(((uint32_t)color0.get_g() * ratio0 + (uint32_t)color1.get_g() * ratio1) / max_val), + (uint8_t)(((uint32_t)color0.get_b() * ratio0 + (uint32_t)color1.get_b() * ratio1) / max_val), + }; + } + + uint32_t val; +}; + +struct Ws2812EncoderTraits { + static constexpr uint32_t kBaudrate = 3310345ULL; + static constexpr uint16_t kSymbolHigh = 0b1110; // 906ns on, 302ns off + static constexpr uint16_t kSymbolLow = 0b1000; // 302ns on, 906ns off + static constexpr size_t kNumBitsPerSymbol = 4; + static constexpr size_t kBitsPerLed = 24; + static constexpr size_t kNumLeds = 1; + using TColor = rgb_t; + using TEncoded = uint16_t; + static uint32_t get_bits(TColor color) { return color.val; } +}; + +using I2sWs2812Encoder = Ws2812Encoder; +constexpr size_t kI2sBufLen = ((I2sWs2812Encoder::get_total_encoded_words(1) + 1) >> 1) << 1; + +class I2sRgbLed { +public: + void init(); + void set_color(rgb_t color); +private: + uint16_t i2s_buf_[kI2sBufLen]; +}; + +#endif // __STATUS_LED_HPP \ No newline at end of file diff --git a/Firmware/Drivers/ws2812.hpp b/Firmware/Drivers/ws2812.hpp new file mode 100644 index 00000000..2a9b392c --- /dev/null +++ b/Firmware/Drivers/ws2812.hpp @@ -0,0 +1,86 @@ +#ifndef __WS2812_HPP +#define __WS2812_HPP + +#include +#include + +/** + * @tparam TTraits::TColor: The type representing a single LED's color + * @tparam TTraits::TEncoded: The data type of the encoded bitstream. + * Typically uint8_t, but can also have a different word size. + * @tparam TTraits::convert: A function that converts an instance of TTraits::TColor + * into the bit representation that should be sent out. + * kBitsPerLed bits are sent out. + * If the returned type has a larger size, it should be left-padded (MSBs + * ignored) + * The MSB (after padding) is sent out first (after padding). + */ +template +struct Ws2812Encoder { + using TEncoded = typename TTraits::TEncoded; + using TColor = typename TTraits::TColor; + + static constexpr size_t kResetTimeUs = 55; // officially 50us, but that doesn't always work + static constexpr size_t kResetBits = (kResetTimeUs * TTraits::kBaudrate) / 1000000ULL; + static constexpr size_t kEncodedWordSize = CHAR_BIT * sizeof(TEncoded); + + static constexpr size_t get_total_encoded_bits(size_t num_leds) { + return num_leds * TTraits::kBitsPerLed * TTraits::kNumBitsPerSymbol + kResetBits; + } + static constexpr size_t get_total_encoded_words(size_t num_leds) { + return (get_total_encoded_bits(num_leds) + kEncodedWordSize - 1) / kEncodedWordSize; + } + + template + static void encode(TColor* colors, size_t num_colors, size_t encoded_offset, TEncoded* encoded_buffer, size_t encoded_buffer_length); +}; + + + +/** + * @brief Encodes an array of colors into a bitstream that can be sent over a + * real-time bit generator like I2S or SPI in order to control a WS2812-type LED chain. + * + * The bits must be sent out MSB-first to generate the correct wave form. + * + * @tparam WrapAround: if true, the encoder wraps around to the first LED when + * the end of the stream is reached useful for continuous data streams. + * If false, the encoded buffer is padded with zeros. + * @param encoded_offset: The position in the encoded stream, indicated in number of encoded words. + * @param encoded_buffer: Buffer where the encoded bit stream will be written. + */ +template +template +void Ws2812Encoder::encode(TColor* colors, size_t num_colors, size_t encoded_offset, TEncoded* encoded_buffer, size_t encoded_buffer_length) { + size_t total_encoded_bits = get_total_encoded_bits(num_colors); + + for (size_t i2s_word_id = 0; i2s_word_id < encoded_buffer_length; ++i2s_word_id) { + uint16_t i2s_word = 0; + + for (size_t i2s_bit_id = 0; i2s_bit_id < kEncodedWordSize; ++i2s_bit_id) { + size_t bitpos = (i2s_word_id + encoded_offset) * kEncodedWordSize + i2s_bit_id; + + if (WrapAround) { + bitpos = bitpos % total_encoded_bits; + } + + size_t symbol_bit_id = TTraits::kNumBitsPerSymbol - (bitpos % TTraits::kNumBitsPerSymbol) - 1; + size_t led_bit_id = TTraits::kBitsPerLed - ((bitpos / TTraits::kNumBitsPerSymbol) % TTraits::kBitsPerLed) - 1; + size_t led_id = (bitpos / TTraits::kNumBitsPerSymbol) / TTraits::kBitsPerLed; + + if (led_id < num_colors) { + auto color = TTraits::get_bits(colors[led_id]); + uint16_t symbol = ((color >> led_bit_id) & 1) ? TTraits::kSymbolHigh : TTraits::kSymbolLow; + + if ((symbol >> symbol_bit_id) & 1) { + i2s_word |= (1 << (kEncodedWordSize - i2s_bit_id - 1)); + } + } + } + + encoded_buffer[i2s_word_id] = i2s_word; + } +} + + +#endif // __WS2812_HPP \ No newline at end of file diff --git a/Firmware/Makefile b/Firmware/Makefile index 82cf9250..426d983f 100644 --- a/Firmware/Makefile +++ b/Firmware/Makefile @@ -12,6 +12,9 @@ include tup.config # source build configuration to get CONFIG_BOARD_VERSION ifneq (,$(findstring v3.,$(CONFIG_BOARD_VERSION))) OPENOCD := openocd -f interface/stlink.cfg $(PROGRAMMER_CMD) -f target/stm32f4x.cfg -c init GDB := arm-none-eabi-gdb --ex 'target extended-remote | openocd -f "interface/stlink-v2.cfg" -f "target/stm32f4x.cfg" -c "gdb_port pipe; log_output openocd.log"' --ex 'monitor reset halt' +else ifneq (,$(findstring v4.,$(CONFIG_BOARD_VERSION))) + OPENOCD := openocd -f interface/stlink.cfg $(PROGRAMMER_CMD) -f target/stm32f7x.cfg -c 'reset_config none separate' -c init + GDB := arm-none-eabi-gdb --ex 'target extended-remote | openocd -f "interface/stlink-v2.cfg" -f "target/stm32f7x.cfg" -c "reset_config none separate" -c "gdb_port pipe; log_output openocd.log"' --ex 'monitor reset halt' else $(error unknown board version) endif diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index f0a2bb64..aee91b8f 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -80,8 +80,10 @@ void safety_critical_arm_brake_resistor() { axes[i].motor_.I_bus_ = 0.0f; } brake_resistor_armed = true; +#if HW_VERSION_MAJOR == 3 htim2.Instance->CCR3 = 0; htim2.Instance->CCR4 = TIM_APB1_PERIOD_CLOCKS + 1; +#endif } } @@ -94,8 +96,10 @@ void safety_critical_disarm_brake_resistor() { CRITICAL_SECTION() { brake_resistor_armed = false; +#if HW_VERSION_MAJOR == 3 htim2.Instance->CCR3 = 0; htim2.Instance->CCR4 = TIM_APB1_PERIOD_CLOCKS + 1; +#endif } // Check necessary to prevent infinite recursion @@ -115,6 +119,7 @@ void safety_critical_apply_brake_resistor_timings(uint32_t low_off, uint32_t hig CRITICAL_SECTION() { if (brake_resistor_armed) { +#if HW_VERSION_MAJOR == 3 // Safe update of low and high side timings // To avoid race condition, first reset timings to safe state // ch3 is low side, ch4 is high side @@ -122,6 +127,7 @@ void safety_critical_apply_brake_resistor_timings(uint32_t low_off, uint32_t hig htim2.Instance->CCR4 = TIM_APB1_PERIOD_CLOCKS + 1; htim2.Instance->CCR3 = low_off; htim2.Instance->CCR4 = high_on; +#endif } } } @@ -162,10 +168,12 @@ void start_adc_pwm() { // Start brake resistor PWM in floating output configuration +#if HW_VERSION_MAJOR == 3 htim2.Instance->CCR3 = 0; htim2.Instance->CCR4 = TIM_APB1_PERIOD_CLOCKS + 1; HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3); HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4); +#endif if (odrv.config_.enable_brake_resistor) { safety_critical_arm_brake_resistor(); diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 834ce743..d04ea747 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -34,6 +34,55 @@ ODrive odrv{}; ConfigManager config_manager; +class StatusLedController { +public: + void update(); +}; + +StatusLedController status_led_controller; + +void StatusLedController::update() { +#if HW_VERSION_MAJOR == 4 + uint32_t t = HAL_GetTick(); + + bool is_booting = std::any_of(axes.begin(), axes.end(), [](Axis& axis){ + return axis.current_state_ == Axis::AXIS_STATE_UNDEFINED; + }); + + if (is_booting) { + return; + } + + bool is_armed = std::any_of(axes.begin(), axes.end(), [](Axis& axis){ + return axis.motor_.is_armed_; + }); + bool any_error = odrv.any_error(); + + if (is_armed) { + // Fast blue pulsating + const uint32_t period_ms = 256; + const uint8_t min_brightness = 0; + const uint8_t max_brightness = 255; + const uint32_t brightness = std::abs((int32_t)(t % period_ms) - (int32_t)(period_ms / 2)) * (max_brightness - min_brightness) / (period_ms / 2) + min_brightness; + status_led.set_color(rgb_t{(uint8_t)(any_error ? brightness / 2 : 0), 0, (uint8_t)brightness}); + } else if (any_error) { + // Red pulsating + const uint32_t period_ms = 1024; + const uint8_t min_brightness = 0; + const uint8_t max_brightness = 255; + const uint32_t brightness = std::abs((int32_t)(t % period_ms) - (int32_t)(period_ms / 2)) * (max_brightness - min_brightness) / (period_ms / 2) + min_brightness; + status_led.set_color(rgb_t{(uint8_t)brightness, 0, 0}); + } else { + // Slow green pulsating + const uint32_t period_ms = 2048; + const uint8_t min_brightness = 16; + const uint8_t max_brightness = 128; + const uint32_t brightness = std::abs((int32_t)(t % period_ms) - (int32_t)(period_ms / 2)) * (max_brightness - min_brightness) / (period_ms / 2) + min_brightness; + status_led.set_color(rgb_t{0, (uint8_t)brightness, 0}); + } +#endif +} + static bool config_read_all() { bool success = board_read_config() && config_manager.read(&odrv.config_) && @@ -217,6 +266,8 @@ void vApplicationIdleHook(void) { odrv.system_stats_.prio_uart = osThreadGetPriority(uart_thread); odrv.system_stats_.prio_startup = osThreadGetPriority(defaultTaskHandle); odrv.system_stats_.prio_can = osThreadGetPriority(odCAN->thread_id_); + + status_led_controller.update(); } } diff --git a/Firmware/MotorControl/utils.hpp b/Firmware/MotorControl/utils.hpp index 8eb044f9..67c1fcea 100644 --- a/Firmware/MotorControl/utils.hpp +++ b/Firmware/MotorControl/utils.hpp @@ -5,6 +5,7 @@ #include #include #include +#include /** * @brief Flash size register address @@ -123,7 +124,7 @@ inline float wrap_pm(float x, float y) { #ifdef FPU_FPV4 float intval = (float)round_int(x / y); #else - float intval = nearbyint(x / y); + float intval = nearbyintf(x / y); #endif return x - intval * y; } diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index f4f240b9..d6a8aec3 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -35,6 +35,14 @@ board_v3 = { ldflags = {'-TBoard/v3/STM32F405RGTx_FLASH.ld', '-LBoard/v3/Drivers/CMSIS/Lib', '-larm_cortexM4lf_math', '-mcpu=cortex-m4', '-mfpu=fpv4-sp-d16'} } +board_v4 = { + dir = 'Board/v4', + root_interface = 'ODrive4', + sources = {'Drivers/DRV8353/drv8353.cpp', 'Drivers/status_led.cpp', 'Board/v4/board.cpp', 'lockdown/rsa_embedded/rsa.c', 'lockdown/sha-2/sha-256.c',}, + flags = {'-DSTM32F722xx', '-DARM_MATH_CM7', '-mcpu=cortex-m7', '-mfpu=fpv5-sp-d16'}, + ldflags = {'-TBoard/v4/STM32F722RETx_FLASH.ld', '-LBoard/v4/Drivers/CMSIS/Lib/GCC', '-larm_cortexM7lfsp_math', '-mcpu=cortex-m7', '-mfpu=fpv5-sp-d16'} +} + -- Switch between board versions boardversion = tup.getconfig("BOARD_VERSION") if boardversion == "v3.1" then @@ -73,6 +81,10 @@ elseif boardversion == "v3.6-56V" then board = board_v3 board.flags += "-DHW_VERSION_MAJOR=3 -DHW_VERSION_MINOR=6" board.flags += "-DHW_VERSION_VOLTAGE=56" +elseif boardversion == "v4.0-56V" then + board = board_v4 + board.flags += "-DHW_VERSION_MAJOR=4 -DHW_VERSION_MINOR=0" + board.flags += "-DHW_VERSION_VOLTAGE=56" elseif boardversion == "" then error("board version not specified - take a look at tup.config.default") else diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 811efac6..eea1efb2 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -30,6 +30,8 @@ #if HW_VERSION_MAJOR == 3 static Introspectable root_obj = ODrive3TypeInfo::make_introspectable(odrv); +#elif HW_VERSION_MAJOR == 4 +static Introspectable root_obj = ODrive4TypeInfo::make_introspectable(odrv); #endif /* Private function prototypes -----------------------------------------------*/ diff --git a/Firmware/communication/interface_can.cpp b/Firmware/communication/interface_can.cpp index d1c748c1..8a557878 100644 --- a/Firmware/communication/interface_can.cpp +++ b/Firmware/communication/interface_can.cpp @@ -135,25 +135,25 @@ bool ODriveCAN::read(can_Message_t &rxmsg) { void ODriveCAN::set_baud_rate(uint32_t baudRate) { switch (baudRate) { case CAN_BAUD_125K: - handle_->Init.Prescaler = 16; // 21 TQ's + handle_->Init.Prescaler = CAN_FREQ / 125000UL; config_.baud_rate = baudRate; reinit_can(); break; case CAN_BAUD_250K: - handle_->Init.Prescaler = 8; // 21 TQ's + handle_->Init.Prescaler = CAN_FREQ / 250000UL; config_.baud_rate = baudRate; reinit_can(); break; case CAN_BAUD_500K: - handle_->Init.Prescaler = 4; // 21 TQ's + handle_->Init.Prescaler = CAN_FREQ / 500000UL; config_.baud_rate = baudRate; reinit_can(); break; case CAN_BAUD_1000K: - handle_->Init.Prescaler = 2; // 21 TQ's + handle_->Init.Prescaler = CAN_FREQ / 1000000UL; config_.baud_rate = baudRate; reinit_can(); break; diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index 28171eda..233fb9a9 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -35,7 +35,13 @@ public: usb_stats_.tx_overrun_cnt++; } // transmit packet +#if HW_VERSION_MAJOR == 3 // TODO: remove preprocessor switch uint8_t status = CDC_Transmit_FS( +#elif HW_VERSION_MAJOR == 4 + uint8_t status = CDC_Transmit_HS( +#else +#error "not supported" +#endif const_cast(buffer) /* casting this const away is safe because... well... it's not actually. Stupid STM. */, length, endpoint_pair_); if (status != USBD_OK) { diff --git a/Firmware/odrive-interface.yaml b/Firmware/odrive-interface.yaml index d88af102..5bfc1ff5 100644 --- a/Firmware/odrive-interface.yaml +++ b/Firmware/odrive-interface.yaml @@ -777,7 +777,7 @@ interfaces: value, the motor gets disarmed immediately. Note that this feature is only works on devices with three current - sensors. + sensors (e.g. ODrive v4). dc_calib_tau: float32 ODrive.Oscilloscope: @@ -1110,6 +1110,44 @@ interfaces: axis0: {type: ODrive.Axis, c_name: get_axis(0)} axis1: {type: ODrive.Axis, c_name: get_axis(1)} + ODrive4: + c_is_class: True + implements: ODrive + attributes: + config: + c_is_class: False + implements: ODrive.Config + attributes: + # TODO: add support for arrays + gpio1_mode: {type: ODrive.GpioMode, doc: Mode of GPIO1 (changes take effect after reboot), c_name: 'gpio_modes[1]'} + gpio2_mode: {type: ODrive.GpioMode, doc: Mode of GPIO2 (changes take effect after reboot), c_name: 'gpio_modes[2]'} + gpio3_mode: {type: ODrive.GpioMode, doc: Mode of GPIO3 (changes take effect after reboot), c_name: 'gpio_modes[3]'} + gpio4_mode: {type: ODrive.GpioMode, doc: Mode of GPIO4 (changes take effect after reboot), c_name: 'gpio_modes[4]'} + gpio5_mode: {type: ODrive.GpioMode, doc: Mode of GPIO5 (changes take effect after reboot), c_name: 'gpio_modes[5]'} + gpio6_mode: {type: ODrive.GpioMode, doc: Mode of GPIO6 (changes take effect after reboot), c_name: 'gpio_modes[6]'} + gpio7_mode: {type: ODrive.GpioMode, doc: Mode of GPIO7 (changes take effect after reboot), c_name: 'gpio_modes[7]'} + gpio8_mode: {type: ODrive.GpioMode, doc: Mode of GPIO8 (changes take effect after reboot), c_name: 'gpio_modes[8]'} + gpio9_mode: {type: ODrive.GpioMode, doc: Mode of GPIO9 (changes take effect after reboot), c_name: 'gpio_modes[9]'} + gpio10_mode: {type: ODrive.GpioMode, doc: Mode of GPIO10 (changes take effect after reboot), c_name: 'gpio_modes[10]'} + gpio11_mode: {type: ODrive.GpioMode, doc: Mode of GPIO11 (changes take effect after reboot), c_name: 'gpio_modes[11]'} + gpio12_mode: {type: ODrive.GpioMode, doc: Mode of GPIO12 (changes take effect after reboot), c_name: 'gpio_modes[12]'} + gpio13_mode: {type: ODrive.GpioMode, doc: Mode of GPIO13 (changes take effect after reboot), c_name: 'gpio_modes[13]'} + gpio14_mode: {type: ODrive.GpioMode, doc: Mode of GPIO14 (changes take effect after reboot), c_name: 'gpio_modes[14]'} + gpio15_mode: {type: ODrive.GpioMode, doc: Mode of GPIO15 (changes take effect after reboot), c_name: 'gpio_modes[15]'} + gpio16_mode: {type: ODrive.GpioMode, doc: Mode of GPIO16 (changes take effect after reboot), c_name: 'gpio_modes[16]'} + gpio17_mode: {type: ODrive.GpioMode, doc: Mode of GPIO17 (changes take effect after reboot), c_name: 'gpio_modes[17]'} + gpio18_mode: {type: ODrive.GpioMode, doc: Mode of GPIO18 (changes take effect after reboot), c_name: 'gpio_modes[18]'} + gpio19_mode: {type: ODrive.GpioMode, doc: Mode of GPIO19 (changes take effect after reboot), c_name: 'gpio_modes[19]'} + gpio20_mode: {type: ODrive.GpioMode, doc: Mode of GPIO20 (changes take effect after reboot), c_name: 'gpio_modes[20]'} + gpio21_mode: {type: ODrive.GpioMode, doc: Mode of GPIO21 (changes take effect after reboot), c_name: 'gpio_modes[21]'} + gpio22_mode: {type: ODrive.GpioMode, doc: Mode of GPIO22 (changes take effect after reboot), c_name: 'gpio_modes[22]'} + + gpio14_pwm_mapping: {type: ODrive.Endpoint, c_name: 'pwm_mappings[3]', doc: Make sure the corresponding GPIO is in `GPIO_MODE_PWM`.} + gpio19_pwm_mapping: {type: ODrive.Endpoint, c_name: 'pwm_mappings[2]', doc: Make sure the corresponding GPIO is in `GPIO_MODE_PWM`.} + gpio20_pwm_mapping: {type: ODrive.Endpoint, c_name: 'pwm_mappings[0]', doc: Make sure the corresponding GPIO is in `GPIO_MODE_PWM`.} + gpio21_pwm_mapping: {type: ODrive.Endpoint, c_name: 'pwm_mappings[1]', doc: Make sure the corresponding GPIO is in `GPIO_MODE_PWM`.} + axis0: {type: ODrive.Axis, c_name: get_axis(0)} + valuetypes: ODrive.GpioMode: values: diff --git a/analysis/thermistors.py b/analysis/thermistors.py index 5d6a30e2..e0fc72be 100644 --- a/analysis/thermistors.py +++ b/analysis/thermistors.py @@ -1,7 +1,7 @@ #%% from odrive.utils import calculate_thermistor_coeffs -Rload = 3300 +Rload = 3300 # 2000 for ODrive v4 R_25 = 10000 Beta = 3434 Tmin = 0 diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 6fac13a8..e7643581 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -241,6 +241,34 @@ This happens from time to time. 4. Power on the ODrive 5. Run `make flash` again +### `Warn : Cannot identify target as a STM32 family.` when flashing using openocd + +**Problem:** When I try to flash ODrive v4.1 with `make flash` then I get: +``` +[...] +** Programming Started ** +auto erase enabled +Info : device id = 0x10006452 +Warn : Cannot identify target as a STM32 family. +Error: auto_probe failed +embedded:startup.tcl:487: Error: ** Programming Failed ** +in procedure 'program' +in procedure 'program_error' called at file "embedded:startup.tcl", line 543 +at file "embedded:startup.tcl", line 487 +``` + +**Solution:** +Compile and install a recent version of openocd from source. The latest official release (0.10.0 as of Nov 2020) doesn't support the STM32F722 yet. +``` +sudo apt-get install libtool libusb-1.0 +git clone https://git.code.sf.net/p/openocd/code openocd +cd openocd/ +./bootstrap +./configure --enable-stlink +make +sudo make install +``` + ## Documentation All *.md files in the `docs/` directory of the master branch are served up by GitHub Pages on [this domain](https://docs.odriverobotics.com). diff --git a/docs/pinout.md b/docs/pinout.md index bc30bbdc..047874b2 100644 --- a/docs/pinout.md +++ b/docs/pinout.md @@ -1,5 +1,11 @@ # Pinout +## ODrive v4.1 + +**TODO** + +## ODrive v3.x + | # | Label | `GPIO_MODE_DIGITAL` | `GPIO_MODE_ANALOG_IN` | `GPIO_MODE_UART_A` | `GPIO_MODE_UART_B` | `GPIO_MODE_PWM` | `GPIO_MODE_CAN_A` | `GPIO_MODE_I2C_A` | `GPIO_MODE_ENC0` | `GPIO_MODE_ENC1` | `GPIO_MODE_MECH_BRAKE` | |----|---------------|------------------------|-----------------------|--------------------|--------------------|-----------------|------------------|-------------------|------------------|------------------|------------------------| | 0 | _not a pin_ | | | | | | | | | | | @@ -24,7 +30,8 @@ (*) ODrive v3.5 and later
(+) On ODrive v3.5 and later these pins have noise suppression filters. This is useful for step/dir input.
-Notes: +## Notes + * Changes to the pin configuration only take effect after `odrv0.save_configuration()` and `odrv0.reboot()` * Bold font marks the default configuration. * If a GPIO is set to an unsupported mode it will be left uninitialized. diff --git a/docs/resources.md b/docs/resources.md index 4278424a..5212dfb3 100644 --- a/docs/resources.md +++ b/docs/resources.md @@ -74,3 +74,59 @@ Take this info with a grain of salt as we might forget to update it from time to | uart | 4096 | 0 | | usb | 4096 | 0 | + +# ODrive v4.0 + +## Interrupt Vectors + + - lowest priority: 15 + - highest priority: 0 + +| # | Name | Prio | +|-----|-------------------------|------| +| -12 | MemoryManagement_IRQn | 0 | +| -11 | BusFault_IRQn | 0 | +| -10 | UsageFault_IRQn | 0 | +| -5 | SVCall_IRQn | 0 | +| -4 | DebugMonitor_IRQn | 0 | +| -2 | PendSV_IRQn | 15 | +| -1 | SysTick_IRQn | 15 | +| 11 | DMA1_Stream0_IRQn | 5 | +| 14 | DMA1_Stream3_IRQn | 5 | +| 15 | DMA1_Stream4_IRQn | 5 | +| 16 | DMA1_Stream5_IRQn | 5 | +| 17 | DMA1_Stream6_IRQn | 5 | +| 18 | ADC_IRQn | 1 | +| 19 | CAN1_TX_IRQn | 6 | +| 20 | CAN1_RX0_IRQn | 6 | +| 21 | CAN1_RX1_IRQn | 6 | +| 22 | CAN1_SCE_IRQn | 6 | +| 26 | TIM1_TRG_COM_TIM11_IRQn | 2 | +| 35 | SPI1_IRQn | 5 | +| 36 | SPI2_IRQn | 5 | +| 38 | USART2_IRQn | 5 | +| 45 | TIM8_TRG_COM_TIM14_IRQn | 0 | +| 47 | DMA1_Stream7_IRQn | 0 | +| 51 | SPI3_IRQn | 5 | +| 59 | DMA2_Stream3_IRQn | 5 | +| 77 | OTG_HS_IRQn | 5 | + +## DMA Streams + + - lowest priority: 0 + - highest priority: 3 + +| Name | Prio | Channel | High Level Func | +|--------------|------|----------------------------------|-----------------| +| DMA1_Stream0 | 1 | 0 (SPI3_RX) | Onboard SPI | +| DMA1_Stream3 | 0 | 0 (SPI2_RX) | Offboard SPI | +| DMA1_Stream4 | 0 | 0 (SPI2_TX) | Offboard SPI | +| DMA1_Stream5 | 0 | 4 (USART2_RX) | UART1 | +| DMA1_Stream6 | 0 | 4 (USART2_TX) | UART1 | +| DMA1_Stream7 | 1 | 0 (SPI3_TX) | Onboard SPI | +| DMA2_Stream0 | 0 | 0 (ADC1) | freerunning ADC | +| DMA2_Stream3 | 0 | 3 (SPI1_TX) | Status LED | + +## Threads + +**TODO** diff --git a/tools/odrive/tests/can_test.py b/tools/odrive/tests/can_test.py index d2a00f9e..10ec2087 100644 --- a/tools/odrive/tests/can_test.py +++ b/tools/odrive/tests/can_test.py @@ -109,8 +109,13 @@ class TestSimpleCAN(): def run_test(self, odrive: ODriveComponent, canbus: CanInterfaceComponent, node_id: int, extended_id: bool, logger: Logger): odrive.disable_mappings() - odrive.handle.config.gpio15_mode = GPIO_MODE_CAN_A - odrive.handle.config.gpio16_mode = GPIO_MODE_CAN_A + if yaml['board-version'].startswith("v3."): + odrive.handle.config.gpio15_mode = GPIO_MODE_CAN_A + odrive.handle.config.gpio16_mode = GPIO_MODE_CAN_A + elif yaml['board-version'].startswith("v4.0-"): + pass # CAN pin configuration is hardcoded + else: + raise Exception("unknown board version {}".format(yaml['board-version'])) odrive.handle.config.enable_can_a = True odrive.save_config_and_reboot() diff --git a/tools/odrive/tests/test_runner.py b/tools/odrive/tests/test_runner.py index f305858d..ae1f147c 100644 --- a/tools/odrive/tests/test_runner.py +++ b/tools/odrive/tests/test_runner.py @@ -207,9 +207,16 @@ class ODriveComponent(Component): def __init__(self, yaml: dict): self.handle = None self.yaml = yaml - #self.axes = [ODriveAxisComponent(None), ODriveAxisComponent(None)] - self.encoders = [ODriveEncoderComponent(self, 0, yaml['encoder0']), ODriveEncoderComponent(self, 1, yaml['encoder1'])] - self.axes = [ODriveAxisComponent(self, 0, yaml['motor0']), ODriveAxisComponent(self, 1, yaml['motor1'])] + + if yaml['board-version'].startswith("v3."): + self.encoders = [ODriveEncoderComponent(self, 0, yaml['encoder0']), ODriveEncoderComponent(self, 1, yaml['encoder1'])] + self.axes = [ODriveAxisComponent(self, 0, yaml['motor0']), ODriveAxisComponent(self, 1, yaml['motor1'])] + elif yaml['board-version'].startswith("v4.0-"): + self.encoders = [ODriveEncoderComponent(self, 0, yaml['encoder0'])] + self.axes = [ODriveAxisComponent(self, 0, yaml['motor0'])] + else: + raise Exception("unknown board version {}".format(yaml['board-version'])) + for i in range(1,9): self.__setattr__('gpio' + str(i), Component(self)) self.can = Component(self) @@ -249,12 +256,15 @@ class ODriveComponent(Component): axis_ctx.handle = self.handle.__dict__['axis{}'.format(axis_idx)] def disable_mappings(self): - self.handle.config.gpio1_pwm_mapping.endpoint = None # here - self.handle.config.gpio2_pwm_mapping.endpoint = None - self.handle.config.gpio3_pwm_mapping.endpoint = None - self.handle.config.gpio4_pwm_mapping.endpoint = None - self.handle.config.gpio3_analog_mapping.endpoint = None - self.handle.config.gpio4_analog_mapping.endpoint = None + if yaml['board-version'].startswith("v3."): + self.handle.config.gpio1_pwm_mapping.endpoint = None + self.handle.config.gpio2_pwm_mapping.endpoint = None + self.handle.config.gpio3_pwm_mapping.endpoint = None + self.handle.config.gpio4_pwm_mapping.endpoint = None + self.handle.config.gpio3_analog_mapping.endpoint = None + self.handle.config.gpio4_analog_mapping.endpoint = None + else: + raise Exception("unknown board version {}".format(yaml['board-version'])) def save_config_and_reboot(self): self.handle.save_configuration() diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index f9313fa3..f7fa7a5e 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -530,6 +530,32 @@ def dump_dma(odrv): ["TIM1_TRIG", "TIM1_CH1", "TIM1_CH2", "TIM1_CH1", "TIM1_CH4/TIM1_TRIG/TIM1_COM", "TIM1_UP", "TIM1_CH3", "-"], ["-", "TIM8_UP", "TIM8_CH1", "TIM8_CH2", "TIM8_CH3", "SPI5_RX", "SPI5_TX", "TIM8_CH4/TIM8_TRIG/TIM8_COM"], ]] + elif odrv.hw_version_major == 4: + dma_functions = [[ + # https://www.st.com/resource/en/reference_manual/dm00305990-stm32f72xxx-and-stm32f73xxx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf Table 26 + ["SPI3_RX", "-", "SPI3_RX", "SPI2_RX", "SPI2_TX", "SPI3_TX", "-", "SPI3_TX"], + ["I2C1_RX", "I2C3_RX", "TIM7_UP", "-", "TIM7_UP", "I2C1_RX", "I2C1_TX", "I2C1_TX"], + ["TIM4_CH1", "-", "-", "TIM4_CH2", "-", "-", "TIM4_UP", "TIM4_CH3"], + ["-", "TIM2_UP/TIM2_CH3", "I2C3_RX", "-", "I2C3_TX", "TIM2_CH1", "TIM2_CH2/TIM2_CH4", "TIM2_UP/TIM2_CH4"], + ["UART5_RX", "USART3_RX", "UART4_RX", "USART3_TX", "UART4_TX", "USART2_RX", "USART2_TX", "UART5_TX"], + ["UART8_TX", "UART7_TX", "TIM3_CH4/TIM3_UP", "UART7_RX", "TIM3_CH1/TIM3_TRIG", "TIM3_CH2", "UART8_RX", "TIM3_CH3"], + ["TIM5_CH3/TIM5_UP", "TIM5_CH4/TIM5_TRIG", "TIM5_CH1", "TIM5_CH4/TIM5_TRIG", "TIM5_CH2", "-", "TIM5_UP", "-"], + ["-", "TIM6_UP", "I2C2_RX", "I2C2_RX", "USART3_TX", "DAC1", "DAC2", "I2C2_TX"], + ], [ + # https://www.st.com/resource/en/reference_manual/dm00305990-stm32f72xxx-and-stm32f73xxx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf Table 27 + ["ADC1", "SAI1_A", "TIM8_CH1/TIM8_CH2/TIM8_CH3", "SAI1_A", "ADC1", "SAI1_B", "TIM1_CH1/TIM1_CH2/TIM1_CH3", "SAI2_B"], + ["-", "-", "ADC2", "ADC2", "SAI1_B", "-", "-", "-"], + ["ADC3", "ADC3", "-", "SPI5_RX", "SPI5_TX", "AES_OUT", "AES_IN", "-"], + ["SPI1_RX", "-", "SPI1_RX", "SPI1_TX", "SAI2_A", "SPI1_TX", "SAI2_B", "QUADSPI"], + ["SPI4_RX", "SPI4_TX", "USART1_RX", "SDMMC1", "-", "USART1_RX", "SDMMC1", "USART1_TX"], + ["-", "USART6_RX", "USART6_RX", "SPI4_RX", "SPI4_TX", "-", "USART6_TX", "USART6_TX"], + ["TIM1_TRIG", "TIM1_CH1", "TIM1_CH2", "TIM1_CH1", "TIM1_CH4/TIM1_TRIG/TIM1_COM", "TIM1_UP", "TIM1_CH3", "-"], + ["-", "TIM8_UP", "TIM8_CH1", "TIM8_CH2", "TIM8_CH3", "SPI5_RX", "SPI5_TX", "TIM8_CH4/TIM8_TRIG/TIM8_COM"], + None, + None, + None, + ["SDMMC2", "-", "-", "-", "-", "SDMMC2", "-", "-"], + ]] print("| Name | Prio | Channel | Configured |") print("|--------------|------|----------------------------------|------------|")