Timer Interrupts on 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

@ Jim; thanks for verifying that mate, much obliged.
Just as a "caution" for anyone using this routine, the setting TC_SetRA doesn't appear to be doing what the routine claims it does; We've varied its value and the pulse ratio seems "stuck" (or remains) at around 50%. We're investigating at present and will post updated code that - hopefully - takes a PWM percentage as well as a Frequency as part of its calling parameters. Much easier for us that way, and for anyone who wants to use it that way.

@WomensFashioArt

Thanks.

Just EDITED previous post, please review!

Jim

@Jim; fantastic. Thanks. The code is a copy of some other code posted in this forum and - frankly - we didn't quite understand it all, but it's much clearer now (relatively old hands at Arduino, but new to the Due)...

So to make sure we understand it correctly, the frequency corresponds with the calling frequency of the routine (uint32_t frequency) delivering a 20Khz timed interrupt, but the digitalWrite(13, I = !I) flips the pin 13 logic value causing a 50us high, followed by a 50us low - creating a 10Khz square wave. That makes perfect sense.

@WomensFashionArt

Yep, your understanding is correct. The software delay I inserted (the 'for' loop) is irrelevant, as the 'digitalWrite' is causing the 2 us odd delay I wanted to get a reasonable pulse out.

I know how easy it is easy to miss something when you're head-scratching, like the toggle 'I = !I' :smiley:

The Due Timer code contributed by others in this thread will be very useful.

Re. your other query:

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?

I would output a pulse each cycle from a digital pin on the 'master' Due, and connect it to an unused digital input on each 'slave' Due. You then need to use a hardware interrupt, i.e. an ISR, on each slave Due to reset each slave timer. I'm afraid I haven't got suitable code for the Due handy, but maybe someone else has?

Jim

@Jim; thanks so much for your pro-active engagement. No can do with the sync I'm afraid, there's 20m of cables between them and all sync is being done over this cable. I'm pretty sure that we'll end up using an interrupt of some sort so that all of them are able to reset at exactly the same time. This is our progress so far. It's reduced the PWM frequency to 2Khz for now (the Due can't handle the logic at the requisite 1,000,000 interrupts / second, but we'll work on that). This seems to work just fine and the comments should show the way.

It's late and we're off for the day, but hope that this is of use to somebody.

We'll update it as we go along so that others may benefit from the discussion.

Thanks again Jim, your help is very much appreciated.

Rgs,
WFA_Dev

@Jim; the code would be rather useful here, wouldn't it... 8)

[FOLLOW-UP EDIT - 6 JAN 2013] Updated the code to approximate our 10Khz signal with 0...100% duty cycle.
Note: the frequency generation algorithm (uint32_t rc = VARIANT_MCK/3000/frequency; ) does not provide an accurate frequency. We've tested between 5Khz and 10Khz, for 5Khz we get approx. 5.1Khz, and for 10Khz we get 9.26Khz - as close as we can get it to 10Khz. In between the 5Khz and 10Khz, results vary wildly... please check your own numbers before relying too much on this routine.

volatile char On;
volatile char Total;
volatile char Max;

int PWM_Percent;
int PWM_Freq;

//TC1 ch 0
void TC3_Handler()
{

// reset interrupt
TC_GetStatus(TC1, 0);

// process percentage-based PWM waveforms

// If count is at leading edge, raise pulse
if (Total == 0) {
digitalWrite(13,1);
}
// If count is at trailing edge, drop pulse
else if (Total == On) {
digitalWrite(13,0);
}

Total += 1;

// If count is at 100%, reset to zero and start all over again
if (Total > Max) {
Total = 0;
}
}

// End TC3_Handler()

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_CLOCK3);
uint32_t rc = VARIANT_MCK/3000/frequency;
TC_SetRA(tc, channel, rc/2);
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(){

// Use the LED for now
pinMode(13,OUTPUT);

// Set the total to zero
Total = 0;

// Set pulse width to 50%
PWM_Percent = 50;

// Set interrupt frequency
PWM_Freq = 10000; //

// -------------------------------
// calculate cycles
On = PWM_Percent; //

// set maximum percentage at full 100%
Max = 100; //

// -------------------------------

// Start the timer
startTimer(TC1, 0, TC3_IRQn, PWM_Freq); //TC1 channel 0, the IRQ for that channel and the desired frequency

}

void loop(){
}

I really don't understand why you guys are not fully using the the complete potential of timer possibilities, of course requires fine register initialization but you could get very precise high frequency PWM even beyond 500 KHz or 1 MHz.

For the moment, i've doing so for years with arduino mega on my power electronics project (plasma drivers, full H-bridge, MWO inverters...) many of them, see old post then specific update code even though now is even more refined http://arduino.cc/forum/index.php/topic,8162.msg65115.html#msg65115 )

I do have an arduino DUE but as explained on other threads recently, I cannot download my actual MEGA projects using advanced arduino mega timer management because USB link of DUE is full of bugs.

Again I insist, just program the proper WAVESEL value and other registers, there is NO need to make interrupts slowing down and skewing the PWM jitter precision if using a timer, in particular with DUE being much more sophisticated timer possibilities than MEGA !

Just my 2 cents

1 Like