Link
On this page

Low-Pass Filter Implementation v2.4+

The LowPassFilter class provides first-order low-pass filtering for noisy sensor measurements, particularly useful for velocity calculations that involve differentiation of position.

Class Definition

From common/lowpass_filter.h:

class LowPassFilter {
public:
    LowPassFilter(float Tf, float Ts = NOT_SET);
    float operator() (float x);
    
    float Tf; // Low pass filter time constant
    float Ts; // Fixed sampling time (optional)
    
protected:
    unsigned long timestamp_prev; // Last execution timestamp
    float y_prev; // Filtered value in previous execution step 
};

Constructor

LowPassFilter::LowPassFilter(float time_constant, float sampling_time)
    : Tf(time_constant)
    , Ts(sampling_time)
    , y_prev(0.0f)
{
    timestamp_prev = _micros();
}

Parameters:

  • Tf: Time constant (seconds) - determines filter response speed
  • Ts: Fixed sampling time (optional) - if not set, adaptive timing is used

Filter Algorithm

The operator() method implements the filtering:

float LowPassFilter::operator() (float x) {
    // Initialize elapsed time with fixed sampling time Ts
    float dt = Ts; 
    
    // If Ts not set, use adaptive sampling time
    if(!_isset(dt)){
        unsigned long timestamp = _micros();
        dt = (timestamp - timestamp_prev)*1e-6f;

        // Handle timing issues
        if (dt < 0.0f) dt = 1e-3f;
        else if(dt > 0.3f) {
            y_prev = x;
            timestamp_prev = timestamp;
            return x;
        }
        timestamp_prev = timestamp;
    }

    // Calculate first-order filter
    float alpha = Tf/(Tf + dt);
    float y = alpha*y_prev + (1.0f - alpha)*x;

    // Save for next iteration
    y_prev = y;
    return y;
}

Filter Equation

The discrete-time filter equation is:

\[y[k] = \alpha \cdot y[k-1] + (1-\alpha) \cdot x[k]\]

Where the smoothing factor is:

\[\alpha = \frac{T_f}{T_f + \Delta t}\]

Variables:

  • \(y[k]\): Current filtered output
  • \(y[k-1]\): Previous filtered output
  • \(x[k]\): Current input sample
  • \(T_f\): Filter time constant
  • \(\Delta t\): Time since last sample

Characteristics

Time Constant (Tf)

The time constant determines the filter’s cutoff frequency:

\[f_c = \frac{1}{2\pi T_f}\]

Effect on filtering:

  • Smaller Tf (e.g., 0.001s = 1ms):
    • Faster response
    • Less smoothing
    • Higher cutoff frequency (~159 Hz)
    • More noise passes through
  • Larger Tf (e.g., 0.1s = 100ms):
    • Slower response
    • More smoothing
    • Lower cutoff frequency (~1.6 Hz)
    • Better noise rejection

Adaptive vs Fixed Timing

Adaptive timing (Ts = NOT_SET):

  • Measures actual time between calls
  • Handles variable execution rates
  • Adds timestamp overhead
  • Includes safety checks for timing anomalies

Fixed timing (Ts set):

  • Assumes constant sampling rate
  • Faster execution (no time measurement)
  • Suitable for fixed-rate control loops
  • More predictable frequency response

Usage in SimpleFOC

Velocity Filtering

// In motor initialization
motor.LPF_velocity.Tf = 0.01;  // 10ms time constant, fc ≈ 16 Hz

// Automatic usage in shaftVelocity() 
// in FOCMotor.cpp
float FOCMotor::shaftVelocity() {
    return LPF_velocity(sensor->getVelocity());
}

Angle Filtering

// Position control with angle filtering
motor.LPF_angle.Tf = 0.005;  // 5ms time constant

// in FOCMotor.cpp move() function
// Used in position control
current_sp = P_angle(shaft_angle_sp - LPF_angle(shaft_angle));

Current Filtering (FOC mode)

// Current measurement filtering
motor.LPF_current_q.Tf = 0.005;  // 5ms
motor.LPF_current_d.Tf = 0.005;  // 5ms

// in FOCMotor.cpp loopFOC() function
// Applied to measured currents
current.q = LPF_current_q(current_sense->getDCCurrent(electrical_angle));
current.d = LPF_current_d(0);  // d-current filtered similarly

Tuning Guidelines

Step 1: Identify Noise Frequency

Measure or estimate the dominant noise frequency \(f_{noise}\):

  • PWM noise: typically 10-50 kHz
  • Sensor quantization: depends on encoder resolution and speed
  • Electromagnetic interference: variable

Step 2: Set Cutoff Frequency

Choose cutoff frequency below noise but above signal:

\[f_c = \frac{f_{signal}}{2} \text{ to } f_{noise} \times 0.1\]

Step 3: Calculate Time Constant

\[T_f = \frac{1}{2\pi f_c}\]

Step 4: Verify Performance

Too much filtering (Tf too large):

  • Sluggish response
  • Phase lag in control
  • Reduced performance at higher speeds

Too little filtering (Tf too small):

  • Noisy measurements
  • Control instability
  • Audible noise from motor

Example: Velocity Filtering

For a motor with:

  • Maximum velocity: 50 rad/s
  • Control frequency: 1 kHz
  • Noisy encoder with ~100 Hz noise
// Signal bandwidth: ~50 rad/s ≈ 8 Hz
// Noise frequency: ~100 Hz
// Target cutoff: 15-20 Hz (between signal and noise)

float fc = 16.0;  // Hz
float Tf = 1.0 / (2.0 * PI * fc);  // ≈ 0.01 seconds

motor.LPF_velocity.Tf = 0.01;  // 10ms time constant

PID Controller Implementation Motion control implementation Torque control implementation