Friday, November 25, 2022

0000 0000 1011 0001

A Padauk PFS154 based "candle"

I have made a few variations of the ATTiny13 based "candle", including dedicated PCBs and assembler code driving two PWM channels to produce a candley flicker.

Lately I have branched out to a dedicated Padauk PFS154 PCB with a combined Stable Joule Thief circuit.

There are so many projects, videos, blogs etc over the years that I have made a little map to help me track what is going on!

I'm expecting a delivery from PCBWay for a small dedicated board (in orange on the diagram) for the LEDs of the project (more on this later), but the current work (in yellow on the diagram) is in the power delivery for the project and also with a PFS154 based PCB. 

The green node on the diagram is pie in the sky ASM code for the Padauk chip. It is my strong desire to get into this aspect of the project, as I really enjoyed the assembly version of the ATTiny13 candle.

The current code is in "C" and looks like this:

/*
  Candle with Three PWM

  Pseudo-random flickering to simulate a candle. Output is
  via 3xPWM channels, variables can be changed to 
  alter the simulation

*/

#include <stdint.h>
#include <stdlib.h>
#include "../device.h"
#include "../easy-pdk/calibrate.h"
#include "../auto_sysclock.h"
#include "../delay.h"
#include <stdbool.h>

#define LED4_BIT 4
#define LED0_BIT 0
#define LED3_BIT 3

uint16_t myrand = 2901;  // happy birthday

// global variables randomised later for flickering, using the
// "waveslow" and "wavefast" arrays
uint8_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;

uint8_t waveslow[] = {20, 30, 70, 100};
uint8_t wavemed[] = {25, 40, 100, 120};
uint8_t wavefast[] = {20, 30, 120, 140};

// booleans to keep track of "fading up" or "fading down"
// in each of the slow and fast cycles
bool fastup = true;
bool slowup = true;
bool medup = true;

void mydelay(uint8_t counter) {

  for (uint8_t thiscount = 0; thiscount <= counter; thiscount++) {
    _delay_us(1);
  }
}

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 getnewslow() {
  slowstart = gimmerand(waveslow[0], waveslow[1]);
  slowend = gimmerand(waveslow[2], waveslow[3]);
}

void getnewmed() {
  medstart = gimmerand(wavemed[0], wavemed[1]);
  medend = gimmerand(wavemed[2], wavemed[3]);
}

// initialise a new fast cycle including the new speed of cycle
void getnewfast() {
  faststart = gimmerand(wavefast[0], wavefast[1]);
  fastend = gimmerand(wavefast[2], wavefast[3]);
  faster = gimmerand(1, 3);
}

// Main program
void main() {

  // Initialize hardware
  // Set LED as output (all pins are input by default)
  PAC |= (1 << LED4_BIT) | (1 << LED0_BIT) | (1 << LED3_BIT);

  // see datasheet
  PWMG1DTL = 0x00; 
  PWMG1DTH = 0x00;
  PWMG1CUBL = 0xff; 
  PWMG1CUBH = 0xff;
  PWMG1C = 0b10100111;
  PWMG1S = 0b00000000;

  PWMG0DTL = 0x00;   
  PWMG0DTH = 0x00;
  PWMG0CUBL = 0xff;
  PWMG0CUBH = 0xff;
  PWMG0C = 0b10100111;
  PWMG0S = 0b00000000;

  PWMG2DTL = 0x00; 
  PWMG2DTH = 0x00;
  PWMG2CUBL = 0xff;
  PWMG2CUBH = 0xff;
  PWMG2C = 0b10100111;
  PWMG2S = 0b00000000;

  getnewfast();
  getnewslow();
  getnewmed();
  slowcounter = slowstart;
  fastcounter = faststart;
  medcounter = medstart;

  // Main processing loop
  while (1) {

    // ramp up slow
    if (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();
      }
    }

    // ramp up med
    if (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();
      }
    }

    // ramp up fast
    if (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();
      }
    }
    // delay + a re-purposed random for ramp speeds
    mydelay(3 + faster);

    PWMG1DTL = slowcounter & 255;
    PWMG1DTH = slowcounter;
    PWMG0DTL = fastcounter & 255;
    PWMG0DTH = fastcounter;
    PWMG2DTL = medcounter & 255;
    PWMG2DTH = medcounter;

  }
}

// Startup code - Setup/calibrate system clock
unsigned char _sdcc_external_startup(void) {

  // Initialize the system clock (CLKMD register) with the IHRC, ILRC, or EOSC clock source and correct divider.
  // The AUTO_INIT_SYSCLOCK() macro uses F_CPU (defined in the Makefile) to choose the IHRC or ILRC clock source and divider.
  // Alternatively, replace this with the more specific PDK_SET_SYSCLOCK(...) macro from pdk/sysclock.h
  
   // AUTO_INIT_SYSCLOCK();

    PDK_SET_SYSCLOCK(SYSCLOCK_ILRC_DIV4);

  // Insert placeholder code to tell EasyPdkProg to calibrate the IHRC or ILRC internal oscillator.
  // The AUTO_CALIBRATE_SYSCLOCK(...) macro uses F_CPU (defined in the Makefile) to choose the IHRC or ILRC oscillator.
  // Alternatively, replace this with the more specific EASY_PDK_CALIBRATE_IHRC(...) or EASY_PDK_CALIBRATE_ILRC(...) macro from easy-pdk/calibrate.h
  
    // AUTO_CALIBRATE_SYSCLOCK(TARGET_VDD_MV);

     EASY_PDK_CALIBRATE_ILRC(18000,3300);

  return 0;   // Return 0 to inform SDCC to continue with normal initialization.
}

The super capacitor node is almost dead at the moment as I cannot get the project to go beyond 4-5 hours of light, even using an 80F Cap.

I'll probably go back to cheap and nasty NiCd or NiMH AAA batteries. There are 250F Lithium Supercaps about, and so I maintain a "flicker" (pun intended) of interest in this branch of the project.

This blog and video, however, is about the PFS154 dedicated Stable Joule Thief (SJT) board.

It's not specifically for the candle project, but the board uses the well tested SJT circuitry to provide enough juice for a PFS154 to do it's thing. The big balancing act has been between lower power delivery, slower clock speed and realistic candley action.

Every time I made a code change while working on this (and there were so many!) I rushed to a dark linen cupboard with the breadboard to test the resultant flickering.

My long time collaborator began to think that I may have lost the plot, but when they found themselves in the cupboard as well (for a second opinion) then things became even more tense!

In the end I found the right balance for both of us, and so the first of the PFS154 candles rolls off the assembly line. May there be many more - they are truely lovely!




No comments:

Post a Comment