BLDC Current Sense

The CurrentSense class provides a field-oriented control (FOC) current sensor for BLDC motors. It implements the CurrentSensorConcept used by BldcMotor, so it can be supplied as a motor’s current sense to enable current-feedback torque control (TorqueControlType::DC_CURRENT and TorqueControlType::FOC_CURRENT).

CurrentSense is decoupled from any specific ADC: the user provides a read_phase_currents callback that returns the most recently sampled phase currents (in amps). The class subtracts the per-phase zero-current offset captured during driver_align(), applies optional per-phase gain-sign correction, reconstructs an unmeasured phase (return NAN for it) assuming Ia + Ib + Ic = 0, and runs the Clarke + Park transforms to produce the d/q currents (get_foc_currents()) and the signed DC current magnitude (get_dc_current()).

For accurate low-side current sensing the samples must be taken while the low-side FETs conduct (the PWM center). Use BldcDriver::register_pwm_sample_callback() to trigger the ADC read at the timer “empty” (TEZ) event and have the read_phase_currents callback return the latest result.

Note

This component is experimental. Zero-current offset calibration is implemented, but automatic phase-ordering / gain-sign discovery is not (set phase_gain_signs in the Config if a sense channel is inverted). Current-mode torque control depends on accurate, PWM-synchronized sampling and per-board tuning and must be validated on hardware.

API Reference

Header File

Classes

template<typename Driver = espp::BldcDriver>
class CurrentSense : public espp::BaseComponent

Generic field-oriented-control (FOC) current sensor for a BLDC motor.

This class turns raw, per-phase current measurements (in amps) into the quantities the FOC current loop needs: the d/q currents (get_foc_currents()) and the signed DC current magnitude (get_dc_current()). It implements the CurrentSensorConcept used by espp::BldcMotor, so it can be supplied as the motor’s current sense to enable detail::TorqueControlType::DC_CURRENT and detail::TorqueControlType::FOC_CURRENT control.

The class is intentionally decoupled from any specific ADC: you provide a `read_phase_currents` callback that returns the most recently sampled phase currents (already converted to amps). For accurate low-side current sensing those samples must be taken while the low-side FETs are conducting (the PWM center). Use espp::BldcDriver::register_pwm_sample_callback() to trigger your ADC read at that instant and have the `read_phase_currents` callback return the latest result.

For boards that only measure two of the three phases, return NAN for the unmeasured phase and it will be reconstructed assuming Ia + Ib + Ic = 0.

Current Sense Example

  static SimFrontEnd front_end;

  espp::CurrentSense<espp::BldcDriver> current_sense({
      // the read callback returns the latest sampled phase currents (amps). On
      // real hardware you would trigger this read from
      // BldcDriver::register_pwm_sample_callback() so it is PWM-synchronized.
      .read_phase_currents = []() -> espp::PhaseCurrent { return front_end.read(); },
      .driver = nullptr, // no driver needed for this offline example
      .calibration_samples = 200,
      .log_level = espp::Logger::Verbosity::INFO,
  });

  // 1) Calibrate the zero-current offsets while the "motor" is at rest.
  front_end.id = 0.0f;
  front_end.iq = 0.0f;
  bool ok = current_sense.driver_align(1.0f);
  logger.info("Calibration {}", ok ? "succeeded" : "failed");

  // 2) Command a known (Id, Iq) at a few electrical angles and confirm the
  //    current sense recovers it (Id ~ 0, Iq ~ commanded), proving the
  //    offset removal + Clarke/Park + phase reconstruction pipeline.
  const float commanded_iq = 2.5f;
  front_end.id = 0.0f;
  front_end.iq = commanded_iq;
  for (float angle = 0.0f; angle < espp::_2PI; angle += espp::_PI_3) {
    front_end.angle = angle;
    auto dq = current_sense.get_foc_currents(angle);
    float dc = current_sense.get_dc_current(angle);
    logger.info(
        "angle={:5.2f} rad -> Id={:6.3f} A, Iq={:6.3f} A (commanded {:.3f}), |Idc|={:6.3f} A",
        angle, dq.d, dq.q, commanded_iq, dc);
  }

Note

This component is EXPERIMENTAL. It performs zero-current offset calibration in driver_align(), but automatic phase-ordering / gain-sign discovery is not yet implemented (provide phase_gain_signs in the Config if your wiring inverts a channel). Current-mode torque control depends on accurate, PWM-synchronized sampling and per-board tuning and must be validated on hardware.

Template Parameters:

Driver – The BLDC driver type (defaults to espp::BldcDriver). Only used (optionally) during driver_align().

Public Types

typedef std::function<PhaseCurrent()> read_phase_currents_fn

Callback returning the most recently sampled phase currents in amps.

The returned currents may include the analog bias/offset of the sense circuit; the offset captured by driver_align() is subtracted internally. Set any phase that is not physically measured to NAN and it will be reconstructed from the others (Ia + Ib + Ic = 0).

Public Functions

inline explicit CurrentSense(const Config &config)

Construct a new CurrentSense.

Parameters:

config – The configuration.

inline bool driver_align(float voltage)

Align the current sense with the driver.

Captures the zero-current ADC offset for each measured phase by averaging `calibration_samples` reads. The motor must be at rest with no phase current while this runs (the BldcMotor calls this after the phases have been driven to zero).

Note

Non-const because it mutates the captured calibration state, as required by the CurrentSensorConcept.

Parameters:

voltage – Alignment voltage (reserved for future active phase/gain discovery; currently unused beyond logging).

Returns:

True if calibration succeeded.

inline PhaseCurrent get_phase_currents() const

Get the calibrated phase currents (amps).

Returns:

Offset-removed, gain-sign-corrected phase currents, with the unmeasured phase (if any) reconstructed from Ia + Ib + Ic = 0.

inline DqCurrent get_foc_currents(float electrical_angle) const

Get the d/q currents for the given electrical angle.

Parameters:

electrical_angle – The motor electrical angle (radians).

Returns:

The d (direct) and q (quadrature) currents in amps.

inline float get_dc_current(float electrical_angle) const

Get the signed DC current magnitude.

Parameters:

electrical_angle – The motor electrical angle (radians), used to give the magnitude a sign relative to the drive (q) direction.

Returns:

The signed current magnitude in amps.

inline bool is_calibrated() const

Whether the current sense has been calibrated (driver_align ran).

Returns:

True if calibrated.

inline const std::string &get_name() const

Get the name of the component

Note

This is the tag of the logger

Returns:

A const reference to the name of the component

inline void set_log_tag(const std::string_view &tag)

Set the tag for the logger

Parameters:

tag – The tag to use for the logger

inline espp::Logger::Verbosity get_log_level() const

Get the log level for the logger

Returns:

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

Parameters:

level – The verbosity level to use for the logger

inline void set_log_verbosity(espp::Logger::Verbosity level)

Set the log verbosity for the logger

See also

set_log_level

Note

This is a convenience method that calls set_log_level

Parameters:

level – The verbosity level to use for the logger

inline espp::Logger::Verbosity get_log_verbosity() const

Get the log verbosity for the logger

See also

get_log_level

Note

This is a convenience method that calls get_log_level

Returns:

The verbosity level of the logger

inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)

Set the rate limit for the logger

Note

Only calls to the logger that have _rate_limit suffix will be rate limited

Parameters:

rate_limit – The rate limit to use for the logger

struct Config

Configuration for the current sense.

Public Members

read_phase_currents_fn read_phase_currents{nullptr}

Callback returning raw phase currents (amps).

std::shared_ptr<Driver> driver = {nullptr}

Optional driver, used by driver_align().

size_t calibration_samples = {500}

Number of samples averaged for offset calibration.

std::array<float, 3> phase_gain_signs{1.0f, 1.0f, 1.0f}

Per-phase (a,b,c) gain sign to correct inverted sense channels.

Logger::Verbosity log_level = {Logger::Verbosity::WARN}

Log verbosity.