Timer Interrupts on Due

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 :slight_smile:

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

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 Arduino Playground - 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?

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 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:

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 Arduino Playground - 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? :slight_smile:

2 Likes

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

1 Like

DuaneB, I see your point, unfortunately I guess the only document close to what you're searching is the datasheet. Indeed having a reference implementation to look at helps a lot.

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

I discovered it by trial and error. I don't know if there is any book that explain it in this level of detail...

Thanks for posting the timer code. We need a 10Khz signal but using 10,000 as the value in the last parameter (as suggested by cmaglie) seems to - in our case at least - deliver half the required frequency (i.e. 5Khz) - according to our scope at least. However, when we use the value of 20,000 - and look at the value on the scope - we get a 100us square wave.

Are we misinterpreting something? This is the code we use to get our 10Khz signal - could someone please verify our findings for us?

Also, we need to synchronise the start of these pulses with some other Due's so that they all output synchronised pulses. Can anyone help us with that? Do we simply re-start the timer and, if so, how?

Thanks all,
Dev team

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, 20000); //TC1 channel 0, the IRQ for that channel and the desired frequency
// 20,000 seems to give 100uS/div signal, which is equivalent to 10,000Hz or 10Khz
}

void loop(){
}

it seems to me you're subject to different waveform coding, with arduino mega look at fast PWM versus phase correct PWM but also frequency & phase correct PWM. The last 2 cases will divide by 2 the actual timer frequency generation.

I've quicked look on SAM3X... datasheet for the equivalent story, same except they use WAVSEL to choose whatever mode. Later on the datasheet, they also speak of center align or left align.

So there is no bug, just program correctly the registers...

@SelfOnlyPath - thanks for the clarification. Certainly the left / centre align is something we'll need to look into and the halving of the clock in certain modes makes perfect sense in the context of our experience. It must be time for us to hit the docco's hard wethinks. Thanks again.

@WomensFashionArt

Just tried your sketch here unmodified and I get a nice 10 KHz (100 us cycle) square wave output on pin 13, as measured on our Rigol scope.

But on further investigation I see that the timer interrupt is in fact at 20 KHz (50 us), but you are toggling the output state in the handler, so each cycle you see is twice 50 us = 100 us.

Try this instead and you'll see a 2 us pulse at 20 KHz:

void TC3_Handler()
{
        TC_GetStatus(TC1, 0);

        digitalWrite(13, 1);    // ON

        for (int i = 0; i < 10; i++)
          int j = i;

        digitalWrite(13, 0);    // OFF
}

HTH
Jim