Help to get megaTinyCore on ATtiny1604 use TCB0 in place of TCA0

Right to it - can you / how do you get megaTinyCore to use TCB0 in place of TCA0 for the ATtiny1604 part?

I am using part ATtiny1604 (OK, what did I know when I purchased them?).

I want to use the megaTinyCore Arduino wrapper (A big thanks here to Dr Azzy).

I want to use millis() (Another big thanks here to Dr Azzy).

I want to have a clock speed capable of sending TTY serial debug.

And here’s the rub - I want to control a servo with a 50Hz PWM signal.

By default, millis() uses the TCA0 counter on this part - and a fine 16 bit counter it is too.

I tried to use TCB0 on this part to generate PWM signal but this ‘16 bit counter’ has the period controlled by the lower 8 bits of CCMP and the duty cycle by the higher 8 bits .

In terms of slowing things down, you only get to select CLK_PER/DIV2.

With a system clock running at 16MHz which is good for Arduino serial.print(), the slowest servo frequency I can get is 160kHz plus.

If I select the 32kHz for the system clock, I can achieve a PWM signal for my servo at 50Hz but this ‘tanks’ the Serial.print() functionality of megaTinyCore.

OK, I can (and have) set up TCB0 to generate a tidy 1kHz interrupt and increment a ‘tick counter’ to replace millis() but I was wondering if anyone knows if you can define something to get megaTinyCore to use TCB0 instead so I can use the ‘full fat’ 16 bit TCA0 counter for my 50Hz PWM servo signal?

BTW - I simply loved the Historical section in the doc
especially the bit about the guy getting bitten by the (watch)dog - it happened to me and bricked an AVR in the process.

I appreciate ‘yall’ want code to pick through but what’s the point in posting code that I know doesn’t work? I’m just chasing concepts here.

Can you help?

In the Arduino IDE, you can chose the timer that millis() uses in the menu settings. That is implemented by

That in turn just translates to injecting -D macros into the compiler invocation.

So, we just have to make sure that the right -DMILLIS_USE_TIMER<timer> macros is activated, aka -DMILLIS_USE_TIMERB0. That can be done via build_flags.

However, special attention must be given to already existing flags. And, if using board = ATtiny1604 in the platformio.ini, that gives the standard flags of

Which already contain -DMILLIS_USE_TIMERA0. So that must be undone with build_unflags.

So all in all, the configuration

platform = atmelmegaavr
board = ATtiny1604
framework = arduino
build_unflags = -DMILLIS_USE_TIMERA0
build_flags = -DMILLIS_USE_TIMERB0

should get you started.

Many thanks Max
That did the trick but I also had to include the function takeOverTCA0(); when setting up timer TCA0. Why is this and what’s going on behind the scenes?
Your use of build flags was an eye opener to me - I think I am missing a trick or two here. Are there any explainers on this subject?
My test code and platformio.ini file below - this tests that millis() still behaves and can time events predictably, my system clock is still fast enough to run a variant of SoftwareSerial and I can generate a 50Hz servo friendly PWM signal using TCA0.
Just for good measure, TCA0 generates an overflow interrupt and toggling a pin here generates an expected 25Hz signal.

Thanks again.
My programme:

// ATtiny1604_TCA0_PWM_Help
// written for ATtiny1604
// Peter Charles-Jones
// cSpell:includeRegExp CStyleComment

// put external code references here:
#include <Arduino.h>
#include <SendOnlySoftwareSerial.h>		// saves a pin for output debug

// put pre-compile code here:
#define BAUD 115200		// 115200 is default BAUD for HM-10 BlueTooth modem

#define BUZ_bp 5		// PA5 - output - Buzzer
#define LED_bp 1		// PB1 - output - LED
#define PWM_bp 0		// PB0 - output - TCA0 controlled WO0 on default pin

#define Tx 9			// equivalent Arduino pin number

unsigned long test_time = millis();				// count time since last LED toggle
int counter = 0;								

// put drivers here:
SendOnlySoftwareSerial mySerial (Tx);		// set up the serial output stream to PA2

// put function declarations here:
void system_init();

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

void loop(){
	// put your main code here, to run repeatedly:
	// this routine tests that millis() is behaving and that I can also send serial data at the correct BAUD
	if ((millis() - test_time) > 100){	// this generates a 5Hz signal at PB1
		VPORTB_IN |= (1<<LED_bp);		//	toggle LED
		mySerial.println(counter++);	// send some sample debug
		test_time = millis();			// reset timer

/* My subroutines */

/* Initialization routines */

// the structure of this was largely lifted from Atmel START Studio 7 code generator
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 and input disabled */
	for (uint8_t i = 0; i < 8; i++){ // PA0 - PA7
		uint8_t *port_pin_ctrl = ((uint8_t *)&PORTA + 0x10 + i);
		*port_pin_ctrl |= (1 << PORT_PULLUPEN_bp);								   // define pull up
		*port_pin_ctrl = (*port_pin_ctrl & ~PORT_ISC_gm) | PORT_ISC_INTDISABLE_gc; // disable input sensing

	/* Set all PORTB pins to low power mode and input disabled */
	for (uint8_t i = 0; i < 4; i++){ // PB0 - PB3 on ATtiny1604
		uint8_t *port_pin_ctrl = ((uint8_t *)&PORTB + 0x10 + i);
		*port_pin_ctrl |= (1 << PORT_PULLUPEN_bp);								   // define pull up
		*port_pin_ctrl = (*port_pin_ctrl & ~PORT_ISC_gm) | PORT_ISC_INTDISABLE_gc; // disable input sensing

void pin_init(void){ 					// PIN initialization
	PORTA_DIRSET = (1<<BUZ_bp);
	PORTB_DIRSET = (1<<LED_bp)|(1<<PWM_bp);

void PWM_0_TCA0_init(){
	takeOverTCA0(); 																// undo the core timer initializations
	PORTMUX_CTRLC |= PORTMUX_TCA00_DEFAULT_gc; 										// Port Multiplexer TCA0 default output WO0 - PB0 (not needed as the _gc = 0)
	TCA0_SINGLE_CMP0BUF = 2000;														// sets mark to 1ms
	TCA0_SINGLE_CTRLB = TCA_SINGLE_WGMODE_SINGLESLOPE_gc | TCA_SINGLE_CMP0EN_bm; 	// select waveform generation mode and enable output pin
	TCA0_SINGLE_PER = 40000;														// for a PWM of 50Hz
	TCA0_SINGLE_CTRLA = TCA_SINGLE_CLKSEL_DIV8_gc | (1<<TCA_SINGLE_ENABLE_bp);  	// Select clock division and enable
	TCA0_SINGLE_INTCTRL = TCA_SINGLE_OVF_bm; 										// Overflow Interrupt enabled

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

void system_init(){ /* system initialization */

// interrupt service routines here:
	/* Insert your TCA overflow interrupt handling code */
	VPORTA_IN |= (1<<BUZ_bp);	//	toggle BUZ - this generates a 25Hz signal which proves the TCA0 counter is overflowing at 50Hz

	/* The interrupt flag has to be cleared manually */

and the 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

platform = atmelmegaavr
board = ATtiny1604
framework = arduino
build_unflags = -DMILLIS_USE_TIMERA0    ; stop megaTinyCore using TCA0
build_flags = -DMILLIS_USE_TIMERB0      ; have it use TIMERB0 

board_build.f_cpu = 16000000L
upload_speed = 115200
upload_port = COM16

monitor_port = COM16
monitor_speed = 115200

upload_flags =
upload_command = pymcuprog write --erase $UPLOAD_FLAGS --filename $SOURCE

and for completeness, the external library was loaded as per image:

I think will explain this very well.

Hi Max
Thank you for your time. Wow that’s a brilliant link.
Do you have a link to an explainer about build_flags for platformio.ini?
I checked your [build_flags] link but then the red mist descended. (build_flags — PlatformIO latest documentation)
Do you have a link to an explainer for this whole side of PlatformIO? (I just loaded something that worked for my ATtiny13A at the time) - I have little understanding of what it’s actually doing - (bit of a newbie here)?

I’ll get there some time, some how.

… now, if I could just remember what I did with those mini solar panels I remember receiving from AliExpress - I’m sure I put them some place safe, I remember keeping the bubble wrap. But can I remember where I put them - I hate being old …

About those solar panels … I had a new charger for my SLR camera battery on the same order. Obvs - I wanted to test this charger worked. I have a space ‘under the stairs’ with all manner of chargers (I so hate all those charger cables lying around the house). The solar panels were there, under the stairs, still in their protective bubble wrap.
Looking at the ‘silver lining’ - I now have a tremendously tidy ‘man cave’.