Well it's 2025 and apart from craziness erupting all over the planet the big news is RISCV continues to impress the punters with it's speed, reliability and efficiency.
Orange Pi are now officially on the RISCV bandwagon which means of course so am I, with a delivery recently of the RV2 single board computer.
It's a lovely piece of kit and in the video below I take it for a spin and hook it up to a touch screen via the Ubuntu sanctioned OS image.
Only one hiccup - the EMMC card needs to be loaded via the OS, not from the image itself. It's a minor issue that I was easily able to solve (in fact I over complicated the whole thing a bit - as usual).
Sit back and enjoy - and please like and subscribe!
It's 0000 0001 0000 0000, or in other words a momentous binary moment of 256 videos for this channel, and...
After around 5 years of development I'm so happy to report that one of my long term projects has been converted from the energy source of a rechargeable (NiMH) battery to a super capacitor.
It seemed unlikely at the outset of this endeavour as there were four major miracles that needed to happen.
1. How to charge a super capacitor to 3.8V from a QX5252 that is used to charging a 1.2V NiMH battery? See this video for the - partial - solution.
2. Is it possible for a super capacitor to run a micro-controller for any significant length of time? See this video for the answer.
3. How does a micro-controller with no ADC determine if there is no sunshine about? See this video for the answer.
4. And finally - putting all of these components together in a fake "candle"? Will that even work? Well, see the video linked below.
Here is the final(ish) code:
/* Super Capacitor Candle with Three PWM Pseudo-random flickering to simulate a candle. Output is via 3xPWM channels, variables can be changed to alter the simulation. Code includes checking to see if there is light and sleeping during the day while the capacitor is charging. Tue 27 May 2025 14:13:42 AEST DEVICE = PFS154 F_CPU = 50000 Hz TARGET_VDD = 3.8 V _________ / | 1--|VCC GND|--8 2--|PA7 PA0|--7 3--|PA6 PA4|--6 4--|PA5 PA3|--5 |__________|PAC = 0b00000000 all inputs as standard output off on pullup ------ --- -- ------pin 2 PA7 PAC = 0b10000000, PA = 0b10000000, PA = 0b00000000, PAPH = 0b10000000pin 3 PA6 PAC = 0b01000000, PA = 0b01000000, PA = 0b00000000, PAPH = 0b01000000pin 4 PA5 PAC = 0b00100000, PA = 0b00100000, PA = 0b00000000, PAPH = 0b00100000pin 5 PA3 PAC = 0b00001000, PA = 0b00001000, PA = 0b00000000, PAPH = 0b00001000pin 6 PA4 PAC = 0b00010000, PA = 0b00010000, PA = 0b00000000, PAPH = 0b00010000pin 7 PA0 PAC = 0b00000001, PA = 0b00000001, PA = 0b00000000, PAPH = 0b00000001*/// libraries needed#include <stdbool.h>#include <stdint.h>#include <stdlib.h>#include "../auto_sysclock.h"#include "../delay.h"#include "../device.h"#include "../easy-pdk/calibrate.h"uint16_t myrand = 2901; // happy birthday// initialise variablesuint8_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;
// variables for different types of flicker - change to suit!constuint8_t percentnormal = 82; // cutoff for normal/calmconstuint8_t percentsputter = 20; // cutoff for sputtering/normaluint8_t flickdelay = 40; // initial speed of flickerconstuint8_t flickdelaysputter = 9; // "sputtering" activityconstuint8_t flickdelaynormal = 40; // "normal" activityconstuint8_t flickdelaycalm = 95; // "calm" activityuint8_t choosearray = 1; // normal wavesuint8_t delaycounter = 50;
uint8_t delaydelay = 20;
bool sunshine = false; // is the sun shining?// can change these too if you want to play with the candleuint8_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;
voidmydelay(uint8_t counter) {
for (uint8_t thiscount = 0; thiscount <= counter; thiscount++) {
_delay_us(1);
}
}
// my random routine that hasn't changed for years!uint16_tgimmerand(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;
}
voidgetnewslow(uint8_t whicharray) {
slowstart = gimmerand(waves[whicharray][0], waves[whicharray][1]);
slowend = gimmerand(waves[whicharray][2], waves[whicharray][3]);
}
voidgetnewmed(uint8_t whicharray) {
medstart = gimmerand(waves[whicharray + 1][0], waves[whicharray + 1][1]);
medend = gimmerand(waves[whicharray + 1][2], waves[whicharray + 1][3]);
}
voidgetnewfast(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);
}
// interrupt triggered when the sun goes away, voltage of small// capacitor is drainedvoidInterrupt(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 voltageboolchecksolar(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;
}
voidcandlingon() {
PAC = 0b00110001;
PA = 0b00000000;
// see datasheet
PWMG1DTL = 0x00;
PWMG1DTH = 0x00;
PWMG1CUBL = 0xff;
PWMG1CUBH = 0xff;
PWMG1C = 0b10100110;
PWMG1S = 0b00000000;
PWMG0DTL = 0x00;
PWMG0DTH = 0x00;
PWMG0CUBL = 0xff;
PWMG0CUBH = 0xff;
PWMG0C = 0b10100110;
PWMG0S = 0b00000000;
PWMG2DTL = 0x00;
PWMG2DTH = 0x00;
PWMG2CUBL = 0xff;
PWMG2CUBH = 0xff;
PWMG2C = 0b10101010;
PWMG2S = 0b00000000;
getnewfast(choosearray);
getnewslow(choosearray);
getnewmed(choosearray);
slowcounter = slowstart;
fastcounter = faststart;
medcounter = medstart;
while (!sunshine) {
// ramp up slowif (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 medif (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 fastif (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;
getnewfast(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
flickdelay = flickdelaycalm;
choosearray = 6;
delaycounter = 100 - delaycounter;
} elseif (delaycounter > percentsputter) { // "normal"
flickdelay = flickdelaynormal;
choosearray = 3;
} else { // sputtering
flickdelay = flickdelaysputter;
choosearray = 0;
}
sunshine = (bool)checksolar();
delaycounter = delaycounter * delaydelay;
}
// finally the actual PWM output
PWMG2DTL = slowcounter & 255;
PWMG2DTH = slowcounter;
PWMG0DTL = fastcounter & 255;
PWMG0DTH = fastcounter;
PWMG1DTL = medcounter & 255;
PWMG1DTH = medcounter;
}
}
// close downvoidcandlingoff() {
PWMG2DTL = 0;
PWMG2DTH = 0;
PWMG0DTL = 0;
PWMG0DTH = 0;
PWMG1DTL = 0;
PWMG1DTH = 0;
PWMG0C = 0b00100000;
PWMG1C = 0b00100000;
PWMG2C = 0b00100000;
}
// here is where the uC goes to sleepvoidsleepnow() {
__disgint(); // disable global interrupts
MISC |= MISC_FAST_WAKEUP_ENABLE; // fast wakeup
PAC = 0;
PA = 0;
PAPH = 0xFF;
PBDIER = 0; // there is no port B on the -S08 package,// without setting this to 0 the uC will wake unexpectedly
INTEN = 0b00010000; // enable comparator interrupt
INTRQ = 0b00010000;
__engint(); // enable global interrupts
__stopsys(); // go to sleep
}
voidmain() {
// page 66 datasheet
GPCC = 0b10010000;
GPCS = 0b00000011; // sensitivity is affected by bit 0-3
_delay_ms(100); // small settle time delaywhile (1) {
if (!sunshine) {
candlingon(); // it's dark, start candling action
} else {
candlingoff();
sleepnow(); // it's light, go to sleep
__reset(); // seems harsh but helps with the flickering anew
}
}
}
// Startup code - Setup/calibrate system clockunsignedchar_sdcc_external_startup(void) {
/* Set the system clock note it is necessary to enable IHRC clock while updating clock settings or CPU will hang */
PDK_USE_ILRC_SYSCLOCK(); /* use ILRC 55kHz clock as sysclock */
PDK_DISABLE_IHRC(); /* disable IHRC to save power */
EASY_PDK_CALIBRATE_ILRC(F_CPU, TARGET_VDD_MV); // Makefile has these values/* DEVICE = PFS154 F_CPU = 50000 TARGET_VDD_MV = 3800 TARGET_VDD = 3.8 */return0; // Return 0 to inform SDCC to continue with normal initialization.
}
Apologies for the non-standard circuit diagram rendered by hand!
And here is a picture of the final product.
If you think this is awesome - like I do - please share widely as feedback on this sort of breakthrough is a really important part of the development of these ideas into future projects.
Well here is another insight into the harmful nature of late night 'under-the-influence' online shopping, although I still enjoyed some of the tasty morsels that arrived here in "downtown Tasmania" this round - there are potentially some great projects ahead.
Also I'm very chuffed to have a legit RISCV OPi in my hands after this video!
Enjoy the video, brought to you for the first time from TWO different angles!
Many times in the past I've fiddled with electronics projects and thought "That'd be useful one day for <insert some long term unicorn quest that's never going to happen>".
So for instance many moons ago in a magnanimous gesture to help out a disinterested thimblerigger, I slaved over an L298N project as part of a commercial gin venture where a motor rocked a gin barrel and <blah blah blah> long story short I gave away the hardware and software and did not get any gin! 😲🤬
Anyhoo, I've nearly recovered my benign composure and here we are in 2025 and I have need for a motor driver to push a linear actuator around so it in turn can torment a window (louvre to be precise) into opening and closing.
I'm also going to once again make the code available to you for free, but this time you can buy your own damn hardware! 😂
The whole shebang worked pretty well and you can check out the github repo for the code and the video linked below for insight into my process.
Any questions feel free to ask in the comments section below the video - also please consider supporting the channel by liking, subscribing and sharing as the YT algorithm is punching me in the guts a bit at the moment!
Quite awhile ago I programmed an ESP32 OTA framework for updating ESP32 firmware "over the air" - in other words it is possible to update the firmware (the actual code running on the ESP32) live at the site of the device with no internet, WiFi etc required.
That video (and blog) showed proof of concept but the project was a bit contrived (simply reporting and responding to temperature).
In the back of my mind were two unresolved issues:
1. Would this work on a "real" project - in this case the control of "grow lights" in a Hot House scenario, and
2. I should really clean up the code to make it more friendly, and furthermore take potential users through the long and boring process of modifying the base files and uploading to an ESP32 in preparation for deployment.
Hence - this video (and some code reworking)!
Firstly I use the KC868 A6 that we have seen before (blog and video).
Then there is interminable waffle as I put together this project - but feel free to step through to the onsite testing if you're a bit bored! I've also put timestamps in the video description for your pleasure.
All the files on the repo - enjoy the video - try the like and subscribe button, they're free - and also please comment in the section below the video to give me some feedback/suggestions!
Every day I am amazed at the many new and wonderful devices out there to make the IoT life a bit easier - and a couple of months ago when contemplating an IoT hothouse, the following KC 868 A6 device grabbed my attention.
But...would it work? Well I had to source the following software for the Arduino IDE to chew on:
The only problem after that was an hour wasted on a DATA ONLY usbc cable (since dispatched to the other side), but actually the whole process was pretty straight forward.
The device works, and works well! Any questions or comments, please put em in the comments section of the following YouTube video - enjoy!
Just the usual assortment of lovely bits from across the globe. And of course also we have the usual "ums" and "ahs" of a confused individual as I try to figure out...