Saturday, April 3, 2021

0000 0000 0101 1011

Why PFS154? Part two...

In a previous blog I celebrated the unlocking of (not one, not two, but...) three independent PWM channels on the Pesky PFS154 Padauk microcontroller. This post will be about the code journey to that point. On the AVR version (ATTiny13) the process was as follows.

1. Choose lower and upper range values for PWM ramping
2. Generate LFSR random numbers
3. Adjust the PWM of each channel
4. Wait a bit
5. Rinse and repeat

The process for the PFS154 is exactly the same, excepting we have three channels and there are some changes to how this is coded and compiled under the FreePDK SDCC toolchain.

I was originally dreading this project because way back when I first achieved a working padauk programmer, I grabbed the FreePDK examples from github, compiled the code (success) and plugged in an LED (failure). No fading as expected.

I eventually (weeks of coding later) "fixed" it by trawling through the back alleys of the interwebs using Chinese to English translation services, reading and re-reading the datasheet and then used my patented "trial and error and error and error and error..." method of programming.

The original FadeLED code (not working):

  PWMG1C = (uint8_t)(PWMG1C_ENABLE | PWMG1C_INVERT_OUT | PWMG1C_OUT_PA4 | PWMG1C_CLK_IHRC);
  PWMG1S = 0x00;                  // No pre-scaler

Without going into the torturous process of elimination, I ended up chucking out the whole "(uint8_t)(PWMG...blah blah" approach (which relies on various *.h files scattered about) and went with the direct accessing of registers in binary. This is my tried and tested preferred approach which, despite the zealots, makes the most sense to me as it is straight from the datasheet.

The code that worked:

  PWMG1C = 0b10000111; // see datasheet


  PWMG1S = 0b00000000; // see datasheet

Once one PWM channel was behaving itself (ramping up and down gently), I turned my attention to the other two channels promised in the datasheet.

Honestly it was a bit of a "cut and paste" hit job and took only a few minutes. The sight of those little LEDs pulsing away in unity was quite a nice reward for all of that brow crinkling.

It was only days later that I realised I got a little lucky as each PWM is matched to a different pin depending on the binary number thrown to PWMG*C where * is either 0,1 or 2. So for instance if I throw PWMG2C = 0b10000111 then the datasheet shows the following "1"s highlighted:

So looking at the table above and the pinout, 0b10000111 enables the PWM (bit 7), 0b10000111 chooses PA3/pin5 (bits 3-1) and 0b10000111 chooses IHRC (Internal High RC oscillator) as the clock source (bit 0). See highlighting.

Similarly for PWMG0C and PWMG1C using 0b10000111 selects PA0 and PA4 as PWM outputs respectively. Clear as mud!

Sidenote: I threw in a PFS173 at this point to see if the chips are interchangeable. They are not - for although the code compiled OK, the registers for the PFS173 for PWM are different to the PFS154 - great!

The last hurdle was to see if the original code from the ATTiny13 candle project would just port across - and sure enough apart from the odd formatting/syntax issue (bool vs boolean as an example), not only did the old code compile for the PFS154, but also it uploaded and produced awesome candlely-goodness on the breadboard.


/*
 -----------------------------------------------------------------
 Description: A fake candle <sigh> running on a PFS154 padauk μC
 connected to 3 leds via three channels, one running fast, one
 medium and one slow.
 
 Author: OneCircuit                    Date: 02/04/2021
 www.onecircuit.blogspot.com
 -----------------------------------------------------------------

*/

#include <stdint.h>
#include <stdlib.h>
#include <pdk/device.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};
uint8_t wavemed[] = {40, 120, 140, 220};
uint8_t wavefast[] = {40, 80, 150, 240};

// 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_ms(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, 4);
}

// 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(6 + 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();

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

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

Next I think that I will make up a PCB and shoehorn the beast into a jar with a Solar Panel running the show. Can't wait!




1 comment:

  1. Hi, Nice to find your blog. Please can you guide step by step about IDE/Compiler to program Paduk microcontrollers. Really very appreciated favor would be for people. Thanks.

    ReplyDelete