Signal Processing Basics
Mathematical foundations for sensor data analysis in robotics.
What is a Signal?
A signal is a sequence of measurements over time:
In our robot: - Accelerometer signal: acceleration in g (gravity units) - Gyroscope signal: rotation rate in °/s - Ultrasonic signal: distance in cm
Sample Rate
Sample rate (Fs) = how many measurements per second
Fs = 500 Hz means 500 samples per second
Each sample is 1/500 = 0.002 seconds = 2 milliseconds apart
Nyquist Theorem
You can only detect frequencies up to Fs/2 (the Nyquist frequency).
At 500 Hz sample rate: max detectable frequency = 250 Hz
Time Domain vs Frequency Domain
Time Domain
Shows when things happen:
Time Domain Signal:
amplitude
↑
1.0│ ∧ ∧ ∧
│ / \ / \ / \
0.0│──/───\──/───\──/───\──→ time
│ \/ \/
-1.0│
Frequency Domain
Shows what frequencies are present:
Frequency Domain (FFT):
magnitude
↑
1.0│ ▌
│ █
0.5│ █
│ ▌ █
0.0│──█──█──█──█──█──→ frequency (Hz)
5 10 15 20 25
Analogy: Music
- Time domain = sheet music (notes over time)
- Frequency domain = list of instruments playing (what sounds)
Key Mathematical Concepts
1. Mean (Average)
The central value of a signal:
Use: Remove DC offset from signal
2. Variance
How spread out the values are from the mean:
Use: Measure vibration intensity (more vibration = higher variance)
3. Standard Deviation
Square root of variance (same units as signal):
4. Magnitude
For 3D vectors (accelerometer, gyroscope):
Use: Combine 3 axes into single value for bump detection
Filtering
Filters remove unwanted parts of a signal.
High-Pass Filter
Removes low frequencies (DC offset, slow drift), keeps fast changes:
Implementation:
# High-pass filter (removes DC, keeps AC)
hp_alpha = 0.95 # Closer to 1 = slower cutoff
hp_prev_in = 0
hp_prev_out = 0
def high_pass(new_sample):
global hp_prev_in, hp_prev_out
hp_out = hp_alpha * (hp_prev_out + new_sample - hp_prev_in)
hp_prev_in = new_sample
hp_prev_out = hp_out
return hp_out
Use: Isolate wheel bumps from accelerometer (bumps are fast changes)
Low-Pass Filter
Removes high frequencies (noise), keeps slow changes:
Implementation (Exponential Moving Average):
# Low-pass filter (smooths signal)
lp_alpha = 0.1 # Smaller = more smoothing
lp_prev = 0
def low_pass(new_sample):
global lp_prev
lp_prev = lp_alpha * new_sample + (1 - lp_alpha) * lp_prev
return lp_prev
Use: Smooth noisy sensor readings
Fourier Transform (FFT)
Converts signal from time domain to frequency domain.
The Idea
Any signal can be built from sine waves at different frequencies:
Original signal = sin(10Hz) + 0.5*sin(25Hz) + 0.3*sin(50Hz)
FFT reveals:
10 Hz: magnitude 1.0
25 Hz: magnitude 0.5
50 Hz: magnitude 0.3
Implementation
import numpy as np
# Sample data
signal = [...] # Your sensor data
Fs = 500 # Sample rate
# Compute FFT
fft_result = np.fft.fft(signal)
frequencies = np.fft.fftfreq(len(signal), 1/Fs)
# Get magnitude (only positive frequencies)
n = len(signal) // 2
magnitudes = np.abs(fft_result[:n]) / n
freqs = frequencies[:n]
# Find peak frequency
peak_idx = np.argmax(magnitudes[1:]) + 1 # Skip DC (index 0)
peak_freq = freqs[peak_idx]
Interpreting Results
| Peak Frequency | Likely Source |
|---|---|
| 0-5 Hz | Slow swaying, drift |
| 10-50 Hz | Wheel bumps |
| 50-100 Hz | Motor vibration |
| 100+ Hz | Electrical noise |
Threshold Detection
Count events by detecting when signal crosses a threshold.
Algorithm
threshold = 0.015
was_below = True
count = 0
for sample in filtered_signal:
is_above = sample > threshold
if is_above and was_below:
# Rising edge detected
count += 1
was_below = not is_above
Signal: ──────/\────/\────/\──────
Threshold: ════════════════════════ (0.015)
Detections: ↑ ↑ ↑
1 2 3
Use: Count wheel bumps for distance estimation
Sliding Window Analysis
Analyze signal in overlapping windows for real-time processing.
Concept
Signal: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Window 1: [1, 2, 3, 4, 5] → variance = 2.5
Window 2: [2, 3, 4, 5, 6] → variance = 2.5
Window 3: [3, 4, 5, 6, 7] → variance = 2.5
...
Implementation
from collections import deque
window = deque(maxlen=50) # 50 samples = 100ms at 500Hz
def add_sample(sample):
window.append(sample)
if len(window) >= 25: # Need at least half window
return variance(list(window))
return 0
Use: Real-time vibration intensity measurement
Practical Example: Bump Detection
Complete pipeline for detecting wheel bumps:
import math
# Configuration
HP_ALPHA = 0.95
THRESHOLD = 0.015
# State
hp_prev_in = 0
hp_prev_out = 0
was_above = False
bump_count = 0
def process_sample(ax, ay, az):
global hp_prev_in, hp_prev_out, was_above, bump_count
# Step 1: Calculate magnitude
mag = math.sqrt(ax**2 + ay**2 + az**2) - 1.0 # Remove gravity
# Step 2: High-pass filter
hp_out = HP_ALPHA * (hp_prev_out + mag - hp_prev_in)
hp_prev_in = mag
hp_prev_out = hp_out
# Step 3: Threshold crossing detection
is_above = hp_out > THRESHOLD
if is_above and not was_above:
bump_count += 1
was_above = is_above
return bump_count
Practical Example: Speed from FFT
def estimate_speed_from_fft(accel_data, sample_rate=500):
"""
Estimate speed from wheel bump frequency.
Args:
accel_data: List of acceleration magnitude samples
sample_rate: Samples per second
Returns:
Estimated speed in cm/s (requires calibration)
"""
import numpy as np
# Remove DC offset
data = np.array(accel_data) - np.mean(accel_data)
# Apply Hann window to reduce spectral leakage
window = np.hanning(len(data))
windowed = data * window
# Compute FFT
fft = np.fft.fft(windowed)
freqs = np.fft.fftfreq(len(data), 1/sample_rate)
# Find peak in bump frequency range (10-100 Hz)
magnitudes = np.abs(fft[:len(fft)//2])
freq_range = (freqs[:len(freqs)//2] > 10) & (freqs[:len(freqs)//2] < 100)
if not any(freq_range):
return 0
peak_idx = np.argmax(magnitudes * freq_range)
bump_freq = freqs[peak_idx]
# Convert to speed (needs calibration for your robot)
# Example: 10 bumps per wheel rotation, 20cm circumference
BUMPS_PER_ROTATION = 10
WHEEL_CIRCUMFERENCE = 20 # cm
rotation_rate = bump_freq / BUMPS_PER_ROTATION # rotations/sec
speed = rotation_rate * WHEEL_CIRCUMFERENCE # cm/sec
return speed
Summary: Which Method When?
| Task | Method | Real-time? | Accuracy |
|---|---|---|---|
| Bump counting | HP filter + threshold | Yes | Good |
| Find bump frequency | FFT | No (batch) | Excellent |
| Vibration intensity | Sliding variance | Yes | Good |
| Speed estimation | Bumps or FFT | Depends | Needs calibration |
| Motion classification | ML (forest/knn) | Yes | Excellent |
Next Steps
- [[Reference/07-data-analysis/overview|Data Analysis Overview]] - Practical workflow
- [[Reference/07-data-analysis/analyzers-reference|Analyzers Reference]] - CLI tool reference
- [[Reference/extras/hardware/picocar-pinout|PicoCar Pinout]] - Hardware configuration