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.



No comments:

Post a Comment