The perpetual QX5252 (Kelly's Light update)
Sometime ago now I made a "perpetual light" for my dog Kelly which used a QX5252 in SOT23-5 form ( blog and video). The idea was to make a random 4-light LED show which went day and night powered by a single 1.2V NiMH battery recharged daily by a small solar panel.
Although I don't like to dwell too much on this, these random happy perpetual lights are part of a memorial to a very special sweet puppy who left us under tragic circumstance way too early. We miss you Kelly!
By rights a single 1.2(ish)V battery should not be able to drive a microcontroller such as the ATTiny13 or the PFS154, let alone some LEDs as well! That's the joy of the Stable Joule Thief project which uses the QX5252 plus some other bits and pieces to provide a stable power output (at say 4V) for the microcontroller to do it's thing.
For a crazy idea years ago to be made flesh in these wonderful PCBs is extremely satisfying - they work better than I ever expected.
All was going well with "Kelly's Light" until the new puppies decided that munching on the old dog's light would be a great use of their growing teeth!
Exit one much loved project and enter the opportunity to update the concept to include a PFS154 Stable Joule Thief PCB that has recently arrived.
Originally I did not think that I would be able to use the PFS154 as the microcontroller for this project because I couldn't see how to make it sleep between blinks (important for lowering the power usage).
I looked at some old code and had a bit of a go at modifying this using the datasheet as a guide, and in the end I was able to make it work quite well!
There were also some issues associated with the GPIO naming not being conducive to easy manipulation using the old ATTiny13 code, and I solved this by making an array of values for the purpose of accessing all 16 values of LED combinations.
When a "random" number is generated, a light combo from the array is chosen and displayed - a bit clunky but it works well.
Here is the code for the original ATTiny13 version:
// -----------------------------------------------------------------
// Description: A lightshow built on four "random" LEDs which flick
// on or off for a random amount of time depending on which state
// is selected.
//
// Author: OneCircuit Date: 28/01/2021
// -----------------------------------------------------------------
//
// MicroChip ATTINY13 μC
//
// +-\/-+
// RESET--ACD0--5/A0--PCINT5--PB5 1| |8 VCC
// CLKI--ACD3--3/A3--PCINT3--PB3 2| |7 PB2--PCINT2--2/A1--SCK--ADC1
// ACD2--4/A2--PCINT4--PB4 3| |6 PB1--PCINT1---1---MISO--OCOB--INT0*
// GND 4| |5 PB0--PCINT0---0---MOSI--OCOA*
// +----+
// * indicates PWM port
//
#include <avr/sleep.h> // the sleep routines
byte getleds = 0b00000000; // initial all leds off
byte sleeptime = 0; // initial timer = 0
uint16_t myrand = 2901; // initial seed -> happy birthday
void setup() {
DDRB = 0b00001111; // only four outputs
PORTB = 0b11110000; // the rest input pullup
myrand = myrand+analogRead(A2); // reset the seed
}
ISR(WDT_vect) {
}
// generate a "random" number between small and big
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 powerDown(void)
{
MCUCR = 0b00110000; // see datasheet
WDTCR = 0b01000010; // 64ms
ADCSRA &= ~(1 << ADEN); // turn off ADC
ACSR |= (1 << ACD); // turn off Analog comparator.
sei(); // enable global interrupts
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep deeply little one
sleep_enable(); // enable sleep mode
cli(); // Disable BOD steps
BODCR = (1 << BODSE) | (1 << BODS);
BODCR = (1 << BODS);
sei();
sleep_cpu();
sleep_disable(); // ISR routine returns here so wake up
ADCSRA |= (1 << ADEN); // turn on ADC
ACSR = (0 << ACD); // turn on Analog comparator.
delay(10); // settle time
}
void loop() {
getleds = gimmerand(0, 16); // returns 0..15
sleeptime = gimmerand(1, 25);
PORTB = getleds;
for (int howmanysleeps = 0; howmanysleeps < sleeptime; howmanysleeps++) {
powerDown();
}
}
// ----------------------------------------------------------------- // Description: A lightshow built on four "random" LEDs which flick // on or off for a random amount of time depending on which state // is selected. // // Author: OneCircuit Date: Wed 11 Jan 2023 15:48:39 AEDT // ----------------------------------------------------------------- // PFS154 pinout // _________ // / | // 1--|VCC GND|--8 // 2--|PA7 PA0|--7 // 3--|PA6 PA4|--6 // 4--|PA5 PA3|--5 // |__________| #include <stdlib.h> #include "../device.h" #include "../easy-pdk/calibrate.h" #include "../auto_sysclock.h" #include "../delay.h" // Variables and Constants uint8_t getleds = 0; // variable for the led configuration uint16_t mytime = 0; // variable for the time asleep uint16_t myrand = 2901; // happy birthday // array of values for the leds on and off int ledlights[16] = { 0b00000000, // all off 0b00000001, // PA0 on 0b00001000, // PA3 on, etc 0b01000000, 0b00010000, 0b00001001, 0b00010001, 0b01000001, 0b01010000, 0b01001000, 0b00011000, 0b00011001, 0b01001001, 0b01010001, 0b01011000, 0b01011001 }; // pseudo-random number generator 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; } // sleep routine - see datasheet void sleep(uint8_t howmany) { for (uint8_t thiscount = 0; thiscount <= howmany; thiscount++) { TM2S = TM2S_PRESCALE_NONE | TM2S_SCALE_DIV8; __stopexe(); } } void main() { // Setup parameters for gpio and sleep PAC = 0b01011001; // All inputs except PA0, PA3, PA4, PA6 PADIER = 0; // Disable pins as wakeup source INTEN = 0; // All interrupts are disabled INTRQ = 0; MISC = MISC_FAST_WAKEUP_ENABLE; // Enable faster wakeup TM2C = TM2C_CLK_ILRC | TM2C_MODE_PWM; TM2CT = 0; TM2B = 1; // Loop while (1) { getleds = gimmerand(0, 16); // random lights PA = ledlights[getleds]; // set PA to light configuration from array mytime = gimmerand(1, 40); // random time sleep(mytime); // sleep } } // Startup code - Setup/calibrate system clock unsigned char _sdcc_external_startup(void) { CLKMD = CLKMD_ILRC | CLKMD_ENABLE_ILRC | CLKMD_ENABLE_IHRC; CLKMD = CLKMD_ILRC | CLKMD_ENABLE_ILRC; PDK_SET_SYSCLOCK(SYSCLOCK_ILRC); EASY_PDK_CALIBRATE_ILRC(50000, 4000); return 0; // Return 0 to inform SDCC to continue with normal initialization. }
The soldering up of the lights and the board was so trivial compared to the earlier version.
Also, check out the "magnetic soldering" technique used around the 12 minute mark of the video below!
It all went well and the new (slightly expanded) version of the light is now sitting on the windowsill generating random combinations of LEDS night and day - "Kelly's Light" continues to shine.
No comments:
Post a Comment