Thursday, January 26, 2023

0000 0000 1011 1010

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