Timer Interrupts on Due

I think I'm getting somewhere with this... try this:

volatile boolean l;

void TC0_Handler()
{
    long dummy=REG_TC0_SR0; // vital - reading this clears some flag
                            // otherwise you get infinite interrupts
    l= !l;
}

void setup(){
  pinMode(13,OUTPUT);
  pinMode(2,OUTPUT);    // port B pin 25  
  analogWrite(2,255);   // sets up some other registers I haven't worked out yet
  REG_PIOB_PDR = 1<<25; // disable PIO, enable peripheral
  REG_PIOB_ABSR= 1<<25; // select peripheral B
  REG_TC0_WPMR=0x54494D00; // enable write to registers
  REG_TC0_CMR0=0b00000000000010011100010000000000; // set channel mode register (see datasheet)
  REG_TC0_RC0=100000000; // counter period
  REG_TC0_RA0=30000000;  // PWM value
  REG_TC0_CCR0=0b101;    // start counter
  REG_TC0_IER0=0b00010000; // enable interrupt on counter=rc
  REG_TC0_IDR0=0b11101111; // disable other interrupts

  NVIC_EnableIRQ(TC0_IRQn); // enable TC0 interrupts

}

void loop(){
      digitalWrite(13,l);
}

The timer controls the output of pin 2 and the interrupt toggles a flag which is output on pin 13.

Hi, Whats the best resource you have found for describing the timer registers ?

Thanks

Duane B

rcarduino.blogspot.com

Not what you're looking for, but on a similar topic: I wanted to use the TC* timers to create a software servo library but I couldn't find out how to use the timers so I ended up using the PWM clock to generate the output.

While testing the PWM pulse I used attachInterrupt on the same pin to measure the pulse timing, effectively creating software interrupts. It's a hack, but if you're interested the code is here: http://arduino.cc/forum/index.php/topic,130631.0.html It doesn't use any registers, so I can't tell you about those - just add attachInterrupt()

timer/counter registers are detailed in chapter 37 of datasheet

stimmer: I think I'm getting somewhere with this... try this:

volatile boolean l;

void TC0_Handler() {     long dummy=REG_TC0_SR0; // vital - reading this clears some flag                             // otherwise you get infinite interrupts     l= !l; }

void setup(){   pinMode(13,OUTPUT);   pinMode(2,OUTPUT);    // port B pin 25    analogWrite(2,255);   // sets up some other registers I haven't worked out yet   REG_PIOB_PDR = 1<<25; // disable PIO, enable peripheral   REG_PIOB_ABSR= 1<<25; // select peripheral B   REG_TC0_WPMR=0x54494D00; // enable write to registers   REG_TC0_CMR0=0b00000000000010011100010000000000; // set channel mode register (see datasheet)   REG_TC0_RC0=100000000; // counter period   REG_TC0_RA0=30000000;  // PWM value   REG_TC0_CCR0=0b101;    // start counter   REG_TC0_IER0=0b00010000; // enable interrupt on counter=rc   REG_TC0_IDR0=0b11101111; // disable other interrupts

  NVIC_EnableIRQ(TC0_IRQn); // enable TC0 interrupts

}

void loop(){       digitalWrite(13,l); }



The timer controls the output of pin 2 and the interrupt toggles a flag which is output on pin 13.

I'll give this a shot thanks. I am curious though why you didn't do a digitalWrite() inside the handler? Arduino's digitalWrite() function is pretty clock-cycle heavy, but the following code works just fine for writing a digital output and only takes a couple of clock cycles if that was the primary concern.

static inline void pinOutput(int pin, int val)
{
    if (val)
        g_APinDescription[pin].pPort->PIO_SODR = g_APinDescription[pin].ulPin;
    else
        g_APinDescription[pin].pPort->PIO_CODR = g_APinDescription[pin].ulPin;
}

On another note, Maple uses a different processor, but gives as good of an explanation / example of how timers work as I've found so far: http://leaflabs.com/docs/timers.html http://leaflabs.com/docs/lang/api/hardwaretimer.html http://leaflabs.com/docs/libmaple/api/timer.html

I haven't tried to implement any of their code yet, but thought I'd share the links.

I recognize that code fragment 8)

As for digitalWrite not being in the handler, actually it was there originally, but you have to understand that I'd been struggling for hours with interrupt code that wouldn't fire, then it only fired once, then it got hammered with constant interrupts, and I didn't know what was going on. So I did what is pretty much standard debugging practice and changed the interrupt handler to do the absolute bare minimum just to be sure that digitalWrite wasn't causing a side effect (there's some architectures where you can't write certain registers in an interrupt). But the important thing turned out to be the dummy read of TC_SR0. By then it was late so I just posted what I'd done.

Since then I have tried putting digitalWrite and direct port writes back in and they don't seem to cause any odd side effects.

The Maple hardware is completely different unfortunately and the registers are incompatible.

I understand. Thanks :)

Using this code, you can set a timer for any of the ISRs TC0_Handler through TC8_Handler, see table of parameters below. It is possible to use the timers without a physically mapped pin, such as TC1 channel 0 (TC3_Handler) shown here:

volatile boolean l;

//TC1 ch 0
void TC3_Handler()
{
        TC_GetStatus(TC1, 0);
        digitalWrite(13, l = !l);
}

void startTimer(Tc *tc, uint32_t channel, IRQn_Type irq, uint32_t frequency) {
        pmc_set_writeprotect(false);
        pmc_enable_periph_clk((uint32_t)irq);
        TC_Configure(tc, channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK4);
        uint32_t rc = VARIANT_MCK/128/frequency; //128 because we selected TIMER_CLOCK4 above
        TC_SetRA(tc, channel, rc/2); //50% high, 50% low
        TC_SetRC(tc, channel, rc);
        TC_Start(tc, channel);
        tc->TC_CHANNEL[channel].TC_IER=TC_IER_CPCS;
        tc->TC_CHANNEL[channel].TC_IDR=~TC_IER_CPCS;
        NVIC_EnableIRQ(irq);
}

void setup(){
        pinMode(13,OUTPUT);
        startTimer(TC1, 0, TC3_IRQn, 4); //TC1 channel 0, the IRQ for that channel and the desired frequency
}

void loop(){
}

Here is the table of parameters:

| ISR/IRQ | TC | Channel | Due pins | | - | - | - | - | | TC0 | TC0 | 0 | 2, 13 | | TC1 | TC0 | 1 | 60, 61 | | TC2 | TC0 | 2 | 58 | | TC3 | TC1 | 0 | none <- this line in the example above | | TC4 | TC1 | 1 | none | | TC5 | TC1 | 2 | none | | TC6 | TC2 | 0 | 4, 5 | | TC7 | TC2 | 1 | 3, 10 | | TC8 | TC2 | 2 | 11, 12 |

Sebastian:

Well done! thanks

[quote author=Sebastian Vik link=topic=130423.msg984328#msg984328 date=1352148520] Using this code, you can set a timer for any of the ISRs TC0_Handler through TC8_Handler, see table of parameters below. It is possible to use the timers without a physically mapped pin, such as TC1 channel 0 (TC3_Handler) shown here:

volatile boolean l;

//TC1 ch 0
void TC3_Handler()
{
        TC_GetStatus(TC1, 0);
        digitalWrite(13, l = !l);
}

void startTimer(Tc *tc, uint32_t channel, IRQn_Type irq, uint32_t frequency) {
        pmc_set_writeprotect(false);
        pmc_enable_periph_clk((uint32_t)irq);
        TC_Configure(tc, channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK4);
        uint32_t rc = VARIANT_MCK/128/frequency; //128 because we selected TIMER_CLOCK4 above
        TC_SetRA(tc, channel, rc/2); //50% high, 50% low
        TC_SetRC(tc, channel, rc);
        TC_Start(tc, channel);
        tc->TC_CHANNEL[channel].TC_IER=TC_IER_CPCS;
        tc->TC_CHANNEL[channel].TC_IDR=~TC_IER_CPCS;
        NVIC_EnableIRQ(irq);
}

void setup(){
        pinMode(13,OUTPUT);
        startTimer(TC1, 0, TC3_IRQn, 4); //TC1 channel 0, the IRQ for that channel and the desired frequency
}

void loop(){
}

Here is the table of parameters:

| ISR/IRQ | TC | Channel | Due pins | | - | - | - | - | | TC0 | TC0 | 0 | 2, 13 | | TC1 | TC0 | 1 | 60, 61 | | TC2 | TC0 | 2 | 58 | | TC3 | TC1 | 0 | none <- this line in the example above | | TC4 | TC1 | 1 | none | | TC5 | TC1 | 2 | none | | TC6 | TC2 | 0 | 4, 5 | | TC7 | TC2 | 1 | 3, 10 | | TC8 | TC2 | 2 | 11, 12 |

[/quote]

Noob question here, could someone please explain the inputs to the startTimer function? What is channel, and how do we use the frequency input? Also, what are the pins in the table for?

Same question; please explain the inputs to the startTimer function.

Are there any ready-to-use-libs (like http://www.arduino.cc/playground/Code/Timer1) for using Hardware Timer on Due?

Or is there a beginner-friendly How-To (like http://www.engblaze.com/microcontroller-tutorial-avr-and-arduino-timer-interrupts/) for the Due?

[quote author=Sebastian Vik link=topic=130423.msg984328#msg984328 date=1352148520] Here is the table of parameters:

| ISR/IRQ | TC | Channel | Due pins | | - | - | - | - | | TC0 | TC0 | 0 | 2, 13 | | TC1 | TC0 | 1 | 60, 61 | | TC2 | TC0 | 2 | 58 | | TC3 | TC1 | 0 | none <- this line in the example above | | TC4 | TC1 | 1 | none | | TC5 | TC1 | 2 | none | | TC6 | TC2 | 0 | 4, 5 | | TC7 | TC2 | 1 | 3, 10 | | TC8 | TC2 | 2 | 11, 12 |

[/quote]

A brief explanation: The SAM3X8E CPU has 3 Timer Counters (TC) they are called TC0, TC1, TC2. Every Timer Counter contains 3 Channels numbered 0, 1 and 2 (this give us a total of 9 Channels). Every Channel has its own counters and interrupt handler that are independent from other Channels.

In other words each Channel can be considered as a separate "Timer", and is like having 9 separate timers.

To initialize a Channel you need the following parameters:

TC0/TC1/TC2 - The Timer Counter instance 0 / 1 / 2 - The Channel number inside Timer Counter

If you want to use interrupts you must enable the NVIC (Nested Vector Interrupt Controller) for that channel with:

NVIC_EnableIRQ(TCx_IRQn);

where TCx_IRQn is the ID of the interrupt to enable. These id are listed in the following table together with the ISR handler function name:

| TC | Chan | NVIC "irq" | IRQ handler function | PMC id | | - | - | - | - | - | | TC0 | 0 | TC0_IRQn | TC0_Handler | ID_TC0 | | TC0 | 1 | TC1_IRQn | TC1_Handler | ID_TC1 | | TC0 | 2 | TC2_IRQn | TC2_Handler | ID_TC2 | | TC1 | 0 | TC3_IRQn | TC3_Handler | ID_TC3 | | TC1 | 1 | TC4_IRQn | TC4_Handler | ID_TC4 | | TC1 | 2 | TC5_IRQn | TC5_Handler | ID_TC5 | | TC2 | 0 | TC6_IRQn | TC6_Handler | ID_TC6 | | TC2 | 1 | TC7_IRQn | TC7_Handler | ID_TC7 | | TC2 | 2 | TC8_IRQn | TC8_Handler | ID_TC8 |

(note that TC*2*_IRQn is the irq id for TC0-channel-2 not for TC2...)

but this is still not enough! Every peripheral in the SAM3X is off by default (to save power) and should be turned on. To turn on you need to run the following command:

pmc_enable_periph_clk(id);

where id is found on the last column of the above table (ID_TCx). It happened that ID_TCx constant equals TCx_IRQn, so Sebastian Vik has simplified a bit the function using TCx_IRQn as input for pmc_enable_periph_clk:

pmc_enable_periph_clk((uint32_t)irq);

Hope this helps to decode whats happening with timers inside SAM3X.

Selachii: Are there any ready-to-use-libs (like http://www.arduino.cc/playground/Code/Timer1) for using Hardware Timer on Due?

Or is there a beginner-friendly How-To (like http://www.engblaze.com/microcontroller-tutorial-avr-and-arduino-timer-interrupts/) for the Due?

Nope, there are no libs neither tutorials.

I've planned a SAM3Timer library (to simplify implementation of Arduino Core and some libraries) but I didn't started it yet.

Volunteers? :)

Send me a Due and I will write some libraries for it.

Duane B

rcarduino.blogspot.com

Quote from: Sebastian Vik on November 05, 2012, 08:48:40 PM Here is the table of parameters: ISR/IRQ TC Channel Due pins TC0 TC0 0 2, 13 TC1 TC0 1 60, 61 TC2 TC0 2 58 TC3 TC1 0 none <- this line in the example above TC4 TC1 1 none TC5 TC1 2 none TC6 TC2 0 4, 5 TC7 TC2 1 3, 10 TC8 TC2 2 11, 12

A brief explanation: The SAM3X8E CPU has 3 Timer Counters (TC) they are called TC0, TC1, TC2. Every Timer Counter contains 3 Channels numbered 0, 1 and 2 (this give us a total of 9 Channels). Every Channel has its own counters and interrupt handler that are independent from other Channels.

In other words each Channel can be considered as a separate "Timer", and is like having 9 separate timers.

To initialize a Channel you need the following parameters:

TC0/TC1/TC2 - The Timer Counter instance 0 / 1 / 2 - The Channel number inside Timer Counter

If you want to use interrupts you must enable the NVIC (Nested Vector Interrupt Controller) for that channel with: Code:

NVIC_EnableIRQ(TCx_IRQn);

where TCx_IRQn is the ID of the interrupt to enable. These id are listed in the following table together with the ISR handler function name:

TC Chan NVIC "irq" IRQ handler function PMC id TC0 0 TC0_IRQn TC0_Handler ID_TC0 TC0 1 TC1_IRQn TC1_Handler ID_TC1 TC0 2 TC2_IRQn TC2_Handler ID_TC2 TC1 0 TC3_IRQn TC3_Handler ID_TC3 TC1 1 TC4_IRQn TC4_Handler ID_TC4 TC1 2 TC5_IRQn TC5_Handler ID_TC5 TC2 0 TC6_IRQn TC6_Handler ID_TC6 TC2 1 TC7_IRQn TC7_Handler ID_TC7 TC2 2 TC8_IRQn TC8_Handler ID_TC8

(note that TC2_IRQn is the irq id for TC0-channel-2 not for TC2...)

but this is still not enough! Every peripheral in the SAM3X is off by default (to save power) and should be turned on. To turn on you need to run the following command:

Code:

pmc_enable_periph_clk(id);

where id is found on the last column of the above table (ID_TCx). It happened that ID_TCx constant equals TCx_IRQn, so Sebastian Vik has simplified a bit the function using TCx_IRQn as input for pmc_enable_periph_clk:

Code:

pmc_enable_periph_clk((uint32_t)irq);

Hope this helps to decode whats happening with timers inside SAM3X.

Thank you for your detailed answer. Can somebody explain the following code? I want to use it to execute a function after every XY CPU counts:

TC_Configure(tc, channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK4); uint32_t rc = VARIANT_MCK/128/frequency; //128 because we selected TIMER_CLOCK4 above TC_SetRA(tc, channel, rc/2); //50% high, 50% low TC_SetRC(tc, channel, rc); TC_Start(tc, channel); tc->TC_CHANNEL[channel].TC_IER=TC_IER_CPCS; tc->TC_CHANNEL[channel].TC_IDR=~TC_IER_CPCS;

mnpumar: Also, what are the pins in the table for?

Good question! If I understood everything correctly, the pins in the table are where you could output the clock of that timer/counter if you wanted to use it for something else than internal software interrupts. Basically you can set up the hardware counter to output a clock signal. For example, TC0 channel 0 could be used to output a clock on the Arduino pin 2 or 13. So if you use TC0 channel 0 for your timer, you would not be able to output a clock on those pins (except using the frequency you set up the channel for). That is why it would be better to use TC1 channels 0, 1 or 2 primarily for this kind of interrupt, as their output is not mapped to any of the Arduino pins and therefore does not limit any of your physically mapped outputs.

Thank you for your detailed answer. Can somebody explain the following code? I want to use it to execute a function after every XY CPU counts:

TC_Configure(tc, channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK4); uint32_t rc = VARIANT_MCK/128/frequency; //128 because we selected TIMER_CLOCK4 above TC_SetRA(tc, channel, rc/2); //50% high, 50% low TC_SetRC(tc, channel, rc); TC_Start(tc, channel); tc->TC_CHANNEL[channel].TC_IER=TC_IER_CPCS; tc->TC_CHANNEL[channel].TC_IDR=~TC_IER_CPCS;

I have the same question. I'm trying to create an Interrupt every 62.5 microseconds but I'm incapable! Do you know if it is possible and if it is how should I proceed? As I have understand I should modify the variable RC and reading through the net I came with a possible answer being (VARIAN_MCK/8)/SPEED of transmission (I need the interruption to modify the VirtualWire library for the DUE, which I have almost done ;)) but it doesn't work!

You should use the code Sebastian Vik already posted, I've slightly rewrite it with some comments

// Black magic
void startTimer(Tc *tc, uint32_t channel, IRQn_Type irq, uint32_t frequency) {
  pmc_set_writeprotect(false);
  pmc_enable_periph_clk((uint32_t)irq);
  TC_Configure(tc, channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK4);
  uint32_t rc = VARIANT_MCK/128/frequency; //128 because we selected TIMER_CLOCK4 above
  TC_SetRA(tc, channel, rc/2); //50% high, 50% low
  TC_SetRC(tc, channel, rc);
  TC_Start(tc, channel);
  tc->TC_CHANNEL[channel].TC_IER=TC_IER_CPCS;
  tc->TC_CHANNEL[channel].TC_IDR=~TC_IER_CPCS;
  NVIC_EnableIRQ(irq);
}

void setup(){
  pinMode(13,OUTPUT);

  // Start timer. Parameters are:

  // TC1 : timer counter. Can be TC0, TC1 or TC2
  // 0   : channel. Can be 0, 1 or 2
  // TC3_IRQn: irq number. See table.
  // 40  : frequency (in Hz)
  // The interrupt service routine is TC3_Handler. See table.

  startTimer(TC1, 0, TC3_IRQn, 40);

  // Paramters table:
  // TC0, 0, TC0_IRQn  =>  TC0_Handler()
  // TC0, 1, TC1_IRQn  =>  TC1_Handler()
  // TC0, 2, TC2_IRQn  =>  TC2_Handler()
  // TC1, 0, TC3_IRQn  =>  TC3_Handler()
  // TC1, 1, TC4_IRQn  =>  TC4_Handler()
  // TC1, 2, TC5_IRQn  =>  TC5_Handler()
  // TC2, 0, TC6_IRQn  =>  TC6_Handler()
  // TC2, 1, TC7_IRQn  =>  TC7_Handler()
  // TC2, 2, TC8_IRQn  =>  TC8_Handler()
}

void loop(){
}

volatile boolean l;

// This function is called every 1/40 sec.
void TC3_Handler()
{
  // You must do TC_GetStatus to "accept" interrupt
  // As parameters use the first two parameters used in startTimer (TC1, 0 in this case)
  TC_GetStatus(TC1, 0);

  digitalWrite(13, l = !l);
}

ForcisConnect: I have the same question. I'm trying to create an Interrupt every 62.5 microseconds but I'm incapable! Do you know if it is possible and if it is how should I proceed? As I have understand I should modify the variable RC and reading through the net I came with a possible answer being (VARIAN_MCK/8)/SPEED of transmission (I need the interruption to modify the VirtualWire library for the DUE, which I have almost done ;)) but it doesn't work!

62.5 uS is 1000000/62.5 = 16000 Hz, use that value as last parameter of startTimer.

Well this last explanation was perfect! I have now managed to translate the VirtualWire library to the Arduino DUE. It is to be knowledge that it is a primary version and that I have tested it only with the standard speed of transmission (2000 bps) and it may have errors changing it (because the time of interrupt is not more dependent of the speed).

VirtualWireDUE.zip (258 KB)

Hi, Is this or something similar going to be included in the Due API ?

EDIT - I have used this approach to get a timer interrupt running at 44.1Khz.

What is a good source of documentation on ARM Development ? I have the datasheet, I have found the API documentation, I have even read the .h and .c source files, but without the head start from Mr Vik I would not have had a hope in hell of getting this working.

As a quick example where is it documented that TC_GetStatus has to be called to re-enable the interrupt ?

Duane B

rcarduino.blogspot.com