Trying to understand SAM21 timers

Hi,

I'm trying to orchestrate a sequence of signals triggered periodically by a signal and implemented with a timer (or 2)

My end goal is to sequence the following series of events:

  1. Rising edge on an input pin triggers interrupt ("trigger interrupt")
  2. Interrupt starts a timer ("ONESHOT timer"), which after a settable period expires and sets a pin HIGH
  3. After a fixed delay, the pin goes back to LOW

What I have so far is:

  1. I can receive an interrupt on a pin and flash an LED, using attachInterrupt()
  2. I can run the timer for some chosen time period and change the state of a pin, using a lot of bit-bashing setup code for TC4. I have it set in ONESHOT mode, and I can see an output on WO[0] and WO[1] (alternating).

The ONESHOT interrupt does not seem to be running because I try toggling an LED inside this ISR and nothing happens.

My confusion is the following:

  1. What's the correct way to start a ONESHOT timer? I am using TC4->COUNT16.CTRLBSET.bit.CMD = TC_CTRLBSET_CMD_RETRIGGER_Val inside the trigger interrupt to start the timer. This does seem to work, but is it the right way?
  2. How do I set up an interrupt handler to run when the ONESHOT ends? I have followed other examples in connecting up the NVIC, and I believe I am only looking for an interrupt on MC0, so I have disabled all other interrupts on TC4.

I am attaching the current code, cleaned up a little bit, but still messier than I like.
Thanks for your analysis. Having spent all day reading the specs and trying things out, I think I am close, but am still missing something.
Best,
Charles

#define TRIGGER_PIN 8

#define DEB1_OUT 3
#define DEB2_OUT 4

int print_count = 0;

bool state = false;
bool state2 = false;

void setup_timer4()
{
  // PORT STUFF
  // Enable the pin multiplexer for digital pins 0 and 1
  PORT->Group[g_APinDescription[0].ulPort].PINCFG[g_APinDescription[0].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[1].ulPort].PINCFG[g_APinDescription[1].ulPin].bit.PMUXEN = 1;

  // Set the multiplexer switch to peripheral E for digtial pins 0 and 1
  PORT->Group[g_APinDescription[1].ulPort].PMUX[g_APinDescription[1].ulPin >> 1].reg = PORT_PMUX_PMUXO_E | PORT_PMUX_PMUXE_E;

  // GENERIC CLOCK STUFF
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) |    // Divide the 48MHz system clock by 1 = 48MHz
                     GCLK_GENDIV_ID(5);      // Set division on Generic Clock Generator (GCLK) 5
  while (GCLK->STATUS.bit.SYNCBUSY);        // Wait for synchronization

  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                      GCLK_GENCTRL_GENEN |         // Enable GCLK 5
                      GCLK_GENCTRL_SRC_DFLL48M |   // Set the clock source to 48MHz
                      GCLK_GENCTRL_ID(5);          // Set clock source on GCLK 5
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                      GCLK_CLKCTRL_GEN_GCLK5 |     // ....on GCLK5
                      GCLK_CLKCTRL_ID_TC4_TC5;     // Feed the GCLK5 to TC4 and TC5
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization


  // TIMER TC4 SETUP
  TC4->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV8 |      // Set print_countr to 8, 48MHz/8 = 6MHz
                            TC_CTRLA_WAVEGEN_MFRQ |        // Toggle output when match val reached
                            TC_CTRLA_MODE_COUNT16;         // Set the TC4 timer to 16-bit mode
  TC4->COUNT16.CTRLA.bit.ENABLE = 1;               // Enable TC4
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);        // Wait for synchronization
 
  TC4->COUNT16.READREQ.reg = TC_READREQ_RCONT |            // Enable a continuous read request
                             TC_READREQ_ADDR(0x10);        // Offset of the COUNT register
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);                // Wait for (read) synchronization

  TC4->COUNT16.CTRLBSET.bit.ONESHOT = 1;                   // Stop counting after match
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);                // Wait for (read) synchronization

  TC4->COUNT16.CC[0].reg = 10000;  // Set CC0 to the TOP value we want
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);                // Wait for (read) synchronization

  NVIC_SetPriority(TC4_IRQn, 3);
  NVIC_EnableIRQ(TC4_IRQn);

  // Clear all interrupt flags
  TC4->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF | TC_INTFLAG_ERR | TC_INTFLAG_SYNCRDY | TC_INTFLAG_MC0 | TC_INTFLAG_MC1;
  // Disable all interrupts except MC0
  TC4->COUNT16.INTENCLR.reg = TC_INTENCLR_OVF | TC_INTENCLR_ERR | TC_INTENCLR_SYNCRDY | TC_INTENCLR_MC1;
  TC4->COUNT16.INTENSET.reg = TC_INTENSET_MC0;
}

void triggerInterrupt()
{
  digitalWrite(DEB1_OUT, HIGH);
  state = !state;

  TC4->COUNT16.CTRLBSET.bit.CMD = TC_CTRLBSET_CMD_RETRIGGER_Val; // Make the TC retrigger
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);                // Wait for (read) synchronization

  digitalWrite(DEB1_OUT, LOW);
}

void TC4_Handler()
{
  if (TC4->COUNT16.INTFLAG.bit.MC0 && TC4->COUNT16.INTENSET.bit.MC0)
  {
    // Clear interrupt flag first
    REG_TC4_INTFLAG = TC_INTFLAG_MC0;

    /* write interrupt code here */
    digitalWrite(DEB2_OUT, state2);
    state2 = !state2;
  }
}

void setup()
{
  SerialUSB.begin(115200);
  while (!SerialUSB);

  setup_timer4();

  SerialUSB.println("===================================================");
  SerialUSB.print("TC4->COUNT32->CTRLA: ");
  SerialUSB.println(TC4->COUNT32.CTRLA.reg, HEX);
  SerialUSB.print("TC4->COUNT32->READREQ: ");
  SerialUSB.println(TC4->COUNT32.READREQ.reg, HEX);
  SerialUSB.print("TC4->COUNT32->CTRLBCLR: ");
  SerialUSB.println(TC4->COUNT32.CTRLBCLR.reg, HEX);
  SerialUSB.print("TC4->COUNT32->CTRLBSET: ");
  SerialUSB.println(TC4->COUNT32.CTRLBSET.reg, HEX);
  SerialUSB.print("TC4->COUNT32->CTRLC: ");
  SerialUSB.println(TC4->COUNT32.CTRLC.reg, HEX);

  pinMode(DEB1_OUT, OUTPUT);
  pinMode(DEB2_OUT, OUTPUT);

  attachInterrupt(digitalPinToInterrupt(TRIGGER_PIN),triggerInterrupt, RISING);
}

void loop()
{
  if (print_count == 100000)
  {
    //SerialUSB.println(TC4->COUNT32.COUNT.reg);       // Output the results
    print_count = -1;
  }
  print_count++;
}

Hi,

I'm still stumped on this, however after reading and re-reading, I have narrowed things down to the timer interrupt.

The timer TC4 does run for the right amount of time, and I can see that with a scope on digital pins 0 and 1 (WO[0] and WO[1]). But after poking around for a while, I am sure that the TC4_Handler() does not run. I unconditionally blink an LED in that handler (outside any flag bit parsing) and the LED never blinks.

So, the upshot would seem that I am not configuring something that would cause the interrupt to happen. The interrupt enable code is contained in the OP, but I reproduce it here:

  // Clear all interrupt flags
  TC4->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF | TC_INTFLAG_ERR | TC_INTFLAG_SYNCRDY | TC_INTFLAG_MC0 | TC_INTFLAG_MC1;
  // Disable all interrupts except MC0
  TC4->COUNT16.INTENCLR.reg = TC_INTENCLR_OVF | TC_INTENCLR_ERR | TC_INTENCLR_SYNCRDY | TC_INTENCLR_MC1;
  TC4->COUNT16.INTENSET.reg = TC_INTENSET_MC0;

I'm following what I've read in this post and this post.

Am I doing something wrong here? Or is there some other enable missing? Any help is appreciated.

Charles

Hi Charles,

I ran you code. To trigger the TC4 timer interrupt sevice routine, it's necessary to change the interrupt flag from Match Compare 0 (MC0) to overflow (OVF), for example:

TC4->COUNT16.INTENSET.reg = TC_INTENSET_MC0;

to

TC4->COUNT16.INTENSET.reg = TC_INTENSET_OVF;

etc...

Thank you! It works!

Now, I don't know why it works, since the specs say a TC can interrupt on a match (section 30.6.4.2), but that was not working for me. I guess this must be an error?

MartinL, I'm sure you recognize that most of my program was copied from your examples in the links I referred to. How did you figure these things out?

Anyway, this is fantastic. I am now close to completion of my little project!

Charles

Hi Charles,

The main source of information about the SAMD21 microcontroller's registers are from the datasheet. The location of the register definitions is described in the post #15 on this Arduino forum thread: Best microcontroller for frequency counter up to 30MHz - Microcontrollers - Arduino Forum.

In the directory path just select "samd21", rather than "samd51". The description and examples of how to use the register definitions is relevant to both the SAMD21 and SAMD51, as both microcontrollers are similar.