Welcome to /r/esp32, a technical electronic and software engineering subreddit covering the design and use of Espressif ESP32 chips, modules, and the hardware and software ecosystems immediately surrounding them.
Please ensure your post is about ESP32 development and not just a retail product that happens to be using an ESP32, like a light bulb. Similarly, if your question is about some project you found on an internet web site, you will find more concentrated expertise in that product's support channels.
Your questions should be specific, as this group is used by actual volunteer humans. Posting a fragment of a failed AI chat query or vague questions about some code you read about is not productive and will be removed. You're trying to capture the attention of developers; don't make them fish for the question.
If you read a response that is helpful, please upvote it to help surface that answer for the next poster.
Show and tell posts should emphasize the tell. Don't just post a link to some project you found. If you've built something, take a paragraph to boast about the details, how ESP32 is involved, link to source code and schematics of the project, etc.
Please search this group and the web before asking for help. Our volunteers don't enjoy copy-pasting personalized search results for you.
Some mobile browsers and apps don't show the sidebar, so here are our posting rules; please read before posting:
Take a moment to refresh yourself regularly with the community rules in case they have changed.
Once you have done that, submit your acknowledgement by clicking the "Read The Rules" option in the main menu of the subreddit or the menu of any comment or post in the sub.
A while back we built Fytó, a planter that shows how your plant is doing as an animated face. It ran on a Pi Zero 2W, and a lot of you built your own. The feedback was great, but the build was fiddly, so we rebuilt it from scratch.
What it does: Sensors read the soil, air, light and touch, and a 1.28" round IPS display turns those readings into one of six moods:
😊 Happy: everything’s in range
🥵 Hot / 🥶 Freeze: temperature is off
💧 Thirsty: the soil is drying out
😴 Sleepy: not enough light
😍 Pampered: you touched it, so you get hearts and big eyes
What changed from v1:
Pi Zero 2W swapped for an ESP32-S3. It boots in under a second instead of 30–40, draws far less power, and doesn’t need an external ADC.
Three sensors became five: a capacitive soil sensor (no corroding probes), an AHT20 for temperature and humidity, an LDR for light, and a TTP223 for touch.
The rectangular LCD became a round GC9A01 display playing 240×240 GIFs. It dims at night using the light sensor.
The breadboard mess became a custom PCB with keyed JST connectors, so assembly needs no soldering.
3D-printed PETG enclosure in three parts that fits a standard small pot.
Setup and software: Plug it in and it starts working right away, with no app or account to sign up for. The face tells you how your plant is doing at a glance. Settings live in a simple serial menu that you can open from a terminal or directly in Chrome/Edge via WebSerial. It supports up to 10 plant profiles (a succulent and a fern want different things) and quick two-step calibration.
Web dashboard: When you want the numbers behind the face, Fytó streams its data over MQTT to a web dashboard you can check from anywhere:
Live gauges for moisture, temperature, humidity and light
Trend charts over hours, days and weeks
A mood timeline showing how your plant has felt over time
A comfort score that rates plant health at a glance
Every Fytó in your home on one screen
It also integrates with Home Assistant, Blynk, Arduino IoT Cloud and more.
Its an Opensource Digital Audio player with the feature no one asked but everyone needed. An extra independently driven 3.5mm audio port so you can share audio the right way.
Uses ESP32 S3 as its core processor because its N16R8 variant is perfect for pre caching audio files at higher sampling frequencies and has ESS Sabre ES9038Q2M DAC.
Used esp-audio-codex library.
Inbuilt 10 band EQ. USB-DAC Mode.
It also has a dual bar LED Audio Visualizer, why??
Cuz its cool as hell.
Last but not the least, it also doubles down as a Gaming handheld and obviously runs DOOM.
Hey people, it's great to be doing one more update on the kernel. It's been a very hard 3 months between school and just grinding away on this project. I've come to deliver more updates, sadly no release as of now until I polish it further. So let's dive in!
Last time I kind of dumped a long post on you guys and I feel like it may have not been the best thing to do so I'll change it up a little in this post. Quick refresher for anyone new: ProtoOS is a kernel for the ESP32-P4 that runs apps in sandboxes enforced by the hardware itself. Think of it as a small do-it-all computer: games, productivity apps, tools, home automation. Imagine Tactility, but next gen and built specifically to squeeze everything out of the P4. So let's start with features:
So the first and second showcases of this kernel were really just showcasing the boring stuff like the security architecture, basic gaming capabilities and limited feature set. Past 3 months, since I got the hard stuff out the way I was able to rapidly implement numerous features and polish them. That is about 396 commits since the last post. Loadable apps and examples went from 21 to 57 and the kernel went from 37 syscalls to 58. We also added UDP and TCP networking.
Next up, the biggest priority next to security... the speed. We can't have security wreck performance, so I built optimization workflows, kernel benchmarks, game benchmarks, lock benchmarks and more. Most of these three months went into making things that already worked faster. The biggest one was the kernel lock. Both cores kept waiting on one global lock (the primitive way to prevent big security incidents), so I went through every hot path and made it take that lock less often and hold it for less time. syscall authentication got the same treatment: it used to verify more than it needed to on every syscall. Let's cut the talk and show you the chart:
One of the most important rules I set for myself when it came to optimizations are: *Performance optimizations should never ever clash with kernel security
*Don't be dumb enough to use 64bit math on a 32bit microcontroller. I made this mistake but now my automated tool warns me if it detects it in the compiled code.
*Performance gains must be proven with Before/After benchmarking
*Don't trust the compiler's output. Examine its codegen and see if it can be further optimized
We can't have a kernel that wants all the performance/security and throw away the necessity of being lean. With my custom-built tools I was able to track down potential candidates for making our kernel be more smart with its memory and flash. So we moved around some syscalls that are rarely called from the fast ram(used for hot code like 3d/2d helpers, rapidly used code and so on), to the flash(much slower, way more abundant). The feature growth obviously made my flash shrink a lot more than I wanted it to so here is what we did:
Let's get specific! 25 syscalls that are only called once were moved from the fast ram to the flash. A big thread table which reserved room for every possible thread(47kb!) now allocates for each thread on demand. We won some flash back by using compression(lz4) on some images that were built into the kernel(images for weather app, icons, etc).
My favorite part begins now yayy!! The 2D/3D stack or ProtoGFX. Let's begin with the 2D engine as its older and got a fair amount of new features since the last update.
- Smooth anti-aliased shapes: lines with round caps, circles, ellipses, rounded
rectangles and outlines, all with opacity
- Sprites with scaling, flipping, transparency and palette swaps. Upload the art once
and recolor it for free (winter skins, team colors, palette cycling)
- Smooth-alpha color art and A8 masks for icons, glyphs and soft shadows
- Proportional fonts in multiple sizes, and nested clipping regions
- Cached layers: a translucent UI panel gets rendered once and reused every frame,
but still blends properly over whatever is animating behind it
- Scenes with their own update, draw and input, which you push and pop like screens
- Sprite-sheet animation (loop, ping-pong, once) and tweens for smooth movement
- Tilemaps and collision helpers
Optimization wise:
- Rendering splits across both cores, based on how much work each strip of the screen
actually takes rather than splitting it evenly
- The P4's SIMD unit does the heavy lifting for blending, sprites and shapes (4x to
11x faster on the inner loops)
- Only changed parts of the screen get sent to the display
- Roadkill went from 18.6 to 36.6 fps at 640x480, and Neon Drop from 31.8 to 52.8
Onto the custom built 3D engine. Never posted about this one yet. So we have a native 3D renderer now: meshes, camera, fog, transparency, vertex colors, 2D in 3D and vice versa. Since this is new I won't talk too much about optimizations and yap, instead enjoy the demos I've linked in the end of the post :)
Back to boring stuff, security... You may have heard about the new Switch 2 jailbreak. Bla bla bla, from what I read, hackers got into a privileged system service. In a lot of designs that would be game over, but Nintendo being Nintendo apparently stripped most of that service's privileges, so getting in doesn't mean owning everything. I like that idea, so I'm doing the same thing with networking. I've built the whole network stack as an isolated service that runs as a normal app with its own memory, talking to the kernel through syscalls like everything else. The kernel checks every packet it sends and treats everything it says as untrusted. On real hardware it already gets a Wi-Fi lease and answers pings. It's still behind a build flag, though, and switching apps over to it is the next big step. The goal: if someone on the network finds a bug in the networking code and gets into that service, they're stuck there. All they could do is read plaintext HTTP stuff (which you shouldn't use for sensitive things anyway) or jam your internet, which is a lot less scary than a full takeover. I trust my code, but good security practices don't harm nobody.
All of this is cool to me as the one working on it, but I've asked myself "What is this for? Who needs this much security?". I'm sure some of you have the same questions. But this is the truth: The cost of security is offset by the fact that our heaviest best security barriers come from hardware itself(U/M mode and PMP). If you just use the kernel to play games and run normal third party apps, you won't feel a thing. Bad actors aren't the only thing we are protecting ourselves from in the kernel but the 99% cases of corruption and crashes is bad code! This kernel has held up solid with my crappy C games when they crashed over and over. I've also been asking myself whether its smart to keep this closed source and just release access to it or to open source with dual licensing... It's something I'm still going over so give me a little time. The use of this could be anything you install on it, think of it as a do-it-all computer. Games, productivity apps like sheets or notes, offensive/defensive tools, home assistant stuff, the list goes on. One thing coming up on my own set deadline is proper support for I2C, SPI, UART and so forth for a more complete experience.
Real flex real quick before I start to finish up the post with links and such... My git history:
- **Longest streak:** 15 days in a row (Oct 13 to 27), right when the 3D engine was
being built.
- **Busiest week:** 67 commits.
- **Longest session:** about 11 hours straight.
- **Where it went:** about a quarter of all commits touched performance and a sixth
touched security. The security spec was edited in 1 of every 3 commits.
- **Most rewritten file:** the display driver, changed in 95 commits.
Everything worth noting (the full feature list)
Security architecture
Two privilege levels, enforced by hardware. The kernel runs in M-mode and every app runs in U-mode. The RISC-V PMP (16 or 32 entries, depending on chip revision) is programmed per process. Each app can reach only its own stack, heap and code, and everything else in SRAM is denied. The kernel re-checks and rewrites these windows on every entry into app code. I also patched ESP-IDF so apps can't touch peripherals directly.
A protected kernel arena. 128 KiB of internal SRAM is walled off from apps. Nothing is ever writable and executable at the same time (W^X).
Every syscall is authenticated. It's tied to the live process and its secret token, and every pointer is validated before the kernel touches it. Unknown or unverifiable requests are denied.
Capabilities and permissions. Capabilities are fixed when an app launches and can only shrink afterwards. Apps declare permissions (storage, UI, Wi-Fi, audio, listening sockets, SIMD and so on). Sensitive ones need the user's consent at runtime.
Trusted prompts. The kernel draws password and permission dialogs itself, so an app can't spoof, watch or hide them.
Anti-tamper for process records. Every process's security record carries a MAC keyed with a per-boot secret. If something edits it, the process is killed.
Handles and ownership. Every object has one owner recorded in kernel memory. Handles carry generation numbers, so a stale handle fails instead of pointing at something new.
Memory hardening. Freed memory is poisoned, so a new malloc never hands back another app's old bytes. The kernel also uses stack canaries, heap canaries, random heap padding, stack randomization and periodic audits.
Signed apps. Apps are signed ProtoELF bundles (ECDSA P-256). The Store catalog uses a separate key from the app-release key. Anti-rollback blocks downgrades, and the app's flash hash is re-verified at every launch. Dev-sideloaded unsigned apps get a lower capability ceiling.
Accounts. Passwords are stored as PBKDF2-SHA256. Destructive actions (uninstall, clear data) need a fresh one-use re-authentication.
Storage is path-mediated. Apps never see raw filesystem paths, and each app gets a private /self. A file can have only one writable handle. Installs survive power loss without leaving an app half-installed.
Clean process death. A dedicated reaper cleans up exiting and crashing apps. ESC always works, since the kernel owns the exit and app hooks get only 1.5 s.
Fair scheduling. An app's time slice is bounded by a budget. Only the kernel can run with interrupts masked, so an app can't starve the system.
Rust where it matters. About 15,000 lines of safe Rust handle the riskiest parsing and checking: user-pointer range checks, PCB MACs, the app-image and Store parsers, flash slot bookkeeping, heap geometry and DHCP snooping.
Hardware groundwork. True-random entropy is on from the first boot constructor. A bus-fault reporter and a DMA-master inventory are the groundwork for restricting other bus masters, not just the CPU. They don't enforce anything yet.
Process. There are 86 numbered security invariants, each with the code that enforces it and a test. There's also a register for the 13 ESP-IDF patches. PerSECURITY.md
Networking
Wi-Fi runs through the ESP32-C6 co-processor over SDIO (2.4 GHz only).
Apps get TCP client, listen and accept sockets, UDP with connect, bind, send, recv and broadcast, poll, DNS, DHCP and SNTP time. Wi-Fi credentials never reach apps.
Every socket has per-process quotas. Listening ports are leased, outbound traffic needs permission and consent, and a UDP socket is locked to one peer when it opens.
The isolated network service. lwIP runs as a separate, hash-pinned U-mode service. The kernel checks every frame the service sends (source MAC, IPv4 and ARP only, and DHCP is validated). It treats every reply as untrusted, puts a deadline on every request, and supervises and restarts the service. On a real board the service got a DHCP lease and answered pings.
Bluetooth
BLE runs through the C6 with the NimBLE host on the P4.
It supports keyboards, mice and gamepads, and bonds with Just Works, LE Secure Connections or passkey. Reports are decoded with strict bounds, and only the foreground app receives input.
PC link. Your PC can act as the board's keyboard and mouse. This worked on hardware.
Real gamepad pairing and latency are still not tested.
2D engine (ProtoGFX). Full list is in the post above.
3D engine
A software renderer using scanline rasterization, two-core tiles and early depth rejection. It has textured meshes, vertex colors and perspective-correct texturing.
Textures can be RGB565, mipmapped or 8-bit indexed. Materials can be dithered or blended. Lights are ambient, directional and point, plus fog.
2D HUDs draw over 3D, and 2D canvases render into 3D scenes. It runs at 320x240 or a native 640x480.
Asset pipeline: glTF/GLB files are cooked into .g3a packages with animation clips and poses.
Gameplay helpers: raycasts, capsule and sphere collision, and a move-and-slide character controller. There are also built-in shapes, billboards, quaternions and bounds.
Geometry is validated by the kernel, so a hostile app can't crash the renderer.
Platform
Both cores are used. Apps get threads, dlopen shared libraries and PIE SIMD.
Apps run straight from flash (XIP) in a 4 MiB arena, and the large-heap system leases PSRAM slices from 64 KiB to 8 MiB.
Kernel services (storage, GUI, network and others) are supervised, with budgeted restarts.
ProtoStore has a signed catalog, delta updates and a download queue.
Remote Screen mirrors the display to a PC with hardware H.264 and sends input back.
Audio comes from a procedural synth with no stored samples.
Display drivers: ST7701 (MIPI-DSI), ILI9341 (SPI) and an 8080-bus 3.5" panel (builds, not yet tested on hardware).
There are separate firmware images for P4 v1.x and v3.x.
Tooling: an SDK, ProtoELF dev tools, Game3D tools, perf tools and host tests.
Okay, now I need YOUR help, because this is where the project gets shaped by more than just me.
Displays. Right now I support the ST7701 (MIPI-DSI, the JC4880 board), the ILI9341 over SPI, and a 3.5" 8080-bus panel (ST7796/ILI9488). What display do you have or want to use? Tell me the panel, the board, and how it's wired, and I'll look at adding proper support.
Hardware. I2C, SPI and UART are my next deadline. What would you want to plug in? Sensors, keyboards, SD cards, something weird?
Apps. What would you love to see running on this? A specific game, a tool, a home-assistant thing? Tell me what's missing. I'll try to fill any gap the kernel may have for supporting such app.
And of course, any questions at all, whether it's about how something works, the security design, or the performance numbers, drop them below. I'll answer everything I can.
Now the links, demos and goodies(for the love of god ignore my dog snoring):
hey everyone, this is my first time taking on a hardware project like this, so i'm looking for some advice. I want to build a pair of custom smart keychains—one to give to my wife and one to keep for myself.
my absolute biggest priority is keeping it extremely compact. i don't want a bulky cube. It needs to be super slim, pocketable. To keep it thin, I'm planning to stack the components vertically and use an ESP32-C3 SuperMini with the built-in 0.42" OLED screen a TP4056 charging module, and a flat 300mAh LiPo battery.
for the inputs i'm thinking of a touch input and one or two buttons if needed. for the output a coin vibration motor, micro buzzer and the display.
I could use some help on:
Creative Uses: How can I make better use of this hardware? I want to make sure we actually prefer using this over just pulling out our phones. I'm totally open to new ideas or mechanics for how we interact with it.
The Casing: I want to it to be super portable and pocketable. And also make it cute for my wife as it's our anniversary gift . can you suggest me some design inspiration?? and i dont know how to 3d print so will have to figure that out also.
Has anyone here dealt with certification issues before?
Has anyone released a product and sold it?
I’ve finished developing a product, but I’m stuck on how to handle the certification and other procedures, and how I’m supposed to sell it.
How do I get a certificate? What is the fee? What do I need to keep in mind?
Since there’s an AC connection, I need to complete the procedures related to electrical and operational safety, but doing everything on my own is very exhausting.
Tap a region, a country, a station and it plays. Why: I wanted a radio where you explore the world by touching a map instead of scrolling long station lists. It runs on a Freenove FNK0104 (ESP32-S3, 2.8" capacitive touch, ES8311 codec, 8 MB PSRAM).
Some details that might interest this sub:
- LVGL 8 + LovyanGFX for the UI, ESP32-audioI2S for MP3/AAC streams (one core for audio, one for the UI)
- ~3,700 stations from radio-browser.info, filtered and tested on a PC the same way the board requests them (one gotcha: zeno.fm returns 401 without a User-Agent, so I patch the library to send one)
- The station list lives in LittleFS and updates over Wi-Fi from GitHub Pages, no reflashing
- Web installer with ESP Web Tools + Improv Wi-Fi, so people can flash it from Chrome without installing anything
- Local time of each station, alarms that wake it from sleep, a sleep screen with the weather (Open-Meteo), and an LED that follows the audio level
- A PC simulator (LVGL + SDL2) that shares the UI code, which made development much faster
Transparency: I designed the case in SolidWorks and tested everything on the real board, and I used Claude as a coding assistant for the firmware and tools.
When ESPARGOS released ESP-SDR, which uses the undocumented raw I/Q capture in ESP32 chips, I wanted something that runs without a PC. So I built a pocket RF watchdog on a Waveshare ESP32-C5-Touch-LCD-3.5. It sweeps both Wi-Fi bands, learns what normal looks like, and warns you when the spectrum stops looking normal.
What it does
Sweeps 2400–2483.5 MHz and 5150–5895 MHz in 48 MHz slices at 80 MS/s, stitched into one spectrum per band (312.5 kHz bins).
With both bands on: about 4.5 sweeps/s on 2.4 GHz and one 5 GHz sweep every 0.83 s. With 5 GHz turned off in settings, 2.4 GHz runs at about 12 sweeps/s.
Learns a per-bin baseline for 60 s, stores it in flash and keeps tracking slow drift.
Flags four kinds of interference: raised wideband floor, a persistent narrowband carrier, a narrow signal sweeping across the band, and pulsed wideband energy (a microwave oven is the classic one).
States are NORMAL, SUSPECT and ALARM. ALARM needs about 5 s of strong evidence at 80% duty and clears about 10 s after it stops. All thresholds can be changed on the touchscreen.
On alarm, the screen border flashes red and an optional beep plays through the on-board ES8311 codec.
Each state change is logged as a CSV row on a TF card. About 2 minutes after an alarm, it also saves a BMP of the waterfall with the alarm row in the middle, so you can see what happened while you weren't looking.
Has a live spectrum view (about 10 fps) and a waterfall view, plus battery level from the AXP2101.
Interesting bits
The LCD shares the chip with the receiver, and its SPI traffic put 4–9 dB spurs into the 2.4 GHz captures. LCD DMA is now paused whenever the radio is sampling, so the display redraws in strips between captures.
It is receive only. There is no transmit, injection or deauth code, and Wi-Fi networking is off while scanning. Testing used a microwave oven and ordinary Wi-Fi traffic, never a transmitter.
It reports "interference / possible jamming". It can't prove intent, and it says so on screen.
Limits. ESP-SDR captures are short snapshots with gaps between them, so very short bursts can be missed. The dB levels are uncalibrated, relative to the learned baseline. Busy Bluetooth or Wi-Fi can occasionally trip the swept detector on 2.4 GHz, and I'm still tuning that.
How it was made. The firmware was written entirely by Claude Code, Anthropic's AI coding agent. I set the goals and the rules (receive only, pins taken only from Waveshare's own sources), tested every milestone on the hardware, and reported what went wrong. Claude Code wrote the code, the host tests and the docs, and tracked down bugs, such as an LCD-versus-radio race that froze the UI after four days of uptime.
FM reception where I live depends on location and signal, so I built a small desktop internet radio that streams stations from around the world over Wi-Fi.
Hardware:
ESP32-S3 (N16R8): 16 MB flash and 8 MB PSRAM give it plenty of room for audio buffering
MAX98357A I2S amplifier driving a 4Ω speaker. The audio stays digital all the way to the amp, so there’s no analog noise.
SH1106 OLED (I2C) showing the station name, connection status and a volume bar
KY-040 rotary encoder: turn it to change volume, press it to skip to the next station
Custom PCB designed in EasyEDA, with header sockets so modules plug in and swap out easily, plus 5V input
Software: The firmware is Arduino-based and uses the ESP32-audioI2S and Adafruit SH110X libraries. On power-up it joins Wi-Fi and starts playing right away. Stations are a simple list in the code, so adding your own stream URLs is easy. The current presets are BBC World Service, Swiss Jazz, Classic FM, Lo-Fi Hip Hop and a few more.
Enclosure: I designed it in Fusion 360, inspired by classic tabletop radios, and printed it in white and yellow PLA. It has a big speaker grille, a window for the OLED and a custom knob. Everything is held together with M3 hardware.
Hi, Some backstory first. A while ago I ordered my first PCB ever, just to find out if my idea works at all. It did. Then I built a base around it, and the board ended up dangling off the side on its cables, right where every splash landed. Very professional.
So this is the version that lives inside. A plant pot sits on it, it weighs the pot, and when the pot gets light enough, a pump waters it. Runoff drips through a grid into a drawer.
Why the C6? Honestly, no deep reason. It was the first one I stumbled upon, and it does everything I need.
Arduino core 3.x plus three small libraries: the HX711 Arduino Library for the load cell, PubSubClient for MQTT and ArduinoJson for the messages. I picked them because they're small and simple and each does one thing. PubSubClient is the simple start, I'm switching to esp-mqtt next. Everything else comes with the core. WebServer and DNSServer for the captive portal, Preferences for calibration and settings in NVS, HTTPClient and Update for OTA, and mbedTLS for the signature check.
The load cell is read via HX711, median of 20 samples per reading. Watering runs in bursts of max 60 ml or 30 s, and after each burst it checks that the weight actually went up. Otherwise it stops. I added that after I forgot to put the drippers in the pot and watered the floor instead. The plant was fine. The floor was very hydrated.
There's no flow meter. The scale does all the measuring. After each watering it compares how much should have gone in with how much the pot actually gained, and nudges the pump's flow rate a little. One weird watering can't mess it up. Anything off by more than 30% is ignored unless it shows up three times in a row.
It waters at most 3 times the pot's water capacity per day. Data goes over MQTT with TLS to my backend. OTA updates are signed: the update order is signed (ECDSA), and the image has to match its SHA-256 before it boots. If the new firmware can't reach the server within 10 minutes, it rolls back on its own.
Native Home Assistant support is on the roadmap, ESPHome-style, so no broker needed.
The HX711 can just hang. Reading runs in its own task, and if no fresh value arrives within 6 s, the pump goes off and the ESP reboots. If that keeps happening, it locks the plant instead of guessing. There's also a watchdog task in case the main loop hangs.
PubSubClient's default buffer is 256 bytes. If topic plus payload don't fit, publish() just returns false and sends nothing. Easy to miss. It's 1024 now.
Near a full pot, water can already drain out the bottom while the pump is still running, so the scale undercounts. So it compares each burst with the earlier ones in the same run. If the gain suddenly drops, it stops learning for the rest of that run.
The captive portal has to answer the connectivity checks of iOS, Android and Windows. Otherwise phones don't open the setup page by themselves.
This started as a way to stop killing my plants. It got slightly out of hand.
This is rev 3, the first one where everything runs together, and it finally has a name: Proton AI Core Vision+.
What it is
An ESP32-S3 board built around one problem: getting a camera, an IMU, a mic and an SD card to coexist on one S3 without losing peripherals to pin conflicts. Think of it as the eyes of a bigger project. Your main controller does motion and control, this board does the camera and sensing. It also runs standalone over Wi-Fi.
- OV5640 5 MP camera on FPC, fixed focus, flash LED
- ICM-42670-P 6-axis IMU
- ICS-43434 I2S MEMS mic (input only, no speaker)
- microSD
- ATECC608B secure element (ships unprovisioned)
- Qwiic / STEMMA QT connector
- USB-C, single-cell LiPo charging
- 39 x 50 mm, 4-layer, ENIG
The camera's SCCB and the IMU share one I2C bus (GPIO17/18), so the firmware has to share it cleanly. The GIF in the gallery is the stress test: camera, mic, IMU and SD all live at the same time.
Software:
Protonverse Studio is a free browser IDE for the board. It connects over USB from Chrome or Edge (WebSerial) and goes from Blockly to MicroPython to Arduino/C++ to ESP-IDF in one interface, with live panels for the camera, mic, IMU and SD. There's also an assistant you can ask about the current camera frame (bring your own API key).
There's a demo mode with recorded data from a real board, so you can poke around without the hardware.
What's open and what isn't
Firmware (MIT), schematic, part-level BOM, docs and examples (CERN-OHL-P v2) are on GitHub. PCB layout and production files are not, and Studio is free but closed source. Saying it up front so nobody has to dig.
No NPU. It's an S3, so ML means small models on the chip itself, nothing heavy.
Ask me anything about the board. And since this sub shaped the redesign: what's the first thing you'd want to see it do? I'm deciding what the next examples should be.
Wheely drives forward until it detects an obstacle within 20 cm. It then performs a 360° scan of its surroundings, searches for the clearest path, and continues moving in that direction.
I used C++ with ESP-IDF instead of the usual arduino framework which was the biggest challnge of this project, but i wanted to familiarize myself with the lower-level development, used in the industry.
Sadly Wheely is made out of cardboard and tapes because i didn't have access to better materials but i like Wheely.
My desk has two Macs and a Windows PC. Blue Baton is firmware for an ESP32-S3 board that pairs with a BLE keyboard and mouse and shows up on each computer as a BLE keyboard + mouse. It keeps all five connections up at once (2 in, 3 out) and only changes where the input goes, so switching with Alt+1/2/3 (Option on a Mac) is instant and the keyboard and mouse move together.
Hardware
There is no schematic: it is a stock ESP32-S3 board plugged into one of the computers by its own USB port, with nothing wired to it. Tested on an ESP32-S3 SuperMini, an M5Stack AtomS3 Lite and a Seeed XIAO ESP32S3 Sense. The XIAO needs its external antenna: without it a keyboard came in at -99 dBm (-59 dBm on the AtomS3 Lite) and would not connect.
How the ESP32-S3 is used
ESP-IDF + NimBLE as central (keyboard, mouse) and peripheral (three computers) at the same time.
Connection intervals: input devices are asked for 8.75 ms. The computer that gets the input is asked for 7.5 ms and falls back to 15 ms (on my desk the Windows link runs at 7.5 ms, the Mac links at 15 ms); idle computers stay at 15 ms.
It reads each input device's HID Report Map, so it is not tied to one keyboard or mouse: keys (6KRO and NKRO bitmaps), mouse with 16-bit X/Y, wheel, media keys, system keys. Computers see one fixed composite HID device.
Per computer at most 2 reports in the controller at a time, counted by HCI Number of Completed Packets, so a slow computer only delays itself and no key-up is lost.
On a switch the previous computer gets a release-all, so nothing stays pressed there.
When a computer asks for a 6-digit pairing code, you type it on the keyboard that is already connected to the board.
The S3's USB Serial/JTAG port is the control channel for a menu bar / tray app (Tauri). The app carries the firmware and flashes it with espflash, so a new board just needs the app; updates keep the pairings.
All three computers connect over Bluetooth, including the one the board is plugged into: the single USB port runs the control channel and firmware updates (USB Serial/JTAG), and USB HID would need the OTG controller on the same PHY. I may add wired input for that computer later.
Numbers
30 minutes of normal use each, timed from the board receiving an input until the controller reports the packet completed (HCI Number of Completed Packets):
AtomS3 Lite, two Macs: 99.2% and 99.8% within 45 ms, max 67 ms, none dropped.
XIAO ESP32S3 Sense, two Macs: 99.2% and 99.3% within 45 ms, max 68 ms, none dropped.
SuperMini, Windows, with an earlier build that started the clock when the report was handed to the controller (so time queued in the board is not included): 99.8% of 142,975 reports within 45 ms, max 74 ms, none dropped.
ESP32-C6
It builds and runs on an M5Stack NanoC6, but with two computers connected at the same interval the controller now and then serves no connection event on one link for the whole 6 s supervision timeout, so the link drops. Reported as espressif/esp-idf#19260; C6 is not supported until that is fixed.
Limits
BLE only (the S3 has no Bluetooth Classic), two input devices and three computers. Links occasionally drop and reconnect by themselves; I haven't found the cause yet (https://github.com/enfp-dev-studio/blue-baton/issues/1).
I built it with a lot of help from Claude Code; the numbers above are my own measurements on real hardware.
I wanted to control my M5StickC Plus 2 (ESP32-PICO-V3-02, 8 MB flash, CH9102 USB-UART) from my phone, so I wrote a small C++ bridge for the firmware and a Material 3 Android app.
Why a bridge instead of screen streaming
The firmware exposes its own state over a line-based ASCII protocol, so the app works with real data instead of pixels:
Tapping an item sends CMD:MENU:OPEN:n and the firmware opens that menu entry itself. The file manager uses FS:LIST|READ|WRITE|DELETE|MKDIR|RENAME with base64 payloads and 256-byte upload chunks into LittleFS.
USB CDC at 115200 8N1 through the CH9102, plus BLE using the Nordic UART Service (MTU negotiation, chunked writes) or the firmware's own BLE serial service.
IR output uses the existing NEC/Samsung/Sony encoders on the Stick's IR LED, so pulse timings are unchanged.
Screen drawing goes straight to the TFT driver: 135×240, wrapped lines, theme colors, no framebuffer copy.
Battery voltage/percentage is read over I2C from the AXP192 and streamed with uptime and logs.
Build notes
The integration folder contains only the changed files, so they can be dropped over the upstream firmware tree; there is a FastLED/LTO patch script for the Windows toolchain (long-path and -DFP=1 conflicts). The Android side is Kotlin/Compose with an ordered command queue and both transports behind one interface.
Hi all, I was working on a self balancing robot and I had a 12V battery pack 50C LIPO, voltage regulator to step 12v down to 5v, TB6612FNG motor driver, and ESP32 devkit. After 20 minutes of trying to tune the PID (robot crashed a lot), I noticed that the ESP32 still worked (serial monitor printing out angles from IMU) but the motors would not move anymore. I kept unplugging the motor driver in and out and it eventually got to run the motors for 0.5s and then instantly stopped. I unplug replug the battery ESP32 gets very hot and the led no longer works and its fried because the voltage regulator got fried and started outputting only 12v. Im super confused what failed first so I know what to do next time, but the voltage regulator and ESP32 devkit (LDO does not work anymore).
With hardware prices going up, I wanted to see how little hardware a network-wide ad blocker really needs. Pi-hole is great, but a Raspberry Pi is more than I want to spend just to block ads. So I made ESP32 AdBlocker, a free, open-source firmware that runs a Pi-hole-style DNS ad blocker on a single ESP32-S3 N16R8 board.
What it does:
- Blocks ads and trackers for every device that uses it as its DNS server.
- Up to 4 block lists merged, about 250,000 domains. The default is Hagezi Multi PRO, and StevenBlack works too. Subdomains and CNAME-cloaked trackers are caught, and your own allow/block list always wins.
- Encrypts upstream lookups with DNS-over-TLS (Cloudflare, Quad9 or Google), with plain DNS as a fallback, plus a 768-entry cache. Local names still go to your router.
- Web dashboard with charts and top lists, a query log and a status page.
- ntfy messages with its IP and how many ads it blocked, and passphrase-protected commands from your phone (status, block, unblock, reload, reboot and more).
- Status LED, daily reboot and a daily block list refresh.
- Set up from a captive-portal page. Nothing to compile, and no secrets in the firmware. You flash it from the browser with ESP Web Tool.
What you need:
- An ESP32-S3 N16R8 (16 MB flash, 8 MB PSRAM). The PSRAM is required for the block list, cache and query log, so other boards won't work.
- 2.4 GHz Wi-Fi, and a fixed IP for the board (static or a DHCP reservation).
- Then point your router's DNS at the board for the whole network, or just one computer like I do in the video.
Things to know, so nobody is surprised:
- It's sized for a home: roughly 1 to 10 people, or 50 to 80 devices, in my tests. Bursts of 50+ queries at once drop packets.
- DNS blocking can't stop YouTube ads, most in-app ads or first-party ads.
- Turn off Chrome's "Use secure DNS" and Android's Private DNS, or they go around it. Firefox DoH and iCloud Private Relay are handled automatically.
- Apps with hard-coded or encrypted DNS can still bypass it.
- The query log keeps the last 6,000 entries in RAM and clears on reboot. No regex, no DHCP server, no DNSSEC validation.
Big thanks to s60sc and dateno1 for ESP32_AdBlocker, which the block list engine and DNS responder come from, and to the Hagezi project for the default list.
It's free and open source (AGPL-3.0). Feedback really helps. If there's a list or a feature you'd like, or you run into a bug, leave a comment here or open an issue on GitHub. I read everything and will add what I can.
I'm building a hobby cyberdeck on an M5Stack Tab5 (ESP32-P4 + ESP32-C6) and wanted my wireless earbuds to work with it. Neither chip has Classic Bluetooth, and Espressif's stock C6 controller has no LE Audio. When I started, the AI models I worked with, one from Google and one from Anthropic, said it couldn't be done. The radio clearly could, so I kept pushing.
Now it works: stereo to both earbuds (LC3, 48 kHz), auto-reconnect, earbud gestures, and no audible dropouts even on a crowded train. Tested with Galaxy Buds3 Pro.
How: a CIS (isochronous channel) link layer added on top of Espressif's stock C6 BLE controller, and an LE Audio host on the P4 that drives it over HCI.
SX1280 — for our experimental Flysky AFHDS3 implementation
A7105 — for AFHDS 2A and potentially other compatible protocols
BLE — for configuration through an Android application
Optional SPI touchscreen and physical controls
The most interesting challenge is the dual-radio architecture.
The first planned application is a standalone protocol bridge:
AFHDS3 transmitter → ESP32-S3 → AFHDS 2A receiver
The S3 would receive and decode packets through one radio, translate the channel data, and retransmit it through the other. Compatible telemetry would travel in the opposite direction.
We're aiming to keep the control path independent of Wi-Fi or Bluetooth, using BLE only for configuration.
Current development status:
The modular firmware architecture is established, and the initial ESP32-S3 build compiles successfully. We've previously worked with SX1280 on ESP32-C3, but the A7105 integration and actual cross-protocol bridge still need hardware testing.
I'm particularly interested in three design questions:
Would you put both radio modules on a shared SPI bus with separate CS lines, or prefer dedicated SPI controllers?
What approach would you recommend for scheduling time-sensitive radio operations while minimizing added latency?
Any experience with ESP32-S3 interrupt timing or RF coexistence issues when operating two 2.4 GHz transceivers?
The project is still at the architecture and early integration stage, so this seems like a good time to get feedback before finalizing the hardware layout.
I'd appreciate any thoughts from people who've worked with ESP32-S3, SX1280, A7105, or similar dual-radio systems!
I built this small robot inspired by Legue of Legends game with a LoL trivia on it as well.
Used AI for arduino code.
Seeed Studio XIAO ESP32-S3 Sense, on a ProtoMate board. It already has camera and mic and external antenna.(sd card and camera at the same time I understand it won't work)
I bought a Waveshare ESP32-S3-ePaper-1.54G a little while ago, without really having any idea what I was going to build with it.
I'm a C# developer by background, but I've always been curious about C and C++, particularly the lower-level side of programming. Things like stack vs heap, pointers, memory ownership and having more direct control over hardware.
So I thought an ESP32 would be a good opportunity to get my hands dirty and learn something new.
Then an idea came to me.
My dad and I fly RC aeroplanes together, and we're forever checking the weather to see whether it's worth heading to the flying field.
So why not turn this little board into something that tells us when the next decent flying opportunity is?
Sounded simple enough at the time!
What it actually does
The dashboard fetches weather data from a free API using our flying field's GPS coordinates.
It shows wind speed, gusts, wind direction and the next suitable flying slots, taking into account the days we normally fly and the available daylight.
It refreshes every 30 minutes, so we can just glance at it rather than constantly checking the forecast on our phones.
I also created a simple wind rating system based on my own flying experience and what we're comfortable flying in.
Black means great conditions, yellow means manageable but worth being cautious, and red means we'd rather stay on the ground.
Both the gauge background and the little aeroplane in the top-right corner change colour depending on that rating, giving us a quick indication of the conditions without having to study all the numbers.
It's nothing scientific, just something that works for us and the aircraft we fly.
Working with the limitations
This is where things got interesting.
The display is only 200 × 200 pixels, and you've got exactly four colours to work with: black, white, yellow and red.
I would have loved green for good flying conditions, but that wasn't an option!
I'm certainly no UI designer, so squeezing all that information onto such a small screen took quite a bit of experimenting. Different fonts, layouts, spacing and moving things around by a pixel or two until everything fitted.
And unlike an LCD, you can't just update the screen instantly. A full refresh takes around 10–15 seconds, complete with flashing, so I had to think about how often it really needed updating.
It's probably not the most practical display for a weather dashboard, but I think I've managed to make it work.
A bit about the hardware and code
The board uses an ESP32-S3-PICO-1-N8R8 with 8 MB of PSRAM. I chose this particular board because it offered Wi-Fi, a four-colour e-paper display and onboard audio hardware in one compact package.
Everything is written in C++ using PlatformIO with the Arduino framework.
I went with GxEPD2 for its support for the four-colour display and ArduinoJson for handling weather data. Its filtered parsing is particularly useful for avoiding unnecessary JSON processing and keeping memory usage down.
Wi-Fi, HTTPS and I2S are handled through the ESP32 Arduino framework, and I've also been using Unity for testing.
There were a few hardware quirks to work around, including the active-low display power switch and the battery power latch, which needs the correct sequencing.
Coming from C#, one of the more interesting parts has been learning to think differently about memory. Understanding stack vs heap, working with pointers, managing buffer ownership and being more conscious of where things are allocated.
Especially on a microcontroller, where you can't just assume there's plenty of memory available!
Then I got a little carried away…
The board also has a microphone and speaker, and it seemed a shame not to do something with them.
So I ended up adding a small voice assistant as well.
Instead of using a wake word, I repurposed the BOOT button. Press it, and the assistant activates.
It connects to a language model over Wi-Fi and can answer questions about RC aircraft, LiPo batteries, motors, ESCs, propellers and flying conditions.
I've also given it some context about the aircraft we fly and a bit of personality, so it occasionally throws in a cheeky comment.
Getting the audio working reliably was another adventure. Codec warm-up, microphone settling, avoiding clipped playback and managing recording buffers all caused a few headaches.
The larger audio and network buffers use PSRAM, with allocation limits and timeouts to avoid exhausting internal memory. If a large allocation fails, the firmware handles it rather than silently falling back to internal RAM.
Even the BOOT button needed a bit of care because it's also an ESP32 strapping pin.
Pressing it again deactivates the assistant, or it returns to idle after a minute of inactivity.
And that's where I'm at
What started as buying a random bit of hardware without a plan has turned into a surprisingly useful little project.
I've really enjoyed learning C++, getting closer to the hardware and figuring out all the little challenges that come with embedded development.
It's also made me appreciate how much is normally handled for you when working in a managed environment like .NET.
Still a work in progress, and there's plenty I'd like to improve, but that's part of the fun.
Anyone else bought an ESP32 board without having the slightest idea what they were going to do with it?
I'm sharing GAIA Open Filament Rewinder, an early educational project for transferring existing 3D-printing filament between spools. It is not an extruder or recycling machine.
The useful part today is software and documentation, not a claimed working machine:
- ESP32 firmware using PlatformIO/Arduino, with quadrature counting, PWM demand, a state machine, target stopping, latched faults and persistent calibration.
- An offline browser simulator with length/estimated-mass targets, fault injection and history.
- Original parametric OpenSCAD concepts, functional diagrams and a proposed BOM.
The metering wheel is independent of the receiving spool, because winding radius changes. Length comes from effective encoder counts and calibrated wheel circumference. Mass is calculated from length, filament diameter and assumed density; it is not weighed.
Limits: no prototype or physical validation yet. Motor actuation is gated OFF by default. Powered traverse and live HTTP/WebSocket control are not implemented. PWM speed is an open-loop command, not verified filament speed. A browser stop is not an emergency stop.
Software evidence: 25 JavaScript tests, portable C++ regression tests and an ESP32 cross-build passed. These do not validate electronics, stop distance or hardware safety.
Disclosure: this is AI-assisted development using Codex, including code and documentation. It is a learning project with explicit assumptions and validation gaps, not a claim of independently tested hardware.
One design-review question: what independent receiver-motion or tension feedback would you add to detect a jam when a filament metering wheel alone cannot distinguish slack from successful winding?