Friday, October 14, 2022

0000 0000 1010 1011

SuperCap II - the low low clock speed

"I'm always chasing rainbows!" - nice lyrics from Thomas Joseph McCarthy and in my case it could be better phrased as "I'm always chasing low clock speeds and energy requirements!"

It's a little fruitless and frustrating at times but I'm a big fan of Thomas Edison who said a couple of things that resonate in these strange pursuits:

1.  "Opportunity is missed by most people because it is dressed in overalls and looks like work.", and

2. "I have not failed 10,000 times. I have successfully found 10,000 ways that will not work."

So in this project the plan was to take a successful light design and change out the NiMH battery for a super-capacitor. The reason is that NiMH batteries have a much smaller cycle lifetime compared to super capacitors.

As well I want to wind down the clock speed on the Padauk PFS154 and see how low it can go. 

Also I will be using the 5050 Leds that I've used before to minimise the energy requirements.

The super-capacitor used in the last video was a "paltry" 4 Farads. The current version is 2x30 Farad capacitors, and I have some bigger ones coming - er, because see 2nd quote from Edison above!

The code side was where the big gains have been made - I was able to successfully wind down the clock to around 17000Hz (!) and still run the "candle".

/*
  Candle with Three PWM

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

  Wed 12 Oct 2022 20:46:06 AEDT

*/

#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[] = {50, 100, 170, 200}; // 1 leds on PA4
uint8_t wavemed[] = {40, 120, 140, 220};  // 1 led on PA3
uint8_t wavefast[] = {40, 80, 150, 240};  // 1 led on PA0

// 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);
  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, 8);
}

// 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);

  PWMG1DTL = 0x00;                // Clear the LED PWM duty value
  PWMG1DTH = 0x00;
  PWMG1CUBL = 0xff; // set PWM counter upper bound to 0xffff
  PWMG1CUBH = 0xff;
  PWMG1C = 0x87;
  PWMG1S = 0b00000000; // prescaler=4, divider=1, no interrupt

  PWMG0DTL = 0x00;                // Clear the LED PWM duty value
  PWMG0DTH = 0x00;
  PWMG0CUBL = 0xff; // set PWM counter upper bound to 0xffff
  PWMG0CUBH = 0xff;
  // PWMG0C = (uint8_t)(PWMG0C_ENABLE | PWMG0C_INVERT_OUT | PWMG0C_OUT_PA0 | PWMG0C_CLK_IHRC);
  PWMG0C = 0x87;
  PWMG0S = 0b00000000; // prescaler=4, divider=1, no interrupt

  PWMG2DTL = 0x00;                // Clear the LED PWM duty value
  PWMG2DTH = 0x00;
  PWMG2CUBL = 0xff; // set PWM counter upper bound to 0xffff
  PWMG2CUBH = 0xff;
  PWMG2C = 0x87;
  PWMG2S = 0b00000000; // prescaler=4, divider=1, no interrupt

  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
  
    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
  
    EASY_PDK_CALIBRATE_ILRC(17000,3000);

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

Compiling was fine and oh so nice to have 2k available instead of the 1k ATTiny13 (although I am still keen to learn Padauk assembler and make this a bit smaller).

$> make program
sdcc -mpdk14 -c --std-sdcc11 --opt-code-size -DPFS154 -DF_CPU=1000000 -DTARGET_VDD_MV=3000 -I. -I../include -o .build/main.rel main.c
sdcc -mpdk14 --out-fmt-ihx -o .output/FadeLED_PFS154.ihx .build/main.rel .build/lib.lib
makebin -p .output/FadeLED_PFS154.ihx .output/FadeLED_PFS154.bin
---------- Segments ----------
.  .ABS.                            00000000    00000000 =           0. bytes (ABS,CON)
.  .ABS.                            00000000    00000000 =           0. bytes (ABS,CON)
HEADER1                             00000000    00000002 =           2. bytes (ABS,CON)
HEADER3                             00000000    00000010 =          16. bytes (ABS,CON)
PREG2                               00000000    00000002 =           2. bytes (ABS,CON)
RSEG0                               00000000    00000002 =           2. bytes (ABS,CON)
DATA                                00000002    00000047 =          71. bytes (REL,CON)
HOME                                00000022    00000002 =           2. bytes (REL,CON)
GSINIT                              00000024    0000006C =         108. bytes (REL,CON)
SSEG                                0000004C    00000001 =           1. bytes (REL,CON)
GSFINAL                             00000090    00000002 =           2. bytes (REL,CON)
CODE                                00000092    000003E6 =         998. bytes (REL,CON)
------------------------------
Size of FadeLED_PFS154.bin: 1144 bytes
easypdkprog -i noblankchk -n PFS154 write .output/FadeLED_PFS154.ihx
Erasing IC... done.
Writing IC (572 words)... done.
Calibrating IC
* ILRC SYSCLK=17000Hz @ 3.00V ... calibration result: 16436Hz (0xF0)  done.

Left to do? Basically I want to keep on "Edisoning" this thing as follows:

a) Change the code to wind back the power of the ramping (e.g. instead of changing PWM from 40 to 220, try 30 to 160 or similar)

b) Upscale the super-caps. At the moment I'm getting around 4 hours of light - I think 6-8 hours is a reasonable goal.

c) Change some or all of the code from C to Assembler. This could be very scary given that assembler programming (which I love) is a pretty good path to madness.

d) Any suggestions??

Here's the video - enjoy!



No comments:

Post a Comment