SAML21 TCO interrupt

Hi everyone,
I’m playing around with the SAML21G18B MCU and the Mattairtech Arduino distribution.

The MCU is very similar to the D21 (Arduino Zero) and I am trying to configure the TC0 timer to generate a periodic interrupt. Unfortunatly none of the existing SAMD timer libraries work with this MCU/distribution so I’ve been trying to configure and use TC0 from scratch …

I’ve taken the analogWrite() code from the Mattairtech core and hacked it into what I hope is something that configures TC0 for normal PWM - the same as when its used for analogWrite().

It compiles (YAY!) but I attempted to read from the count register to see if it is counting, and I get nothing. I also have no idea how to configure it to generate an interrupt on a match with the CC0 register.

All my code is below, any help, pointers, tips to get the interrupts working would be appreciated.

void TC0_Handler() {
  //interrupt handler ...
}

static void SYNC_TC(Tc* TCx){
  while(TCx->COUNT16.SYNCBUSY.reg & (TC_SYNCBUSY_SWRST | TC_SYNCBUSY_ENABLE | TC_SYNCBUSY_CTRLB | TC_SYNCBUSY_STATUS | TC_SYNCBUSY_COUNT));
}

//initialise TCO
void initTc0(bool sixteenBit, uint16_t value){
  uint8_t timerCh = 0;
  Tc*  TCx  = (Tc*)TC0;

  //GCLK setup
  GCLK->PCHCTRL[GCM_TC0_TC1].reg = (GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK0);
  while ( (GCLK->PCHCTRL[GCM_TC0_TC1].reg & GCLK_PCHCTRL_CHEN) == 0 );        // wait for sync
  //Disable TC
  TCx->COUNT16.CTRLA.bit.ENABLE = 0;
  SYNC_TC(TCx);
  // Set Timer counter Mode to 16 bits, normal PWM
  if(sixteenBit) TCx->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16;
  else{
    // Set Timer counter Mode to 8 bits, normal PWM
    TCx->COUNT8.CTRLA.reg |= TC_CTRLA_MODE_COUNT8;
    SYNC_TC(TCx);
    // Set PER to maximum counter value
    TCx->COUNT8.PER.reg = 0xFF;
  }
  SYNC_TC(TCx);
  // Set TCx as normal PWM
  TCx->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_NPWM;
  SYNC_TC(TCx);
  // Set the initial value for CC
  if(sixteenBit) TCx->COUNT16.CC[timerCh].reg = (uint16_t) value;
  else TCx->COUNT8.CC[timerCh].reg = (uint8_t) value;
  
  SYNC_TC(TCx);
  // Enable TCx
  TCx->COUNT16.CTRLA.bit.ENABLE = 1;
  SYNC_TC(TCx);
}

//Set the CCBuf register
void setTc(bool sixteenBit, uint16_t value){
  uint8_t timerCh = 0;
  Tc*  TCx  = (Tc*)TC0;
  if(sixteenBit) TCx->COUNT16.CCBUF[timerCh].reg = (uint16_t)value;
  else TCx->COUNT8.CCBUF[timerCh].reg = (uint8_t)value;
  SYNC_TC(TCx);
}

uint16_t getCount(){
  Tc*  TCx  = (Tc*)TC0;
  TCx->COUNT16.CTRLBSET.bit.CMD = TC_CTRLBSET_CMD_READSYNC;
  return TCx->COUNT16.COUNT.reg;
}

void setup() {
  // put your setup code here, to run once:
  initTc0(1, 0); 
  setTc(1, 500);
  Serial.begin(1000000);
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.println(getCount());
  delay(100);
}

Ok, answered my own question, so I'll post the code here just in case it is of any use to others.

I ripped the tone.cpp code to pieces and used that because I realised it was using a timer to generate interrupts that would toggle a pin. On the SAML21 tone used TC1, so I modified this to use TC0.

You need to put these two at the start of the sketch as well.

include "variant.h"

include "sam.h"

//***************Use this for TC0

#define TCTimer     TC0
#define timerIRQ    TC0_IRQn
//Timer handler function if using TC0 - put whatever ISR code you want in here.
void TC0_Handler (void){
  //clear the interrupt flag
  TCTimer->COUNT16.INTFLAG.bit.MC0 = 1;
  isrCounts++;
}

//***************Use this for TC1
/*
#define TCTimer     TC1
#define timerIRQ    TC1_IRQn
//Timer handler function if using TC1 - put whatever ISR code you want in here.
void TC1_Handler (void){
  //clear the interrupt flag
  TCTimer->COUNT16.INTFLAG.bit.MC0 = 1;
  isrCounts++;
}*/

//Wait for registers to synchronise
#define WAIT_TC16_REGS_SYNC(x) while(x->COUNT16.SYNCBUSY.reg);

#define TONE_TC_TOP     0xFFFF
#define TONE_TC_CHANNEL 0

//Reset the timer hardware
static inline void resetTC (Tc* TCx){
  // Disable TCx
  TCx->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE;
  WAIT_TC16_REGS_SYNC(TCx)
  // Reset TCx
  TCx->COUNT16.CTRLA.reg = TC_CTRLA_SWRST;
  WAIT_TC16_REGS_SYNC(TCx)
  while (TCx->COUNT16.CTRLA.bit.SWRST);
}

//setup the timer with a prescaler clock value, and the CC value.
//The timer incriments until it hits CC, then fires an interrupt and starts over
//With prescaler at 1 and CC at 65535 it interrupts 733 times per second
//Reduce CC to get a faster interrupt rate
//Prescaler must only be 1,2,4,8,16,64,25 or 1024
void setupTCTimer(uint32_t prescaler, uint32_t ccVal ){
  NVIC_DisableIRQ(timerIRQ);
  NVIC_ClearPendingIRQ(timerIRQ);

  NVIC_SetPriority(timerIRQ, 0);

  // Enable GCLK for timer used

  GCLK->PCHCTRL[GCM_TC0_TC1].reg = (GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK0);
  while ( (GCLK->PCHCTRL[GCM_TC0_TC1].reg & GCLK_PCHCTRL_CHEN) == 0 );        // wait for sync

  uint32_t prescalerConfigBits;
  uint32_t ccValue;

  ccValue = ccVal;//toneMaxFrequency / frequency - 1;
  prescalerConfigBits = TC_CTRLA_PRESCALER_DIV1;
  switch(prescaler){
    case 0:     prescalerConfigBits = TC_CTRLA_PRESCALER_DIV1;      break;
    case 1:     prescalerConfigBits = TC_CTRLA_PRESCALER_DIV1;      break;
    case 2:     prescalerConfigBits = TC_CTRLA_PRESCALER_DIV2;      break;
    case 4:     prescalerConfigBits = TC_CTRLA_PRESCALER_DIV4;      break;
    case 8:     prescalerConfigBits = TC_CTRLA_PRESCALER_DIV8;      break;
    case 16:    prescalerConfigBits = TC_CTRLA_PRESCALER_DIV16;     break;
    case 64:    prescalerConfigBits = TC_CTRLA_PRESCALER_DIV64;     break;
    case 256:   prescalerConfigBits = TC_CTRLA_PRESCALER_DIV256;    break;
    case 1024:  prescalerConfigBits = TC_CTRLA_PRESCALER_DIV1024;   break;
    default:    prescalerConfigBits = TC_CTRLA_PRESCALER_DIV1;      break;
  }
  resetTC(TCTimer);
  uint16_t tmpReg = 0;
  tmpReg |= TC_CTRLA_MODE_COUNT16;  // Set Timer counter Mode to 16 bits
  tmpReg |= prescalerConfigBits;
  TCTimer->COUNT16.CTRLA.reg |= tmpReg;
  WAIT_TC16_REGS_SYNC(TCTimer)
  TCTimer->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ;
  WAIT_TC16_REGS_SYNC(TCTimer)
  TCTimer->COUNT16.CC[TONE_TC_CHANNEL].reg = (uint16_t) ccValue;
  WAIT_TC16_REGS_SYNC(TCTimer)
  // Enable the TCTimer interrupt request
  TCTimer->COUNT16.INTENSET.bit.MC0 = 1;
}

//Start the timer
//you need to call setupTCTimer with prescaler and CC values before you start the timer
void startTCTimer(){
// Enable TCTimer
  TCTimer->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;
  WAIT_TC16_REGS_SYNC(TCTimer)
  NVIC_EnableIRQ(timerIRQ);
  timerActive = true;
}

//stop the timer
//This resets it so you need to call setupTCTimer with prescaler and CC values before restarting
void stopTCTimer(){
  resetTC(TCTimer);
  timerActive = false;
}