mirror of
https://github.com/odriverobotics/ODrive.git
synced 2026-08-17 08:51:59 +08:00
initial ODrive v4.0 support
This commit is contained in:
Vendored
+41
-5
@@ -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}",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
#ifndef __DRV8353_HPP
|
||||
#define __DRV8353_HPP
|
||||
|
||||
#include "stdbool.h"
|
||||
#include "stdint.h"
|
||||
|
||||
#include <Drivers/gate_driver.hpp>
|
||||
#include <Drivers/STM32/stm32_spi_arbiter.hpp>
|
||||
#include <Drivers/STM32/stm32_gpio.hpp>
|
||||
|
||||
|
||||
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
|
||||
@@ -48,6 +48,20 @@
|
||||
#define FLASH_SECTOR_B_BASE (const volatile uint8_t*)0x80E0000UL
|
||||
#define FLASH_SECTOR_B_SIZE 0x20000UL
|
||||
|
||||
#elif defined(STM32F722xx)
|
||||
|
||||
#include <stm32f722xx.h>
|
||||
#include <stm32f7xx_hal.h>
|
||||
|
||||
// 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
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#if defined(STM32F405xx)
|
||||
#include <stm32f405xx.h>
|
||||
#elif defined(STM32F722xx)
|
||||
#include <stm32f722xx.h>
|
||||
#else
|
||||
#error "unknown STM32 microcontroller"
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
#include "status_led.hpp"
|
||||
#include <i2s.h>
|
||||
|
||||
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<false>(stripe, 1, 0, i2s_buf_, kI2sBufLen);
|
||||
HAL_I2S_Transmit_DMA(&hi2s1, i2s_buf_, kI2sBufLen);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#ifndef __STATUS_LED_HPP
|
||||
#define __STATUS_LED_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
#include <Drivers/ws2812.hpp>
|
||||
|
||||
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<uint32_t max_val>
|
||||
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<Ws2812EncoderTraits>;
|
||||
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
|
||||
@@ -0,0 +1,86 @@
|
||||
#ifndef __WS2812_HPP
|
||||
#define __WS2812_HPP
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
|
||||
/**
|
||||
* @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<typename TTraits>
|
||||
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<bool WrapAround>
|
||||
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<typename TTraits>
|
||||
template<bool WrapAround>
|
||||
void Ws2812Encoder<TTraits>::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
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
#include <cmath>
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
|
||||
#if HW_VERSION_MAJOR == 3
|
||||
static Introspectable root_obj = ODrive3TypeInfo<ODrive>::make_introspectable(odrv);
|
||||
#elif HW_VERSION_MAJOR == 4
|
||||
static Introspectable root_obj = ODrive4TypeInfo<ODrive>::make_introspectable(odrv);
|
||||
#endif
|
||||
|
||||
/* Private function prototypes -----------------------------------------------*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<uint8_t*>(buffer) /* casting this const away is safe because...
|
||||
well... it's not actually. Stupid STM. */, length, endpoint_pair_);
|
||||
if (status != USBD_OK) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
+8
-1
@@ -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 <br>
|
||||
(+) On ODrive v3.5 and later these pins have noise suppression filters. This is useful for step/dir input. <br>
|
||||
|
||||
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.
|
||||
|
||||
@@ -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**
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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("|--------------|------|----------------------------------|------------|")
|
||||
|
||||
Reference in New Issue
Block a user