Fastest Arduino Due IRQ

Hello,

What is the fastest Interrupt I can get on due? It should be somewhere around 21/10.5 Mhz right?

WIth this Code:

  pmc_set_writeprotect(false);
  pmc_enable_periph_clk((uint32_t)TC3_IRQn);
  TC1->TC_CHANNEL[0].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1  // MCK/2, clk on rising edge
                              | TC_CMR_WAVE               // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC        // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR          // Clear TIOA0 on RA compare match
                              | TC_CMR_ACPC_SET;           // Set TIOA0 on RC compare match

  TC1->TC_CHANNEL[0].TC_RC = 2;  //<*********************  Frequency = (Mck/2)/TC_RC  Hz = 21 MHz
  TC1->TC_CHANNEL[0].TC_RA = 1;  //<********************   50% Duty Cycle

  TC1->TC_CHANNEL[0].TC_IER = TC_IER_CPCS;                 // Interrupt on RC compare match
  NVIC_EnableIRQ(TC3_IRQn);                                // TC1 Interrupt enable

  TC1->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Software trigger TC1 counter and enable

I somehow get only around 1.7 MHz which is pretty low for the Due which should have an internal Clock of 84 MHz. So whats wrong with my code?

Also I took Waveformmode because everyone did but what does it do? I want to generate an external clock with it (toggle 1 or more pins) in the ISR and I need the fastest I can get.

An interruption prologue will need ~10 clock cyles, the epilogue ~10 more. In between prologue and epilogue there will be your code, as short as possible. Obviously, with this method you won't get the fastest output square wave.

A first workaround would be to preceed the interrupt Handler with the naked attribute and add some inline assembler instructions inside the Handler to mimic the smallest possible prologue and the smallest possible epilogue. A better one would be to poll the interrupt bit in status register in loop() and not enable interruptions.

Edit : In the code I gave you in this thread, reply #1:

Arduino Due Timers - Microcontrollers - Arduino Forum 1

arduino pin 2 (PB25) toggles at a 1 MHz frequency. Modify TC_RC to 2 and the pin will toggle at a 21 MHz frequency.

IMO, the best solution is this one:

If you want to produce a square wave output at the highest possible frequency, there are registers for that, it's called PMC->PMC_PCKx (0<= x<=2), see Sam3x datasheet page 552. On 3 pins, PMC_PCKx can output one of the internal clocks (from slow clock = 32.768 Hz to UPLL = 480 MHz) divided by a prescaler (from 1 to 64).

With chaging TC_RC to 2 and RA to 1 it wont toggle with a frequency of 21 MHz. I get about 1.666 MHz but not even close to 21MHz. Thats why I got up with the question in first place. Even if i only toggle a pin.

Did you disable the interruption in code ?

What do you mean with disabling the interruption in code?

As I previously explained, you have to remove the TC interruption. Do that by commenting this line in code

//NVIC_EnableIRQ(TC0_IRQn); // TC1 Interrupt enable

The interruption will no more fire again ! and pin 2(PB25) will toggle at a 21 MHz frequency.

Like what ard_newbie has said, there is an overhead when it comes to entering and leaving an interrupt. (Prologue and epilogue). Entering takes ~10 clock cycles. Leaving takes another ~10 cycles.

Let's do some math.

If we're looking at a basic interrupt, ~10 cycles to enter, ~10 cycles to leave, and ~5 cycles to toggle a pin. (Don't take these numbers too seriously. They're approximations.)

1 clock cycle for Clock 1 lasts approximately 0.02381 us.

This interrupt lasts around 0.59525 us (0.02381*25). That results in 1.68 MHz which, to me, explains why you're getting 1.7 MHz. At this point, the prologue and epilogue is what's limiting you.

To get it faster, ard_newbie has mentioned attribute((naked)) because this can minimize the overhead, but code written in naked functions are in assembly. Correct me if I'm wrong?

The ARM CM3 interrupt processing pushes an 8-word stack frame IN HARDWARE, so any interrupt will take at least 16 cycles between the entry and exit NOT INCLUDING any function prologue/epilogue. (in fact, ARM ISR functions are just normal functions, without any extra prologue/etc, because the hardware stacking matches up with with the ABI standard for function calls.) That means that your maximum interrupt rate would be around 5MHz.

(Except: see also "tail chaining" - if the ARM hardware detects a new interrupt while returning from the old interrupt, it can decide to run the new ISR before it restores the registers, therefore omitting both the save (already done) and restore.)

Don't forget to take into account flash wait states, and the fact that it's pretty likely that any "flash acceleration" that the system implements will be impacted by switching execution to an ISR...

Cycle-accurate Prediction of timing on an ARM chip seems to be pretty close to impossible. :frowning: It would be hard enough based on the complexities of the ARM architecture itself, but at least most of that is documented. The exact behavior of the flash memory interface, OTOH...

Thanks for all the replies! Helped me a lot to understand the problem ! Sadly I cant use PIN 2 as SCLK because I also need to write data at the speed of SCLK but I will surely keep this in mind thanks!

Remember that a Serial.print() takes hundreds of us. That would reduce the maximum print frequency ( and consequently the maximum interrupt frequency) to about 1 MHz....

The general rule is to keep your code inside the interrupt Handler as short as possible and never Serial print inside but set a flag to be leveraged in loop().

So I porgrammed this Code for a 32x48 LED Matrix and it works but it is too slow, so the LEDs won't shine pretty bright. So how do I get this done faster with the GCLK and SCLK timing? Lowering the TC_RA and TC_RC register won't do anything same with adding attribute((naked)) before my Interrupt Handler and/or the data void. I can't lower the if compares becasue I need a certain timing.

void setup() {
  Serial.begin(115200);
  PMC->PMC_PCER0 |= PMC_PCER0_PID12;// PIOB power ON

  // TLC5944 Init
  pinMode(12, OUTPUT); //SIN0
  pinMode(11, OUTPUT); //GCLK
  pinMode(10, OUTPUT); //DCSEL
  pinMode(9, OUTPUT); //SCLK
  pinMode(8, OUTPUT); //XLAT
  pinMode(7, OUTPUT); //BLANK

  //  74HC595 Init

  pinMode(6, OUTPUT); // !SCLR
  pinMode(5, OUTPUT); //  SCLK
  pinMode(4, OUTPUT); //  RCLK
  pinMode(3, OUTPUT); //  !OE
  pinMode(2, OUTPUT); //  SER



  /*************  Timer Counter 1 Channel 0 to generate PWM pulses thru TIOA0  ************/
  pmc_set_writeprotect(false);
  pmc_enable_periph_clk((uint32_t)TC3_IRQn);
  TC1->TC_CHANNEL[0].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1  // MCK/2, clk on rising edge
                              | TC_CMR_WAVE               // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC        // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR          // Clear TIOA0 on RA compare match
                              | TC_CMR_ACPC_SET;          // Set TIOA0 on RC compare match
  //|TC_CMR_CPCTRG;

  TC1->TC_CHANNEL[0].TC_RC = 10;  //<*********************  Frequency = (Mck/2)/TC_RC  Hz = 21 MHz
  TC1->TC_CHANNEL[0].TC_RA = 5;  //<********************   50% Duty Cycle

  TC1->TC_CHANNEL[0].TC_IER = TC_IER_CPCS;                 // Interrupt on RC compare match


  TC1->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Software trigger TC1 counter and enable

  REG_PIOC_SODR ^= 0x1 << 23;                           // BLANK HIGH
  digitalWrite(9, LOW);                                 //DCSEL LOW
  digitalWrite(3, LOW);                                 //!OE LOW
  digitalWrite(6, HIGH);                                //!SCLR HIGH
  digitalWrite(2, HIGH);
  NVIC_EnableIRQ(TC3_IRQn);                           // TC1 Interrupt enable

}

void TC3_Handler() {
  TC1->TC_CHANNEL[0].TC_SR; // Read and clear status register
  /*******    SPALTENTREIBER TCL5944    *******/
  PIOC->PIO_ODSR ^= PIO_ODSR_P21 | PIO_ODSR_P25; //SCLK Spalten- & Zeilentreiber
  PIOD->PIO_ODSR ^= PIO_ODSR_P7;    //GCLK
  REG_PIOC_CODR ^= 0x1 << 26;       //Clear RCLK
  data();
}

void data() {
  /*******    Zeilentreiber    *******/
  if (j < 32) {
    if (Zeilen[Zeile][j]) {       //Zeilendaten in SER
      REG_PIOB_CODR ^= 0x1 << 25;
    } else {
      REG_PIOB_SODR ^= 0x1 << 25;
    }
  }
  if (j == 31) {
    REG_PIOC_SODR ^= 0x1 << 26;   //Set RCLK
    if (Zeile == 15) {
      Zeile = 0;
    }
    else {
      Zeile++;
    }
  }
  j++;
  /*******   Spaltentreiber Daten übernehmen  *******/
  if (j == 3071) {
    REG_PIOC_SODR ^= 0x1 << 22; //XLAT HIGH
  }
  if (j == 3072) {
    REG_PIOC_CODR ^= 0x1 << 22; //XLAT LOW
  }
  if (j == 3073) {
    REG_PIOC_CODR ^= 0x1 << 23; //BLANK LOW
  }
  if (j == 3074) {
    REG_PIOC_SODR ^= 0x1 << 23; //BLANK HIGH
  }
  /*******    Abwarten der GCLK Takte zum Zeilenwechsel   *******/
  if (j == 4099) {
    j = 0;
  }
  /*******    DATA OUT    *******/
  if (j % 2 ) {

    if ((0xFFFFF >> (Place - 1)) & 0x1) {
      REG_PIOD_SODR = 0x1 << 8;
    }
    else {
      REG_PIOD_CODR = 0x1 << 8;
    }
    Place--;
    if (Place == 0) {
      Place = 16;
      i++;
    }
    if (i == 1536) {
      i = 0;
    }
  }
}

You can't do an XOR with PIO_SODR or PIO_CODR because they are Write only registers, and only writting a 1 has an effect. However, you can do an XOR with PIO_ODSR register (See Sam3x datasheet page 642,…).

If you don't use the interrupt handler, you can still compare the TC status register with TC_SR bits and decide, whether or not to set/clear a pin. Here is an example sketch of that method to detect a rising edge of TIOA0 at a 7 MHz frequency:

/***********************************************************************************/
/*                   Detection of 7 MHz frequency on TIOA0                         */
/***********************************************************************************/

void setup() {

  /*************  Timer Counter 0 Channel 0 to generate PWM pulses thru TIOA0  ************/
  PMC->PMC_PCER0 |= PMC_PCER0_PID27;                      // Timer Counter 0 channel 0 IS TC0
  PIOB->PIO_PDR |= PIO_PDR_P25 | PIO_PDR_P27;
  PIOB->PIO_ABSR |= PIO_ABSR_P25 | PIO_ABSR_P27;

  TC0->TC_CHANNEL[0].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1  // MCK/2, clk on rising edge
                              | TC_CMR_WAVE               // Waveform mode
                              | TC_CMR_WAVSEL_UP_RC        // UP mode with automatic trigger on RC Compare
                              | TC_CMR_ACPA_CLEAR          // Clear TIOA0
                              | TC_CMR_ACPC_SET;           // Set TIOA0

  TC0->TC_CHANNEL[0].TC_RC = 6;  //<*********************  Frequency = (Mck/2)/TC_RC = 7 MHz
  TC0->TC_CHANNEL[0].TC_RA = 1;  //<********************   Duty cycle = (TC_RA/TC_RC) * 100  %

  TC0->TC_CHANNEL[0].TC_CCR = TC_CCR_SWTRG | TC_CCR_CLKEN; // Reset TC0 counter and enable

  PMC->PMC_PCER0 |= PMC_PCER0_PID12; // PB27 as a GPIO output
  PIOB->PIO_PER = PIO_PER_P27;  
  PIOB->PIO_OER |= PIO_OER_P27;

}

void loop() {
  uint32_t Count = 7000000;
  
  while (true) {
    if (TC0->TC_CHANNEL[0].TC_SR & TC_SR_CPCS) { // Read and clear TC status register
      Count--;
      if (Count == 0) {
        PIOB->PIO_ODSR ^= PIO_ODSR_P27;
        Count = 7000000;
      }
    }
  }

}

Oh okay makes sense but still works somehow.
Awesome I didn't know that I can do something like this to skip the 20(?) duty-cycles while going into my ISR and stepping out.
So I will just write my code like this:

void loop(){
while(true){
if(TC1->TC_CHANNEL[0].TC_SR & TC_SR_CPCS){
/*********** here comes my Code with all the ifs and counter increases ***********/ 
}
}
}

And I dont have the delay becasue of stepping into and stepping out of my ISR?

That's OK