Project 10: The Web-Based Oscilloscope (WebSocket Dashboard)
Build a microcontroller-hosted web app that streams ADC samples over WebSockets and renders a live waveform in the browser.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Advanced |
| Time Estimate | 1-2 weeks |
| Main Programming Language | C (ESP-IDF) + JavaScript (browser) |
| Alternative Programming Languages | Arduino C++ + JS |
| Coolness Level | Very High |
| Business Potential | Medium (instrumentation) |
| Prerequisites | ADC basics, web sockets, embedded networking |
| Key Topics | ADC sampling, DMA buffers, WebSockets, streaming UI |
1. Learning Objectives
By completing this project, you will:
- Configure ADC continuous sampling and DMA buffers.
- Stream binary sample frames over WebSockets.
- Render real-time waveforms in a browser with stable FPS.
- Implement backpressure to avoid buffer overruns.
- Measure throughput and optimize latency.
2. All Theory Needed (Per-Concept Breakdown)
2.1 Concept 1: ADC Sampling, DMA, and Signal Integrity
Fundamentals
The ADC (Analog-to-Digital Converter) samples analog voltage and converts it to digital values. The sampling rate must be high enough to capture the signal of interest (Nyquist). Continuous sampling can be handled by DMA, which moves data from the ADC to memory without CPU intervention. DMA buffers allow high sampling rates, but they must be sized correctly to avoid overflow. Signal integrity matters: noisy inputs, poor grounding, and wrong attenuation settings produce inaccurate waveforms.
Deep Dive into the Concept
ADC sampling is a time-domain measurement. At each sample, the ADC captures the instantaneous voltage and outputs a digital code (often 12-bit). The sampling rate determines how many points per second you get. If you sample a 2 kHz signal at 4 kHz, you are at Nyquist and risk aliasing if there is noise above 2 kHz. For a waveform display, you typically want at least 5-10x the signal frequency to capture shape. The ADC on ESP32 has selectable attenuation and resolution. Attenuation adjusts input range; choose the correct setting to match your signal. If your signal exceeds the range, it saturates and the waveform clips.
DMA is essential for high-rate sampling because it decouples the CPU from the ADC. The ADC writes samples into a circular DMA buffer. The CPU processes buffer chunks in a task. Buffer size is a tradeoff: larger buffers reduce overhead but increase latency. Smaller buffers give lower latency but increase interrupt frequency. For a real-time oscilloscope, latency and smoothness matter more than absolute throughput, so you should balance buffer size with desired frame rate.
Signal integrity is often the hidden challenge. The ESP32 ADC is sensitive to noise from Wi-Fi and digital activity. You should use a clean analog source, short wires, and proper grounding. If you see jitter or spikes, it might be digital noise, not your signal. Averaging or simple filtering can improve the display, but it also blurs high-frequency details. For a learning project, use a simple signal source (potentiometer, sine wave generator) and document the limitations.
Finally, sampling rate and data volume must match your streaming capabilities. If you sample at 10 kHz with 12-bit samples, you generate 20 kB/s (since 2 bytes per sample). Streaming that over Wi-Fi is feasible, but if you increase to 100 kHz, you generate 200 kB/s and may stress the network. The ADC configuration and network bandwidth must be considered together.
How this fits on projects
You will configure ADC continuous mode, use DMA buffers, and design a sampling rate that your WebSocket stream can carry.
Definitions & Key Terms
- ADC: Analog-to-digital converter.
- Sampling rate: Samples per second.
- Nyquist: Minimum sample rate to avoid aliasing.
- DMA: Direct Memory Access, hardware data transfer.
- Attenuation: ADC input range setting.
Mental Model Diagram (ASCII)
Analog signal -> ADC -> DMA buffer -> CPU task
How It Works (Step-by-Step)
- Configure ADC resolution and attenuation.
- Start continuous sampling with DMA.
- DMA fills buffer with samples.
- Task reads buffer and packs samples into frames.
- Frames are sent over WebSocket.
Minimal Concrete Example
// Pseudocode for DMA buffer handling
adc_continuous_read(handle, buffer, buffer_size, &bytes_read);
Common Misconceptions
- “Higher sampling rate is always better.” It increases data volume and may exceed bandwidth.
- “ADC output is noise-free.” Digital noise can distort readings.
- “DMA removes all CPU load.” You still must process buffers in time.
Check-Your-Understanding Questions
- What is the Nyquist rate for a 2 kHz signal?
- How does buffer size affect latency?
- Why does ADC attenuation matter?
Check-Your-Understanding Answers
- At least 4 kHz.
- Larger buffers increase latency; smaller buffers increase CPU overhead.
- It sets the voltage range and prevents clipping.
Real-World Applications
- Battery monitoring and power analysis.
- Industrial sensor data acquisition.
Where You’ll Apply It
- See Section 3.2 Functional Requirements and Section 5.10 Phase 1.
- Also used in: P02 Deep Sleep Champion for signal measurement.
References
- ESP32 ADC documentation
- “The Art of Electronics” (ADC basics)
Key Insights
Sampling rate and buffer size determine both signal quality and streaming feasibility.
Summary
ADC sampling with DMA enables high-rate data capture, but you must manage buffer sizes and signal integrity to produce accurate waveforms.
Homework/Exercises to Practice the Concept
- Compute data rate for 10 kHz sampling at 12-bit resolution.
- Choose an attenuation setting for a 0-3.3V signal.
Solutions to the Homework/Exercises
- 10k samples/s * 2 bytes = 20 kB/s.
- Use 11 dB attenuation for full-scale 3.3V range (approx).
2.2 Concept 2: WebSockets, Binary Frames, and Backpressure
Fundamentals
WebSockets provide a persistent, bidirectional connection between a browser and the device. Unlike HTTP polling, WebSockets allow low-latency streaming. Binary frames are more efficient than text for sample data. However, the browser and device must handle backpressure: if the browser cannot render fast enough or the network is slow, the device must throttle sending or drop frames.
Deep Dive into the Concept
WebSockets begin with an HTTP upgrade handshake. After the handshake, the connection switches to a full-duplex binary/text stream. For streaming samples, binary frames are best because they avoid base64 overhead. A sample frame can contain a header (timestamp, sample rate, count) followed by raw 16-bit samples. The browser reads the frame as an ArrayBuffer and updates the waveform.
Backpressure is a real issue. The embedded device can produce data at a fixed rate, but the network and browser may not keep up. If you blindly send every buffer, you can fill the TCP send buffer and increase latency. The solution is to implement a simple flow control: send only when the previous frame has been acknowledged (or after a fixed interval), and drop frames if the queue is full. On the browser side, you should render at a fixed FPS (e.g., 30 fps) and skip frames when needed. This keeps the UI responsive.
The WebSocket library in ESP-IDF often uses event callbacks. You should keep the callback lightweight and push data from a task that knows the current queue depth. If the socket is closed or the client disconnects, you should stop sending. Reconnection logic is helpful but optional for this project.
In the browser, you should decouple parsing from rendering. Parse the binary frame, store samples in a buffer, and render in a requestAnimationFrame loop. This prevents the WebSocket onmessage handler from doing heavy work and blocking. You can also implement simple downsampling: if the frame contains too many samples for the canvas width, average or decimate to avoid drawing too many points.
How this fits on projects
You will stream binary frames over WebSockets and implement a browser UI that renders waveforms smoothly without backlog.
Definitions & Key Terms
- WebSocket: Persistent full-duplex connection over TCP.
- Binary frame: WebSocket frame carrying binary data.
- Backpressure: Throttling data production when consumers are slow.
- ArrayBuffer: JS binary buffer type.
Mental Model Diagram (ASCII)
Device -> WebSocket -> Browser
Samples -> Binary frame -> Canvas
How It Works (Step-by-Step)
- Browser opens WebSocket connection.
- Device sends binary frame with samples.
- Browser parses frame into ArrayBuffer.
- Render loop draws waveform.
- Backpressure logic drops frames if needed.
Minimal Concrete Example
ws.onmessage = (ev) => {
const buf = new DataView(ev.data);
// parse samples
};
Common Misconceptions
- “WebSockets are always faster.” They still depend on TCP and rendering.
- “Send all frames.” This causes latency buildup.
- “Text frames are fine.” Binary is more efficient for samples.
Check-Your-Understanding Questions
- Why use binary frames instead of text?
- What happens if the browser cannot keep up with the stream?
- How can you implement backpressure on the device?
Check-Your-Understanding Answers
- Binary avoids encoding overhead and is smaller.
- Frames queue up, increasing latency and memory use.
- Throttle sending or drop frames when queue is full.
Real-World Applications
- Real-time dashboards and telemetry UIs.
- Web-based oscilloscopes and monitoring tools.
Where You’ll Apply It
- See Section 3.7 Real World Outcome and Section 5.10 Phase 2.
- Also used in: P08 Wi-Fi Sniffer for streaming strategies.
References
- WebSocket RFC 6455 summary
- MDN WebSocket documentation
Key Insights
A smooth UI requires backpressure and deliberate frame pacing.
Summary
WebSockets enable real-time streaming, but you must manage binary framing and flow control to keep the UI responsive.
Homework/Exercises to Practice the Concept
- Design a binary frame header with sample count and timestamp.
- Decide a target FPS and compute samples per frame.
Solutions to the Homework/Exercises
- Example header: uint32 timestamp, uint16 sample_rate, uint16 count.
- At 10 kHz and 30 fps, ~333 samples per frame.
3. Project Specification
3.1 What You Will Build
A web-based oscilloscope that samples an analog input on the XIAO, streams samples over WebSockets, and renders a live waveform on a browser page hosted by the device.
3.2 Functional Requirements
- ADC Sampling: Continuous sampling with configurable rate (2-10 kHz).
- DMA Buffers: Use DMA to collect samples.
- Web Server: Serve HTML/JS page from the device.
- WebSocket Stream: Send binary frames with sample data.
- UI Rendering: Render waveform at 30 fps.
3.3 Non-Functional Requirements
- Stability: Runs for 10 minutes without disconnect.
- Latency: End-to-end latency under 300 ms.
- Efficiency: CPU usage leaves headroom for Wi-Fi stack.
3.4 Example Usage / Output
[HTTP] Serving /index.html
[WS] Client connected
[ADC] rate=5000Hz frame=256 samples
3.5 Data Formats / Schemas / Protocols
Binary frame format:
uint32_t timestamp_ms
uint16_t sample_rate_hz
uint16_t sample_count
int16_t samples[sample_count]
3.6 Edge Cases
- Client disconnects mid-stream.
- Wi-Fi link drops.
- Sample rate too high for network.
3.7 Real World Outcome
A browser page at http://<device-ip>/ shows a live waveform that updates smoothly.
3.7.1 How to Run (Copy/Paste)
idf.py set-target esp32s3
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor
3.7.2 Golden Path Demo (Deterministic)
- Fixed sample rate 5 kHz.
- Fixed frame size 256 samples.
Expected logs:
[WS] Sent frame ts=1000 count=256
[WS] Sent frame ts=1010 count=256
3.7.3 Failure Demo (Network Backpressure)
W (1234) WS: send queue full, dropping frame
3.7.4 If CLI
No standalone CLI. Exit codes not applicable.
3.7.5 If Web App
URL: http://<device-ip>/
- Screen shows a title, current sample rate, and a canvas waveform.
- Loading state: “Connecting…”
- Error state: “Disconnected, retrying…”
ASCII wireframe:
+----------------------------------+
| XIAO Web Oscilloscope |
| Sample rate: 5000 Hz |
|----------------------------------|
| |
| waveform canvas |
| |
|----------------------------------|
| Status: Connected |
+----------------------------------+
3.7.6 If API
No external API beyond WebSocket. Error JSON shape not applicable.
3.7.7 If GUI / Desktop / Mobile
Not applicable (browser UI only).
3.7.8 If TUI
Not applicable.
4. Solution Architecture
4.1 High-Level Design
ADC -> DMA -> Sample buffer -> WebSocket -> Browser -> Canvas
4.2 Key Components
| Component | Responsibility | Key Decisions | |———-|—————-|—————| | ADC driver | Configure sampling rate | Continuous mode | | DMA buffer | Store samples | 256-512 samples | | Web server | Serve HTML/JS | Embedded assets | | WebSocket | Stream binary frames | Fixed header | | UI renderer | Draw waveform | requestAnimationFrame |
4.3 Data Structures (No Full Code)
struct frame_header {
uint32_t ts_ms;
uint16_t rate_hz;
uint16_t count;
};
4.4 Algorithm Overview
Key Algorithm: Stream Loop
- Read DMA buffer into frame.
- Attach header.
- Send over WebSocket if socket ready.
- Drop or throttle if backpressure.
Complexity Analysis:
- Time: O(n) per frame
- Space: O(buffer size)
5. Implementation Guide
5.1 Development Environment Setup
idf.py set-target esp32s3
idf.py build
5.2 Project Structure
p10_web_oscilloscope/
+-- main/
| +-- adc_stream.c
| +-- ws_server.c
| +-- web_ui.c
+-- assets/
+-- index.html
+-- app.js
5.3 The Core Question You’re Answering
“How do I visualize hardware data in real time from a microcontroller?”
5.4 Concepts You Must Understand First
- ADC sampling and DMA buffering
- WebSocket binary framing
- Backpressure and rendering limits
5.5 Questions to Guide Your Design
- What sample rate can your Wi-Fi link sustain?
- How many samples fit in one frame for 30 fps?
- How will you handle client disconnects?
5.6 Thinking Exercise
Compute the data rate of 10 kHz sampling at 12-bit resolution. Can Wi-Fi handle it?
5.7 The Interview Questions They’ll Ask
- Why use WebSockets instead of HTTP polling?
- What is the Nyquist rate for a 2 kHz signal?
- How do you avoid blocking the Wi-Fi stack while sampling?
5.8 Hints in Layers
Hint 1: Start with a low sample rate (2-5 kHz).
Hint 2: Use binary WebSocket frames.
Hint 3: Separate sampling task and streaming task.
Hint 4: Drop frames when the queue is full.
5.9 Books That Will Help
| Topic | Book | Chapter | |——|——|———| | ADC basics | The Art of Electronics | ADC chapter | | WebSockets | High Performance Browser Networking | WebSocket chapter |
5.10 Implementation Phases
Phase 1: ADC Stream (2 days)
Goals:
- Configure ADC continuous mode.
- Validate samples via serial logs.
Tasks:
- Setup ADC and DMA buffer.
- Log sample values.
Checkpoint: Samples are stable and within range.
Phase 2: Web UI (2-3 days)
Goals:
- Serve HTML/JS from device.
- Render a static waveform with test data.
Tasks:
- Build web page with canvas.
- Render a fake sine wave.
Checkpoint: UI loads and renders correctly.
Phase 3: Streaming (2-3 days)
Goals:
- Stream live samples to UI.
- Implement backpressure.
Tasks:
- Implement WebSocket binary frames.
- Add frame drop policy.
Checkpoint: Live waveform updates smoothly.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale | |———-|———|—————-|———–| | Sample rate | 2k vs 10k | 5k | Balance detail and bandwidth | | Frame size | 128 vs 512 | 256 | Smooth rendering | | Rendering | Every frame vs fixed FPS | Fixed FPS | Avoid overload |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples | |———-|———|———-| | Unit Tests | Frame packing | Header + samples | | Integration Tests | End-to-end streaming | Browser display | | Edge Case Tests | Disconnect and reconnect | Reconnect logic |
6.2 Critical Test Cases
- UI Load: Web page loads from device.
- Waveform: Live samples render at 30 fps.
- Backpressure: Frames drop without crash.
6.3 Test Data
Sample rate: 5000 Hz
Frame size: 256 samples
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution | |——–|———|———-| | Too high sample rate | Laggy UI | Lower rate or drop frames | | Blocking ADC task | Wi-Fi drops | Use separate tasks | | Wrong ADC range | Clipped waveform | Adjust attenuation |
7.2 Debugging Strategies
- Start with a potentiometer input to verify full range.
- Log average and min/max sample values.
7.3 Performance Traps
- Rendering too many points per frame can stall the browser.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add a pause button on the UI.
- Add a grid overlay and scale labels.
8.2 Intermediate Extensions
- Add trigger detection (rising edge).
- Add simple FFT visualization.
8.3 Advanced Extensions
- Multi-channel sampling if hardware supports.
- Save frames to SD card and download via HTTP.
9. Real-World Connections
9.1 Industry Applications
- Embedded instrumentation and sensor monitoring.
- Diagnostics dashboards for field devices.
9.2 Related Open Source Projects
- sigrok - signal analysis tools.
- ESP32 oscilloscope examples - community projects.
9.3 Interview Relevance
- Real-time streaming and buffering problems are common in embedded interviews.
10. Resources
10.1 Essential Reading
- ESP32 ADC documentation
- WebSocket protocol overview
10.2 Video Resources
- “ADC and Sampling Theory” (lecture)
- “WebSockets for Real-Time UI” (tutorial)
10.3 Tools & Documentation
- ESP-IDF HTTP server and WebSocket examples
- Browser dev tools for network inspection
10.4 Related Projects in This Series
- P08 Wi-Fi Sniffer - RF capture and streaming
- P02 Deep Sleep Champion - measurement discipline
11. Self-Assessment Checklist
11.1 Understanding
- I can explain Nyquist and sampling rate.
- I can design a binary WebSocket frame.
- I understand backpressure in streaming systems.
11.2 Implementation
- Web page loads from device.
- Waveform updates smoothly.
- Streaming runs for 10 minutes without dropouts.
11.3 Growth
- I can add a trigger or FFT view.
- I can explain latency sources in the pipeline.
12. Submission / Completion Criteria
Minimum Viable Completion:
- Web page loads and displays a live waveform.
Full Completion:
- Stable streaming with backpressure handling.
Excellence (Going Above & Beyond):
- Triggered capture or FFT visualization.
- Exported data to file or remote storage.