A real-time embedded-to-desktop signal visualization project built to demonstrate cross-platform data streaming and hardware-software integration. Developed using C++ (Arduino framework with FreeRTOS) on the ESP32 and Python on the desktop side, the system generates a live signal, transmits it via serial communication, and renders an animated real-time graph upon reception via PySide6.
┌─────────────────────┐ Serial / UART ┌──────────────────────────┐
│ ESP32 │ ──────────────────────────► │ Desktop (Python) │
│ │ 115200 baud │ │
│ FreeRTOS Tasks │ │ PySide6 + QtGraphs │
│ ├─ toggleLED1 │ PIN2:HIGH | Task:LED1 │ ├─ Serial reader thread │
│ ├─ toggleLED2 │ PIN2:LOW | Task:LED2 │ ├─ Ring buffer + lock │
│ └─ serialWriter │ │ └─ Live step-plot graph │
└─────────────────────┘ └──────────────────────────┘
Two FreeRTOS tasks toggle a shared GPIO pin at different intervals (500 ms and 300 ms). Every state change is queued and transmitted over UART. The Python desktop app reads the stream, parses each message, and plots a live step-function graph that updates every 50 ms.
- Real-time step-plot graph — live HIGH/LOW waveform rendered with QtGraphs
- Dual FreeRTOS tasks — two independent tasks competing for a shared GPIO pin via mutex
- Dedicated serial writer task — UART output is decoupled from the GPIO critical section via a FreeRTOS queue, minimising lock contention
- Finite mutex timeout — 100 ms watchdog instead of
portMAX_DELAY; timeouts are logged without hanging the task - Thread-safe Python bridge — ring buffer protected by
threading.Lock, with a dirty flag to skip idle UI redraws - Connection-aware status indicator — animated dot turns green when connected, red when disconnected
- Auto-reconnect — serial reader automatically re-opens the port on disconnect
├── RTOS_Blinking_LED # ESP32 firmware (C++ / Arduino + FreeRTOS)
├── main.py # Desktop application (Python / PySide6)
└── main.qml # UI layout and live graph (Qt Quick / QtGraphs)
| Item | Detail |
|---|---|
| Board | ESP32 (any variant) |
| Framework | Arduino with FreeRTOS |
| LED | Connected to GPIO 2 (built-in on most boards) |
| Baud rate | 115200 |
| Package | Version |
|---|---|
| Python | 3.9+ |
| PySide6 | 6.x |
| pyserial | 3.x |
1. Flash the ESP32
Open RTOS_Blinking_LED.ino in the Arduino IDE (or PlatformIO), select your board and port, then upload.
2. Start the desktop app
python main.pyThe graph will begin plotting as soon as the serial connection is established.
| Task | Priority | Interval | Role |
|---|---|---|---|
toggleLED1 |
2 | 500 ms | Drives GPIO HIGH → LOW |
toggleLED2 |
2 | 300 ms | Drives GPIO HIGH → LOW |
serialWriter |
1 (lowest) | event-driven | Drains log queue to UART |
GPIO writes are the only operations inside the mutex critical section. Serial output happens entirely outside the lock via a 16-slot FreeRTOS queue, eliminating slow UART I/O from the critical path.
Serial thread (daemon) Qt main thread
────────────────────── ──────────────
readline() [blocking] QTimer @ 50 ms
→ parse line → _flush()
→ timestamp → check dirty flag
→ acquire lock → snapshot buffers
append to deque → release lock
set dirty = True → format + emit signal
→ release lock → QML redraws graph
The dirty flag ensures _flush() is a no-op on idle ticks, avoiding redundant list copies and Qt signal emissions.
The ESP32 outputs lines in the following format over UART:
PIN2:HIGH | Task:LED1
PIN2:LOW | Task:LED2
The Python parser splits on |, reads the state (HIGH/LOW) from the first segment, and the task name from the second.
This project was reviewed, upgraded, and documented with the help of Claude (Sonnet 4.6), an AI assistant made by Anthropic.
AGPL-3.0 license