Mailbag #48 - rapid deployment
Thanks to an unsightly backlog, this mailbag (and possibly the next few) will be more packages at a rapid pace to get through them all - let me know what you think about this new 2026 format in the comments!
Each week I will feature a circuit based on one or more of the electronic parts I have ordered then forgotten about over the years.
Thanks to an unsightly backlog, this mailbag (and possibly the next few) will be more packages at a rapid pace to get through them all - let me know what you think about this new 2026 format in the comments!
Hi all and welcome to 2026 - I've been conspicuously absent due to some MAJOR projects plus some holiday time
But it's back to business now and in this video we take a look at what's been happening here at OneCircuit and what is planned for this year.
Let's take a look.
This week I make a lovely candle from a cheap solar light - then give it away!
Here is the code for the candle as promised:
/* Candle with Three PWM Pseudo-random flickering to simulate a candle. Output is via 3xPWM channels, variables can be changed to alter the simulation Sun 30 Nov 2025 19:53:23 AEDT */ #include <stdint.h> #include <stdlib.h> #include "../device.h" #include "../easy-pdk/calibrate.h" #include "../auto_sysclock.h" #include "../delay.h" #include <stdbool.h> #define LED4_BIT 4 #define LED0_BIT 0 #define LED3_BIT 3 bool debug = false; uint16_t myrand = 2901; // happy birthday uint8_t slowcounter = 0; uint8_t medcounter = 0; uint8_t fastcounter = 0; uint8_t slowstart = 0; uint8_t slowend = 0; uint8_t medstart = 0; uint8_t medend = 0; uint8_t faststart = 0; uint8_t fastend = 0; uint8_t faster = 0; // cutoff percentages for types of flicker uint8_t percentnormal = 82; // cutoff for normal/calm uint8_t percentsputter = 20; // cutoff for sputtering/normal uint8_t flickdelay = 40; // initial speed of flicker uint8_t flickdelaysputter = 9; // "sputtering" activity uint8_t flickdelaynormal = 40; // "normal" activity uint8_t flickdelaycalm = 95; // "calm" activity uint8_t choosearray = 1; // normal waves int delaycounter = 50; int delaydelay = 20; uint8_t waves[9][4] = { {4, 6, 40, 50} , // sputter flicker waveslow {6, 8, 50, 80} , // sputter flicker wavemed {8, 10, 110, 130} , // sputter flicker wavefast {15, 25, 60, 100} , // normal flicker waveslow {10, 25, 110, 140} , // normal flicker wavemed {20, 25, 100, 120} , // normal flicker wavefast {40, 60, 100, 140} , // calm flicker waveslow {50, 70, 120, 160} , // calm flicker wavemed {70, 80, 140, 180} // calm flicker wavefast }; bool fastup = true; bool slowup = true; bool medup = true; void mydelay(uint8_t counter) { for (uint8_t thiscount = 0; thiscount <= counter; thiscount++) { _delay_us(1); } } uint16_t gimmerand(uint16_t small, uint16_t big) { myrand ^= (myrand << 13); myrand ^= (myrand >> 9); myrand ^= (myrand << 7); if (abs(myrand) % 13 == 0) { myrand = myrand - 23; } if (abs(myrand) % 17 == 0) { myrand = myrand + 11; } return abs(myrand) % 23 * (big - small) / 23 + small; } void getnewslow(uint8_t whicharray) { slowstart = gimmerand(waves[whicharray][0], waves[whicharray][1]); slowend = gimmerand(waves[whicharray][2], waves[whicharray][3]); } void getnewmed(uint8_t whicharray) { medstart = gimmerand(waves[whicharray+1][0], waves[whicharray+1][1]); medend = gimmerand(waves[whicharray+1][2], waves[whicharray+1][3]); } void getnewfast(uint8_t whicharray) { faststart = gimmerand(waves[whicharray+2][0], waves[whicharray+2][1]); fastend = gimmerand(waves[whicharray+2][2], waves[whicharray+2][3]); faster = gimmerand(2, 6); } void myblink(uint8_t howmany) { for (uint8_t blinkcount = 1; blinkcount <= howmany; blinkcount++) { PA = 0b00000000; _delay_ms(10); PA = 0b00100000; _delay_ms(10); } } // Main program void main() { _delay_ms(100); // settle if (debug) { PAC |= (1 << 5); myblink(5); } // see datasheet PWMG1DTL = 0x00; PWMG1DTH = 0x00; PWMG1CUBL = 0xff; PWMG1CUBH = 0xff; PWMG1C = 0b10100111; PWMG1S = 0b00000000; PWMG0DTL = 0x00; PWMG0DTH = 0x00; PWMG0CUBL = 0xff; PWMG0CUBH = 0xff; PWMG0C = 0b10100111; PWMG0S = 0b00000000; PWMG2DTL = 0x00; PWMG2DTH = 0x00; PWMG2CUBL = 0xff; PWMG2CUBH = 0xff; PWMG2C = 0b10100111; PWMG2S = 0b00000000; getnewfast(choosearray); getnewslow(choosearray); getnewmed(choosearray); slowcounter = slowstart; fastcounter = faststart; medcounter = medstart; PAC |= (1 << LED4_BIT) | (1 << LED0_BIT) | (1 << LED3_BIT); // Main processing loop while (1) { // ramp up slow if (slowup) { slowcounter++; if (slowcounter > slowend) { // ramp finished so switch boolean slowup = !slowup; } } else { // ramp down slow slowcounter--; if (slowcounter < slowstart) { // ramp finished so switch boolean slowup = !slowup; getnewslow(choosearray); } } // ramp up med if (medup) { medcounter++; if (medcounter > medend) { // ramp finished so switch boolean medup = !medup; } } else { // ramp down med medcounter--; if (medcounter < medstart) { // ramp finished so switch boolean medup = !medup; getnewmed(choosearray); } } // ramp up fast if (fastup) { fastcounter = fastcounter + faster; if (fastcounter > fastend) { // ramp finished so switch boolean fastup = !fastup; } } else { // ramp down fast fastcounter = fastcounter - faster; if (fastcounter < faststart) { // ramp finished so switch boolean fastup = !fastup; getnew...and here is the video for your enjoyment... vid
fast(choosearray); } } // delay + a re-purposed random for ramp speeds mydelay(flickdelay + faster); delaycounter = delaycounter - 1; if (delaycounter == 0) { delaycounter = gimmerand(1, 100); if (delaycounter > percentnormal) { // calm if (debug) myblink(3); flickdelay = flickdelaycalm; choosearray = 6; delaycounter = 100-delaycounter; } else if (delaycounter > percentsputter) { // "normal" if (debug) myblink(2); flickdelay = flickdelaynormal; choosearray = 3; } else { // sputtering if (debug) myblink(1); flickdelay = flickdelaysputter; choosearray = 0; } delaycounter = delaycounter * delaydelay; } PWMG2DTL = slowcounter & 255; PWMG2DTH = slowcounter; PWMG0DTL = fastcounter & 255; PWMG0DTH = fastcounter; PWMG1DTL = medcounter & 255; PWMG1DTH = medcounter; } } // Startup code - Setup/calibrate system clock unsigned char _sdcc_external_startup(void) { AUTO_INIT_SYSCLOCK(); AUTO_CALIBRATE_SYSCLOCK(TARGET_VDD_MV); return 0; }
...and here is the video for your enjoyment! Good luck in the competition.
Forget last video featuring very helpful artificial intelligence - this week we plummet back down to "NO intelligence" (NI) as we open a pile of packets arriving from China to my bench-top.
It's a problem when you have too many clocks - but oh so nice when they all show the same time!
Enjoy the video - leave a comment if you have any questions!
Awhile back I did a Little Bin project which worked fine until our TelCo (Optus, based in Singapore) decided to nuke it's network (and kill a few people in the process. I got off lightly, but still had some issues with compiling (library updates and some code noodling required), hardware access (the ESP32-S3 was literally in a little bin!) and finally some code snafus which Gemini helped me sort out. See below for Gemini's analysis, and see my github site (https://github.com/bovineck/LittleBin/tree/main) for the code. Video below!
An exploration of two ESP32 programming methodologies, highlighting why the implementation approach is more critical than the library choice.
This is the most important concept to understand. While the first code file used a "non-blocking" library, its implementation was fundamentally blocking. Conversely, the second file used a simpler "blocking" library but implemented a brilliant non-blocking methodology, making its overall approach far superior.
The `ESPAsyncWebServer` library is powerful and non-blocking, but the code's use of `while` loops and `delay()` for tasks like connecting to WiFi and NTP effectively **froze the microcontroller**. This prevented it from doing anything else and negated the library's primary advantage. It's a classic case of using a powerful tool incorrectly.
The `WebServer` library is simpler, but the programmer's decision to use a **state-based, `millis()`-driven approach** allowed the program's main loop to run continuously. This non-blocking methodology ensures all tasks are managed concurrently, leading to a much more stable and responsive system.
Explore the four key differences in approach below.
This non-blocking, event-driven library is powerful. Like a modern waiter handling multiple tables at once, it doesn't wait for one task to finish before starting another. However, its benefits were undermined by blocking code elsewhere in the program.
// Event-driven handlers are set up once
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/html", index_html);
});
server.on("/", HTTP_POST, [](AsyncWebServerRequest* request) {
// Logic to handle form data
});
This standard library is simpler but blocking. Like a traditional waiter who serves one table at a time, `server.handleClient()` must be called constantly in the main loop, otherwise the server becomes unresponsive.
// Must be called in every loop iteration
void loop() {
server.handleClient();
// Other non-blocking code...
}
The code relies on `delay()` and `while` loops that halt the entire program. While waiting for WiFi to connect, the ESP32 can do nothing else—it can't serve web pages or update LEDs. This is known as "blocking" and is highly inefficient.
void initWiFi() {
while (WiFi.status() != WL_CONNECTED) {
delay(100); // Program is FROZEN here
// No other tasks can run.
}
}
This approach avoids `delay()` entirely. It uses `millis()` to check if enough time has passed to perform a task. The main `loop` runs thousands of times per second, ensuring all tasks are managed concurrently and the system remains responsive.
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - lastCheck >= interval) {
lastCheck = currentMillis;
// Perform a non-blocking task...
}
}
The error handling is blunt: if a connection fails after a set number of tries, the device reboots with `ESP.restart()`. This is disruptive, causing several seconds of downtime and losing any temporary state.
if (numtries > wifitries) {
numtries = 0;
ESP.restart(); // Forces a full reboot
}
This method is far more graceful. If the connection is lost, it continuously tries to reconnect in the background while signaling the failure with a visual indicator (blinking LEDs). The rest of the system remains fully operational.
if (WiFi.status() != WL_CONNECTED) {
isWifiConnected = false;
signalFailure(); // Calls a non-blocking function
}
The same logic for flashing LEDs is duplicated in multiple functions (`initWiFi()`, `GetLocalTime()`). This makes the code harder to read, maintain, and debug, as a change in one place must be remembered in others.
// In GetLocalTime()...
while (!getLocalTime(&timeinfo)) {
// ... LED flashing code ...
delay(100);
}
// Same logic exists in initWiFi()
The code is broken down into small, reusable, single-purpose functions like `signalFailure()` and `handleRoot()`. This modular approach makes the code clean, easy to understand, and simple to extend with new features.
// A reusable, single-purpose function
void signalFailure() {
// Logic for blinking LEDs
}
// Called from anywhere it's needed
This diagram illustrates the "Delay is Death" principle. The blocking loop gets stuck on long tasks, while the non-blocking loop handles multiple tasks concurrently, leading to a responsive system.
Start Loop
Begin checking tasks...
Attempting WiFi Connect...
Program is FROZEN. Waiting for 3 seconds...
Handle Web Request
Cannot run. Blocked by WiFi task.
Update LEDs
Cannot run. Blocked by WiFi task.
Check WiFi Status
Is it time? Yes. Check and move on. (1ms)
Handle Web Request
Any requests? No. Move on. (1ms)
Update LEDs
Is it time? Yes. Update and move on. (1ms)
Check NTP Time
Is it time? No. Move on. (1ms)
Loop repeats thousands of times per second.
A quick(ish) video about modifying a little butterfly toy. Just to make a nice change from all the coding!
I hope that you enjoy the change of pace - regular programming will resume shortly!
I have been tinkering with Solar Power and WiFi for a while now - and the arrival of some new solar panels and the tiny Seeed Studio ESP32-C6 prompted me to put together a prototype for testing.
I also decided to (briefly) use an IP2312 charging module - but as you will see in the video below it didn't really work out for me - more exploration required at this point!
Another snag was that the little board needed a software coded switch to activate the external antenna, and so I had to insert that code into the project, and ended up creating my own github folder with the tweaks, including compiled binary files if you want to just lock and load the code.
// Added by OneCircuit (and Gemini AI) on Tue 12 Aug 2025 14:49:14 AEST // https://www.youtube.com/@onecircuit-as #include "driver/gpio.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" // Define GPIO pin numbers #define ESP32C6_WIFI_ENABLE_PIN GPIO_NUM_3 #define ESP32C6_WIFI_ANT_CONFIG_PIN GPIO_NUM_14 void initialise_ext_antenna(void) { // Configure GPIO pins as outputs gpio_reset_pin(ESP32C6_WIFI_ENABLE_PIN); gpio_set_direction(ESP32C6_WIFI_ENABLE_PIN, GPIO_MODE_OUTPUT); gpio_reset_pin(ESP32C6_WIFI_ANT_CONFIG_PIN); gpio_set_direction(ESP32C6_WIFI_ANT_CONFIG_PIN, GPIO_MODE_OUTPUT); // Set pin levels gpio_set_level(ESP32C6_WIFI_ENABLE_PIN, 0); // Activate RF switch control (LOW) // Delay vTaskDelay(pdMS_TO_TICKS(100)); // Use a FreeRTOS delay function gpio_set_level(ESP32C6_WIFI_ANT_CONFIG_PIN, 1); // Use external antenna (HIGH) }
This is a version of the ESP32 router code found at:
https://github.com/dchristl/esp32_nat_router_extended, which in turn is a version of the code found here:
https://github.com/martin-ger/esp32_nat_router
I also slightly modified the partitions csv file to work with the ESP32C6, and added a file of the commands I used to load the binaries using esptool.py
Also thanks to Gemini AI who came in after three days of me banging me head against a wall trying to merge two coding platforms and sorted out the last little hiccups in my code.
Finally, for LOLS, I moved from the Arduino IDE to Visual Code Studio with PlatformIO and ESP-IDF extensions running - it's own little adventure in the end.
The whole project taught me a great deal about all of these components - hardware, software, tweaking and even some antenna rabbit holes!
Enjoy and please leave a comment if you have a moment!