*
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
|
||||
#include "drv8301.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "cmsis_os.h"
|
||||
#include "board.h"
|
||||
|
||||
const SPI_InitTypeDef Drv8301::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 Drv8301::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
|
||||
|
||||
// for reference:
|
||||
// 20V/V on 500uOhm gives a range of +/- 150A
|
||||
// 40V/V on 500uOhm gives a range of +/- 75A
|
||||
// 20V/V on 666uOhm gives a range of +/- 110A
|
||||
// 40V/V on 666uOhm gives a range of +/- 55A
|
||||
|
||||
uint16_t gain_setting = 3;
|
||||
float gain_choices[] = {10.0f, 20.0f, 40.0f, 80.0f};
|
||||
while (gain_setting && (gain_choices[gain_setting] > requested_gain)) {
|
||||
gain_setting--;
|
||||
}
|
||||
|
||||
if (actual_gain) {
|
||||
*actual_gain = gain_choices[gain_setting];
|
||||
}
|
||||
|
||||
RegisterFile new_config;
|
||||
|
||||
new_config.control_register_1 =
|
||||
(21 << 6) // Overcurrent set to approximately 150A at 100degC. This may need tweaking.
|
||||
| (0b01 << 4) // OCP_MODE: latch shut down
|
||||
| (0b0 << 3) // 6x PWM mode
|
||||
| (0b0 << 2) // don't reset latched faults
|
||||
| (0b00 << 0); // gate-drive peak current: 1.7A
|
||||
|
||||
new_config.control_register_2 =
|
||||
(0b0 << 6) // OC_TOFF: cycle by cycle
|
||||
| (0b00 << 4) // calibration off (normal operation)
|
||||
| (gain_setting << 2) // select gain
|
||||
| (0b00 << 0); // report both over temperature and over current on nOCTW pin
|
||||
|
||||
bool regs_equal = (regs_.control_register_1 == new_config.control_register_1)
|
||||
&& (regs_.control_register_2 == new_config.control_register_2);
|
||||
|
||||
if (!regs_equal) {
|
||||
regs_ = new_config;
|
||||
state_ = kStateUninitialized;
|
||||
enable_gpio_.write(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Drv8301::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(40); // mimumum pull-down time for full reset: 20us
|
||||
state_ = kStateUninitialized; // make is_ready() ignore transient errors before registers are set up
|
||||
enable_gpio_.write(true);
|
||||
osDelay(20); // t_spi_ready, max = 10ms
|
||||
|
||||
// Write current configuration
|
||||
bool wrote_regs = write_reg(kRegNameControl1, regs_.control_register_1)
|
||||
&& write_reg(kRegNameControl1, regs_.control_register_1)
|
||||
&& write_reg(kRegNameControl1, regs_.control_register_1)
|
||||
&& write_reg(kRegNameControl1, regs_.control_register_1)
|
||||
&& write_reg(kRegNameControl1, regs_.control_register_1) // the write operation tends to be ignored if only done once (not sure why)
|
||||
&& write_reg(kRegNameControl2, regs_.control_register_2);
|
||||
if (!wrote_regs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wait for configuration to be applied
|
||||
delay_us(100);
|
||||
state_ = kStateStartupChecks;
|
||||
|
||||
bool is_read_regs = read_reg(kRegNameControl1, &val) && (val == regs_.control_register_1)
|
||||
&& read_reg(kRegNameControl2, &val) && (val == regs_.control_register_2);
|
||||
if (!is_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 Drv8301::do_checks() {
|
||||
if (state_ != kStateUninitialized && !nfault_gpio_.read()) {
|
||||
state_ = kStateUninitialized;
|
||||
}
|
||||
}
|
||||
|
||||
bool Drv8301::is_ready() {
|
||||
return state_ == kStateReady;
|
||||
}
|
||||
|
||||
Drv8301::FaultType_e Drv8301::get_error() {
|
||||
uint16_t fault1, fault2;
|
||||
|
||||
if (!read_reg(kRegNameStatus1, &fault1) ||
|
||||
!read_reg(kRegNameStatus2, &fault2)) {
|
||||
return (FaultType_e)0xffffffff;
|
||||
}
|
||||
|
||||
return (FaultType_e)((uint32_t)fault1 | ((uint32_t)(fault2 & 0x0080) << 16));
|
||||
}
|
||||
|
||||
bool Drv8301::read_reg(const RegName_e regName, uint16_t* data) {
|
||||
tx_buf_ = build_ctrl_word(DRV8301_CtrlMode_Read, regName, 0);
|
||||
if (!spi_arbiter_->transfer(spi_config_, ncs_gpio_, (uint8_t *)(&tx_buf_), nullptr, 1, 1000)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
delay_us(1);
|
||||
|
||||
tx_buf_ = build_ctrl_word(DRV8301_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 (rx_buf_ == 0xbeef) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
*data = rx_buf_ & 0x07FF;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Drv8301::write_reg(const RegName_e regName, const uint16_t data) {
|
||||
// Do blocking write
|
||||
tx_buf_ = build_ctrl_word(DRV8301_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,145 @@
|
||||
#ifndef __DRV8301_HPP
|
||||
#define __DRV8301_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 Drv8301 : public GateDriverBase, public OpAmpBase {
|
||||
public:
|
||||
typedef enum : uint32_t {
|
||||
FaultType_NoFault = (0 << 0), //!< No fault
|
||||
|
||||
// Status Register 1
|
||||
FaultType_FETLC_OC = (1 << 0), //!< FET Low side, Phase C Over Current fault
|
||||
FaultType_FETHC_OC = (1 << 1), //!< FET High side, Phase C Over Current fault
|
||||
FaultType_FETLB_OC = (1 << 2), //!< FET Low side, Phase B Over Current fault
|
||||
FaultType_FETHB_OC = (1 << 3), //!< FET High side, Phase B Over Current fault
|
||||
FaultType_FETLA_OC = (1 << 4), //!< FET Low side, Phase A Over Current fault
|
||||
FaultType_FETHA_OC = (1 << 5), //!< FET High side, Phase A Over Current fault
|
||||
FaultType_OTW = (1 << 6), //!< Over Temperature Warning fault
|
||||
FaultType_OTSD = (1 << 7), //!< Over Temperature Shut Down fault
|
||||
FaultType_PVDD_UV = (1 << 8), //!< Power supply Vdd Under Voltage fault
|
||||
FaultType_GVDD_UV = (1 << 9), //!< DRV8301 Vdd Under Voltage fault
|
||||
FaultType_FAULT = (1 << 10),
|
||||
|
||||
// Status Register 2
|
||||
FaultType_GVDD_OV = (1 << 23) //!< DRV8301 Vdd Over Voltage fault
|
||||
} FaultType_e;
|
||||
|
||||
Drv8301(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 DRV8301 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 {
|
||||
DRV8301_CtrlMode_Read = 1 << 15, //!< Read Mode
|
||||
DRV8301_CtrlMode_Write = 0 << 15 //!< Write Mode
|
||||
};
|
||||
|
||||
enum RegName_e {
|
||||
kRegNameStatus1 = 0 << 11, //!< Status Register 1
|
||||
kRegNameStatus2 = 1 << 11, //!< Status Register 2
|
||||
kRegNameControl1 = 2 << 11, //!< Control Register 1
|
||||
kRegNameControl2 = 3 << 11 //!< Control Register 2
|
||||
};
|
||||
|
||||
struct RegisterFile {
|
||||
uint16_t control_register_1;
|
||||
uint16_t control_register_2;
|
||||
};
|
||||
|
||||
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 DRV8301 register */
|
||||
bool read_reg(const RegName_e regName, uint16_t* data);
|
||||
|
||||
/** @brief Writes data to a DRV8301 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 DRV8301 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 // __DRV8301_HPP
|
||||
Reference in New Issue
Block a user