RadioHead ASK on ATtiny84

I dearly want to use an ATtiny84 with a RadioHead ASK transmitter project (I have it running on a ProMini but want to make it smaller).
I read up about using an external 8MHz crystal for frequency stability (hence I am not using an ATtiny85 - not enough pins) and can load a ‘Blinki’ sketch on my target ATtiny84 running with its external 8MHz clock.
When I try to load the sample ASK transmitter code - the compiler gets hung up on the definition of HardwareSerial in the RH_Serial.h file.
Does anyone know what I am doing wrong or a fix for this?
BTW - I read a similar post and included the SPI ignore line in the platformio.ini but it had no effect.
I’d also really like to run this on an ATtiny1604 - oddles more memory but I’ve not seen any doco says RadioHead support for Series ‘0’ ATtiny. Do you know any different?

My source, platformio.ini, output from compiler are all below:

// ask_transmitter.pde
// -*- mode: C++ -*-
// Simple example of how to use RadioHead to transmit messages
// with a simple ASK transmitter in a very simple way.
// Implements a simplex (one-way) transmitter with an TX-C1 module
// Tested on Arduino Mega, Duemilanova, Uno, Due, Teensy, ESP-12

#include <RH_ASK.h>
#ifdef RH_HAVE_HARDWARE_SPI
#include <SPI.h> // Not actually used but needed to compile
#endif

//RH_ASK driver;
// RH_ASK driver(2000, 4, 5, 0); // ESP8266 or ESP32: do not use pin 11 or 2
 RH_ASK driver(2000, 3, 4, 0); // ATTiny, RX on D3 (pin 2 on attiny85) TX on D4 (pin 3 on attiny85), 
// RH_ASK driver(2000, PD14, PD13, 0); STM32F4 Discovery: see tx and rx on Orange and Red LEDS

void setup()
{
#ifdef RH_HAVE_SERIAL
    Serial.begin(9600);	  // Debugging only
#endif
    if (!driver.init())
#ifdef RH_HAVE_SERIAL
         Serial.println("init failed");
#else
	;
#endif
}

void loop()
{
    const char *msg = "hello";

    driver.send((uint8_t *)msg, strlen(msg));
    driver.waitPacketSent();
    delay(200);
}
; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:attiny84]
platform = atmelavr
board = attiny84
framework = arduino
upload_protocol = custom
upload_port = usb
upload_flags = 
	-C
	${platformio.packages_dir}/tool-avrdude/avrdude.conf
	-p
	$BOARD_MCU
	-P
	$UPLOAD_PORT
	-c
	usbasp
upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i
lib_deps = mikem/RadioHead@^1.120
lib_ignore = SPI

 *  Executing task in folder ATTiny84_RH_ASK_Test: C:\Users\Peter\.platformio\penv\Scripts\platformio.exe run 

Processing attiny84 (platform: atmelavr; board: attiny84; framework: arduino)
---------------------------------------------------------------------------------------------------------------------------------------------------Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/atmelavr/attiny84.html
PLATFORM: Atmel AVR (3.4.0) > Generic ATtiny84
HARDWARE: ATTINY84 8MHz, 512B RAM, 8KB Flash  
DEBUG: Current (simavr) On-board (simavr)     
PACKAGES:
 - framework-arduino-avr-attiny @ 1.5.2       
 - toolchain-atmelavr @ 1.70300.191015 (7.3.0)
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 9 compatible libraries
Scanning dependencies...
Dependency Graph
|-- RadioHead @ 1.120.0
Building in release mode
Compiling .pio\build\attiny84\lib464\RadioHead\RH_Serial.cpp.o
Compiling .pio\build\attiny84\FrameworkArduino\WString.cpp.o
Compiling .pio\build\attiny84\FrameworkArduino\abi.cpp.o
Compiling .pio\build\attiny84\FrameworkArduino\main.cpp.o
Compiling .pio\build\attiny84\FrameworkArduino\new.cpp.o
Compiling .pio\build\attiny84\FrameworkArduino\wiring.c.o
Compiling .pio\build\attiny84\FrameworkArduino\wiring_analog.c.o
Compiling .pio\build\attiny84\FrameworkArduino\wiring_digital.c.o
In file included from .pio\libdeps\attiny84\RadioHead\RH_Serial.cpp:6:0:
.pio\libdeps\attiny84\RadioHead/RH_Serial.h:159:29: error: expected ')' before '&' token
     RH_Serial(HardwareSerial& serial);
                             ^
.pio\libdeps\attiny84\RadioHead/RH_Serial.h:163:5: error: 'HardwareSerial' does not name a type; did you mean 'HardwareSerial_h'?
     HardwareSerial& serial();
     ^~~~~~~~~~~~~~
     HardwareSerial_h
In file included from .pio\libdeps\attiny84\RadioHead\RH_Serial.cpp:6:0:
.pio\libdeps\attiny84\RadioHead/RH_Serial.h:247:5: error: 'HardwareSerial' does not name a type; did you mean 'HardwareSerial_h'?
     HardwareSerial& _serial;
     ^~~~~~~~~~~~~~
     HardwareSerial_h
*** [.pio\build\attiny84\lib464\RadioHead\RH_Serial.cpp.o] Error 1
In file included from C:\Users\Peter\.platformio\packages\framework-arduino-avr-attiny\cores\tiny\Arduino.h:187:0,
                 from C:\Users\Peter\.platformio\packages\framework-arduino-avr-attiny\cores\tiny\wiring_private.h:35,
                 from C:\Users\Peter\.platformio\packages\framework-arduino-avr-attiny\cores\tiny\wiring_digital.c:29:
C:\Users\Peter\.platformio\packages\framework-arduino-avr-attiny\variants\tinyX4/pins_arduino.h:170:2: warning: #warning "This is the COUNTERCLOCKWISE pin mapping - make sure you're using the pinout diagram with the pins in counter clockwise order" [-Wcpp]
 #warning "This is the COUNTERCLOCKWISE pin mapping - make sure you're using the pinout diagram with the pins in counter clockwise order"
  ^~~~~~~
=========================================================== [FAILED] Took 6.76 seconds ===========================================================

RadioHead: RadioHead Packet Radio library for embedded microprocessors says

But the SpenceKonde core is exactly what’s being used in PlatformIO per

Hi Max

Thanks again for your non judgemental response.

Yes I was just hoping someone had a magic silver bullet here.

In my quest to build a very small RF ASK transmitter, I previously tried to jig up my own version of the excellent RadioHead transmitter based on my (limited) understanding of the DSS protocol - this is used by model railway enthusiasts - essentially a ‘Morse Code’ style protocol. I was using an ATtiny85 (internal 8MHz clock).

I tried to put this across an RF link - I am aware of the need for training sequences to allow the decoder AGC to align, trying to keep the envelope going up and down and the use of a rudimentary packet with check sum. This was a basic bit bang approach and it almost worked - well, worked some of the time, but not all of the time) - maybe the bit I was missing was the external 8MHz clocked MCU. I was scared to commit but now I have a home made HV programmer - just in case.

I’ll try my ‘home brew’ protocol approach again - maybe I can roll an ATtiny1604 into the mix - I’m keen to save power and these devices have insane low power in Sleep Mode - Standby with window based ADC measurements via event linked RTC also comes out near 4uA - I could live with that.

I have learned that life is a compromise.

Can the sketch be compiled in the Arduino IDE when using that other mentioned core? (GitHub - damellis/attiny: ATtiny microcontroller support for the Arduino IDE)

Hi Max

Thanks again for your suggestion.

I did try developing in Arduino IDE and managed to get a rudimentary RH_ASK transmitter going with another core (I even used TinyHead) on an ATtiny85 - but it was very flakey - I thought it might have been down to the clock, hence moving to ATtiny84 with external crystal.

I am dyslexic and struggle with Arduino IDE as it’s all black 'n white and typing errors are not flagged until compile time. PlatformIO is so much better, I’ll work round things in this environment.

I’ve moved on - I found a RH_ASK Class Reference and noticed a comment

With AtTiny x14 (such as 1614 etc) using Spencer Kondes megaTinyCore…

I also seem to remember reading somewhere that the internal oscillators in these ‘new’ ATtiny parts is much better - the ATtiny 1604 part doesn’t support external crystals anyway.

I just received a packet of ATtiny1604s and am just in the middle of trying to get one to work with RH_ASK transmitter (to RH_ASK receiver running on a [previously tested] Nano).

In PlatformIO, it compiles, loads and runs without locking up - I just need to see if I can get the frequency right and see how temp stable it is.

Time passes - I successfully used ATtiny1604 to run RH_ASK transmitter.

I was initially loosing some packets but I performed some clock Tuning

My packets seem to get through 100% now that I can select the

16MHz internal - tuned

clock option from the Arduino IDE burn bootloader option.

I doesn’t seem to matter that the RH_ASK code seems to ask for a 8Mhz clock - I just set up F_CPU at 16000000 in both the source and platformio.ini file.

I can even get serial data from the USART at the baud rate I select.

Happy days

1 Like

Hi Peter
Is there any chance that you can post this code For the ATtiny?
I have been trying (unsuccessfully for a week) to get something going.
Thanks
Mike

Hi Mike
Sure thing but for those reading this - please remember this was my first ATtiny1604 project - I’d do it differently today.

I’ve had a quick scan through the code and it’s not very nice.

For starters - check out what Spence Konde has to say about direct port manipulation - these series 0, 1 and 2 devices are a bit different from the old ATtiny84 / ATtiny85 devices. Direct Port Manipulation
Don’t use (for example) VPORTA.OUT ^= (1<<BUZ_bp);
Instead use PORTA_OUTTGL = (1<<BUZ_bp);

I was trying to save space by using flags but have since come to realise that using a boolean variable uses less flash in the long run when you try to update and read them.

My pinChange() routine sucks - there is absolutely no need to pass the variable I use for debounce as this is defined globally.

I think I’d use a serial library like SendOnlySoftwareSerial now in place of initializing the USART directly - I only used this for debug anyway (oh and I’d use the UPDI pin as GPIO for the debug output - that way I could keep the debug output in the final code - this adds some complexity as you then need an HV UPDI programmer but you can then use the same serial card for programming and TTY debug output.

I am a numpty at manipulating strings - I should have included #include <string.h> to make life easier.

I used a tick counter incremented by the RTC overflow and used this for button debounce timing - not sure why I didn’t use millis() like everyone else.

The one thing I struggled with was getting the serial coms to work in the first place - I had to tune the device as per the reference in the comments.

So here’s an example that worked for me - warts 'n all…

// cSpell:includeRegExp CStyleComment
// ATtiny1604_Magpie_TX
// written for ATtiny1604
// Peter Charles-Jones
// 19 August 2023

/*	What does this do

	This is the RF button part of my Magpie geocache

	In essence the unit consumes little power until a button is pressed. 

	The one on the outside of the box (SW_M), needs to be pressed a set number (7) times for the unit to 
	power up the RF transmitter and transmit a P to the main unit to cause it to unlock.

	The main unit is fashioned as a bird box and will wind out a cache on fishing line.

	The main unit will retract the cache if the same button sequence is pressed.

	The main unit will auto retract if it senses no PIR activity.

	There is another button inside the unit. If this is pressed, it will cause the main unit 
	to go into one of two programming modes depending on whether the cache is home inside the 
	main unit or extended. This allows for on site setting of working light level and the amount 
	of line unreeled when the cache is unlocked.
*/

/*	Principles of operation

	The unit is normally in POWER DOWN sleep mode. Either button will wake up unit.

	If the main external button is pressed (SW_M), the units starts a RTC and PIT to run in STANDBY 
	mode. The unit briefly wakes up on RTC overflow to check how many times the button has been pressed.

	The RTC overflow interrupt is used to increment a tick counter which is used in much the same way as millis() 
	to debounce the buttons.

	Each time a button is pressed, a feed back LED is set on and the RTC overflow interrupt is used to count how 
	long to keep the LED on. The unit goes back into STANDBY sleep mode between successive RTC overflow events.

	If the PIT triggers (no button activity for a while), it measures how many button presses there were 
	and if it matches the target (7), a sequence of LED flashes - again controlled by the RTC overflow interrupt  - is started.

	At the end of the LED flash sequence, the MCU switches on for a chunk of time to run the RF transmission payload.

	The situation with the P button is slightly different in that only one button press is required.
*/

/*	Things I learned

	You need to stop and restart the RTC and PIT to ensure that the first PIT timeout occurs at the same lapse time.

	During initialization, the data sheet says you need to check the RTC and PIT status flags are 0 before making changes
	but mid programme, doing this can cause the code to hang, waiting for status to be 0.

	The RTC and PIT didn't perform consistently when started and stopped so I mostly just enabled and disabled them 
	generating interrupts.
*/

/* 	Device clock tuning

	Device was tuned to 16M <https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/Ref_Tuning.md> 
	First the device had a bare bones sketch loaded using Arduino IDE and a boot loader burnt at 16M with Millis disabled
	Then the MegaTinyTuner example sketch was loaded into target and run
	Then the boot loader was re-loaded with 16M tuned selected as clock
	PA5 is the LED_BUILTIN
*/

/* 	Issue - if the P button is not held down long enough, the button up isn't detected and the next button press is missed.
	I've spent too long looking for a solution so plan to just live with it.
*/

#include <Arduino.h>

#define F_CPU 16000000
#include <avr/sleep.h>      // useful macros for putting the unit into sleep mode
#include <RH_ASK.h>

// You can initialise like this: RH_ASK driver(2000, 6, 7); which will transmit on digital pin 7 == PB0 == physical pin 9 on Attiny x14 and receive on digital pin 6 == PB1 == physical pin 8 on Attiny x16 Uses Timer B1.

/*	Arduino PIN numbering

This core uses a simple scheme for assigning the Arduino pin numbers: 
Pins are numbered starting from the the I/O pin closest to Vcc as pin 0 
and proceeding counterclockwise, skipping the (mostly) non-usable UPDI pin. 
The UPDI pin is then assigned to the last pin number (as noted above, it is possible to read the UPDI pin 
(both analog and digital reads work) even if it is not set as GPIO). 
We recommend this as a last resort: the UPDI pin always has its pullup enabled when not set as a GPIO pin, 
and a signal which looks too much like the UPDI enable sequence will cause undesired operation.
*/

/* ATtiny1604 / ARDUINO pin out
//                          _____
//                  VDD   1|*    |14  GND
// (nSS)  (AIN4) PA4  0~  2|     |13  10~ PA3 (AIN3)(SCK)(EXTCLK)
//        (AIN5) PA5  1~  3|     |12  9   PA2 (AIN2)(MISO)
// (DAC)  (AIN6) PA6  2   4|     |11  8   PA1 (AIN1)(MOSI)
//        (AIN7) PA7  3   5|     |10  11  PA0 (nRESET/UPDI)
// (RXD) (TOSC1) PB3  4   6|     |9   7~  PB0 (AIN11)(SCL)
// (TXD) (TOSC2) PB2  5~  7|_____|8   6~  PB1 (AIN10)(SDA)
//
*/


/* Alternate pins 
Pin Name  Other     ADC0  AC0   USART0  SPIO  TWI0  TCA0  TCB0  CCL

 1  VDD
 2  PA4             AIN4        XDIR(*) SS          WO4         LUT0-OUT
 3  PA5             AIN5  OUT                       WO5   WO
 4  PA6             AIN6  AINN0
 5  PA7             AIN7  AINP0                                 LUT1-OUT
 6  PB3                         RxD                 WO0(*)
 7  PB2   EVOUT1                TxD                 WO2
 8  PB1             AIN10       XCK           SDA   WO1
 9  PB0             AIN11       XDIR          SCL   WO0
 10 PA0 RESET/UPDI  AIN0                                        LUT0-IN0
 11 PA1             AIN1        TxD(*)  MOSI                    LUT0-IN1
 12 PA2 EVOUT0      AIN2        RxD(*)  MISO                    LUT0-IN2
 13 PA3 EXTCLK      AIN3        XCK(*)  SCK         WO3
 14 GND

*/ 

#define TXD_bp  2   // PB2 - pin 7
#define RXD_bp  3   // PB3 - pin 6

#define PWR_bp  4  	// PA4 - pin 2
//					// PA5 - pin 3 - reserved for ASK TX (Arduino pin 1)
#define SW_P_bp 2   // PA2 - pin 12
#define SW_M_bp 1   // PA1 - pin 11
#define BUZ_bp  7   // PA7 - pin 5
#define LED_bp  6	// PA6 - pin 4
//pin 13 - seems to be fixed for PTT unless the 4th parameter is set to -1
					
#define rtc_period 6						// sets the RTC overflow hence periodicity. 6
#define PIT_RTC_PERIOD RTC_PERIOD_CYC2048_gc// sets the PIT periodicity

#define powerOnDelay 1						// delay to alow RF unit to settle
#define buttonDebounce 4    				// tick counter 4
#define CNF_countMax 14						// how many Bips in a Confirm - must be even number 14
#define BIP_countMax 2					// how many ticks should a Bip last 4

//volatile uint8_t PINA_History; 

volatile uint8_t INT_FLAG; 				// register of the following flags for communicating with ISR
#define PC_Interrupt_bp 1				// a Pin Change Interrupt has occurred
#define PIT_Interrupt_bp 2				// a Periodic Interrupt Timer interrupt has occurred
#define RTC_Overflow_bp 4				// a RTC Overflow interrupt has occurred

uint8_t FLAG = 0;					// specific bits described below
#define input_bp 0					// button pressing in progress
#define BIP_on_bp 1					// Bip output is on
#define CNF_on_bp 2					// Confirm output is on
#define TX_P 3						// flag to trigger transmission P
#define TX_M 4 						// flag to trigger transmission M

#define pressCountTarget 7			// how many times M button needs - 'secret never to be told'
unsigned long tickCount = 0;


//volatile char tempPINA;                             				// used as snapshot for PINA status if it changes mid process
#define Serial_BAUD 19200			

#define USART0_BAUD_RATE(BAUD_RATE) ((float)(F_CPU * 64 / (16 * (float)BAUD_RATE)) + 0.5)

// message code references used by RF piece
#define P 0 // to program the unit
#define M 1 // to open the unit

// RF message text defined here
char *myStrings[] = {(char*)"P", (char*)"M"};

RH_ASK driver(2000, -1, PIN_PA5, -1); // ATTiny, No RX, TX on PA5, no PTT

// put function declarations here:
void system_init();           		
// void USART0_init();
//void ptChar(char c);
//void ptString(char *str);
//void ptInteger(int n);
int pinChange (unsigned long* last_time, int pinID, char tPIN, char hPIN, char* pins);
int pinHIGH (int pinID, char* pins);
void goToSleep();
void transmit(int idx);
void powerDown(char * debounced_pins);
void tog(uint8_t pin);					// debug
void bip(uint8_t pin);					// debug
void flash(uint8_t pin, uint8_t max);	// debug
void dummyPayload();					// debug
void disableRTCandPIT();
void enableRTCandPIT();
void enablePITInterrupts();
void disablePITInterrupts();
void enablePortAInterrupts(uint8_t pin);
void disablePortAInterrupts(uint8_t pin);
// void enablePortAPullup(uint8_t pin);
void disablePortAPullup(uint8_t pin);
void disableRTCInterrupts();
void enableRTCInterrupts();
int pinChange (unsigned long* last_time, int pinID, char tPIN, char hPIN, char* pins);
int pinHIGH (int pinID, char* pins);

void setup(){
	// put your setup code here, to run once:
    system_init();
	bip(LED_bp); //debug
	//Serial.begin(Serial_BAUD);
	//Serial.println(F("Starting"));
    //ptString("\r\nStarting\r\n");
    if (!driver.init()){
        //ptString("RH_ASK failed\r\n");        
    }
	//goToSleep();
}

void loop(){
	//ptString("\r\nloop");
	//Serial.println(F("Loop"));
	
	// put your main code here, to run repeatedly:
	static unsigned long last_SW_P_time = tickCount;                // debounce timer
	static unsigned long last_SW_M_time = tickCount;                // debounce timer
	static unsigned long last_BIP_time = tickCount;                	// debounce timer
	static char tempPINA;                             				// used as snapshot for PINA status if it changes mid process
	static char pinAHistory = VPORTA_IN;              				// the last read PINA status initialised at start of program
	static int pressCount = 0;										// tracks how many times button has been pressed
	static int bipCount = 0;										// tracks the bips in the confirm feedback
	static char debounced_PINA = VPORTA_IN;							// debounced copy of the PINA register
	
	// poll the switches to see if any have been pressed
	tempPINA = VPORTA_IN;											// get latest PINA settings
	
	// Check SW_M for a key press - to send an M to trigger the main unit
	if (pinChange(&last_SW_M_time, SW_M_bp, tempPINA, pinAHistory, &debounced_PINA)){
		
		if (!(pinHIGH(SW_M_bp,&debounced_PINA))){               // SW_M is LOW - CC - button pressed
			Serial.println(F("M"));		
	   		FLAG |= (1<<BIP_on_bp);								// set BIP on flag
			VPORTA.OUT ^= (1<<BUZ_bp);							// toggle the BUZ pin
			last_BIP_time = tickCount;							// reset the tick count for the Bip
			pressCount ++;										// increment press counter
			enablePITInterrupts();								// start the timer to enter correct number fo button presses
    	}
		if (!(FLAG & (1 << BIP_on_bp) || (FLAG & (1 << CNF_on_bp)))){	// if no feedback signal is in progress
			disableRTCInterrupts();										// no longer needed as button confirmed as changed
		}
	}

	// Check SW_P for a key press - to send a P to program the main unit
	if (pinChange(&last_SW_P_time, SW_P_bp, tempPINA, pinAHistory, &debounced_PINA)){
		//tog(LED_bp);
		if (!(pinHIGH(SW_P_bp,&debounced_PINA))){               // SW_M is LOW - CC - button pressed
			FLAG |= (1 << TX_P);								// set flag to transmit a P
	   		FLAG |= (1 << BIP_on_bp);							// set BIP on flag
			VPORTA.OUT ^= (1<<BUZ_bp);							// toggle the BUZ pin
			last_BIP_time = tickCount;							// reset the tick count for the Bip
			enableRTCInterrupts();								// enable RTC interrupts to track bip time
			//disablePITInterrupts();								// disable PIT as we don't need it for P 
    	}
		if (!(FLAG & (1 << BIP_on_bp) || (FLAG & (1 << CNF_on_bp)))){	// if no feedback signal is in progress
			disableRTCInterrupts();								// we're done with timing for responses
		}
		disablePITInterrupts();									// don't need to time for M presses
	}
	pinAHistory = tempPINA; 

	// Activity time tracking section

	// test to see if we can shut down if no button activity
	if ((tickCount - last_SW_M_time > (buttonDebounce + 1)) || (tickCount - last_SW_P_time > (buttonDebounce + 1))){	
		if (!(FLAG & (1 << BIP_on_bp) || (FLAG & (1 << CNF_on_bp)))){	// if no feedback signal is in progress
			disableRTCInterrupts();										// stop detecting next button press
			FLAG &= ~(1 << input_bp);									// clear the input in progress flag
		}
	}

	if (FLAG & (1<<BIP_on_bp))	{										// the BIP feedback is running
		if (tickCount - last_BIP_time > (BIP_countMax)){				// the Bip has been on long enough
			if (!(FLAG & (1 << CNF_on_bp))){							// if not running a confirmation feedback 
				VPORTA.OUT ^= (1<<BUZ_bp);								// toggle the BUZ pin 
			}
			FLAG &= ~(1<<BIP_on_bp);									// clear BIP on flag
			if (!((FLAG & (1 << input_bp))||(FLAG & (1 << CNF_on_bp)))){// if a button is not being pressed or confirmation being sent
				disableRTCInterrupts();									// stop detecting next button press
			}
			if (FLAG & (1 << TX_M)){
				FLAG &= ~(1 << TX_M);
				transmit(M);
				//dummyPayload();	// M payload
				powerDown(&debounced_PINA);
			}

			if (FLAG & (1 << TX_P)){
				FLAG &= ~(1 << TX_P);
				transmit(P);
				//dummyPayload();	// P payload
				powerDown(&debounced_PINA);
			}
		}
	}
	
	if (INT_FLAG & (1<<PIT_Interrupt_bp)){						// a PIT interrupt has happened - no SW_M button pressed for a while
		INT_FLAG &= ~(1<<PIT_Interrupt_bp);						// clear the flag
		//flash(PWR_bp,1);						// debug
	
		if (pressCount == pressCountTarget){					// payload time as correct button sequence 
			FLAG |= (1 << CNF_on_bp);							// set confirmation feedback flag on
			bipCount = 0;										// reset bip counter
			FLAG |= (1<<BIP_on_bp);								// set BIP on flag
			VPORTA.OUT ^= (1<<BUZ_bp);							// toggle the BUZ pin
			last_BIP_time = tickCount;							// reset the bip timer
			enableRTCInterrupts();								// to track the bip on time
			pressCount = 0;
		}
		else{
			disablePITInterrupts();				
			pressCount = 0;												// reset press count
			powerDown(&debounced_PINA);	// power down payload and go into POWERDOWN - SW_bp pin (tremble switch) will wake up
		
		}
	}

	if (FLAG & (1 << CNF_on_bp)){								// while confirmation feedback is running 
		if (bipCount < CNF_countMax){
			if (!(FLAG & (1 << BIP_on_bp))){					// if the bip is not currently running
				FLAG |= (1<<BIP_on_bp);								// set BIP on flag
				VPORTA.OUT ^= (1<<BUZ_bp);							// toggle the BUZ pin
				last_BIP_time = tickCount;							// reset the bip timer
				enableRTCInterrupts();
				//enableRTC();
				bipCount ++;										// increment the bip counter
			}
		}
		else{
			FLAG &= ~(1 << CNF_on_bp);							// clear the Confirmation in progress flag
			FLAG |= (1 << TX_M);								// set flag to transmit a M
		}
	}

	if (INT_FLAG & (1<<PC_Interrupt_bp)){						// a button has been pressed
		INT_FLAG &= ~(1<<PC_Interrupt_bp);
		//tog(LED_bp);
		enableRTCInterrupts();
		enablePITInterrupts();
		enableRTCandPIT();
		last_SW_M_time = tickCount;								// restart the debounce counter	
		last_SW_P_time = tickCount;								// restart the debounce counter	
		FLAG |= (1 << input_bp);								// flag that button input is in progress
	}

	if (INT_FLAG & (1<<RTC_Overflow_bp)){						// a RTC overflow 
		INT_FLAG &= ~(1<<RTC_Overflow_bp);						// clear the flag
    	tickCount ++;                                 			// increment the tick counter for debounce
	}
	goToSleep();												// go back to STANDBY mode
	
}

/* Interrupt Service Routines */
ISR(PORTA_PORT_vect){
	/* Insert your PORT A interrupt handling code here */
	INT_FLAG |=(1<<PC_Interrupt_bp);
	
	/* Clear interrupt flags */
	VPORTA_INTFLAGS = (1<<SW_M_bp)|(1<<SW_P_bp);
}

ISR(RTC_PIT_vect){										// puts unit back to sleep if unit is not light a while after tremble switch interrupt
	/* Insert your PIT interrupt handling code here */
	INT_FLAG |=(1<<PIT_Interrupt_bp);

	/* The interrupt flag has to be cleared manually */
	RTC.PITINTFLAGS = RTC_PI_bm;
}

ISR(RTC_CNT_vect){										
	/* Insert your RTC Overflow interrupt handling code */
	if (RTC.INTFLAGS & RTC_OVF_bm){
		INT_FLAG |= (1<<RTC_Overflow_bp);
	}
	/* Overflow interrupt flag has to be cleared manually */
	RTC.INTFLAGS = RTC_OVF_bm | RTC_CMP_bm;
}


/* Debug routines   - not called in production code */

void dummyPayload(void){
	VPORTA.OUT |= (1<<PWR_bp);	
	//_delay_ms(500);
	delay(500);
	VPORTA.OUT &= ~(1<<PWR_bp);	
}

void tog(uint8_t pin){								// used for debug
	VPORTA.OUT ^= (1<<pin);							// toggle
}

void bip(uint8_t pin){								// used for debug
	VPORTA.OUT ^= (1<<pin);							// toggle
	//_delay_ms(10);
	delay(10);
	VPORTA.OUT ^= (1<<pin);							// toggle
	//_delay_ms(40);
	delay(40);
}

void flash(uint8_t pin, uint8_t max){				// used for debug
	for (uint8_t i = 0; i < max; i++){
		bip(pin);
	}
}

/*	Main functional routines */

int pinChange (unsigned long* last_time, int pinID, char tPIN, char hPIN, char* pins) { // has the pin changed state?
	if ((tPIN & (1<<pinID))!= (hPIN & (1<<pinID) )){			// last pin state different
    	*last_time = tickCount;
  	}
	if ((tickCount - *last_time) > buttonDebounce){				// debounce time has elapsed
		if ((tPIN & (1<<pinID))!=(*pins & (1<<pinID))){			// is the current pin state different from the current one
			*pins &= ~(1<<pinID);								// mask out the bit position
    		*pins |= (tPIN & (1<<pinID));						// get the bit value at bit location and OR into PINS
    		return 1;											// pin has changed
    	}
  	}
	return 0;                                                 	// pin has not changed state
}

int pinHIGH (int pinID, char* pins){
	if (*pins & (1<<pinID)){                                  // pin value is 1.. switch / button is Open Circuit
    	return 1;                                             // HIGH
  	}
	else{                                                     // pin value is 0 .. switch / button is Closed to ground
    	return 0;                                             //LOW
	}
}

void goToSleep(void){		/* put the unit into Power down sleep mode */
	sleep_enable();
	sleep_cpu();
	sleep_disable();
}

void powerDown(char * debounced_pins){
	disableRTCandPIT();								// to prevent unnecessary interrupts during POWER DOWN mode
	VPORTA.OUT &= ~(1<<BUZ_bp);						// power down BUZ
	VPORTA.OUT &= ~(1<<PWR_bp);						// power down RF
	*debounced_pins = VPORTA_IN;					// update the debounced pin state 
	set_sleep_mode(SLEEP_MODE_PWR_DOWN);			// select POWER DOWN sleep mode
	goToSleep();								    // the button switch is wake up source
	enableRTCandPIT();								// start the RTC so it can generate overflow events to trigger ADC and PIT to measure a timeout for this activity
	set_sleep_mode(SLEEP_MODE_STANDBY);				// select STANDBY sleep mode so we can measure button presses
}

void transmit(int idx){                         // routine sends a string message twice     
	VPORTA_OUT |= (1<<PWR_bp);					// PWR on
	delay(powerOnDelay);						// wait a bit for RF unit to stabilize
	for (int i= 0; i <= 1; i++){
    	const char *msg = myStrings[idx];
    	driver.send((uint8_t *)msg, strlen(msg));
    	driver.waitPacketSent();
  	}
}

/*	Helper routines - to make the code more understandable */

void enablePortAInterrupts(uint8_t pin){
	uint8_t *port_pin_ctrl = ((uint8_t *)&PORTA + 0x10 + pin);               	// calculate port PINCTRL address for pin
	*port_pin_ctrl = (*port_pin_ctrl & ~PORT_ISC_gm) | PORT_ISC_BOTHEDGES_gc;	// enable PC interrupt on SW	
}

void disablePortAInterrupts(uint8_t pin){
	uint8_t *port_pin_ctrl = ((uint8_t *)&PORTA + 0x10 + pin);               	// calculate port PINCTRL address for pin
	*port_pin_ctrl = (*port_pin_ctrl & ~PORT_ISC_gm) | PORT_ISC_INTDISABLE_gc;	// disable PC interrupt on SW	
}

void disablePortAPullup(uint8_t pin){
	uint8_t *port_pin_ctrl = ((uint8_t *)&PORTA + 0x10 + pin);					// calculate port PINCTRL address for pin
	*port_pin_ctrl &= ~(1 << PORT_PULLUPEN_bp);									// disable pull up
}
void enablePITInterrupts(){
	RTC.PITINTFLAGS |= (1 << RTC_PI_bp); 	// clears the PIT interrupt 
	RTC.PITINTCTRL = 1 << RTC_PI_bp; 		// ensable PIT interrupts
}

void disablePITInterrupts(){
	RTC.PITINTCTRL = 0 << RTC_PI_bp;		// disable PIT interrupts
	RTC.PITINTFLAGS |= (1 << RTC_PI_bp);	// clears the PIT interrupt  
}

void disableRTCInterrupts(){
	RTC.INTCTRL = (0 << RTC_CMP_bp)  | (0 << RTC_OVF_bp); 
}

void enableRTCInterrupts(){
	RTC.INTCTRL = (0 << RTC_CMP_bp)  | (1 << RTC_OVF_bp); 
}

void disableRTCandPIT(){
	RTC.CTRLA = RTC_PRESCALER_DIV1_gc | (0 << RTC_RTCEN_bp) | (1 << RTC_RUNSTDBY_bp); 
	RTC.PITCTRLA = PIT_RTC_PERIOD | (0 << RTC_PITEN_bp); 
}

void enableRTCandPIT(){								// make sure RTC and PIT start from the same time origin
	disableRTCandPIT();
	while (RTC.STATUS > 0){}						// required to allow synchronisation
	RTC.CTRLA = RTC_PRESCALER_DIV1_gc | (1 << RTC_RTCEN_bp) | (1 << RTC_RUNSTDBY_bp); 
	while (RTC.PITSTATUS > 0) {}					// required to allow synchronisation
	RTC.PITINTFLAGS |= (1 << RTC_PI_bp); 			// clears the PIT interrupt 
	RTC.PITCTRLA = PIT_RTC_PERIOD | (1 << RTC_PITEN_bp);
}

// Initialization routines 
void mcu_init(void)							/* MCU initialization */
{
	/* On AVR devices all peripherals are enable from power on reset, this
	 * disables all peripherals to save power. Driver shall enable
	 * peripheral if used */

	/* Set all PORTA pins to low power mode */
	for (uint8_t i = 0; i < 8; i++) {                                   // PA0 - PA7
		*((uint8_t *)&PORTA + 0x10 + i) |= (1 << PORT_PULLUPEN_bp);
	}

    /* Set all PORTB pins to low power mode */
	for (uint8_t i = 0; i < 4; i++) {                                   // PB0 - PB3 on ATtiny1604
		*((uint8_t *)&PORTB + 0x10 + i) |= (1 <<  PORT_PULLUPEN_bp);
	}
}

void pin_init(void){ 						/* PIN initialization */
	
	// define BUZ as output
	VPORTA.DIR |= (1<<BUZ_bp);																// set pins as output
	VPORTA.OUT &= ~(1<<BUZ_bp);																// set pins LOW	
	disablePortAPullup(BUZ_bp);
	disablePortAInterrupts(BUZ_bp);

	// define LED as output
	VPORTA.DIR |= (1<<LED_bp);																// set pins as output
	VPORTA.OUT &= ~(1<<LED_bp);																// set pins LOW	
	disablePortAPullup(LED_bp);
	disablePortAInterrupts(LED_bp);

	// define PWR as output 
	VPORTA.DIR |= (1<<PWR_bp);																// set pins as output
	VPORTA.OUT &= ~(1<<PWR_bp);																// set pins LOW	
	disablePortAPullup(PWR_bp);
	disablePortAInterrupts(PWR_bp);

	// enable interrupts on switches
	enablePortAInterrupts(SW_M_bp);
	enablePortAInterrupts(SW_P_bp);
}

void RTC_0_init(void){						/* Realtime clock initialization */
	/* Wait for all register to be synchronized */
	while (RTC.STATUS > 0){}

	/* 32KHz Internal Ultra Low Power Oscillator (OSCULP32K) */
	//RTC.CLKSEL = RTC_CLKSEL_INT32K_gc; 

	/* 32KHz divided by 32 */
	RTC.CLKSEL = RTC_CLKSEL_INT1K_gc; 
	
	/* Period: */
	RTC.PER = rtc_period;
	//RTC.CMP = rtc_compare;

	//RTC.INTFLAGS |= (1<<RTC_OVF_bp); // clear RTC overflow interrupt

	/* Compare Match Interrupt disabled, Overflow Interrupt enabled */
	RTC.INTCTRL = (0 << RTC_CMP_bp)  | (1 << RTC_OVF_bp); 

	/* Compare Match Interrupt disabled, Overflow Interrupt enabled -  */
	//RTC.INTCTRL = (0 << RTC_CMP_bp)  | (1 << RTC_OVF_bp); 

	/* Prescaler of 1, disable, run in standby */
	RTC.CTRLA = RTC_PRESCALER_DIV1_gc | (0 << RTC_RTCEN_bp) | (1 << RTC_RUNSTDBY_bp); 

	/* Prescaler of 1, enable, run in standby */
	//RTC.CTRLA = RTC_PRESCALER_DIV1_gc | (1 << RTC_RTCEN_bp) | (1 << RTC_RUNSTDBY_bp); 


	/* Wait for all register to be synchronized */
	while (RTC.PITSTATUS > 0) {}

	/* RTC Clock Cycles 8192, Enable: disabled */
	//RTC.PITCTRLA = RTC_PERIOD_CYC8192_gc | (0 << RTC_PITEN_bp); 

	/* RTC Clock Cycles 8192, Enable: enabled */
	//RTC.PITCTRLA = RTC_PERIOD_CYC256_gc | (1 << RTC_PITEN_bp); 

	/* RTC Clock Cycles PIT_RTC_PERIOD, Enable: disabled */
  	RTC.PITCTRLA = PIT_RTC_PERIOD | (0 << RTC_PITEN_bp); 

	/* Periodic Interrupt: enabled */
	RTC.PITINTCTRL = 1 << RTC_PI_bp; 

	/* Periodic Interrupt: disabled */
	//RTC.PITINTCTRL = 0 << RTC_PI_bp; 

	//RTC.PITINTFLAGS |= (1 << RTC_PI_bp); // clears the PIT interrupt 
}

void CPUINT_init(void){						/* Interrupt initialisation */
	/* Enable interrupts */
	sei();
}

void SLPCTRL_init(void){					/* Sleep controller initialization*/
	/* Sleep disabled, Standby Mode */
	//SLPCTRL.CTRLA = 0 << SLPCTRL_SEN_bp | SLPCTRL_SMODE_STDBY_gc; 
	//SLPCTRL.CTRLA = 0 << SLPCTRL_SEN_bp | SLPCTRL_SMODE_PDOWN_gc; 
	//set_sleep_mode(SLEEP_MODE_PWR_DOWN);
	set_sleep_mode(SLEEP_MODE_STANDBY);
}

void BOD_init(void) {						/* Brown Out Detection initialization */

	/* The following registers have Configuration Change Protection */	
	//SLEEP in BOD.CTRLA

	/* Brown Out Detection disabled in sleep mode */
	_PROTECTED_WRITE(BOD.CTRLA, BOD_SLEEP_DIS_gc);
}

void system_init(){							/* system initialization */
	mcu_init();
	pin_init();
    RTC_0_init();
	CPUINT_init();
	SLPCTRL_init();
	BOD_init();	
}

Here’s the platformio.ini with the RadioHead library reference

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:ATtiny1604]
platform = atmelmegaavr
board = ATtiny1604
framework = arduino
board_build.f_cpu = 16000000L
upload_speed = 115200
upload_port = COM12
upload_flags =
    --tool
    uart
    --device
    ATtiny1604
    --uart
    $UPLOAD_PORT
    --clk
    $UPLOAD_SPEED
upload_command = pymcuprog write --erase $UPLOAD_FLAGS --filename $SOURCE
lib_deps = mikem/RadioHead@^1.120

If you want to see how I powered up the ASK radio transmitter - just post a request.