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
See also
See also
- Returns:
The verbosity level of the logger
-
inline void set_log_level(espp::Logger::Verbosity level)
Set the log level for the logger
See also
See also
- 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
See also
See also
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
See also
See also
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
See also
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.
-
read_phase_currents_fn read_phase_currents{nullptr}