I've been working in the background on replacing the NiMH battery in the candle project with a supercapacitor. It's been quite the journey and at times I definitely did not believe that it would ever happen.
There is a great benefit when you are a mad pioneer in being ignorant of what is conventionally regarded as not possible, so I just assumed in my usual trial and error, and error, and error method of experimentation that the problem would eventually be solved.
Three impossible things needed to happen for this project to be completed:
1. A supercapacitor needed to be able to run a microcontroller (just ticking over anaemically at a very low clock rate) for a useful amount of time (e.g. overnight) - done
2. A microcontroller (e.g. the PADAUK PFS154) needed to be able to detect when it was night time just from the charge profile of a capacitor - done
3. A circuit designed to have at it's heart a NiMH battery would need to be fooled and use a collection of passives instead
The final result needs more polishing, but all three miracles have occurred and now the candle project will be re-imagined as a supercapacitor project. Done?
The SOP8 version of the CH32v003 is pretty amazing, and one "feature" is the ability to remap many functions/protocols to the same pins.
This is fine up to the point where there might be a clash. For instance, just look how busy pin 8 is - overloaded including the function that allows it to be programmed and reprogrammed (the one-wire SWIO "single wire" protocol) that for some reason is on the same pin as serial TX.
So...what if you have engaged serial communication AND want to reprogram the chip?
Disaster! But, there are a few ways I have found to get around the problem.
1. Use the minichlink program as we saw in a previous blog and video.
2. Only open up Serial when you need it, and then the chances of interrupting the program to "reprogram" are increased (dodgy I know)
3. Use a mysterious function on a mysterious piece of software to somehow use the one-wire protocol to "printf" from pin 8.
4. Re-map the pins! (see below)
5. Buy the version with more pins you cheapskate!
Now the deep deep deep dive into pin re-allocation took me through many websites, forums and configurations files, but here is what you need to do:
a) find the files PeripheralPins.c and variant_CH32V003F4.h in the core directory as follows:
b) Make the following config file edits to re-map the pins:
After that the only "gotcha" is to remember to switch the connections as follows:
Then you can reprogram at your leisure and enjoy serial communication as well. Speaking of enjoyment, please see the video below and let me know if any of these workarounds did the trick for you as well.
Looking to power a fan to keep an Orange Pi 3B cool inside it's enclosure, I turned to a "power" mosfet I hadn't worked with before, the super little guy AO3400.
Rated to 30V and a fanciful 5.8A it seemed to be perfect for the job because it could "open the gate" at around 1.1V.
I designed a suitable circuit, soldered the little bits together on a SOP8 to DIP8 adapter (!!) as per below:
The design seemed to work for an LED circuit, so I boxed it up and ran it from the 5V rail of the OPi 3B.
See the result below. Please add comments on YT below the video, especially if you have a view on fan orientation!
Newbies with cool tech like me are prone to "bricking" chips which basically means you can't program the things anymore.
Different chips react differently in these circumstances. Once upon a time I invested in a High Voltage AVR Programmer for reviving ATTiny85 chips that I had unwittingly fried with poor code. And it worked a treat!
The CH32v003 peeps in their wisdom have fortunately included in their one-wire communication protocol the ability to reset and therefore un-brick a chip.
There's a little VCC dancing to make it happen, but...well, take a look...
I've had great success with the Orange Pi range over the years. I will say that I've always bought the cheap(est) option and even now I'm not sure if I'd take the plunge on an OPi 5 with all the fruit - that's a lot of dollarydoos!
In the meantime I have an OPi 3 LTS chugging away in the background on both PiHole duties as well as a Media Server - and it works great!
But recently I grabbed hold of the holy money belt and shook like crazy to purchase the following pack of goodies.
I think that this machine might end up being the new NAS and Pi-hole combo. The first thing to do is roll-my-own Armbian. I did start off with a community version of Ubuntu, which booted fine, but I had trouble copying the system over to the EMMC module - so roll your own Armbian it is!
The addition of a 1Tb NVME solid state drive should give a few options for video and music streaming - time will tell. It's my impression from reading that you could use this fast SSD option to preload or buffer video and audio from a larger HDD and thus smooth out some rough edges that occasionally appear.
Here are the stats on this little beauty - I still think it's cheap for the power and the potential, a nice mix of stats and options making it one of the more versatile units in this range.
And the video? I've tried to show the whole process from unpacking to running the desktop on the device.
Straight after playing around with the CH32v003 as a SOP-20 on a module in the last video, I saw a plea from the #socials about running a SOP-8 version on a breadboard.
So I soldered up a little beast and started to play!
I got a little carried away with this one - but if you want to configure a Raspberry Pi 5 from scratch, or talk to the CH32v003, then this long long video might just be what you need.
Grab a tea, coffee or other libation and settle in for a procession of treats from AliExpress (and one annoying local offering).
Oh and if you want to 3D print the breadboard jumper maker:click this link.
In the 1970s an uncle of mine spent t-h-o-u-s-a-n-d-s of dollars on audio equipment. High end speakers, valve amplifiers, turntables, tape decks, etc., etc.
I was in awe, and the sound was so amazing as to be akin to a religious experience. He was so into it that he paid for an audio engineer to come in and "ping" his lounge room so that the speakers could be precisely placed in the exact spots to eliminate any dead zones.
Of course his wife shifted them that afternoon due to her interior design concerns. They are not married anymore - coincidence?
Fast forward to 2024 and his nephew (that's me!) has just bought a TDA7297 based amplifier from AliExpress coming in at the princely sum of AU$2.57. Embarrassing - and even more so because it doesn't seem to work.
Or does it?
Also, a colleague at work handed over some ear pod thingies that weren't going the distance in terms of charging - I have no idea what to do to help, and there is now video evidence of that fact!
This is not electronics but a weird combination of coding and mathematics! Young children look away now.
Some time ago I thought it might be useful to look for patterns in primes because, well, it's like a mountain to climb! If it's there then you gotta do it.
Also there has been a LOT of work done in this field before.
So during a lull in my holidays (?), and after attending a math conference recently, I decided to start a coding journey that examines the position of primes in geometric patterns for both squares and hexagons.
Here's the square version:
from math import sqrt
from turtle import *
xrow = 10
ycol = 11
count = 2
squaresize = 4
tracer(False)
speed("fastest")
pendown()
def isPrime(n):
for i in range(2,int(n**0.5)+1):
if n%i==0:
returnFalsereturnTruedef makesq():
forward(squaresize)
right(90)
forward(squaresize)
right(90)
forward(squaresize)
right(90)
forward(squaresize)
right(90)
forward(squaresize)
returndef turn():
right(90)
forward(squaresize)
return
fillcolor("red")
begin_fill()
makesq() # 1 is not prime
end_fill()
fillcolor("blue")
while(count < 25000):
for laying in range(1,xrow):
if(isPrime(count)):
begin_fill()
makesq()
end_fill()
count = count + 1
turn()
for laying in range(1,ycol):
if(isPrime(count)):
begin_fill()
makesq()
end_fill()
count = count + 1
turn()
xrow=xrow+1
ycol=ycol+1
update()
And here is the more interesting hexagon version.
from math import sqrt
from turtle import *
import os
from turtle import Screen, Turtle
height = 900
width = 900
screen = Screen()
screen.setup(width, height)
short = 1global count
count = 1
hexsize = 10
turnarray = [4,3,3,3,3,2,3]
#tracer(False)
speed("fastest")
pendown()
def isPrime(n):
for i in range(2,int(n**0.5)+1):
if n%i==0:
returnFalsereturnTruedef makehex(sides):
for making in range(0,sides):
forward(hexsize)
right(60)
return
fillcolor("red")
begin_fill()
makehex(6) # 1 is not prime
end_fill()
makehex(2) # 1 is not prime
left(120)
count = count + 1
fillcolor("blue")
def turn():
global count
if(isPrime(count)):
begin_fill()
makehex(6)
end_fill()
makehex(3)
left(120)
count = count + 1returndef straight(numhex):
global count
for length in range(0,numhex):
if(isPrime(count)):
begin_fill()
makehex(6)
end_fill()
makehex(2)
left(120)
count = count + 1returnwhile(count < 9):
if(isPrime(count)):
begin_fill()
makehex(6)
end_fill()
makehex(turnarray[count-2])
left(120)
count = count + 1for layer in range(1,65):
for sides in range(0,4):
turn()
straight(layer)
turn()
straight(layer+1)
turn()
straight(layer)
update()
Now I'm keen to "fold" the shapes and have a look at the 3D possibilities - anyone with me?
The next phase of the NiMH replacement project is to test if a Super Capacitor can power a PFS154 through the night - pumping out candley goodness via it's three PWM channels.
Since the last video and blog I have continued to work on the code - a subscriber picked up a SNAFU from the last video, and as well I am not yet convinced that the PFS154 is sleeping.
I have an idea to move the candle project from a AA NiMH rechargeable battery to a supercapacitor. There are issues!
In previous incarnations of this dream/nightmare (one and two) I failed due to not being able to drive the candle for more than a few hours overnight - and charging was an issue. The QX5252 outputs 1.3V and the supercap I'm experimenting with is rated 3.8V.
So I've set myself three impossible things to solve for this project to be supercap based:
a) "Boost" the voltage output of the QX5252 to match the supercap b) Figure out a way to extend the overnight life of the candle c) Work out some way of making the PFS154 (missing an ADC) pretend it's got an ADC and can therefore react to voltage changes (ideally via interrupts)
Working backwards on these issues (why not?) I have been torturing a PFS154 on the breadboard, trying out a weird internal comparator feeding an interrupt signal to turn on...why an LED of course!
Enjoy the journey - next in line is the second impossible thing - extending the life of the supercap for overnight light.
Late Mail! Due to an online suggestion via the comments section underneath the video below, here is the updated code which is working well! Thank you to @ivolol
/* "Alice laughed: "There's no use trying," she said; "one can't believe impossible things." "I daresay you haven't had much practice," said the Queen. "When I was younger, I always did it for half an hour a day. Why, sometimes I've believed as many as six impossible things before breakfast." Lewis Carroll This particular impossible thing is a PFS154 sleeping "stopsys()" and being woken up by the comparator which is using PA3 as the negative input and the internal reference voltage as the positive input. Also impossible? F_CPU = 10000 (that's Hz!) PFS154 pinout +-\/-+ VDD 1| |8 GND PA7/X1 2| |7 PA0/AD10/CO/INT0/PG0PWM PA6/X2 3| |6 PA4/AD9/CIN+/CIN1-/INT1/PG1PWM PA5/PRSTB/PG2PWM 4| |5 PA3/AD8/CIN0-/TM2PWM/PG2PWM +----+ Mon 03 Jun 2024 22:05:27 AEST https://www.youtube.com/c/onecircuit-as https://onecircuit.blogspot.com/*/#include "../device.h"#include "../easy-pdk/calibrate.h"#include "../auto_sysclock.h"#include "../delay.h"#include <stdbool.h>bool sunshine = false; // is the sun shining?// interrupt triggered when the sun goes away, voltage of small// capacitor is drainedvoid Interrupt(void) {
__disgint(); // disable global interrupts
INTEN = 0; // disable all interrupts
INTRQ = 0;
sunshine = false;
__engint(); // enable global interrupts
}
// compares the cap voltage with the internal voltagebool checksolar(void)
{
uint8_t compresult = 0; // initially a byte
compresult = GPCC & 0b01000000; // mask the result output
compresult = compresult >> 6; // shift it to the least significant bit
sunshine = (bool)compresult; // cast result as a booleanreturn sunshine;
}
// this is where the candle code will go - for now, turn on LEDvoid candlingon() {
PAC = 0b00010000;
PA = 0b00010000;
}
// here is where the uC goes to sleepvoid sleepnow() {
__disgint(); // disable global interrupts
PA = 0b00000000; // turn off LED
MISC |= MISC_FAST_WAKEUP_ENABLE; // fast wakeup
INTEN = 0b00010000; // enable comparator interrupt
INTRQ = 0b00010000;
__engint(); // enable global interrupts
__stopsys(); // go to sleep
}
void main() {
// PA4 is output, all GPIOs low
PAC = 0b00010000;
PA = 0b00000000;
// page 66 datasheet
GPCC = 0b10000000;
// bit 7 enable comparator// bit 6 plus input < minus input// bit 5 result output NOT sampled by TM2_CLK// bit 4 polarity is NOT inversed// bit 3-1 000 : PA3 selected as -ve input// bit 0 internal voltage set as +ve input
GPCS = 0b10000110;
// bit 7 output to PA0 disabled// bit 6 reserved// bit 5 high range selected// bit 4 low range selected// bit 3-0 Selection the internal reference voltage level of comparator // 0000 (lowest) ~ 1111 (highest) as a fraction of vdd
_delay_ms(20); // small settle time delaywhile (1) {
_delay_ms(200); // small loop delay
sunshine = (bool)checksolar(); // check the sunshineif (!sunshine) {
candlingon(); // it's dark, start candling action
}
else
{
sleepnow(); // it's light, go to sleep
}
}
}
// Startup code - Setup/calibrate system clockunsignedchar _sdcc_external_startup(void) {
AUTO_INIT_SYSCLOCK();
AUTO_CALIBRATE_SYSCLOCK(TARGET_VDD_MV);
return0;
}
There's generosity and then there's Steve. He contacted me a couple of months ago and asked if I'd like an old oscilloscope he wasn't using - plus a few other things.
Well the oscilloscope is lovely and the "few other things" turned out to be a treasure trove. Steve you little beauty!
Recently Atul Ravi of PICUNO fame asked me why I "switched" from ATtiny13 to the PFS154. I haven't really - in fact the ATtiny13 is still near and dear to my heart, not least because I spent six months writing assembly code snippets for this little microcomputer. You can read all about it by clicking on this link.
However, I do use the PFS154 for the candle project (among others) for the following reasons:
Ability to wind down the voltage and current requirements by running the IC as low as 15kHz!
Challenging to learn a new toolchain
In the great comparison, here is what ChatGPT had to say (usual caveats apply):
It's not all beer and skittles though, and one missing feature that rankled was the lack of an analogue to digital converter (ADC) on the PFS154. I mean - why!? It's such a basic requirement to take an analogue signal (e.g. distance from object using an ultrasonic signal).
Anyway, I'm not bitter at all.
And so enter stage left the previously shelved PFS173, on the bench at 14 cents each from LCSC ordered in June 2020 (now discontinued for some reason).
Originally I just wanted to talk to it (e.g. blinky), but in the end not only did I get some PWM action, but also the ADC - this in itself is interesting as to date I have not seen any code available for linking ADC to PWM on this chip. See for instance the github site for examples for these chips:
I do have one embarrassing admission despite the win. My final code does not use interrupts but rather gallops along around 100kHz and checks every single time it loops for the ADC conversion, and the "time" for the LED to blink. It's positively neanderthal, but nonetheless effective.
I will aim to find out more about the PFS173 interrupt landscape and update this travesty in the future. In the meantime, here is the code for the blink and PWM version.
/* Test Code for PFS173 Blink and PWM +-\/-+ VDD 1| |8 GND PA7/X1 2| |7 PA0/AD10/CO/INT0/PG0PWM PA6/X2 3| |6 PA4/AD9/CIN+/CIN1-/INT1/PG1PWM PA5/PRSTB/PG2PWM 4| |5 PA3/AD8/CIN0-/TM2PWM/PG2PWM +----+ Tue 23 Apr 2024 17:35:37 AES https://www.youtube.com/c/onecircuit-as https://onecircuit.blogspot.com/*/#include "../device.h"#include "../easy-pdk/calibrate.h"#include "../auto_sysclock.h"#include "../delay.h"#include <stdbool.h>#define LED4_BIT 4 // PWM#define LED3_BIT 3 // Blinky#define turnLedOn() PA &= ~(1 << LED3_BIT)#define turnLedOff() PA |= (1 << LED3_BIT)bool ledon = true;
// crude timing for blinkylong mytimer = 30;
volatilelong counter = 0;
// PFS173 PWM#define PWM_MAX 255// check in here to see if LED is on or offvoid checkled() {
counter = counter + 1;
if (counter > mytimer) {
counter = 0;
if (ledon) {
turnLedOff();
ledon = false;
}
else {
turnLedOn();
ledon = true;
}
}
}
// Main programvoid main() {
// Initialize hardware
PAC |= (1 << LED4_BIT);
PAC |= (1 << LED3_BIT);
PWMGCUBL = PWM_MAX << 5; // Set the PWM upper bound (lower 3 bits)
PWMGCUBH = PWM_MAX >> 3; // (upper 5 bits)
PWMG1DTL = 0x00; // Clear the LED PWM duty value
PWMG1DTH = 0x00;
PWMGCLK = (uint8_t)(PWMGCLK_PWMG_ENABLE | PWMGCLK_CLK_IHRC);
PWMG1C = (uint8_t)(PWMG1C_INVERT_OUT | PWMG1C_OUT_PWMG1 | PWMG1C_OUT_PA4);
ledon = false;
// Main loopwhile (1) {
uint8_t fadeValue;
// Fade in from min to max in increments of 5for (fadeValue = 0; fadeValue < PWM_MAX; fadeValue += 5) {
PWMG1DTL = fadeValue << 5; // Set the LED PWM duty value (lower 3 bits)
PWMG1DTH = fadeValue >> 3; // (upper 8 bits)
_delay_ms(30); // wait for 30 milliseconds to see the dimming effect
checkled();
}
// Fade out from max to min in increments of 5for (fadeValue = PWM_MAX; fadeValue > 0; fadeValue -= 5) {
PWMG1DTL = fadeValue << 5; // Set the LED PWM duty value (lower 3 bits)
PWMG1DTH = fadeValue >> 3; // (upper 8 bits)
_delay_ms(30); // wait for 30 milliseconds to see the dimming effect
checkled();
}
}
}
// Startup code - Setup/calibrate system clockunsignedchar _sdcc_external_startup(void) {
AUTO_INIT_SYSCLOCK();
AUTO_CALIBRATE_SYSCLOCK(TARGET_VDD_MV);
return0; // Return 0 to inform SDCC to continue with normal initialization.
}
And here is the code which does Blink and ADC linked to PWM (a triumph!):
/* Test Code for PFS173 ADC and PWM +-\/-+ VDD 1| |8 GND PA7/X1 2| |7 PA0/AD10/CO/INT0/PG0PWM PA6/X2 3| |6 PA4/AD9/CIN+/CIN1-/INT1/PG1PWM PA5/PRSTB/PG2PWM 4| |5 PA3/AD8/CIN0-/TM2PWM/PG2PWM +----+ Wed 24 Apr 2024 22:38:05 AEST https://www.youtube.com/c/onecircuit-as https://onecircuit.blogspot.com/*/#include "../device.h"#include "../easy-pdk/calibrate.h"#include "../auto_sysclock.h"#include "../delay.h"#include "../device/pfs173.h"#include <stdbool.h>#define LED4_BIT 4 // PWM#define LED3_BIT 3 // Blinky#define LED0_BIT 0 // ADC#define turnLedOn() PA &= ~(1 << LED3_BIT)#define turnLedOff() PA |= (1 << LED3_BIT)bool ledon = true;
// crude timing for blinkylong mytimer = 45;
volatilelong counter = 0;
volatileint PWM_MAX = 255; // will be set by ADC// check in here to see if LED is on or offvoid checkled() {
counter = counter + 1;
if (counter > mytimer) {
counter = 0;
if (ledon) {
turnLedOff();
ledon = false;
}
else {
turnLedOn();
ledon = true;
}
}
}
// Main programvoid main() {
// Initialize hardware
PAC |= (1 << LED4_BIT);
PAC |= (1 << LED3_BIT);
PAC |= (0 << LED0_BIT);
// setup ADC
PAPH |= (0 << LED0_BIT);
PADIER |= (0 << LED0_BIT);
ADCRGC = 0; // VDD is ref
ADCC = ADCC_ADC_ENABLE | ADCC_CH_AD10_PA0; //enable ADC and use channel 10 (PA0)// setup PWM
PWMGCUBL = PWM_MAX << 5; // Set the PWM upper bound (lower 3 bits)
PWMGCUBH = PWM_MAX >> 3; // (upper 5 bits)
PWMG1DTL = 0x00; // Clear the LED PWM duty value
PWMG1DTH = 0x00;
PWMGCLK = (uint8_t)(PWMGCLK_PWMG_ENABLE | PWMGCLK_CLK_IHRC);
PWMG1C = (uint8_t)(PWMG1C_INVERT_OUT | PWMG1C_OUT_PWMG1 | PWMG1C_OUT_PA4);
ledon = false;
// Main loopwhile (1) {
ADCC |= ADCC_ADC_CONV_START; //start ADC conversionwhile( !(ADCC & ADCC_ADC_CONV_COMPLETE) );
PWM_MAX = ADCR; //read the ADC value
PWMG1DTL = PWM_MAX << 5; // Set the LED PWM duty value (lower 3 bits)
PWMG1DTH = PWM_MAX >> 3; // (upper 8 bits)
_delay_ms(20); // little delay for checkled
checkled();
}
}
// Startup code - Setup/calibrate system clockunsignedchar _sdcc_external_startup(void) {
AUTO_INIT_SYSCLOCK();
AUTO_CALIBRATE_SYSCLOCK(TARGET_VDD_MV);
return0; // Return 0 to inform SDCC to continue with normal initialization.
}
Both of these are in a folder as "main.c" with a Makefile that looks like this: