Nano Every PWM Port Manipulation

Trying to turn multiple outputs on simultaneously at a specific PWM duty cycle and frequency (31kHz) . I’ve managed to achieve this with timer TCB0 for pin D6:

void setup() {



// Pin D6, PF4
 
 pinMode(6,OUTPUT);
     

 PORTMUX.TCBROUTEA |= PORTMUX_TCB0_bm; // change ouput pin of counter from PA2 to PF4 
 
  TCB0.CTRLB = 0 << TCB_ASYNC_bp      // Asynchronous Enable: disabled 
               | 1 << TCB_CCMPEN_bp   // Pin Output Enable: enabled 
               | 0 << TCB_CCMPINIT_bp // Pin Initial State: disabled 
               | TCB_CNTMODE_PWM8_gc; // 8-bit PWM 

  TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc  // CLK_PER (No Prescaling 62.3kHz) =  _CLKDIV1.... CLK_PER/2 = _CLKDIV2 (31.17kHZ) 
               | 1 << TCB_ENABLE_bp   // Enable: enabled 
               | 0 << TCB_RUNSTDBY_bp // Run Standby: disabled 
               | 0 << TCB_SYNCUPD_bp; // Synchronize Update: disabled 
 
  TCB0.CTRLA |= TCB_ENABLE_bm;
 
 
  TCB0.CCMPL = 255; // PWM Period

  TCB0.CCMPH = 128;   // PWM Compare. 128 approximately 50% duty cycle.


 



}
  
void loop() {
 
// Pin6 Low 

PORTF.OUT &= ~0b00010000;


 delay(96000); // 3 sec delay


 //Pin6 High 
 

PORTF.OUT |= 0b00010000;

 delay(96000); // 3 sec delay


}

However, when i try with timer TCA0 i’m not having any luck. I’ve followed the instructions from the data sheet (http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega4808-09-DataSheet-DS40002173B.pdf, Pages 191/192) but the output is not at the specified duty cycle.
Does anyone know where i’ve gone wrong?

void setup() {

  Serial.begin(9600); 
  
// Set pins to output 
 
 pinMode(10,OUTPUT);
 pinMode(9,OUTPUT);
 pinMode(5,OUTPUT);



 TCA0.SINGLE.CTRLA &= ~TCA_SINGLE_ENABLE_bm;
                                        // Turn off timer while we change parameters.
  TCA0.SINGLE.CTRLA &= ~TCA_SINGLE_CLKSEL_gm;
                                        // Clear all CLKSEL bits.
  TCA0.SINGLE.CTRLA |= TCA_SINGLE_CLKSEL_DIV2_gc;
                                        // Set prescaler to 2. // Can be changed from clk_per,clk_per/2..... clk_per/ 1024 (Datasheet PP 198)
 

TCA0.SINGLE.CNT = 0x00;

//Select single slope PWM mode & enable compare channel

 TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm | TCA_SINGLE_WGMODE_SINGLESLOPE_gc;

//TCA0 counting clock ticks

TCA0.SINGLE.EVCTRL &= 0b11111110;

 TCA0.SINGLE.PER = 255;
 TCA0.SINGLE.CMP0 = 128; /* PWM Period*/

   TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm;
                                        // Re-enable timer. Pins 5 and 9 and 10 now run at
                                        // 31.25 kHz. 
}
  
void loop() {
 

//Pins 5, 9, 10 Low 
VPORTB.OUT &= ~0b00010111; 

 delay(96000); // 3 sec delay


 //Pins 5, 9, 10 High 
 
VPORTB.OUT |= 0b00010111; 


 delay(96000); // 3 sec delay

}

Solution.

This book has been a big help for a beginner learning to use the 4809.

https://tomalmy.com/nano-every-book-now-available/

    /*  ******************************TCA0************************************/ 
    
  PORTMUX.TCAROUTEA |= 0x01; // Route signal to port B

  TCA0.SINGLE.CTRLA &= ~TCA_SINGLE_ENABLE_bm;
                                        // Turn off timer while we change parameters.
  TCA0.SINGLE.CTRLA &= ~TCA_SINGLE_CLKSEL_gm;
                                        // Clear all CLKSEL bits.
  TCA0.SINGLE.CTRLA |= TCA_SINGLE_CLKSEL_DIV2_gc;
                                        // Set prescaler to 2. // Can be changed from clk_per,clk_per/2..... clk_per/ 1024 (Datasheet PP 198)
  TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm;
                                        // Re-enable timer. 
                                        // @31.25 kHz. 
  
  TCA0.SINGLE.PERBUF = 255; //  period
  TCA0.SINGLE.CMP0BUF = 255; // duty cycle PB0 
  TCA0.SINGLE.CMP1BUF = 0; //  duty cycle PB1
  TCA0.SINGLE.CMP2BUF = 255; //  duty cycle PB2
  TCA0.SINGLE.CTRLB = 0x73; // Three channels on, Single Slope PWM

Hello,

if the pins are present on the Nano Every or if you re-route them, you are welcome to try the code.
Is tested with the ATmega4809 Curiosity Nano Board.

Replace the function “atmel_start_init” with your own config.
It contains the setting of the CPU clock (not applicable for the Arduino) and the setting of the pins to output.

#include <avr/io.h>
#include <atmel_start.h>

void Port_Init(void);
void TCA0_HardReset(void);
void TCA0_SplitMode_Init (void);

uint8_t low_TCA_Frequenz   = 88;
uint8_t low_TCA_DutyCycle  = 88; 
uint8_t high_TCA_Frequenz  = 199;
uint8_t high_TCA_DutyCycle = 199; 

int main(void)
{
    atmel_start_init();           // Initializes MCU, drivers and middleware
   TCA0_HardReset();
   Port_Init();
   TCA0_SplitMode_Init();
    
    while (1)
   {
   }
}


void TCA0_SplitMode_Init (void)
{
   PORTMUX.TCAROUTEA = PORTMUX_TCA0_PORTA_gc;   // set waveform output default PORT.A, Manual 14.2
   
   TCA0.SPLIT.CTRLD  = TCA_SPLIT_SPLITM_bm;     // enable split mode
   
   TCA0.SPLIT.CTRLB |= TCA_SPLIT_LCMP0EN_bm;    // PA0, enable compare channel 0 for the lower byte, Datasheet 4.1
   TCA0.SPLIT.CTRLB |= TCA_SPLIT_LCMP1EN_bm;    // PA1, enable compare channel 1 
   TCA0.SPLIT.CTRLB |= TCA_SPLIT_LCMP2EN_bm;    // PA2, enable compare channel 2 
   TCA0.SPLIT.CTRLB |= TCA_SPLIT_HCMP0EN_bm;    // PA3, enable compare channel 0 for the higher byte
   TCA0.SPLIT.CTRLB |= TCA_SPLIT_HCMP1EN_bm;    // PA4, enable compare channel 1 
   TCA0.SPLIT.CTRLB |= TCA_SPLIT_HCMP2EN_bm;    // PA5, enable compare channel 2 
   
   TCA0.SPLIT.LPER  = low_TCA_Frequenz;         // set PWM Frequenz
   TCA0.SPLIT.LCMP0 = low_TCA_DutyCycle/7*1;    // set Duty Cycles
   TCA0.SPLIT.LCMP1 = low_TCA_DutyCycle/7*2;
   TCA0.SPLIT.LCMP2 = low_TCA_DutyCycle/7*3;

   TCA0.SPLIT.HPER  = high_TCA_Frequenz;        // set PWM Frequenz
   TCA0.SPLIT.HCMP0 = high_TCA_DutyCycle/7*4;   // set Duty Cycles
   TCA0.SPLIT.HCMP1 = high_TCA_DutyCycle/7*5;
   TCA0.SPLIT.HCMP2 = high_TCA_DutyCycle/7*6;

   TCA0.SPLIT.CTRLA  = TCA_SPLIT_CLKSEL_DIV8_gc; // set Prescaler 8
   TCA0.SPLIT.CTRLA |= TCA_SPLIT_ENABLE_bm;      // start Timer
}


void Port_Init(void)
{
   PORTA.DIR |= PIN0_bm;   // set PA0 as output
   PORTA.DIR |= PIN1_bm; 
   PORTA.DIR |= PIN2_bm;   
   PORTA.DIR |= PIN3_bm;   
   PORTA.DIR |= PIN4_bm;   
   PORTA.DIR |= PIN5_bm;   // set PA5 as output
}

void TCA0_HardReset(void)  // must be used when switching from single mode to split mode
{
   TCA0.SINGLE.CTRLA &= ~(TCA_SINGLE_ENABLE_bm);      // stop timer
   TCA0.SINGLE.CTRLESET = TCA_SINGLE_CMD_RESET_gc;    // force a hard reset
}