Wednesday, October 7, 2026

0000 0001 0001 0011

The death of the QX5252?

For years I have been working on the candle project - the idea of an environmentally friendly perpetual "candle", solar powered, that does justice to the gentle flickering of a real candle, including sputtering, has driven a lot of content on this channel!

The only unease I have felt is in using NiMH batteries for solar power storage. Heavy and having a fairly low power cycle count, the main issue was environmental - some nasty chemicals and a not particularly long life meant too many would end up in landfill.

Then in the super capacitor realm we had 120F and 250F LIC capacitors that charged to 3.8V, has a cycle count between 100000 and 1000000 charge/discharge cycles, was light weight and seemed to contain a number of problematic barriers:

1. Making the code so efficient that a super capacitor could drive the project

2. Make the PFS154 better at monitoring the day/night interface to optimise power usage

3. Boost/control the output of the solar panel to charge a 3.8V super capacitor safely

This third hurdle bothered me A LOT because the QX5252 was 80% efficient on a good day and I was using a 3.9V zener clamp on the output which further wasted power.

I needed a good DC-DC boost converter that could step into it's shoes (after all there must be tens of videos on this channel that feature this little soldier).

And so the search began - with some parameters to help me weed out contenders. AI was not much help (a bit too "chicken Little" for my taste)

I am really happy with the TPS6102X family (featured before on this channel), but it is too expensive at the moment, and so with cost in mind I compromised to the BL8530/1N5817 combo.

Please take a look at the video and let me know what you think - especially if you have another contender!




Monday, September 21, 2026

0000 0001 0001 0010

The final candle code(?)

Those who have been with this channel for awhile would know that this fake candle obsession of mine dates back at least to 2017!

Over the years this one project has driven my understanding of code, both C and ASM, as well as some deep dives into microprocessors such as the PFS154 and the ATTiny13A. I've learned about "randomness" and even written a book on Assembler.

Now I feel like it's time to release all the code into the wild (well as wild as github gets) and also pose a question for you - if you could download working candle code for an ATTiny13, or ATTiny85, or a CH32V003 - would you be interested?

The code is pretty mature now and the latest version (just 1.8kB compiled) contains such elements as:

  • 32 bit PRNG Xorshift code
  • Noise seeding the random generator using the timer and the comparator
  • Dusk/Dawn Hysteresis
  • Code efficiencies including array dimension
  • Super capacitor over discharge protection

Enjoy the video, and the repository, and please comment if you'd like to see the project extend to other micro-controllers, and name your favourite!

Links:



Friday, September 18, 2026

0000 0001 0001 0001

Cheap Bluetooth Speaker Set

Forever I've wanted to build a Bluetooth Speaker - they seem hideously expensive IRL (and recently I've had one fail on me) but the components seem fairly cheap, so I wanted to see if we are being ripped off by the retailers (spoiler alert: holy hell yes!)

It all started when I spied a TSA3110 module on AliExpress for about $2, and a similarly priced HW425 Bluetooth module/decoder.


I also had a few old (free) computers lying around so I grabbed an ATX breakout module which gives me the slightly underpowered +12V for the amplifier board and +5V for the Bluetooth board.


Also, you don't really need to buy speakers if you frequent (as I do) local recycling depots - what people throw out is scandalous, but also great for you and me! But I did splurge on two speakers which had great reviews and more than doubled the cost of the project. Good speakers are worth the search!

Anyhoo - this project resulted in a lovely sounding Bluetooth speaker which is a little butt ugly due to my complete lack of carpentry skills (see building montage), but I'm sure it will be a great addition to the lab for those long days/nights of soldering.




Sunday, September 6, 2026

0000 0001 0001 0000

Success! (...and failure)

This video and post is a quick follow up on the last one where I thought it was remiss of me not to actually test the TPS61023 to see if it could actually charge the 3.8V LIC SuperCap. Spoiler alert - it can!


Also I talked briefly last time about the nonsense PCB I made which featured a "fake battery" to fool the QX5252 into output - as it turns out the fool was me! Lesson learned...



Tuesday, September 1, 2026

0000 0001 0000 1111

Back from the edge

Hi all! It's been awhile (last blog Wednesday, April 22, 2026:  132 days)

In that time I've had  ̶m̶y̶ ̶b̶r̶a̶i̶n̶ ̶r̶e̶m̶o̶v̶e̶d̶  a tumour in my brain removed, and have been slowly recovering from what has been a ludicrously intense few weeks.


I have also been working on a few things:

  1. The supercap candle (video)
  2. An RPi NAS via Tailscale and Nextcloud
  3. Some writings

I have also been thinking about how on earth I re-introduce myself to you all after such a long break - and it seemed easiest to just dive in and do what I've done in the past - take a deep dive into an IC and then wreck it by soldering some silly nonsense to it.

Enjoy and thank you for waiting...




Wednesday, April 22, 2026

0000 0001 0000 1110

A little ripper!

In the middle of weeks of rabbit holes regarding the sleeping PFS154, a ray of light arrived in the form of the XIAO ESP-32 C5.

I'd ordered it for projects where a strong 5GHz connection was more preferable to the crowded 2.4GHz band in my house where "smart" lights jostled for attention on my horrible router (another video sometime perhaps).

All I wanted to do was to test if it really was dual band, so the following code emerged after a brief session with Gemini - and yes - the little guy came through.

Here is the code you will need...


/*
  OneCircuit ESP32-C5 Dual-Band Discovery Demo
  Target: Espressif ESP32-C5 (Dual-Band Wi-Fi 6)

  YouTube: https://www.youtube.com/@onecircuit-as
  Blog: https://onecircuit.blogspot.com/
  Github: https://github.com/bovineck/
  
  IMPORTANT: This code requires ESP32 Arduino Core v3.0.0 or higher.
  The ESP32-C5 is a dual-band SoC (2.4GHz and 5GHz). 
  1. This demo scans each band separately using setBandMode().
  2. To protect privacy, SSIDs are NOT printed to the Serial Monitor.
  3. Signal strength (RSSI) is used to verify the discovery of local bands.
  4. An external antenna is required for optimal 5GHz performance.

  Wed 22 Apr 2026 18:45:00 AEST

  DEVICE = XIAO ESP32-C5
       __________
      /  FRONT   |
  1--|D0       5V|--14
  2--|D1      GND|--13
  3--|D2     3.3V|--12
  4--|D3      D10|--11
  5--|D4       D9|--10
  6--|D5       D8|--9
  7--|D6       D7|--8
     |___________| 

*/

#include "WiFi.h"

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA); // Set to station mode
  WiFi.disconnect();
  delay(100);
  Serial.println("--- ESP32-C5 Privacy Dual-Band Scan ---");
}

void scanBand(const char* bandLabel, wifi_band_mode_t bandMode) {
  // Set the hardware to the specific band
  WiFi.setBandMode(bandMode);
  
  Serial.printf("Scanning %s band... ", bandLabel);
  int n = WiFi.scanNetworks();
  
  if (n <= 0) {
    Serial.printf("No %s SSIDs discovered.\n", bandLabel);
  } else {
    int strongestRSSI = -100; // Start low
    
    // Find the strongest signal without storing or printing SSIDs
    for (int i = 0; i < n; ++i) {
      if (WiFi.RSSI(i) > strongestRSSI) {
        strongestRSSI = WiFi.RSSI(i);
      }
    }
    
    Serial.printf("A %s SSID has been discovered with the following strength: %d dBm\n", 
                  bandLabel, strongestRSSI);
  }
  
  WiFi.scanDelete(); // Clear results from memory
}

void loop() {
  // 1. Scan 2.4 GHz
  scanBand("2.4GHz", WIFI_BAND_MODE_2G_ONLY);
  delay(1000);

  // 2. Scan 5 GHz
  scanBand("5GHz", WIFI_BAND_MODE_5G_ONLY);
  
  Serial.println("------------------------------------------");
  delay(5000); // Wait 5 seconds before the next update
}

One caveat - there is no onboard antenna, you will need to plug in the provided external antenna to ensure a strong connection - the difference is remarkable, see results below.

Before external antenna:

------------------------------------------
Scanning 2.4GHz band... A 2.4GHz SSID has been discovered with the following strength: -86 dBm
Scanning 5GHz band... A 5GHz SSID has been discovered with the following strength: -56 dBm
------------------------------------------

After external antenna:

------------------------------------------
Scanning 2.4GHz band... A 2.4GHz SSID has been discovered with the following strength: -17 dBm
Scanning 5GHz band... A 5GHz SSID has been discovered with the following strength: -26 dBm
------------------------------------------

Here's the (brief and interrupted) video below - enjoy!



Sunday, March 22, 2026

0000 0001 0000 1101

From Blinken-lights to Organic VU Meters: Optimising the CH32V003


The WCH CH32V003 is a marvel of modern "frugal" engineering: a RISC-V core for roughly the price of a postage stamp. This project represents the arc of moving from basic GPIO toggling to a sophisticated, multi-mode lighting controller, testing various aspects of "normal" Arduino programming (blinking, fading, analog reads, randomising, etc.) as we progress through the code.

The Development Arc: Beating the "Flash" Wall

Our journey began with a simple goal: create a high-quality 8-LED array with organic transitions. The first hurdle was PWM. Without enough hardware PWM pins for the entire array, we implemented a custom Software PWM engine. This allowed us to achieve silky 8-bit fading (0-255) on any GPIO, giving the "Breathe" and "Twinkle" modes a natural, non-linear glow.

The real challenge arrived with the Voice-Reactive VU Meter. Initially, using standard floating-point math for signal smoothing instantly "overflowed" the 16KB Flash limit. By pivoting to Fixed-Point Integer Math and bit-shifting (using >> 7 instead of division), we reclaimed over 4KB of space while actually increasing execution speed. Thanks Gemini!

Hardware & Wiring

The setup is centered around a CH32V003 breakout and a custom 8-LED module. We used a standard analog microphone module to provide the audio input.

Key Connections:

  • LED Array: PD4, PC4, PC3, PC2, PC1, PC0, PA2, PA1 (mapped in code via LedArray[]).

  • Mic Input: Analog Pin A4 (PA4).

  • Power: 3.3V to 5V (depending on your specific board and LED resistors).

The Result: Organic Interaction

The firmware cycles through three distinct phases:

  1. The Initialisation: A rapid timing check to ensure all segments are healthy, includes blinking, fading individually and fading all LEDs.

  2. The Starfield: A randomized "Twinkle" engine that varies peak brightness and pulse duration, ensuring no two flashes look the same.

  3. The Dampened VU Meter: This is the star of the show. Using an Exponential Moving Average (EMA) filter, the meter ignores high-frequency noise and tracks the "envelope" of your voice. The result is a meter that feels "weighted"—more like an analog needle than a jittery digital display.

Strengths & Lessons Learned

  • Efficiency: We achieved complex filtering and 8-channel PWM while staying under 90% Flash usage.

    Sketch uses 14496 bytes (88%) of program storage space. Maximum is 16384 bytes. Global variables use 568 bytes (27%) of dynamic memory, leaving 1480 bytes for local variables. Maximum is 2048 bytes

    Note that I used the Arduino IDE (Version: 2.3.8, 
    Date: 2026-02) with the core coming from this github. If you use the JSON and Arduino IDE preferences to install the board definitions, remember to download the update from the github site and, after unzipping, overwrite the files found here:


  • Calibration: By externalising MIC_FLOOR and MIC_CEILING constants, the module is easily tuned for different environments.

  • The Power of RISC-V: Even at this price point, the 48MHz clock handles our software PWM and bit-shifted math without a hint of flicker.

Future Directions

While the current module is solid, there is room to grow. Future iterations could include a "Peak Decay" feature (where the highest LED stays lit momentarily) or a FFT-based frequency visualiser.

For now, it’s a nice refutation of my last mailbag video which not only featured an embarrassment of riches, but also just plain embarrassment!

Check out the full source code on my GitHub: bovineck/CH32V003-module-code

/*
CH32V003 module programmed by OneCircuit and Gemini
Sat 21 Mar 2026 18:12:43 AEDT
YouTube: https://www.youtube.com/@onecircuit-as
Blog: https://onecircuit.blogspot.com/
Github: https://github.com/bovineck/
*/

const uint8_t LedArray[] = { PD4, PC4, PC3, PC2, PC1, PC0, PA2, PA1 };
const uint8_t sizeArray = 8;
const int timeDelay = 300;
const uint8_t breatheSpeed = 2;
const int filterFactor = 6;  // to dampen the sound readings
int dampenedVolume = 0;

// VU Meter Tuning
const int MIC_FLOOR = 50;     // Ignore noise below this level
const int MIC_CEILING = 450;  // Full scale (all LEDs on) at this level
const int micRange = MIC_CEILING-MIC_FLOOR;
const uint8_t LED_COUNT = 8;  // Total number of LEDs

void initialise_pins(int timing) {
  for (int mypins = 0; mypins < sizeArray; mypins++) {
    pinMode(LedArray[mypins], OUTPUT);
    digitalWrite(LedArray[mypins], HIGH);
    delay(timing);
    digitalWrite(LedArray[mypins], LOW);
  }
}

void twinkle(int durationMillis) {
  unsigned long start = millis();

  while (millis() - start < durationMillis) {
    int ledA = random(0, sizeArray);
    int peak = random(40, 180);  // Random max brightness (out of 255)
    int speed = random(1, 5);    // Random increment (1 = slow, 5 = fast)
    int timing = random(5, 12);  // Random pulse width multiplier

    for (int duty = 0; duty < peak; duty += speed) {
      digitalWrite(LedArray[ledA], HIGH);
      delayMicroseconds(duty * timing);
      digitalWrite(LedArray[ledA], LOW);
      delayMicroseconds((peak - duty) * timing);
    }

    for (int duty = peak; duty > 0; duty -= speed) {
      digitalWrite(LedArray[ledA], HIGH);
      delayMicroseconds(duty * timing);
      digitalWrite(LedArray[ledA], LOW);
      delayMicroseconds((peak - duty) * timing);
    }
    delay(random(50, timeDelay));
  }
}

void setup() {
  initialise_pins(0);
  randomSeed(analogRead(0));
  Serial.begin(115200);
  delay(2000);
}

void loop() {
  Serial.println(F("Blinken de lights"));
  initialise_pins(timeDelay);
  delay(timeDelay);
  Serial.println(F("Faden de lights"));

  // Fade Up
  for (int mypins = 0; mypins < sizeArray; mypins++) {
    for (int duty = 0; duty < 255; duty++) {
      digitalWrite(LedArray[mypins], HIGH);
      delayMicroseconds(duty * 10);
      digitalWrite(LedArray[mypins], LOW);
      delayMicroseconds((255 - duty) * 10);
    }
    // Fade Down
    for (int duty = 255; duty > 0; duty--) {
      digitalWrite(LedArray[mypins], HIGH);
      delayMicroseconds(duty * 5);
      digitalWrite(LedArray[mypins], LOW);
      delayMicroseconds((255 - duty) * 2);
    }
  }
  delay(timeDelay);
  Serial.println(F("All de lights Faden"));

  for (int direction = 0; direction < 2; direction++) {
    for (int dutyCycle = 0; dutyCycle < 255; dutyCycle++) {
      int duty = (direction == 0) ? dutyCycle : (255 - dutyCycle);
      for (int times = 0; times < breatheSpeed; times++) {
        for (int i = 0; i < sizeArray; i++) digitalWrite(LedArray[i], HIGH);
        delayMicroseconds(duty * 10);
        for (int i = 0; i < sizeArray; i++) digitalWrite(LedArray[i], LOW);
        delayMicroseconds((255 - duty) * 10);
      }
    }
  }

  delay(timeDelay);
  Serial.println(F("Twinklen de lights"));
  twinkle(20 * timeDelay);
  delay(timeDelay);

  while (1) {
    int rawVolume = analogRead(A4);
    dampenedVolume = ((dampenedVolume * (128 - filterFactor)) + (rawVolume * filterFactor)) >> 7;
    int response = (dampenedVolume - MIC_FLOOR) * (LED_COUNT + 1) / micRange;
    if (response < 0) response = 0;
    if (response > sizeArray) response = sizeArray;
    for (uint8_t i = 0; i < sizeArray; i++) {
      digitalWrite(LedArray[i], (i < response));
    }
    delay(10);
  }
}


...and of course as usual check out the video below!