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!





Monday, March 16, 2026

0000 0001 0000 1100

Mailbag #49 - another embarrassing big haul

I'm trying to scoot through as much mail as I can at the moment! Here is the "running sheet" and links for the products opened:

00:29 Last Video - Subscriber Counter https://youtu.be/0rnqVKd8tJo

00:48 8 Bit LED CC indicator Module https://www.aliexpress.com/item/1005004567063273.html

04:12 8 Bit LED CA indicator Module https://www.aliexpress.com/item/1005004567063273.html

07:06 CH32V003 Development Board https://www.aliexpress.com/item/1005008029017618.html

08:50 CN3791 MPPT Adjustable Solar Charger https://www.aliexpress.com/item/1005008744020414.html

11:55 SOT-23 DIP Adapters https://www.aliexpress.com/item/1005007036755126.html

13:03 ESP32-S3 UNO R3 Module https://www.aliexpress.com/item/1005008212186971.html

14:56 SOP20 DIP/SMD adapter https://www.aliexpress.com/item/1005010427929042.html

15:38 SMD diode SOD-123 1N4007 https://www.aliexpress.com/item/1005009614408948.html

16:57 10MM LED Diode LEDs Assorted https://www.aliexpress.com/item/1005006217233878.html

19:10 3.81cm 128x128 OLED Screen Display Module https://www.aliexpress.com/item/1005008489717998.html

20:22 MB85RC256V IC 32KB FRAM Module https://www.aliexpress.com/item/1005008550835113.html

21:42 Fram video https://youtu.be/GmSQMHzKIMY

22:26 MAX232EPE DIP-16 IC https://www.aliexpress.com/item/1005003078617951.html

Enjoy the video, especially the comical "running" man at the start!