ATtiny402 Help Requested with Events

I am fairly new to setting up event channels and users on the ATtiny series 0 - specifically the ATtiny402. Please may I have some help and guidance.

I have some code that successfully sets up the ADC to trigger in Window mode from RTC overflow in STANDBY mode and only generate an interrupt if the ADC result is within the window.

I want to extend this to use one of the counters available, TCA0 or TCB0 to cause an interrupt on overflow by counting the RTC overflow event.

I have read the spec sheet; it seems to imply that TCA0 can count events whereas TCB0 is unable to do so.

16-bit Timer/Counter Type A (TCA)
Features
• Count on Event

In the detail it said

Optional: By writing a ‘1’ to the Enable Count on Event Input bit (CNTEI) in the Event Control
register (TCAn.EVCTRL), event inputs are counted instead of clock ticks.

However, looking at the Event System config - whereas the ADC0 user multiplexer may be associated with Asynch User 1, TCA0 is not listed, instead TCB0 is against Ayynch User 0.

TCA0 seems to be associated with Synch User 0.

However the Synchronous channel generator selector does not list the RTC.

So switching to looking at the TCB0, which is listed against the Asynch User 0, the description in the device data sheet did not sound convincing about actually counting RTC overflow generated events

16-bit Timer/Counter Type B (TCB)
Features
– Input capture
• On event

I tried both approaches but managed only to get TCB0 generating an overflow interrupt on events but it seemed not to be counting RTC overflow generated events, but rather the underlying clock.

I have included my code below - I would be keen to know if I am on a hiding to nothing. Put another way, I am trying to count a long period of time (~120s) using events and only to trigger an interrupt (to take some action) at the end of this time. The RTC overflow event generates a suitable time base for measuring a light level in STANDBY mode so I was keen to some how count these also in STANDBY mode without starting up the processor. I have two counters to play with and I was hoping I could set one up to count the event generated by my RTC overflow in STANDBY and generate an interrupt on overflow (or compare match). Can you help?

Much of the ‘setup’ for the peripherals was generated then copied from a START Studio7 project.

/*	ATtiny402EventHelp	*/
#ifdef F_CPU
#undef F_CPU
#define F_CPU 32000UL		// Main clock is internal 32kHz oscillator - required for delay function
#endif

#include <avr/interrupt.h>  // so we can use interrupt vector names
#include <avr/sleep.h>      // useful macros for putting the unit into sleep mode
#include <util/delay.h>     // needed for the delay functions

#define ADC_Threshold_High 1000 	// 1024 is O/C at LDR = very dark
#define ADC_Threshold_Low	900

#define rtc_period 250					// sets the RTC overflow hence periodicity. 
										//Changing this changes the ADC sample rate but does not affect the TCB0 oveflow time

#define TCA0_period 500

#define TCB0_CCMP 0xFFFF				// sets the value of TOP that generates an interrupt


uint16_t adc_result;					// ADC light level in here (1024 = total dark)

volatile uint8_t INT_FLAG; 				// register of the following flags for communicating with ISR
#define ADC_conversion_bp 0				// an ADC conversion has occurred
#define PC_Interrupt_bp 1				// a Pin Change Interrupt has occurred
#define PIT_Interrupt_bp 2				// a Periodic Interrupt Timer interrupt has occurred
#define TCA0_Interrupt_bp 3       		// TCA0 overflow has occurred
#define RTC_Overflow_bp 4				// a RTC Overflow interrupt has occurred
#define TCB0_Interrupt_bp 5				// a TCB0 periodic interrupt has occurred
#define RTC_CompareMatch_bp 6			// a RTC compare Match interrupt has occurred

/*  Pin information
			    ATtiny402
			    ----u---
    		VDD|		|GND
   		LED	PA6|		|PA3 PWR
  		ADC PA7|		|PA0 UDIP
		LOW PA1|		|PA2 SW
    			--------
*/

/*	Notes on circuit

	SW not involved in this sample code but will be a tremble switch to 0v
	LED and PWR are connected to LEDs for debug
	ADC has LDR connected to LOW and when measuring light level, 
    the internal pullup on PA7 is enabled but this bit of code has been omitted here.
*/

#define LOW_bp 1 // the only reason this pin is used is to make physical build easier - as a virtual 0v
#define PWR_bp 3
#define ADC_bp 7
#define SW_bp 2
#define LED_bp 6

void goToSleep();
void bip(uint8_t pin);					// debug
void flash(uint8_t pin, uint8_t max);	// debug
void enableTCB0();
void disableTCB0();
void system_init();

int main(void) {
  // put your setup code here, to run once:
	system_init();												// initialize the system
	flash(LED_bp,3);											// useful to see start up and to check delay versus clock setting
	flash(PWR_bp,3);											// useful to see start up and to check delay versus clock setting
	goToSleep();												// STANDBY mode
	
	while(1){
    if (INT_FLAG & (1<<ADC_conversion_bp)){						// an ADC conversion has happened
    	INT_FLAG &= ~(1<<ADC_conversion_bp);					// clear the flag
    	adc_result = ADC0.RES;									// fetch the result
		flash(LED_bp,1);										// shows RTC overflow event triggers ADC in Window mode.
         }

	if (INT_FLAG & (1<<RTC_Overflow_bp)){						// a RTC overflow - but should not happen
      INT_FLAG &= ~(1<<RTC_Overflow_bp);						// clear the flag
      flash(LED_bp,3);											// shows no RTC interrupts generated - just events 
    }

    if (INT_FLAG & (1<<PIT_Interrupt_bp)){						// a PIT interrupt has happened - so no light detected
      INT_FLAG &= ~(1<<PIT_Interrupt_bp);						// clear the flag
      //flash(LED_bp,2);										// shows that the PIT overflow triggers from RTC in standby
    }

    if (INT_FLAG & (1<<TCA0_Interrupt_bp)){
      INT_FLAG &= ~(1<<TCA0_Interrupt_bp);
      //flash(PWR_bp,1);
    }

    if (INT_FLAG & (1<<TCB0_Interrupt_bp)){
      INT_FLAG &= ~(1<<TCB0_Interrupt_bp);
      flash(PWR_bp,1);											// seems to be based on underlying clock rather than desired RTC overflow events
    }
	goToSleep();												// STANDBY mode
  }
}

// put function definitions here:

/* Interrupt Service Routines */

ISR(TCA0_OVF_vect){
	/* Insert your TCA overflow interrupt handling code */
	INT_FLAG |= (1<<TCA0_Interrupt_bp);

	/* The interrupt flag has to be cleared manually */
	TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bp;
}


ISR(TCB0_INT_vect)
{
	/* Insert your PIT interrupt handling code here */
	INT_FLAG |=(1<<TCB0_Interrupt_bp);

	/* The interrupt flag has to be cleared manually */
	TCB0.INTFLAGS = TCB_CAPT_bm;
}


ISR(PORTA_PORT_vect){
	/* Insert your PORTA interrupt handling code here */
	INT_FLAG |= (1<<PC_Interrupt_bp);

	/* Clear interrupt flags */
	VPORTA_INTFLAGS = (1<<SW_bp);
}

ISR(RTC_PIT_vect)
{
	/* 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);
	}

	if (RTC.INTFLAGS & RTC_CMP_bm){
		INT_FLAG |= (1<<RTC_CompareMatch_bp);
	}

	/* Overflow interrupt flag has to be cleared manually */
	RTC.INTFLAGS = RTC_OVF_bm | RTC_CMP_bm;
}

ISR(ADC0_RESRDY_vect){						
	/* Insert your RTC Overflow interrupt handling code */
	INT_FLAG |= (1<<ADC_conversion_bp);			// signal to main.c that a conversion has occurred
		
	// Clear the interrupt flag
	ADC0.INTFLAGS = ADC_RESRDY_bm;
}

ISR(ADC0_WCOMP_vect){						
	/* Insert your RTC Overflow interrupt handling code */
	INT_FLAG |= (1<<ADC_conversion_bp);			// signal to main.c that a conversion has occurred
	
	// Clear the interrupt flag
	ADC0.INTFLAGS = ADC_WCMP_bm;
}

/* Other routines  */

void bip(uint8_t pin){								// used for debug
	disableTCB0();									// TCB0 seems to mess up the _delay_ms_() routine so this probably uses TCB0
	VPORTA.OUT ^= (1<<pin);							// toggle
	_delay_ms(10);
	VPORTA.OUT ^= (1<<pin);							// toggle
	_delay_ms(40);
	enableTCB0();									// reenable TCB0
}

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

void goToSleep(){		/* put the unit to sleep */
	sleep_enable();
	sleep_cpu();
	sleep_disable();
}

void enableTCB0(){
	// CLK_PER (No Prescaling), Enable: enabled, Run Standby: enabled, Synchronize Update: disabled
	TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | 1 << TCB_ENABLE_bp | 1 << TCB_RUNSTDBY_bp | 0 << TCB_SYNCUPD_bp; 
}

void disableTCB0(){
	// CLK_PER (No Prescaling), Enable: disabled, Run Standby: enabled, Synchronize Update: disabled
	TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | 0 << TCB_ENABLE_bp | 1 << TCB_RUNSTDBY_bp | 0 << TCB_SYNCUPD_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 pins to low power mode */
	for (uint8_t i = 0; i < 8; i++) {
		*((uint8_t *)&PORTA + 0x10 + i) |= 1 << PORT_PULLUPEN_bp;
	}
}

void pin_init(void){ 						/* PIN initialization */
	
	// define PWR as output
	VPORTA.DIR |= (1<<PWR_bp);																// set pins as output
	VPORTA.OUT &= ~(1<<PWR_bp);																// set pins LOW	
	*((uint8_t *)&PORTA + 0x10 + PWR_bp) &= ~(1 << PORT_PULLUPEN_bp);						// disable pullup for PWR pin

	// define LED as output
	VPORTA.DIR |= (1<<LED_bp);																// set pins as output
	VPORTA.OUT &= ~(1<<LED_bp);																// set pins LOW	
	*((uint8_t *)&PORTA + 0x10 + LED_bp) &= ~(1 << PORT_PULLUPEN_bp);						// disable pullup for LED pin

	// define LOW as output used as a virtual ground to make hardware build easier
	VPORTA.DIR |= (1<<LOW_bp);																// set pins as output
	VPORTA.OUT &= ~(1<<LOW_bp);																// set pins LOW	
	*((uint8_t *)&PORTA + 0x10 + LOW_bp) &= ~(1 << PORT_PULLUPEN_bp);						// disable pullup for PWR pin
	
  	uint8_t *SW_port_pin_ctrl = ((uint8_t *)&PORTA + 0x10 + SW_bp);							// get port PINCTRL address for SW pin
	*SW_port_pin_ctrl = (*SW_port_pin_ctrl & ~PORT_ISC_gm) | PORT_ISC_BOTHEDGES_gc;			// enable interrupts for SW pin
		
	uint8_t*ADC_port_pin_ctrl = ((uint8_t *)&PORTA + 0x10 + ADC_bp);						// get port PINCTRL address for ADC pin
	*ADC_port_pin_ctrl = (*ADC_port_pin_ctrl & ~PORT_ISC_gm) | PORT_ISC_INPUT_DISABLE_gc;	// INPUT_DISABLED
}

void CLKCTRL_init(void){					/* Main clock initialization */
	
	/* The following registers have Configuration Change Protection */	
	// CLKCTRL.MCLKCTRLB 
	// CLKCTRL.MCLKLOCK 
	// CLKCTRL.MCLKCTRLA 
	// CLKCTRL.OSC20MCTRLA 
	// CLKCTRL.OSC20MCALIBA 
	// CLKCTRL.OSC20MCALIBB 
	// CLKCTRL.OSC32KCTRLA 

	/* Set the Main clock to internal 32kHz oscillator*/
	_PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSCULP32K_gc);

  	/* Set the Main clock to internal 20MHz oscillator*/
	//_PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSC20M_gc);

	/* run the 32kHz oscillator in standby mode*/
	_PROTECTED_WRITE(CLKCTRL.OSC32KCTRLA, 1 << CLKCTRL_RUNSTDBY_bp);

	/* Set the Main clock prescaler divisor to 2X and disable the Main clock prescaler */
	_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, CLKCTRL_PDIV_2X_gc | 0 << CLKCTRL_PEN_bp); // without enable

	/* ensure 20MHz isn't forced on*/
	_PROTECTED_WRITE(CLKCTRL.OSC20MCTRLA, 0 << CLKCTRL_RUNSTDBY_bp);

	/* wait for system oscillator changing to finish */
	while (CLKCTRL.MCLKSTATUS & CLKCTRL_SOSC_bm) {
	}
}

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;

	/* Compare Match Interrupt disabled, Overflow Interrupt disabled */
	RTC.INTCTRL = (0 << RTC_CMP_bp)  | (0 << 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_CYC8192_gc | (1 << RTC_PITEN_bp); 

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

void VREF_0_init(void){						/* Voltage reference initialization */
	/* Voltage reference at 0.55V */
	//VREF_CTRLA = VREF_ADC0REFSEL_0V55_gc; // why is this different in Atmel START 
	//VREF.CTRLA = VREF_ADC0REFSEL_0V55_gc;  
}

void ADC_0_initialization(void){			/* ACD initialization */

	/* 1 ADC sample */
	ADC0.CTRLB = ADC_SAMPNUM_ACC1_gc; 

	/* CLK_PER divided by 2, internal reference and Sample Capacitance - 0 recommended if ref voltage less than 1v  */
	//ADC0.CTRLC = ADC_PRESC_DIV2_gc | ADC_REFSEL_INTREF_gc | 0 << ADC_SAMPCAP_bp; 

	/* CLK_PER divided by 2, VDD reference and Sample Capacitance reduced for high ref voltages  */
	ADC0.CTRLC = ADC_PRESC_DIV2_gc | ADC_REFSEL_VDDREF_gc | 1 << ADC_SAMPCAP_bp; 

	/* CLK_PER divided by 128, VDD reference and Sample Capacitance reduced for high ref voltages  */
	//ADC0.CTRLC = ADC_PRESC_DIV128_gc | ADC_REFSEL_VDDREF_gc | 1 << ADC_SAMPCAP_bp; 

	/* Automatic Sampling Delay Variation disabled. Sampling Delay Selection: 0x0, Delay 0 CLK_ADC cycles */
	ADC0.CTRLD = 0 << ADC_ASDV_bp | 0x0 << ADC_SAMPDLY_gp | ADC_INITDLY_DLY0_gc; 

	/* No Window comparison*/
	//ADC0.CTRLE = ADC_WINCM_NONE_gc; 

	/* Below Window  - use to detect when it's light*/
	ADC0.CTRLE = ADC_WINCM_BELOW_gc;
	
	/* Above Window -  */
	//ADC0.CTRLE = ADC_WINCM_ABOVE_gc;
	
	/* Inside Window */
	//ADC0.CTRLE = ADC_WINCM_INSIDE_gc;

	/* Outside Window */
	//ADC0.CTRLE = ADC_WINCM_OUTSIDE_gc;
	
	/* Start Event Input Enable: enabled */
	ADC0.EVCTRL = 1 << ADC_STARTEI_bp; 

	/* Result Ready Interrupt Disabled, Window Comparator Interrupt Enabled */
	ADC0.INTCTRL = 0 << ADC_RESRDY_bp | 1 << ADC_WCMP_bp; 

	/* Result Ready Interrupt Enable, Window Comparator Interrupt Disabled */
	//ADC0.INTCTRL = 1 << ADC_RESRDY_bp | 0 << ADC_WCMP_bp; 

	/* Result Ready Interrupt Enable, Window Comparator Interrupt Enabled */
  	//ADC0.INTCTRL = 1 << ADC_RESRDY_bp | 1 << ADC_WCMP_bp; 
	
	/* ADC input pin 7 */
	ADC0.MUXPOS = ADC_MUXPOS_AIN7_gc; 

	/* Sample length: 0x0 */
	//ADC0.SAMPCTRL = 0b00000 << ADC_SAMPLEN_gp; 
	
	/* Sample length: 0x0 */
	ADC0.SAMPCTRL = 0b11111 << ADC_SAMPLEN_gp; 

	/* Window Comparator High Threshold*/
	//ADC0.WINHT = ADC_Window_High_Threshold; 
	ADC0.WINHT = ADC_Threshold_High; 
	
	/* Window Comparator Low Threshold: */
	//ADC0.WINLT = ADC_Window_Low_Threshold; 
	ADC0.WINLT = ADC_Threshold_Low; 

	/* ADC Enabled, ADC Freerun mode disabled, 10-bit mode, Run standby mode enabled */
	ADC0.CTRLA = 1 << ADC_ENABLE_bp | 0 << ADC_FREERUN_bp | ADC_RESSEL_10BIT_gc | 1 << ADC_RUNSTBY_bp; 
}

void EVENT_SYSTEM_0_initialization(void){	/* Event initialization */
	/* Set up the Asynch Event Channel 0 to  be connected to Real Time Counter overflow */
	EVSYS.ASYNCCH0 = EVSYS_ASYNCCH0_RTC_OVF_gc; // this works

	/* ADC0 is reserved for Asynch User 1 - Asynchronous Event Channel 0 */
	EVSYS.ASYNCUSER1 = EVSYS_ASYNCUSER1_ASYNCCH0_gc; // this works

	/* TCBO is reserved for Asynch User 0 - Asynchronous Event Channel 0 */
	EVSYS.ASYNCUSER0 = EVSYS_ASYNCUSER0_ASYNCCH0_gc; 	


	/* Set up the Synch Event Channel 0 to be connected to TCA0 overflow */
	//EVSYS.SYNCCH0 = EVSYS_SYNCCH0_TCA0_OVF_LUNF_gc;
		
	/* TCA0 is reserved for Synch User 0 */
	//EVSYS.SYNCUSER0 = EVSYS_SYNCUSER0_SYNCCH0_gc;
}

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_STANDBY);
	//set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}

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 TIMER_0_init_TCA0(void){
  // TCA0.SINGLE.CMP0 = 0x0; /* Compare Register 0: 0x0 */

	// TCA0.SINGLE.CMP1 = 0x0; /* Compare Register 1: 0x0 */

	// TCA0.SINGLE.CMP2 = 0x0; /* Compare Register 2: 0x0 */

	// TCA0.SINGLE.CNT = 0x0; /* Count: 0x0 */

	// TCA0.SINGLE.CTRLB = 0 << TCA_SINGLE_ALUPD_bp /* Auto Lock Update: disabled */
	//		 | 0 << TCA_SINGLE_CMP0EN_bp /* Compare 0 Enable: disabled */
	//		 | 0 << TCA_SINGLE_CMP1EN_bp /* Compare 1 Enable: disabled */
	//		 | 0 << TCA_SINGLE_CMP2EN_bp /* Compare 2 Enable: disabled */
	//		 | TCA_SINGLE_WGMODE_NORMAL_gc; /*  */

	// TCA0.SINGLE.CTRLC = 0 << TCA_SINGLE_CMP0OV_bp /* Compare 0 Waveform Output Value: disabled */
	//		 | 0 << TCA_SINGLE_CMP1OV_bp /* Compare 1 Waveform Output Value: disabled */
	//		 | 0 << TCA_SINGLE_CMP2OV_bp; /* Compare 2 Waveform Output Value: disabled */

	// TCA0.SINGLE.DBGCTRL = 0 << TCA_SINGLE_DBGRUN_bp; /* Debug Run: disabled */

  /* Count on Event Input: enabled Count on positive edge event */
	TCA0.SINGLE.EVCTRL = 1 << TCA_SINGLE_CNTEI_bp | TCA_SINGLE_EVACT_POSEDGE_gc;

	/* Compare 0 Interrupt: disabled Compare 1 Interrupt: disabled Compare 2 Interrupt: disabled Overflow Interrupt: disabled */
   //TCA0.SINGLE.INTCTRL = 0 << TCA_SINGLE_CMP0_bp | 0 << TCA_SINGLE_CMP1_bp | 0 << TCA_SINGLE_CMP2_bp | 0 << TCA_SINGLE_OVF_bp; 

	/* Compare 0 Interrupt: disabled, Compare 1 Interrupt: disabled, Compare 2 Interrupt: disabled, Overflow Interrupt: enabled */
   TCA0.SINGLE.INTCTRL = 0 << TCA_SINGLE_CMP0_bp | 0 << TCA_SINGLE_CMP1_bp | 0 << TCA_SINGLE_CMP2_bp | 1 << TCA_SINGLE_OVF_bp; 


  	/* Period: 0xffff */
	TCA0.SINGLE.PER = TCA0_period; 

	/* System Clock  Module Enable: enabled */
	TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc | 1 << TCA_SINGLE_ENABLE_bp ;
}

void TIMER_0_init_TCB0(void){
	/* Asynchronous Enable: enabled, Pin Output Enable: disabled,  Periodic Interrupt */
	TCB0.CTRLB = 1 << TCB_ASYNC_bp | 0 << TCB_CCMPEN_bp | 0 << TCB_CCMPINIT_bp | TCB_CNTMODE_INT_gc;  

	// TCB0.DBGCTRL = 0 << TCB_DBGRUN_bp; /* Debug Run: disabled */

	/* Event Input Enable: enabled, Event Edge: enabled, Input Capture Noise Cancellation Filter: disabled */
	TCB0.EVCTRL = 1 << TCB_CAPTEI_bp | 1 << TCB_EDGE_bp | 0 << TCB_FILTER_bp; 

	TCB0.INTCTRL = 1 << TCB_CAPT_bp /* Capture or Timeout: enabled */;

	/* CLK_PER (No Prescaling), Enable: enabled, Run Standby: enabled, Synchronize Update: disabled */
	TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | 1 << TCB_ENABLE_bp | 1 << TCB_RUNSTDBY_bp | 0 << TCB_SYNCUPD_bp; 

	/* CLK_PER (Divide by 2), Enable: enabled, Run Standby: enabled, Synchronize Update: disabled */
	//TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc | 1 << TCB_ENABLE_bp | 1 << TCB_RUNSTDBY_bp | 0 << TCB_SYNCUPD_bp; 

	TCB0.CCMP = TCB0_CCMP;
	
}

void system_init(){							/* system initialization */
	mcu_init();
	pin_init();
	CLKCTRL_init();
	RTC_0_init();
	TIMER_0_init_TCA0();
	TIMER_0_init_TCB0();
	EVENT_SYSTEM_0_initialization();
	VREF_0_init();
	ADC_0_initialization();
	SLPCTRL_init();
	BOD_init();
	CPUINT_init();
}

Just for completeness, here’s my Platformio.ini

; 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:ATtiny402]
platform = atmelmegaavr
board = ATtiny402
;framework = arduino
upload_speed = 115200
upload_flags =
    --tool
    uart
    --device
    attiny402
    --uart
    $UPLOAD_PORT
    --clk
    $UPLOAD_SPEED
upload_command = pymcuprog write --erase $UPLOAD_FLAGS --filename $SOURCE

Looking at the two timers I have available, TCB0 has a TCB_RUNSTDBY_bp bit in TCB0.CTRLA but there is no such bit for TCA0 so I’m stuck with using TCB0.

At least something to focus on.

I’d still be very interested in an answer as to how to use RTC overflow events to cause another counter to overflow and generate an interrupt but I think I have a work around.

Accepting that I am stuck with TCB0 as this runs in STANDBY and all I can do is have this count and overflow on the main clock in STANDBY here’s what I’m working with.

If I divide my 32kHz RTC clock by 8 and use this as the system clock.

/* Set the Main clock prescaler divisor to 8X and enable the Main clock prescaler */
	_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, CLKCTRL_PDIV_8X_gc | 1 << CLKCTRL_PEN_bp); 

I can generate long times to overflow and control this with the TCB0.CCMP value. As I am not using ISP, I don’t have to worry how slow my board is running (yes I’ve bricked a few this way).

I can ‘start’ the timer at will by disabling TCB0, resetting the CNT value, waiting a while, re-enabling Capture or Timeout and restarting TCB0 as below:

void enableTCB0Timeout(void){
	disableTCB0();						// disable TCB0 - it was running as delay routines were using it
	TCB0.CNT = 0; 						// reset TCB0 counter 
	_delay_ms(2);						// wait a bit or an interrupt gets generated immediately at the start
	TCB0.INTCTRL = 1 << TCB_CAPT_bp; 	// enable Capture or Timeout
	enableTCB0();						// reenable TCB0
}

I’d welcome any thoughts / comments. Is there a more elegant way of restarting the timer so it doesn’t immediately generate an overflow interrupt without using the cludgy 2ms delay?

Ah - just tested again and I’m still getting an initial interrupt even with the delay. Something to finesse.

Sorted. I was inadvertently restarting the TCB0 at the end of my bip() routine - should have instead disabled it as _delay_ms() obviously starts it up. Doh!

1 Like

The strategy I have adopted is not to try to count the RTC overflow generated events in Standby sleep mode but instead start the TCB0 in Periodic Interrupt Mode and have this count up in Standby sleep mode until I have timed my period and then have this generate an interrupt so I can take some action.
I have to jiggle my system clock (sourced from the 32kHz oscillator) to get it slow enough to give me the time period I want whilst keeping the RTC overflow event at a few hertz to drive the ADC measurement in Standby sleep mode. I was a bit concerned that the clock would be to slow for the ADC conversion but this seems not the case. Happy days! I’d still be interested in some code examples using events and timers or inter peripheral linkage using events.