How do you use millis() with ATtiny402?

I am struggling to use the Arduino millis() function when I build a project on an ATtiny402.

If I hover over the millis() a window pops up saying #define millis millis Expands to: millis Here millis() is in blue text - as in not a function. Here’s a screen shot
Capture

On another board,where I successfully used millis(), the expression expanded to unsigned long millis(). Here millis() is in yellow text - as in a function. Here’s a screen shot

Capture1

How do I get the milllis() to initialise correctly with my ATtiny402 board?

I tried to force the use of TCA0 by #define MILLIS_USE_TIMERA0 1
I tried to initialise it by calling init_millis();

Neither of these tactics worked.

I want to use RTC in the project and have the thing run on 32kHz low power oscillator.

Please see full test code here (apologies I am relatively new to PlatformIO, ATtiny402 devices and programming in C/C++ for that matter).

#ifdef F_CPU
#undef F_CPU
#endif
#define F_CPU 32000UL

#include <Arduino.h>
#include <avr/interrupt.h>  // so we can use interrupt vector names
#include <util/delay.h>     // needed for the delay functions

#define MILLIS_USE_TIMERA0 1 // same result without this 

/*  pin information
				     ATtiny402
		  	     ----u---
  		    VDD|		 |GND
    	PWR PA6|		 |PA3 SIG
       SW_M PA7|		 |PA0 UDIP
	  	BUZ PA1|		 |PA2 SW_P
		  	     --------
*/  

#define SIG_bp 3

#define rtc_period 960	  // 960 is a rate of 1Hz

void system_init();           // placeholder for compiler

void bop(){ 						// debug routine
	VPORTA_IN |= (1<< SIG_bp);		// toggle LED1 
	_delay_ms(10);
	VPORTA_IN |= (1<<SIG_bp);		// toggle LED1
}

void setup() {
	// put your setup code here, to run once:
	system_init();
	bop();
	init_millis(); // same result without this 
}

void loop() {
	// put your main code here, to run repeatedly:
	
	static unsigned long last_SW_M_time;                 // debounce timer
		
	if (millis() - last_SW_M_time > 1000){
		VPORTA_IN |= (1<< SIG_bp);
		last_SW_M_time = millis();
	}

}

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 */
	
/*  pin information
				ATtiny402
		  	   	----u---
  		    VDD|		 |GND
    	PWR PA6|		 |PA3 SIG
       SW_M PA7|		 |PA0 UDIP
	  	BUZ PA1|		 |PA2 SW_P
		  	     --------
*/  
	// define  output pins
	VPORTA_DIR |= (1<<SIG_bp);				// debug

	// set output pins LOW
	VPORTA_OUT &= ~((1<<SIG_bp));			// debug

}

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

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

	/* 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 */ //- may not need this 
	/* Wait for all register to be synchronized */
	while (RTC.STATUS > 0); 

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

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

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

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

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

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

And platformio.ini here

; 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

It compiles OK but the millis() isn’t returning a meaningful value - I want to use this for some key debounce.

Here’s the compile / load output

 *  Executing task in folder ATtiny402_test copy: C:\Users\Peter\.platformio\penv\Scripts\platformio.exe run --target upload 

Processing ATtiny402 (platform: atmelmegaavr; board: ATtiny402; framework: arduino)
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/atmelmegaavr/ATtiny402.html
PLATFORM: Atmel megaAVR (1.6.0) > ATtiny402        
HARDWARE: ATTINY402 16MHz, 256B RAM, 4KB Flash     
PACKAGES:
 - framework-arduino-megaavr-megatinycore @ 2.5.11 
 - tool-avrdude-megaavr @ 3.60300.220118 (6.3.0)   
 - toolchain-atmelavr @ 3.70300.220127 (7.3.0)     
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 15 compatible libraries
Scanning dependencies...
No dependencies
Building in release mode
Compiling .pio\build\ATtiny402\src\main.cpp.o
Linking .pio\build\ATtiny402\firmware.elf
Checking size .pio\build\ATtiny402\firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [=         ]   5.5% (used 14 bytes from 256 bytes)
Flash: [==        ]  15.4% (used 632 bytes from 4096 bytes)
Building .pio\build\ATtiny402\firmware.hex
Configuring upload protocol...
AVAILABLE: jtag2updi
CURRENT: upload_protocol = jtag2updi
Looking for upload port...
Auto-detected: COM7
Uploading .pio\build\ATtiny402\firmware.hex
Connecting to SerialUPDI
Pinging device...
Ping response: 1E9227
Erasing device before writing from hex file...
Writing from hex file...
Writing flash...
Done.

Any help would be much appreciated.

I think it’s the slow speed of the system clock - maybe too slow to resolve ms.
I’ll check this now…

Still struggling - weirdly, the millis() appears as a function until the PlatformIO builds at start up, then it morphs into a #define

If this is the needed activation macro, have you tried putting it in build_flags to enable it globally in the platformio.ini?

build_flags = -DMILLIS_USE_TIMERA0=1 

It works now, my blinky is flashing away like a wild thing but I’d still like to understand what’s going on.

I selected the 16/20MHz internal clock as system clock but have this not running in standby.

(How do you read the FREQSEL bits in the in the Oscillator Configuration Fuse to find out if its running at 16 or 20MHz?)

I removed the #define MILLIS_USE_TIMERA0 1 and init_millis();

I played around with the #define F_CPU value - it made not difference to the flash rate.

The millis() still expands to millis rather than an unsigned long returned by the function millis().

How does this work when I’m trying to load an unsigned long value into my debounce variable?

I’m a bit confused about setting the main clock prescaler (data sheet implied it defaulted to 6 after a reset) and the need to define F_CPU. But when I set it to 6 (commented out code), my blinky flash rate slowed right down so by the time my code runs, the main clock prescaler cannot still be 6.

I think the Arduino framework is doing stuff I’m not aware of.

I’m only using this framework so I can leverage on millis (and some other Arduino libraries).

If you can shed any light on what’s happening, I’d still appreciate the information.

//#ifdef F_CPU
//#undef F_CPU
//#endif
//#define F_CPU 32000UL
//#define F_CPU 3333333UL

#include <Arduino.h>
#include <avr/interrupt.h>  // so we can use interrupt vector names
#include <util/delay.h>     // needed for the delay functions

/*  pin information
				     ATtiny402
		  	     ----u---
  		    VDD|		 |GND
    	PWR PA6|		 |PA3 SIG
       SW_M PA7|		 |PA0 UDIP
	  	BUZ PA1|		 |PA2 SW_P
		  	     --------
*/  

#define SIG_bp 3

#define rtc_period 960	  			// 960 is a rate of 1Hz

void system_init();           		// placeholder for compiler

void bop(){ 						// debug routine
	VPORTA_IN |= (1<< SIG_bp);		// toggle LED1 
	_delay_ms(10);
	VPORTA_IN |= (1<<SIG_bp);		// toggle LED1
}

void setup() {
	// put your setup code here, to run once:
	system_init();
}

void loop() {
	// put your main code here, to run repeatedly:
	static unsigned long last_SW_M_time; 	// debounce timer
	if (millis() - last_SW_M_time > 50){	// debug routine to flash LED 
		VPORTA_IN |= (1<< SIG_bp);			// toggle SIG 
		last_SW_M_time = millis();			// reset debounce timer
	}
}

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 */

/*  pin information
				ATtiny402
		  	   	----u---
  		    VDD|		 |GND
    	PWR PA6|		 |PA3 SIG
       SW_M PA7|		 |PA0 UDIP
	  	BUZ PA1|		 |PA2 SW_P
		  	     --------
*/  
	// define  output pins
	VPORTA_DIR |= (1<<SIG_bp);				// debug

	// set output pins LOW
	VPORTA_OUT &= ~((1<<SIG_bp));			// debug
}

void CLKCTRL_init(void){					/* Main clock initialization */

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

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

        /* disable the 16/20MHz oscillator in standby mode*/
	_PROTECTED_WRITE(CLKCTRL.OSC20MCTRLA, 0 << CLKCTRL_RUNSTDBY_bp);

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

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

	/* 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 */ //- may not need this 
	/* Wait for all register to be synchronized */
	while (RTC.STATUS > 0); 

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

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

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

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

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

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

HI Max
Thanks for response - I don’t know if this is a needed activation macro - I only picked on it as I was wandering through the MILLIS section of timers.h and fond some conditional compile code that I frankly didn’t follow (I’m a newbie).
It makes some sense to me that using a clock of only 32kHz might be too slow for the millis() code - seems to work OK now with no special config - other than specifying the 16/20MHz system clock.
I’m still wondering why millis() expands to a #define millis millis.

Here’s the relevant code snippet from line 115 of timers.h


#if !defined(MILLIS_USE_TIMERNONE)
  #define millis millis
  #if !defined(MILLIS_USE_TIMERRTC)
    #define micros micros
  #endif
#endif

You primarily affect this value through the build system, not by doing #define F_CPU in the code. The default is 16MHz. See ATtiny402 — PlatformIO v6.1 documentation

1 Like

Without putting too fine a point on it, it most definitely is doing a lot of work behind the scenes! Happy to discuss further – drop me a private message if you want more information.

However, it is doing this to make life easy for beginners in the use of microcontrollers, so that they can concentrate on getting hardware working without worrying about setting up the compiler and such like. Even just the compilation of a sketch hides an awful lot from you. I documented it at Tutorial for creating multi cpp file arduino project - #36 by normandunbar some time ago.

Other things that the Arduino does for you, at least on an Arduino Uno, are:

  • Enables global interrupts.
  • Creates a main() function which calls setup() once, then loops around, calling loop(). Both setup() and loop() are the ones in your sketch, obviously!
  • Configures the three timers in 8 bit mode, with a divide by 64 prescaler, and with PWM enabled on both channels to give 6 pins with PWM available. Timer 0 uses Fast Hardware PWM while Timers 1 and 2 use Phase Correct PWM. PWM being the analogWrite() function in your code.
  • Configures timer 0’s overflow interrupt to count up the time that has passed since power on/reset. This is “surfaced” in the micros() and millis() functions.
  • Disables the USART so that pins D0 and D1 can be used for GPIO purposes. This will be disabled when Serial.begin() is called to initialise the USART for Serial output.
  • The ADC is configured with a divide by 128 prescaler, enabled and powered on regardless of whether the sketch uses it or not. The analogRead() function in your code uses the ADC.
  • The Arduino boards are fused to set the BOD – brown out detector – to a voltage that is far too low for a 16MHz clock speed!

If it wasn’t for the stuff that the Arduino and its language hide from you, you, the developer, would have a lot more work to do even to get an LED blinking every second!

HTH

Cheers,
Norm.

Hi Max - many thanks - I’ll do some reading on link you sent.

Hi Norman
Many thanks and thanks also for not chewing my head off - it explains why I’ve had battles with timers etc (I started programming ATtiny85/13A without the Arduino framework in Studio7 but have now seen the light and am making a home in PlatformIO) - I’ll strive to get to grips with Arduino as I like the varied libraries available.
Regards Peter

1 Like