Running Bluetooth Low Energy (BLE) scanning and Wi-Fi networking at the same time on an ESP32 sounds straightforward—until it isn’t.
Many developers discover that once BLE scanning starts, their Wi-Fi web server becomes unreliable. Pages load intermittently, HTTP requests time out, or BLE scans suddenly stop when Wi-Fi traffic increases.
In this article, we’ll walk through why this happens and how using FreeRTOS (RTOS) on the ESP32 cleanly solves the concurrency problem.
The Core Problem
On ESP32, BLE and Wi-Fi share critical hardware resources:
- The same radio
- The same CPU
- The same event loop when using blocking APIs
A naïve implementation often looks like this:
This design appears to work at first, but quickly degrades under real usage.
Typical Symptoms
- BLE scanning pauses while HTTP requests are active
- The web server becomes unreachable during scans
- Random socket resets
- BLE callbacks fire late or not at all
These issues are not bugs in BLE or Wi-Fi—they are architectural problems.
Why Delays and Timers Don’t Fix It
A common attempt is to add delay() calls:
Unfortunately:
- delay() blocks the current task
- BLE callbacks still execute in the same context
- Wi-Fi servicing becomes opportunistic
This approach does not guarantee fairness between BLE and Wi-Fi.
The problem is not timing — it’s task isolation.
The ESP32 Advantage: Built-in FreeRTOS
Unlike simpler microcontrollers, the ESP32 runs FreeRTOS natively, offering:
- Preemptive multitasking
- Two CPU cores
- Task prioritization
- Core pinning
Instead of fighting the scheduler, we can design with it.
High-Level Design
The solution is to split responsibilities into independent RTOS tasks, each with a single purpose.
Each subsystem runs independently, coordinated only through minimal shared state.
Task Responsibilities
BLE Task (Core 0)
The BLE task is responsible for:
- Running short, non-blocking BLE scans
- Parsing advertisement payloads
- Updating shared state when new data arrives
Example:
Key characteristics:
- No networking code
- No JSON formatting
- Frequent yielding to the scheduler
HTTP Task (Core 1)
The HTTP task handles all networking:
This task remains responsive regardless of BLE activity.
Shared State: Keep It Simple
Shared data between tasks is intentionally minimal:
Why this works safely:
- Single-writer pattern
- No dynamic allocation
- No STL containers
- Short critical sections
- volatile ensures cross-core visibility
This approach avoids the need for mutexes while maintaining robustness.
Handling Data Expiration (TTL)
To prevent stale data from being served indefinitely, we added a time-to-live (TTL) mechanism:
This mirrors real-world scanning behavior:
- Data appears
- Data ages out
- Clients stop receiving old results
Why This Architecture Works
1. True Concurrency
BLE scanning and Wi-Fi networking never block each other.
2. Predictable Scheduling
FreeRTOS ensures both tasks get CPU time when needed.
3. Improved Stability
No more random disconnects or missed scans.
4. Easy Scalability
Additional tasks (MQTT, OTA, logging) can be added cleanly.
Results
After moving to an RTOS-based design:
- BLE scanning runs continuously
- The web server is always reachable
- No socket resets under load
- No missed BLE advertisements
- System behavior is deterministic and production-ready
Key Takeaways
- BLE + Wi-Fi issues on ESP32 are architectural, not library bugs
- Blocking code in loop() does not scale
- FreeRTOS is not optional for serious ESP32 applications
- Separate concerns into dedicated tasks
- Keep shared state small and explicit
Final Thoughts
If you’re building an ESP32 application that combines BLE scanning and Wi-Fi networking, embracing FreeRTOS is the turning point between a fragile prototype and a reliable system.
Once tasks are isolated and scheduled correctly, the ESP32 becomes an extremely capable concurrent platform.






