%%{init: {'theme': 'base', 'themeVariables': {'primaryColor':'#2C3E50','primaryTextColor':'#fff','primaryBorderColor':'#16A085','lineColor':'#16A085','secondaryColor':'#E67E22','tertiaryColor':'#7F8C8D','clusterBkg':'#ECF0F1','clusterBorder':'#16A085','edgeLabelBackground':'#ECF0F1'}}}%%
flowchart TD
A["True Signal<br/>50 Hz Vibration<br/>(Motor @ 3000 RPM)"] --> B{"Sampling Rate?"}
B -->|"100+ Hz<br/>(Correct)"| C["Sampled Signal<br/>Accurate 50 Hz<br/>✅ No Aliasing"]
B -->|"40 Hz<br/>(Too Slow)"| D["Sampled Signal<br/>Appears as 10 Hz<br/>❌ ALIASED"]
B -->|"20 Hz<br/>(Way Too Slow)"| E["Sampled Signal<br/>Appears as 10 Hz<br/>❌ SEVERELY ALIASED"]
D --> F["Problem: Fast signal<br/>masquerades as<br/>slow signal"]
E --> F
F --> G["Solution:<br/>Anti-Aliasing Filter<br/>(Low-Pass before ADC)"]
style A fill:#2C3E50,stroke:#16A085,stroke-width:3px,color:#fff
style B fill:#7F8C8D,stroke:#2C3E50,stroke-width:2px,color:#fff
style C fill:#16A085,stroke:#2C3E50,stroke-width:3px,color:#fff
style D fill:#E67E22,stroke:#2C3E50,stroke-width:3px,color:#fff
style E fill:#E67E22,stroke:#2C3E50,stroke-width:3px,color:#000
style F fill:#ECF0F1,stroke:#E67E22,stroke-width:2px,color:#000
style G fill:#16A085,stroke:#2C3E50,stroke-width:3px,color:#fff
67 Aliasing and ADC Resolution
67.1 Learning Objectives
By the end of this chapter, you will be able to:
- Recognize Aliasing: Identify when sampling is too slow and how it distorts signals
- Calculate ADC Resolution: Determine appropriate bit depth for sensor applications
- Select ADC Parameters: Choose resolution based on signal bandwidth, precision, and power budget
- Apply Digital Filters: Implement moving average, median, and low-pass filters to reduce noise
- Match Components: Select ADC specifications to match sensor accuracy and application needs
Fundamentals: - Signal Processing Overview - Previous chapter on sampling basics - Data Representation - Binary numbers and ADC resolution - Voice Compression - Next chapter on audio signals - Sensor Dynamics - Temporal response characteristics
Sensing: - Sensor Fundamentals - Sensor types and outputs - Sensor Circuits - Signal conditioning circuits - Analog/Digital Electronics - ADC deep dive
Practical: - Hardware Prototyping - ADC selection for platforms - Signal Processing Labs - Hands-on ESP32 experiments
67.2 Prerequisites
Before diving into this chapter, you should understand:
- Signal Processing Overview: Nyquist theorem and sampling fundamentals
- Data Representation: Binary numbers and bit depth
- Basic math: Logarithms and percentages for resolution calculations
67.3 Aliasing: When Sampling is Too Slow
What happens if you sample too slowly?
Fast signals masquerade as slow signals—this is called aliasing.
Visual Example: Imagine a wheel spinning at 60 RPM (1 revolution/second = 1 Hz). If you sample at 1.5 Hz (every 0.67 seconds), the wheel appears to spin slowly backward instead of forward!
IoT Example: - True signal: 50 Hz vibration (motor spinning at 3000 RPM) - Sampling rate: 40 Hz (too slow! Violates Nyquist) - Observed signal: Appears as 10 Hz vibration (completely wrong!)
Solution: Use anti-aliasing filter (low-pass) before ADC to remove frequencies above Nyquist limit.
Aliasing Effect: When sampling rate is below Nyquist (2× signal frequency), high-frequency signals incorrectly appear as low-frequency signals. A 50 Hz vibration sampled at 40 Hz masquerades as 10 Hz. Anti-aliasing filters prevent this by removing high frequencies before sampling. {fig-alt=“Flowchart showing aliasing effect: 50 Hz true signal with three sampling scenarios. 100+ Hz sampling (teal) is correct. 40 Hz and 20 Hz sampling (orange) cause aliasing, making signal appear as 10 Hz. Solution is anti-aliasing low-pass filter before ADC.”}
67.4 ADC Resolution: How Precisely to Measure?
Resolution determines measurement precision:
8-bit ADC (0-255):
Voltage range: 0-3.3V
Step size: 3.3V / 256 = 12.9 mV
Reading 127 means: 127 × 12.9mV = 1.638V ± 6.4mV
12-bit ADC (0-4095):
Voltage range: 0-3.3V
Step size: 3.3V / 4096 = 0.8 mV
Reading 2048 means: 2048 × 0.8mV = 1.638V ± 0.4mV
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor':'#2C3E50','primaryTextColor':'#fff','primaryBorderColor':'#16A085','lineColor':'#16A085','secondaryColor':'#E67E22','tertiaryColor':'#7F8C8D','clusterBkg':'#ECF0F1','clusterBorder':'#16A085','edgeLabelBackground':'#ECF0F1'}}}%%
graph LR
subgraph bit8["8-bit ADC"]
A1["256 Levels<br/>0-255"]
A2["Step Size<br/>12.9 mV<br/>(for 3.3V range)"]
A3["Precision<br/>±0.4%"]
end
subgraph bit12["12-bit ADC"]
B1["4,096 Levels<br/>0-4095"]
B2["Step Size<br/>0.8 mV<br/>(for 3.3V range)"]
B3["Precision<br/>±0.025%"]
end
subgraph bit16["16-bit ADC"]
C1["65,536 Levels<br/>0-65535"]
C2["Step Size<br/>0.05 mV<br/>(for 3.3V range)"]
C3["Precision<br/>±0.0015%"]
end
A1 --> A2 --> A3
B1 --> B2 --> B3
C1 --> C2 --> C3
style bit8 fill:#E67E22,stroke:#2C3E50,stroke-width:3px,color:#000
style bit12 fill:#16A085,stroke:#2C3E50,stroke-width:3px,color:#fff
style bit16 fill:#2C3E50,stroke:#16A085,stroke-width:3px,color:#fff
style A1 fill:#ECF0F1,stroke:#E67E22,stroke-width:2px,color:#000
style A2 fill:#ECF0F1,stroke:#E67E22,stroke-width:2px,color:#000
style A3 fill:#ECF0F1,stroke:#E67E22,stroke-width:2px,color:#000
style B1 fill:#ECF0F1,stroke:#16A085,stroke-width:2px,color:#000
style B2 fill:#ECF0F1,stroke:#16A085,stroke-width:2px,color:#000
style B3 fill:#ECF0F1,stroke:#16A085,stroke-width:2px,color:#000
style C1 fill:#ECF0F1,stroke:#2C3E50,stroke-width:2px,color:#000
style C2 fill:#ECF0F1,stroke:#2C3E50,stroke-width:2px,color:#000
style C3 fill:#ECF0F1,stroke:#2C3E50,stroke-width:2px,color:#000
Quantization Levels Comparison: Higher bit depth provides more levels and finer resolution. 8-bit (orange) is coarse with 256 steps. 12-bit (teal) is typical for IoT with 4096 steps. 16-bit (navy) provides medical-grade precision with 65536 steps at the cost of power and speed. {fig-alt=“Three-column comparison of ADC quantization levels: 8-bit (256 levels, 12.9 mV steps, 0.4% precision), 12-bit (4096 levels, 0.8 mV steps, 0.025% precision), and 16-bit (65536 levels, 0.05 mV steps, 0.0015% precision). Each shows number of levels, voltage step size for 3.3V range, and measurement precision percentage.”}
Trade-offs: | Resolution | Levels | Precision | Cost | Power | Speed | |————|——–|———–|——|——-|——-| | 8-bit | 256 | ~0.4% | Low | Low | Fast | | 10-bit | 1024 | ~0.1% | Medium | Medium | Medium | | 12-bit | 4096 | ~0.025% | Medium | Medium | Medium | | 16-bit | 65536 | ~0.0015% | High | High | Slow |
Rule of thumb for IoT: - 8-bit: Simple applications, on/off detection - 10-12-bit: Most sensor applications (temperature, light, pressure) - 16-bit: High-precision measurements (audio, medical, scientific)
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor':'#2C3E50','primaryTextColor':'#fff','primaryBorderColor':'#16A085','lineColor':'#16A085','secondaryColor':'#E67E22','tertiaryColor':'#7F8C8D','clusterBkg':'#ECF0F1','clusterBorder':'#16A085','edgeLabelBackground':'#ECF0F1'}}}%%
flowchart TD
A["ADC Selection<br/>Decision Process"] --> B{"Signal Bandwidth?"}
B -->|"< 1 kHz<br/>(Temperature,<br/>Pressure)"| C["Low Speed OK"]
B -->|"1-100 kHz<br/>(Audio,<br/>Vibration)"| D["Medium Speed"]
B -->|"> 100 kHz<br/>(RF, High-Speed<br/>Signals)"| E["High Speed<br/>Required"]
C --> F{"Required Precision?"}
D --> F
E --> F
F -->|"Simple<br/>On/Off"| G["8-bit ADC<br/>✅ Low Cost<br/>✅ Fast<br/>✅ Low Power"]
F -->|"Typical<br/>Sensor"| H["10-12-bit ADC<br/>✅ Good Balance<br/>✅ Most IoT Apps<br/>✅ Moderate Power"]
F -->|"High<br/>Precision"| I["16-bit ADC<br/>⚠️ Higher Cost<br/>⚠️ Slower<br/>⚠️ More Power"]
G --> J{"Power Budget?"}
H --> J
I --> J
J -->|"Battery<br/>Powered"| K["Optimize:<br/>- Duty cycle ADC<br/>- Lower resolution<br/>- Reduce sample rate"]
J -->|"Mains<br/>Powered"| L["Flexible:<br/>- Higher resolution OK<br/>- Faster sampling OK<br/>- Better performance"]
style A fill:#2C3E50,stroke:#16A085,stroke-width:3px,color:#fff
style B fill:#7F8C8D,stroke:#2C3E50,stroke-width:2px,color:#fff
style F fill:#7F8C8D,stroke:#2C3E50,stroke-width:2px,color:#fff
style J fill:#7F8C8D,stroke:#2C3E50,stroke-width:2px,color:#fff
style G fill:#E67E22,stroke:#2C3E50,stroke-width:2px,color:#fff
style H fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
style I fill:#2C3E50,stroke:#16A085,stroke-width:2px,color:#fff
style K fill:#ECF0F1,stroke:#E67E22,stroke-width:2px,color:#000
style L fill:#ECF0F1,stroke:#16A085,stroke-width:2px,color:#000
ADC Selection Decision Tree: Choose ADC based on three key factors: signal bandwidth (sampling speed), required precision (bit depth), and power budget. Most IoT applications use 10-12-bit ADCs as they balance precision, speed, and power consumption. {fig-alt=“Decision tree flowchart for ADC selection. Starts with signal bandwidth decision (low/medium/high speed), then precision requirements (8-bit simple, 10-12-bit typical, 16-bit high precision), finally power budget (battery vs mains powered). Each path shows trade-offs in cost, speed, and power consumption.”}
67.5 Digital Filtering
Problem: ADC readings are noisy due to electrical interference, sensor limitations, and quantization.
Solution: Apply digital filters in firmware.
67.5.1 Moving Average Filter
Simplest filter: Average the last N samples.
Readings: [23.4, 23.6, 23.5, 23.7, 23.5]
5-point average: (23.4+23.6+23.5+23.7+23.5)/5 = 23.54°C
Pros: Simple, smooths noise Cons: Slow response to rapid changes
67.5.2 Median Filter
Better for removing spikes: Take the middle value of last N samples.
Readings: [23.5, 23.6, 99.9, 23.7, 23.5] (99.9 is a spike/outlier)
5-point median: Sort → [23.5, 23.5, 23.6, 23.7, 99.9]
Median = 23.6°C (spike removed!)
Pros: Removes outliers effectively Cons: More complex than moving average
67.5.3 Low-Pass Filter (Simple RC)
Removes high-frequency noise, keeps slow changes:
// Simple exponential moving average
float filtered_value = 0.9 * previous_value + 0.1 * new_reading;Weight factor (0.9) determines how much history to keep: - High weight (0.95): Very smooth, slow response - Low weight (0.5): Faster response, less smoothing
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor':'#2C3E50','primaryTextColor':'#fff','primaryBorderColor':'#16A085','lineColor':'#16A085','secondaryColor':'#E67E22','tertiaryColor':'#7F8C8D','clusterBkg':'#ECF0F1','clusterBorder':'#16A085','edgeLabelBackground':'#ECF0F1'}}}%%
graph TB
subgraph input["Noisy Input Signal"]
A["Temperature Readings:<br/>23.4, 23.6, 99.9, 23.5, 23.7, 23.5°C<br/>(Contains spike at 99.9)"]
end
A --> B["No Filter"]
A --> C["Moving Average<br/>(5-point)"]
A --> D["Median Filter<br/>(5-point)"]
A --> E["Low-Pass Filter<br/>(α = 0.9)"]
B --> B1["Output:<br/>23.4, 23.6, 99.9, 23.5, 23.7<br/>❌ Spike remains<br/>❌ Noise visible"]
C --> C1["Output:<br/>(23.4+23.6+99.9+23.5+23.7)/5<br/>= 38.8°C<br/>❌ Spike corrupts average<br/>✅ Smooths gradual noise"]
D --> D1["Output:<br/>Sort: 23.4, 23.5, 23.6, 23.7, 99.9<br/>Median = 23.6°C<br/>✅ Spike removed!<br/>✅ Best for outliers"]
E --> E1["Output:<br/>0.9×prev + 0.1×new<br/>Gradual approach to true value<br/>✅ Smooth<br/>⚠️ Slow response"]
style input fill:#E67E22,stroke:#2C3E50,stroke-width:3px,color:#fff
style A fill:#ECF0F1,stroke:#E67E22,stroke-width:2px,color:#000
style B fill:#7F8C8D,stroke:#2C3E50,stroke-width:2px,color:#fff
style C fill:#7F8C8D,stroke:#2C3E50,stroke-width:2px,color:#fff
style D fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
style E fill:#7F8C8D,stroke:#2C3E50,stroke-width:2px,color:#fff
style B1 fill:#ECF0F1,stroke:#E67E22,stroke-width:2px,color:#000
style C1 fill:#ECF0F1,stroke:#E67E22,stroke-width:2px,color:#000
style D1 fill:#ECF0F1,stroke:#16A085,stroke-width:2px,color:#000
style E1 fill:#ECF0F1,stroke:#7F8C8D,stroke-width:2px,color:#000
{fig-alt=“Four-way comparison of digital filtering techniques applied to noisy temperature sensor data containing outlier spike at 99.9°C. Input shows readings 23.4, 23.6, 99.9, 23.5, 23.7, 23.5°C. No filter preserves all noise and spikes (red X). Moving average calculates mean 38.8°C, corrupted by outlier (orange X). Median filter sorts values and selects middle 23.6°C, successfully removing spike (teal checkmark, recommended). Low-pass exponential filter with alpha 0.9 gradually approaches true value, smooth but slow response (gray warning). Median filter highlighted as best approach for spike and outlier removal.”}
Digital Filter Comparison: Four approaches to handling noisy temperature data with outlier spike (99.9°C). No filter keeps noise and spikes. Moving average is corrupted by outliers. Median filter (teal) effectively removes spikes by selecting middle value. Low-pass filter smooths gradually but responds slowly to changes. {fig-alt=“Comparison of four digital filtering approaches on temperature sensor data with outlier spike. Input signal contains readings 23.4, 23.6, 99.9 (spike), 23.5, 23.7, 23.5°C. No filter preserves noise. Moving average (38.8°C) is corrupted by spike. Median filter correctly outputs 23.6°C by removing outlier. Low-pass filter smooths gradually with slow response. Median filter highlighted in teal as best for spike removal.”}
This variant presents filtering as a decision-making process based on your specific noise problems:
%% fig-alt: "Decision tree for selecting the right digital filter for IoT sensor data. Start with the question: What is your main noise problem? If the problem is sudden spikes or outliers (like EMI, loose connections, or sensor glitches), choose Median Filter - it removes spikes while preserving real signal changes. If the problem is gradual noise or jitter (like thermal noise, electrical hum, or quantization), choose Low-Pass Filter - it smooths variations but slows response time. If you need both (spikes AND gradual noise), use Median first to remove spikes, then Low-Pass to smooth. If memory is limited (less than 32 bytes available), use Exponential Moving Average (EMA) - it only stores one value. Real-world tip: start with median filter, then add smoothing if needed."
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#7F8C8D'}}}%%
flowchart TD
START["What's your<br/>noise problem?"] --> Q1{"Sudden spikes<br/>or outliers?"}
Q1 -->|"Yes"| MED["MEDIAN FILTER<br/>• Removes spikes<br/>• Preserves real changes<br/>• Good for: EMI, glitches"]
Q1 -->|"No"| Q2{"Gradual noise<br/>or jitter?"}
Q2 -->|"Yes"| LP["LOW-PASS FILTER<br/>• Smooths variations<br/>• Slows response<br/>• Good for: thermal noise"]
Q2 -->|"Both!"| BOTH["COMBO: Median → Low-Pass<br/>1. Remove spikes first<br/>2. Then smooth result"]
Q1 -->|"Memory limited?"| EMA["EMA (α filter)<br/>• Stores only 1 value<br/>• 0.9×old + 0.1×new<br/>• Good for: tiny MCUs"]
MED --> TIP["Start with median,<br/>add smoothing if needed"]
LP --> TIP
BOTH --> TIP
EMA --> TIP
style START fill:#2C3E50,stroke:#16A085,stroke-width:2px,color:#fff
style Q1 fill:#7F8C8D,stroke:#2C3E50,stroke-width:1px,color:#fff
style Q2 fill:#7F8C8D,stroke:#2C3E50,stroke-width:1px,color:#fff
style MED fill:#16A085,stroke:#2C3E50,stroke-width:2px,color:#fff
style LP fill:#E67E22,stroke:#2C3E50,stroke-width:2px,color:#fff
style BOTH fill:#2C3E50,stroke:#16A085,stroke-width:2px,color:#fff
style EMA fill:#7F8C8D,stroke:#E67E22,stroke-width:2px,color:#fff
style TIP fill:#ECF0F1,stroke:#16A085,stroke-width:1px,color:#000
Why this variant helps: The original compares all filters side-by-side. This decision tree helps students answer the practical question: “Which filter should I use for MY problem?” It turns knowledge into actionable decisions by matching filter type to noise characteristics.
Scenario: You’re designing a smart irrigation system using a turbine water flow sensor. The sensor outputs: - Voltage range: 0.5V (no flow) to 4.5V (maximum flow 10 L/min) - Signal characteristics: Pulsed output, 1-50 Hz depending on flow rate - Noise: ±50mV from pump electrical interference
Your microcontroller options: - Option A: 8-bit ADC, 100 ksps (samples/second), 10µA power - Option B: 12-bit ADC, 10 ksps, 100µA power - Option C: 16-bit ADC, 1 ksps, 500µA power
Think about: 1. What sampling rate do you need to accurately measure 50 Hz pulses? (Nyquist) 2. What ADC resolution do you need to measure ±0.1 L/min precision (assuming linear sensor)? 3. Which ADC option provides the best balance of precision, speed, and power?
Key Insights:
Sampling rate requirement: - Maximum frequency: 50 Hz - Nyquist requirement: > 100 samples/second - Practical target: 250-500 samples/second for safety - ✅ All options meet this (slowest is 1000 sps)
Resolution requirement: - Voltage span: 4.5V - 0.5V = 4.0V - Flow span: 10 L/min - 0 L/min = 10 L/min - Sensitivity: 4.0V / 10 L/min = 0.4V per L/min - Desired precision: ±0.1 L/min = ±0.04V
8-bit ADC: - Step size: 5V / 256 = 19.5 mV - Precision: 19.5mV / 400mV per L/min = ±0.05 L/min ✅ (Just barely meets requirement!)
12-bit ADC: - Step size: 5V / 4096 = 1.2 mV - Precision: 1.2mV / 400mV per L/min = ±0.003 L/min ✅ (Excellent!)
Noise consideration: - Noise: ±50mV - 8-bit: Noise = ±2.5 steps (poor SNR) - 12-bit: Noise = ±40 steps (good SNR, averaging helps)
Best choice: Option B - 12-bit ADC - ✅ Plenty of resolution (0.003 L/min vs. 0.1 L/min requirement) - ✅ Adequate sampling rate (10,000 sps >> 100 sps needed) - ✅ Reasonable power (100µA vs. 500µA for 16-bit) - ✅ Good SNR with noise (40 steps margin)
Option A (8-bit) would work but has minimal margin for noise. Option C (16-bit) is overkill and wastes 5× more power for precision you don’t need.
Real-world lesson: Don’t over-specify. Match ADC resolution to actual sensor precision and application requirements. The 16-bit ADC costs more, draws 5× power, and provides 400× the precision—but irrigation doesn’t need microliter accuracy!
67.6 Summary
This chapter covered critical aspects of analog-to-digital conversion:
Key Concepts: 1. Aliasing: Undersampling makes fast signals appear slow—always sample >2× signal frequency 2. Anti-Aliasing Filters: Low-pass filters before ADC prevent aliasing by removing high frequencies 3. ADC Resolution: Match bit depth to sensor accuracy (8-bit for simple, 10-12-bit for typical IoT, 16-bit for precision) 4. Digital Filtering: Apply median filters for spike removal, low-pass for smoothing 5. Trade-offs: Higher resolution costs more power and speed—choose appropriately
Practical Guidelines: - Use 12-bit ADC for most IoT sensor applications - Apply median filtering first to remove outliers, then smooth with low-pass if needed - Don’t over-specify—16-bit precision is wasted if your sensor is only ±1% accurate - Anti-aliasing filters are non-negotiable for signals approaching Nyquist limit
What’s Next: - Voice and Audio Compression - Applying signal processing to audio - Sensor Dynamics - Understanding temporal response - Signal Processing Labs - Hands-on ADC experiments with ESP32