%%{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
65 Quantization and Digital Filtering
65.1 Learning Objectives
By the end of this chapter, you will be able to:
- Understand Quantization: Calculate ADC resolution and its impact on measurement accuracy
- Select ADC Resolution: Choose appropriate bit depth for your application
- Apply Digital Filters: Implement moving average, median, and low-pass filters
- Choose Appropriate Filters: Match filter type to noise characteristics
65.2 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 x 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 x 0.8mV = 1.638V +/- 0.4mV
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)
65.2.1 ADC Selection Decision Tree
%%{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.”}
65.3 Digital Filtering
Problem: ADC readings are noisy due to electrical interference, sensor limitations, and quantization.
Solution: Apply digital filters in firmware.
65.3.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.54C
Pros: Simple, smooths noise Cons: Slow response to rapid changes
65.3.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.6C (spike removed!)
Pros: Removes outliers effectively Cons: More complex than moving average
65.3.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
65.3.4 Filter Comparison
%%{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.5C<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/>(a = 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.8C<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.6C<br/>Spike removed!<br/>Best for outliers"]
E --> E1["Output:<br/>0.9xprev + 0.1xnew<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.9C. Input shows readings 23.4, 23.6, 99.9, 23.5, 23.7, 23.5C. No filter preserves all noise and spikes (red X). Moving average calculates mean 38.8C, corrupted by outlier (orange X). Median filter sorts values and selects middle 23.6C, 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.9C). 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.
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 then Low-Pass<br/>1. Remove spikes first<br/>2. Then smooth result"]
Q1 -->|"Memory limited?"| EMA["EMA (alpha filter)<br/>Stores only 1 value<br/>0.9xold + 0.1xnew<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.
65.4 Practical Quiz: ADC Configuration
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), 10uA power - Option B: 12-bit ADC, 10 ksps, 100uA power - Option C: 16-bit ADC, 1 ksps, 500uA 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 (100uA vs. 500uA 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 5x 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 5x power, and provides 400x the precision - but irrigation doesn’t need microliter accuracy!
65.5 Summary
Key concepts from this chapter:
- ADC Resolution: Higher bits = more precision, but also more power and cost
- Step Size: V_ref / 2^bits determines measurement granularity
- Match Resolution to Sensor: Don’t use 16-bit ADC for a sensor that’s only 1% accurate
- Digital Filters:
- Moving average: Smooths noise, but corrupted by outliers
- Median filter: Best for spike/outlier removal
- Low-pass (EMA): Smooths gradual noise, slow response
- Combination: Median first (remove spikes), then low-pass (smooth)
Decision Framework: 1. Identify signal bandwidth -> choose sampling rate 2. Identify precision needs -> choose ADC resolution 3. Identify noise type -> choose filter strategy 4. Consider power budget -> optimize all choices
65.6 What’s Next
Continue to Voice Compression for IoT to learn about audio signal processing techniques for bandwidth-constrained devices, or proceed to Sensor Dynamics and Response to understand how sensors behave over time.