Autonomo internal interupts / timers

Hello everyone,

The problem i have is to get an internal interupt working on the autonomo.
There are no examples on the website to do this with the arm processor.
Any help?

Thanks in advance,
Erik

When you say “internal interrupt”, which one are you referring to?

The Arduino Core has pre-defined names for all interrupt handlers. The list can be found in
~/.arduino15/packages/SODAQ/hardware/samd/1.6.4/cores/arduino/cortex_handlers.c

Say if you want the handle the TCC0 interrupt you can simply add a void TCC0_Handler() {...} in your code (sketch).
But then you also need to initialize the timer registers to make it generate interrupts.

Hi kees,

Thanks for the reply.
What i want is an timer based interrupt.
So in short, setting the interrupt registry to trigger on compare( bigger, smaller, equal), when x amount of clock cycles has passed.
I normally do this by setting particular registry’s on arduino, like a prescaler, compare registers etc.
Arduino even has a library to do it for me, But this only works with avr/io.

In general, i want code to trigger 1/sec, without the use of delay().

Thanks in advance,
Erik

Hi,

Did you check the examples of GabrielNotman


I’ve successfully used his RTC_WakeNoISR example to do exactly this.

Best, Jan-Willem

I did Jan-willem,

He has external interupts, but no internal.
Looked at the RTC_ examples, looks like it uses alarms.
Havent looked into the underlying code yet.

What i need an counter next to the clock pulse to trigger interrupts.

Hi erikbozelie,

There are several ways you can achieve this with the SAMD21.

You could use the RTC (in Mode0 or Mode1) or one of the TC or TCC units. In each of these cases, the setup is quite similar for this task. You need to configure a clock source, a clock provider and then feed that into the unit you are using.

Using the RTC (Real Timer Counter) as an example, in the RTCZero library the RTC is configured in Mode2 which is calendar mode. There are two other modes, Mode0 is a 32 bit counter and Mode1 is a 16bit counter.

Here is a brief description of the RTCZero library setup. First the clock source is configured, in this case it is the 32KHz external oscillator. Then a generic clock provider is configured to use that source (GCLK2 is used). The clock provider applies a prescalar of 32 and so outputs a 1KHz signal (1024Hz). The RTC is then configured in Mode2 and uses GCLK2 as a source. The RTC also applies a prescalar of 1024 resulting in a 1Hz clock signal.

In Mode2 the interrupt is triggered by an alarm function which is effectively a specialised compare which allows you to mask which elements you wish to ignore (e.g. there is a mask that will match seconds only, resulting in an interrupt every minute).

Note that many of the clocks sources will already be configured and running. Also GCLK unit can be shared and that GCLK0, GCLK1 & GCLK3 are already used by the system.

GCLK0: 48 MHz (prescalar 1)
GCLK1: 32 KHz (prescalar 1)
GCLK3: 8 MHz (prescalar 1)

I am quite familiar with the way it should work, but unfamiliar with the processor.
Using the real time clock sounds like a great idea aswell, but would prefer using the 7 counters on the processor itself.
Heres the code which i normally use:

ISR(TIMER1_COMPA_vect)
{
// do something
TCNT1 = 0; // reset timer to zero.
}
void timer1_setup (byte mode, int prescale, byte outmode_A, byte outmode_B, byte capture_mode)
{
//Disable interrupts
cli();
noInterrupts();

//display the bitrange… sanity reasons
mode &= 15 ;
outmode_A &= 3 ;
outmode_B &= 3 ;
capture_mode &= 3 ;

//define clock mode
byte clock_mode = 0 ;
//set clock mode
switch (prescale)
{
case 1: clock_mode = 1 ; break ; //001
case 8: clock_mode = 2 ; break ; //010
case 64: clock_mode = 3 ; break ;//011
case 256: clock_mode = 4 ; break ;//100
case 1024: clock_mode = 5 ; break ;//101
default:
if (prescale < 0)
clock_mode = 7 ;
}
//sets the registers
TCCR1A = (outmode_A << 6) | (outmode_B << 4) | (mode & 3) ;
TCCR1B = (capture_mode << 6) | ((mode & 0xC) << 1) | clock_mode ;
//Enable interrupts
interrupts();
sei();
//set timer vallue to zero
TCNT1 = 0;
//vallue to count to: (16mhz / prescaler) -1
OCR1A = 31250; //7812; //15624;
//enable the compare flag
TIMSK1 |= (1 << OCIE1A);
}

void setup()
{
timer1_setup(0100,256,00,00,00);
}

This way, i use the internal counters to proc a function every interval.
Could you help me rewrite this for the autonomo?

Thanks in advance,
Erik

PS. I would suggest putting examples up for this processor. Internal interupts are one of the requirements for multitasking on micro controlers.

Here is an example I wrote today. The TC3 timer is setup with a 1024Hz source clock. It uses GCLK4 and the XOSC32K. The whole chain of modules are set to run in standby (XOSC32K, GCLK4, TC3) and so the interrupts will wake the board from sleep.

volatile bool TC_flag_CC0 = false;
volatile bool TC_flag_CC1 = false;


void setup() 
{
  delay(5000);
  SerialUSB.println("Startup");

  configureTC3(1024, 3072);

  SerialUSB.println("Configuration Complete");
}


void loop() 
{
  // If there was a CC0 match
  if (TC_flag_CC0)
  {
    TC_flag_CC0 = false;
    SerialUSB.print("Matched on CC0, ms: ");
    SerialUSB.println(millis());
    flashLED();
  }

  // If there was a CC1 match
  if (TC_flag_CC1)
  {
    TC_flag_CC1 = false;
    SerialUSB.print("Matched on CC1, ms: ");
    SerialUSB.println(millis());
    flashLED();
  }

  systemSleep();
}


void flashLED()
{
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
  delay(100);
  digitalWrite(LED_BUILTIN, LOW);
}


void systemSleep()
{
  // Set the sleep mode
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

  if (USB->DEVICE.FSMSTATUS.bit.FSMSTATE == USB_FSMSTATUS_FSMSTATE_SUSPEND_Val) {
    // Disable USB
    USBDevice.detach();
    
    // SAMD sleep
    __WFI();

    // Enable USB and wait for resume if attached
    USBDevice.attach();
    USB->DEVICE.CTRLB.bit.UPRSM = 0x01u;
    while (USB->DEVICE.CTRLB.bit.UPRSM);
  }
}


void configureTC3(uint16_t match0, uint16_t match1)
{
  // The GCLK clock provider to use
  // GCLK0, GCLK1 & GCLK3 are used already
  uint8_t GCLK_SRC = 4;

  // Configure the XOSC32K to run in standby
  SYSCTRL->XOSC32K.reg |= SYSCTRL_XOSC32K_RUNSTDBY;
 
  // Setup clock provider GCLK_SRC with a 32 source divider
  // GCLK_GENDIV_ID(X) specifies which GCLK we are configuring
  // GCLK_GENDIV_DIV(Y) specifies the clock prescalar / divider
  // If GENCTRL.DIVSEL is set (see further below) the divider 
  // is 2^(Y+1). If GENCTRL.DIVSEL is 0, the divider is simply Y
  // This register has to be written in a single operation
  GCLK->GENDIV.reg = GCLK_GENDIV_ID(GCLK_SRC) | 
                     GCLK_GENDIV_DIV(4);

  // Configure the GCLK module
  // GCLK_GENCTRL_GENEN, enable the specific GCLK module
  // GCLK_GENCTRL_SRC_XOSC32K, set the source to the XOSC32K
  // GCLK_GENCTRL_ID(X), specifies which GCLK we are configuring
  // GCLK_GENCTRL_DIVSEL, specify which prescalar mode we are using
  // GCLK_RUNSTDBY, keep the GCLK running when in standby mode
  // Output from this module is 1khz (32khz / 32)
  // This register has to be written in a single operation.
  GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | 
                      GCLK_GENCTRL_SRC_XOSC32K | 
                      GCLK_GENCTRL_ID(GCLK_SRC) | 
                      GCLK_GENCTRL_RUNSTDBY |
                      GCLK_GENCTRL_DIVSEL;
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);

  // Turn the power to the TC3 module on
  PM->APBCMASK.reg |= PM_APBCMASK_TC3;

  // Set TC3 (shared with TCC2) GCLK source to GCLK_SRC
  // GCLK_CLKCTRL_CLKEN, enable the generic clock
  // GCLK_CLKCTRL_GEN(X), specifices the GCLK generator source
  // GCLK_CLKCTRL_ID(X), specifies which generic clock we are configuring
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | 
                      GCLK_CLKCTRL_GEN(GCLK_SRC) | 
                      GCLK_CLKCTRL_ID(GCM_TCC2_TC3);
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);

  // Disable TC3 this is required (if enabled already)
  // before setting certain registers
  TC3->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE;
  while (TC3->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);

  // Set the mode to 16 bit and set it to run in standby
  // TC_CTRLA_MODE_COUNT16, specify 16bit mode
  // TC_CTRLA_RUNSTDBY, keep the module running when in standby
  // TC_CTRLA_PRESCALER_DIV1, set the prescalar to 1
  // Prescalar options include: DIV1, DIV2, DIV4, DIV8, 
  // DIV16, DIV64, DIV256, DIV1024
  TC3->COUNT16.CTRLA.reg = TC_CTRLA_MODE_COUNT16 |
                           TC_CTRLA_RUNSTDBY |
                           TC_CTRLA_PRESCALER_DIV1 ;
  while (TC3->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);

  // Set the compare channel 0 value
  TC3->COUNT16.CC[0].reg = match0;
  while (TC3->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);

  // Set the compare channel 1 value
  TC3->COUNT16.CC[1].reg = match1;
  while (TC3->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);
 
  // Enable interrupt on both match compare channels
  // TC_INTENSET_MC0, enable an interrupt on match channel 0
  // TC_INTENSET_MC1, enable an interrupt on match channel 0
  TC3->COUNT16.INTENSET.reg = TC_INTENSET_MC0 | TC_INTENSET_MC1;
  while (TC3->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);

  // Enable TC3
  TC3->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;
  while (TC3->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);

  // Enable the TC3 interrupt vector
  // Set the priority to max
  NVIC_EnableIRQ(TC3_IRQn);
  NVIC_SetPriority(TC3_IRQn, 0x00); 
}


// TC3 ISR
void TC3_Handler()
{
  if (TC3->COUNT16.INTFLAG.reg & TC_INTFLAG_MC0) {
    // Set compare match flag for CC0
    TC_flag_CC0 = true;

    // Reset MC0 interrupt flag
    TC3->COUNT16.INTFLAG.reg |= TC_INTFLAG_MC0;
  }

  if (TC3->COUNT16.INTFLAG.reg & TC_INTFLAG_MC1) {
    // Set compare match flag for CC1
    TC_flag_CC1 = true;

    // Reset MC1 interrupt flag
    TC3->COUNT16.INTFLAG.reg |= TC_INTFLAG_MC1;

    // Reset the counter to repeat
    // Fails if CC1 < CC0
    TC3->COUNT16.COUNT.reg = 0;
  }
}

Thanks Gabriel,

Looks great, I will test this tommorow.

Thanks,
Erik

Works great.

I do have an 1 ms offset between triggers, but other then that, works great!

Thanks again Gabriel.

I know with the RTC the interrupt is generated on the next clock cycle after the match. If the behaviour is the same on the TC, you will see an additional ~1ms as the source clock is 1024Hz.

You can compensate for this by reducing the compare value by 1. Alternatively, using a higher frequency clock source (after scaling) will reduce the amount of time till that next clock cycle.

Here is my code, The interrupts are being called randomly between 1000ms and 1001ms.
Setting the counter to 1000 gives me 980ms,
and 2020 gives me 999/ 1000

//volatile bool TC_flag_CC0 = false;
volatile bool TC_flag_CC1 = false;

//delete later
volatile bool led = false;
volatile int temp = 0;
void setup() 
{
  delay(5000);
  pinMode(LED_BUILTIN, OUTPUT);
  configureTC3(1021);
  SerialUSB.println("Configuration Complete");
}
void loop() 
{
  //Timer flag
  if (TC_flag_CC1)
  {
    TC_flag_CC1 = false;                                              // turn flag off
    flashLED();                                                       // flash led
  }
}
void flashLED()
{
  led = !led;                                                         //flip status led
  if(led){ digitalWrite(LED_BUILTIN, LOW);  }                         //flip LED
  else { digitalWrite(LED_BUILTIN, HIGH);  }
}
void configureTC3(uint16_t match)
{
  uint8_t GCLK_SRC = 4;                                               // clock 4, (0,1,3 are used)
  GCLK->GENDIV.reg = GCLK_GENDIV_ID(GCLK_SRC) | GCLK_GENDIV_DIV(4);   // source | prescaler
  //enable clock mod | select the oscilator source | select clock | prescalar | keep running in standby | 
  GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_ID(GCLK_SRC) | GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_DIVSEL;
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
  PM->APBCMASK.reg |= PM_APBCMASK_TC3;                                // Turn the power to the TC3 module on
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |  GCLK_CLKCTRL_GEN(GCLK_SRC) |  GCLK_CLKCTRL_ID(GCM_TCC2_TC3); // tc3 source to clock | generator source | select clock
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
  TC3->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE;                         //disable TC3
  while (TC3->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);
  TC3->COUNT16.CTRLA.reg = TC_CTRLA_MODE_COUNT16 | TC_CTRLA_RUNSTDBY | TC_CTRLA_PRESCALER_DIV1 ; // count xbit | standby | prescalar_div1 | 
  while (TC3->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY); //wait
  TC3->COUNT16.CC[1].reg = match;                                     // Set the compare channel 1 value
  while (TC3->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);//wait
  TC3->COUNT16.INTENSET.reg = TC_INTENSET_MC1;                        // enable interrupt
  while (TC3->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);//wait
  TC3->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;                          // enable interrupt
  while (TC3->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);//wait
  NVIC_EnableIRQ(TC3_IRQn);                                           // Enable the TC3 interrupt vector
  NVIC_SetPriority(TC3_IRQn, 0x00);                                   // Set the priority to max
}
void TC3_Handler()
{
  if (TC3->COUNT16.INTFLAG.reg & TC_INTFLAG_MC1) 
  {
    TC3->COUNT16.COUNT.reg = 0;                                       // Reset the counter to repeat  
    TC3->COUNT16.INTFLAG.reg |= TC_INTFLAG_MC1;                       // Reset MC1 interrupt flag
    TC_flag_CC1 = true; //Set compare match flag
    SerialUSB.println("Difference:" + (String)(millis()-temp)+ " \t Millis: " +millis());
    temp = millis();  
  }
}

The millis() counter isn’t especially accurate. You might get closer values if you use the 48Mhz DFLL48M source. However, the millis() system generates a interrupt every millisecond and the ISR increments the counter, this results in some overhead. Also I believe the XOSC32K is the most accurate clock source available.

Just a note, generally you don’t want to be doing any print statements or any lengthy code within the ISR.

Couldnt agree more with the lengthy code, but wanted as little as posible delay between proccing the interrupt and printing the milis();,

So, here we have a nice example to use TC3 for timer interrupt. If the timer match value is set to 1024 it should create an interval of exactly 1 second.

I’m a bit concerned that, if I look at millis in loop(), it will roughly show 1003, 1004, 1005 compared to the previous run through loop.

Shouldn’t we try to figure out why millis is “faster”?

Yes, the XOSC32K is probably the most accurate clock.
The millis clock is driven by SysTick. I wish it was a bit more accurate. Perhaps it is not configured optimally. Does anyone know how its “reload” value is determined, and how/when it is loaded?

In wiring.c there is init(), which sets the SysTick to generate interrupts at 1ms. It’s based on the CPU clock (48MHz in our case).

Does that mean we have to live with its inaccuracy? Hmm.

With atmel processors, delay and milis() etc are based on timer/counter0,
Is it the case with arm processors aswell? and if so, did we set anything in the example shown above?

AVR and SAMD are not comparable. I was talking about SAMD (subject of this thread is Autonomo).

For SAMD the Arduino Core uses SysTick, which is derived from the CPU clock. Have a look at wiring.c, function init(). That’s where SysTick is initialized.

I was just curious why millis (i.e. being fed by SysTick) was so much less accurate then the sketch above using TC3 (and the 32KHz crystal). I don’t buy the overhead explanation that Gabriel mentions above. At least not without further experimenting.

I have created a second sketch which configures TC4 to run continuously off the same clock source (XOSC32K fed at 1024Hz). I am getting steady output but there seems to be a 4 cycles overhead per interrupt. My theory is that certain registers take a number of clock cycles to read or write.

I tried setting the continuous synchronisation using the READREQ register. This results in cyclic pattern of overhead of 2, 2, 8 cycles of overhead. This works out as the same average 4 cycles overhead.