Thursday, April 25, 2024

0000 0000 1110 0110

SOP8 WARS!

Recently Atul Ravi of PICUNO fame asked me why I "switched" from ATtiny13 to the PFS154. I haven't really - in fact the ATtiny13 is still near and dear to my heart, not least because I spent six months writing assembly code snippets for this little microcomputer. You can read all about it by clicking on this link.

However, I do use the PFS154 for the candle project (among others) for the following reasons:

  • Exotic new tech interests me
  • Lower power requirements
  • "Free" programmer (which cost me months of pain)
  • Three PWM channels, versus two
  • Ability to wind down the voltage and current requirements by running the IC as low as 15kHz!
  • Challenging to learn a new toolchain

In the great comparison, here is what ChatGPT had to say (usual caveats apply):

It's not all beer and skittles though, and one missing feature that rankled was the lack of an analogue to digital converter (ADC) on the PFS154. I mean - why!? It's such a basic requirement to take an analogue signal (e.g. distance from object using an ultrasonic signal).

Anyway, I'm not bitter at all.

And so enter stage left the previously shelved PFS173, on the bench at 14 cents each from LCSC ordered in June 2020 (now discontinued for some reason).

Originally I just wanted to talk to it (e.g. blinky), but in the end not only did I get some PWM action, but also the ADC - this in itself is interesting as to date I have not seen any code available for linking ADC to PWM on this chip. See for instance the github site for examples for these chips:

I do have one embarrassing admission despite the win. My final code does not use interrupts but rather gallops along around 100kHz and checks every single time it loops for the ADC conversion, and the "time" for the LED to blink. It's positively neanderthal, but nonetheless effective.

I will aim to find out more about the PFS173 interrupt landscape and update this travesty in the future. In the meantime, here is the code for the blink and PWM version.

/*
  Test Code for PFS173 Blink and PWM

                      +-\/-+
                VDD  1|    |8  GND
             PA7/X1  2|    |7  PA0/AD10/CO/INT0/PG0PWM
             PA6/X2  3|    |6  PA4/AD9/CIN+/CIN1-/INT1/PG1PWM
   PA5/PRSTB/PG2PWM  4|    |5  PA3/AD8/CIN0-/TM2PWM/PG2PWM
                      +----+

  Tue 23 Apr 2024 17:35:37 AES

  https://www.youtube.com/c/onecircuit-as
  https://onecircuit.blogspot.com/

*/

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

#define LED4_BIT 4 // PWM
#define LED3_BIT 3 // Blinky

#define turnLedOn()   PA &= ~(1 << LED3_BIT)
#define turnLedOff()  PA |= (1 << LED3_BIT)

bool ledon = true;

// crude timing for blinky
long mytimer = 30;
volatile long counter = 0;

// PFS173 PWM
#define PWM_MAX               255

// check in here to see if LED is on or off
void checkled() {

  counter = counter + 1;
  if (counter > mytimer) {
    counter = 0;
    if (ledon) {
      turnLedOff();
      ledon = false;
    }
    else {
      turnLedOn();
      ledon = true;
    }
  }
}

// Main program
void main() {

  // Initialize hardware
  PAC |= (1 << LED4_BIT); 
  PAC |= (1 << LED3_BIT);

  PWMGCUBL = PWM_MAX << 5;   // Set the PWM upper bound (lower 3 bits)
  PWMGCUBH = PWM_MAX >> 3;   // (upper 5 bits)
  PWMG1DTL = 0x00;           // Clear the LED PWM duty value
  PWMG1DTH = 0x00;
  PWMGCLK = (uint8_t)(PWMGCLK_PWMG_ENABLE | PWMGCLK_CLK_IHRC);
  PWMG1C = (uint8_t)(PWMG1C_INVERT_OUT | PWMG1C_OUT_PWMG1 | PWMG1C_OUT_PA4);

  ledon = false;

  // Main loop
  while (1) {
  
  uint8_t fadeValue;

    // Fade in from min to max in increments of 5
    for (fadeValue = 0; fadeValue < PWM_MAX; fadeValue += 5) {

      PWMG1DTL = fadeValue << 5;  // Set the LED PWM duty value (lower 3 bits)
      PWMG1DTH = fadeValue >> 3;  // (upper 8 bits)
      _delay_ms(30);              // wait for 30 milliseconds to see the dimming effect
      checkled();
    }

    // Fade out from max to min in increments of 5
    for (fadeValue = PWM_MAX; fadeValue > 0; fadeValue -= 5) {

      PWMG1DTL = fadeValue << 5;  // Set the LED PWM duty value (lower 3 bits)
      PWMG1DTH = fadeValue >> 3;  // (upper 8 bits)
      _delay_ms(30);              // wait for 30 milliseconds to see the dimming effect
      checkled();
    }
  }
}

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

  AUTO_INIT_SYSCLOCK();
  AUTO_CALIBRATE_SYSCLOCK(TARGET_VDD_MV);

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

And here is the code which does Blink and ADC linked to PWM (a triumph!):

/*
  Test Code for PFS173 ADC and PWM

                      +-\/-+
                VDD  1|    |8  GND
             PA7/X1  2|    |7  PA0/AD10/CO/INT0/PG0PWM
             PA6/X2  3|    |6  PA4/AD9/CIN+/CIN1-/INT1/PG1PWM
   PA5/PRSTB/PG2PWM  4|    |5  PA3/AD8/CIN0-/TM2PWM/PG2PWM
                      +----+

  Wed 24 Apr 2024 22:38:05 AEST

  https://www.youtube.com/c/onecircuit-as
  https://onecircuit.blogspot.com/

*/

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

#define LED4_BIT 4 // PWM
#define LED3_BIT 3 // Blinky
#define LED0_BIT 0 // ADC


#define turnLedOn()   PA &= ~(1 << LED3_BIT)
#define turnLedOff()  PA |= (1 << LED3_BIT)

bool ledon = true;

// crude timing for blinky
long mytimer = 45;
volatile long counter = 0;

volatile int PWM_MAX = 255; // will be set by ADC

// check in here to see if LED is on or off
void checkled() {

  counter = counter + 1;
  if (counter > mytimer) {
    counter = 0;
    if (ledon) {
      turnLedOff();
      ledon = false;
    }
    else {
      turnLedOn();
      ledon = true;
    }
  }
}

// Main program
void main() {

  // Initialize hardware
  PAC |= (1 << LED4_BIT); 
  PAC |= (1 << LED3_BIT);
  PAC |= (0 << LED0_BIT);

  // setup ADC
  PAPH |= (0 << LED0_BIT);
  PADIER |= (0 << LED0_BIT);
  ADCRGC = 0; // VDD is ref
  ADCC = ADCC_ADC_ENABLE | ADCC_CH_AD10_PA0; //enable ADC and use channel 10 (PA0)

  // setup PWM
  PWMGCUBL = PWM_MAX << 5;   // Set the PWM upper bound (lower 3 bits)
  PWMGCUBH = PWM_MAX >> 3;   // (upper 5 bits)
  PWMG1DTL = 0x00;           // Clear the LED PWM duty value
  PWMG1DTH = 0x00;
  PWMGCLK = (uint8_t)(PWMGCLK_PWMG_ENABLE | PWMGCLK_CLK_IHRC);
  PWMG1C = (uint8_t)(PWMG1C_INVERT_OUT | PWMG1C_OUT_PWMG1 | PWMG1C_OUT_PA4);

  ledon = false;

  // Main loop
  while (1) {
  
  ADCC |= ADCC_ADC_CONV_START;  //start ADC conversion
  while( !(ADCC & ADCC_ADC_CONV_COMPLETE) );  
  PWM_MAX = ADCR;               //read the ADC value
  PWMG1DTL = PWM_MAX << 5;      // Set the LED PWM duty value (lower 3 bits)
  PWMG1DTH = PWM_MAX >> 3;      // (upper 8 bits)
  _delay_ms(20);                // little delay for checkled           
  checkled();   
  }
}

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

  AUTO_INIT_SYSCLOCK();
  AUTO_CALIBRATE_SYSCLOCK(TARGET_VDD_MV);

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

Both of these are in a folder as "main.c" with a Makefile that looks like this:

DEVICE = PFS173
F_CPU = 100000
TARGET_VDD_MV = 5000
TARGET_VDD = 5.0

# ---------------------------------------------------------------------

OUTPUT_NAME = BlinkLED_$(DEVICE)

include ../arch-from-device.mk

ROOT_DIR = ..
BUILD_DIR = .build
OUTPUT_DIR = .output

OUTPUT = $(OUTPUT_DIR)/$(OUTPUT_NAME)

SOURCES = main.c
OBJECTS = $(patsubst %.c,$(BUILD_DIR)/%.rel,$(SOURCES))

# http://sdcc.sourceforge.net/doc/sdccman.pdf
COMPILE = sdcc -m$(ARCH) -c --std-sdcc11 --opt-code-size -D$(DEVICE) -DF_CPU=$(F_CPU) -DTARGET_VDD_MV=$(TARGET_VDD_MV) -I. -I$(ROOT_DIR)/include
LINK = sdcc -m$(ARCH)
EASYPDKPROG = easypdkprog

# symbolic targets:
all: size

print-%: ; @echo $* = $($*)

$(BUILD_DIR)/%.rel: %.c
	@mkdir -p $(dir $@)
	$(COMPILE) -o $@ $<

$(OUTPUT).ihx: $(OBJECTS)
	@mkdir -p $(dir $(OUTPUT))
	$(LINK) --out-fmt-ihx -o $(OUTPUT).ihx $(OBJECTS)

$(OUTPUT).bin: $(OUTPUT).ihx
	makebin -p $(OUTPUT).ihx $(OUTPUT).bin

build: $(OUTPUT).bin

size: build
	@echo '---------- Segments ----------'
	@egrep '(ABS,CON)|(REL,CON)' $(OUTPUT).map | gawk --non-decimal-data '{dec = sprintf("%d","0x" $$2); print dec " " $$0}' | /usr/bin/sort -n -k1 | cut -f2- -d' '
	@echo '------------------------------'
	@stat -L --printf "Size of $(OUTPUT_NAME).bin: %s bytes\n" $(OUTPUT).bin

program: size
	$(EASYPDKPROG) -n $(DEVICE) write $(OUTPUT).ihx

run:
	$(EASYPDKPROG) -r $(TARGET_VDD) start

clean:
	rm -r -f $(BUILD_DIR) $(OUTPUT_DIR)

Finally, here is all the blinky, PWM and ADC action that you could possible need as SSOP8 wars comes to a bench near you.



Saturday, April 20, 2024

0000 0000 1110 0101

Raspberry Pi Uno?

Atul Ravi very kindly sent me a prototype for his Arduino Uno form factor RP2040 based development board called the PICUNO.

It's a very tasty piece of kit with some lovely specs - in the VERY long video posted below I explore some of it's features.

The best bit of the video - a lovely interview with Atul! I've timestamped the video so that you can skip around if you're board. Leave a comment - what do you think of Atul's creation?

Update on shield failure from Atul’s GitHub: Fundamental flaw on PicUNO. On UNO, first pin on Power headers is GND. Second is 5V. On PicUNO it is vice-versa. TO be fixed.



Sunday, March 24, 2024

0000 0000 1110 0100

An old function generator

On the scrap heap with an oscilloscope featured before on this channel and blog was an ancient function generator.

Does it work - is it accurate? See the video below for the exploration of this device.



Sunday, March 17, 2024

0000 0000 1110 0011

Solar Hall Light

V3 of the Hall Light sees the PFS154 swing into action (well, only at around 50kHz) and as well the Stable Joule Thief throwing solar energy at a little AAA NiCad battery.

The code has come a long way since the early days, and here it is in all it's glory:

/*
   OneCircuit Hall Light V3
   (based on PFS154)
   Uses PIR to sense warm body, PIR wakes up IC
   PFS154 uses PWM to light up path for given
   number of seconds, then sleep resumes.

                      +-\/-+
                VDD  1|    |8  GND
             PA7/X1  2|    |7  PA0/INT0/COM2/CO/PG0PWM
             PA6/X2  3|    |6  PA4/COM3/CIN+/CIN4-/PG1PWM
   PA5/PRSTB/PG2PWM  4|    |5  PA3/TM2PWM/COM4/CIN1-/PG2PWM
                      +----+

   Mon 11 Mar 2024 14:31:51 AEDT

*/

#include <pdk/device.h>
#include "auto_sysclock.h"
#include "delay.h"
#include <stdbool.h>

// Pin Defines - all pins are on port A
#define WAKEUP_PIN            0     // PIR sensor input pin, used to wake from deep sleep
#define LED_PIN               3     // LED output pin

// LED is active low (current sink)
#define turnLedOn()         PA &= ~(1 << LED_PIN)
#define turnLedOff()        PA |= (1 << LED_PIN)

// change speed of pwm ramp, and the hold time for full brightness
#define rampspeed 50
#define holdtime 150

volatile bool lightmeup = false;


// PFS154 has space for two arrays, but you could also
// read one of them "backwards"

uint8_t ledup[] =  {1, 1, 2, 2, 3, 3, 4, 5, 7, 9, 11, 14, 17, 22, 28,
                    35, 44, 56, 70, 89, 112, 142, 180, 200
                     };

uint8_t leddown[] =  {200, 180, 142, 112, 89, 70, 56, 44, 35, 28, 22,
                      17, 14, 11, 9,  7,  5,  4,  3,  3,  2,  2,  1,  1
                     };
void sleeping() {

  MISC = MISC_FAST_WAKEUP_ENABLE;  // fast wakeup

  PADIER = 0;    // on reset all pins are set as wake pins, 0 to disable
  PBDIER = 0;    // no port B on PFS154, but still needs to be set

  // Set PIR Sensor pin as input
  PAC &= ~(1 << WAKEUP_PIN);        // set as input
  PAPH |= (1 << WAKEUP_PIN);        // enable pullup resistor

  __disgint();                // disable global interrupts
  INTEN = 0;                  // disable all interrupts
  PADIER = (1 << WAKEUP_PIN); // enable only wakeup pin
  PBDIER = 0;                 // port B does not wake
  INTEGS |= INTEGS_PA0_FALLING;

  // trigger when switch closes and pulls pin to ground
  INTEN |= INTEN_PA0;         // enable interrupt on wakeup pin
  INTRQ = 0;                  // reset interrupts
  turnLedOff();
  __engint();                 // enable global interrupts
  __stopsys();                // go to deep sleep

  return;
}

void ramping() { // see candle projects for explanation

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

  for (uint8_t i = 0; i < sizeof(ledup) / sizeof(ledup[0]); i++) {
    PWMG2DTL = ledup[i] & 255;
    PWMG2DTH = ledup[i];
    _delay_ms(rampspeed);
  }

  for (uint16_t i = 0; i < holdtime; i++) {  
   _delay_ms(rampspeed);
    }

  for (uint8_t i = 0; i < sizeof(leddown) / sizeof(leddown[0]); i++) {
    PWMG2DTL = leddown[i] & 255;
    PWMG2DTH = leddown[i];
    _delay_ms(rampspeed);
  }

  PWMG2DTL = 0 & 255;
  PWMG2DTH = 0;
  PWMG2C = 0b00100001;
  lightmeup = false;
  return;
}

void interrupt(void) __interrupt(0) {

  INTEN = 0;                  // disable all interrupts
  PADIER = 0;                 // disable wakeup pin
  PBDIER = 0;                 // disable port B wake pins to be sure
  lightmeup = true;
}

// Main program
void main() {

  // Initialize hardware
  PAC |= (1 << LED_PIN);      // Set LED as output

  // Main processing loop
  while (1) {

    if (lightmeup) {
      ramping();
    } else {
      sleeping();
    }
  }
}

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

  AUTO_INIT_SYSCLOCK();
  AUTO_CALIBRATE_SYSCLOCK(TARGET_VDD_MV);

  return 0;  
}

I love the finished product and the journey was pretty nice as well!




Saturday, March 2, 2024

0000 0000 1110 0010

Mailbag #38 - very silly stuff

This mailbag is testament to the dangers of late night online shopping mixed with a fervour for silliness that needs urgent and assertive attenuation.


Yep that's right - in amongst this lot are hats that are Bluetooth enabled and require charging.

Good luck divining meaning from this lot - and enjoy!



Wednesday, February 21, 2024

0000 0000 1110 0001

Tay Tay Nay Nay to the Solder Fumes

While 96% of Australians lined up recently to spend their hard earned cash on Taylor Swift's Eras Tour, I retreated to my dungeon and re-vamped the solder fume extractor I'd been relying on to keep me safe over the last few years.

The journey involved a new "soft-start" circuit and some laughs with interruptions and other snafu moments.

It was a very good result in the end, but oh my what a journey.

The worst part was that when I emerged triumphant Taylor was still singing her heart out in Sydney!

Your thoughts, as usual, appreciated!



Sunday, February 11, 2024

0000 0000 1110 0000

TDA1308 Microphone Amplifier Board

A few mailbags ago I unearthed a little red board that I initially thought might be an amplifier (the "TDA" label is usually a big hint!)

I was correct in that it was an audio module, but the little microphone in the corner was the clue that this board was a microphone plus amplifier.

After fruitlessly searching for an appropriate library I did a little "roll your own" code and found out that it works well! See for yourself in the video below.