Timer Interrupts on Due

P.S. You might notice from time 7min 0s in the video that actually I live update by ISR (Interrupt Software Routines) all the FOUR internal 16-bit timers register so you'll se a global frequency sweeping up. Due to the special topology of my circuit, this is equivalent to have arduino MEGA-generate phase locked 8 independent PWM rails aka 8 channel frequency generator.

Once DUE will become more mature and guess as you said, the Atmel datasheet, such project will be easier with more timers and software possibilities with incredible few ns precision jitter.

@selfonlypath: Very impressive - well done!

@WomensFashionArt:

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

... the Due can't handle the logic at the requisite 1,000,000 interrupts / second

Couple of queries:

  • How well synchronized do the different Dues have to be? 10 us, 1 ms, 1s ?

  • And how did you get the 1 MHz figure? Is it from the precision you want for each part of the PWM cycle?

Best,
Jim

Hi Jim,

Thanks for the questions.

  1. "How well synchronised do the different Dues have to be"

We'd like to get it below 20us. However, there are some obstacles to this. Firstly, total capacitance, line driver & logic gate delays will add some delays. We have not yet calculated those, but they are - presently - thought to be within our limits. Our second problem is that - for a new design - we'd like to go radio (probably some variant of 802.11 for its broad availability). That puts the cat among the pigeons because now that very fast response time is becoming increasingly difficult to achieve. Again, we haven't done the maths, but we are pretty sure that there are going to be complications. Our middle name... LOL.

  1. "How did you get the 1 Mhz figure"?

Well, 1,000,000 interrupts for 10 Khz leaves 100 interrupts for each cycle. In other words, 10 Khz @ between 0% and 100% duty cycle in 1% increments.

However, as these units will - initially - only see service inside our studios, we are fine with 5 Khz (which corresponds to exactly 5 pulses of light for a 1/1000 shutter speed on our cameras) - something which, thanks to the Due's fast clock - we have already achieved.

==============================

Unfortunately we only have a small hobby scope at our disposal and its resolution is inadequate for testing the Sync. However, there is a solution (we think) in that we are considering using an 8 input AND gate which will allow us to test the synchronicity of up to 8 Due's by the following methodologies:

  • Assume pulses are initially out-of-phase by - say - 50%. That gives a net duty cycle of 25% on a 50% PWM (the AND gate is shut for the rest of the time because not all inputs are high).
  • Issue a SYNC pulse to the Due's
  • Verify that the AND gate output pulses now show a 50% duty cycle.

Whilst the above is inadequate to verify whether or not we were able to keep our response time sub 20 us, it will show us that our SYNC system is working, and that all the PWM signals are now lining up nicely.

The reason "jitter" isn't that much of an issue for us is, that at lower shutter speeds (and @ 10 Khz), each exposure is likely to be subjected to - say - 80 light pulses (1/125 sec). Even if we missed an entire pulse (not overly desirable, but not catastrophic either), we'd only experience a variance of just over one percent; not distinguishable by the naked eye.

Hope that answers some of your questions (as well as some you haven't asked, but are directly related).

Rgds, J.

Hi all,
We were wondering, with so much confusion about timers, PWM etc. whether one of our standard code blocks might come in handy.
This one trades resolution for frequency (as you do). Hope it is useful to somebody; nothing lost if it's not.

// Software PWM for the Arduino Atmel 328p @ 16MHz
//
// Women's Fashion Art Dev team
// 7 January 2013
//
// Default settings:
// volatile byte Maximum_Resolution_In_Percent = 50;
// byte Duty_Cycle_In_Percent = 50;
//
// Measured performance
// Freq: 3.27kHz
// Duty: 49.9%
// Vrms: 2.44V
// Vpp: 5.02V
// Vmin: 80.0 mV
// Vmax: 5.12V
// Vavg: 2.60V
// Target: Arduino Duemilanove - genuine
//
// Version 1.0 - 18 December 2012
//

#include <io.h>

// Variables
volatile byte Count;

// User variables - change these to suit
// volatiles
//
volatile byte Pin = 9;
volatile byte HighCount = 0;
volatile byte Maximum_Resolution_In_Percent = 50; // do not exceed 100% here...

// non volatiles
//
byte Duty_Cycle_In_Percent = 50; // do not exceed 100% here either...

// Setup routine
//
void setup(){

// Set user's preferred pin to output
//
pinMode(Pin, OUTPUT);

// Set up the high count
HighCount = DoCalcs(Maximum_Resolution_In_Percent, Duty_Cycle_In_Percent);

// Clear interrupts
//
cli();

// Configure timer registers
//
TCCR2A = 0;//
TCCR2B = 0;//
TCNT2 = 0;//

// set compare match register
// OCR2A = 99; // 200 Hz at 100% or 400 Hz at 50%
// OCR2A = 49; // 400 Hz at 100% or 800 Hz at 50%
// OCR2A = 24; // 800 Hz at 100% or 1.6 Khz at 50%
OCR2A = 11; // 1.6 kHz at 100% or 3.2 kHz at 50%

// CTC mode
//
TCCR2A |= (1 << WGM21);
//CS11 bit for 8 prescaler
//
TCCR2B |= (1 << CS11);
// timer compare interrupt
//
TIMSK2 |= (1 << OCIE2A);

// Re-enable interrupts
//
sei();

// Create a dependable PWM sequence
//
digitalWrite(Pin, HIGH); // Start off with a high pulse
}

// Interrupt Service Vector (IDE preset)
//
ISR(TIMER2_COMPA_vect){

// PWM High / Low Routine
// Pull pin low once HighCount is achieved
//
if (Count == HighCount) {
digitalWrite(Pin, LOW);
}
// Pull pin high once at the end of our resolution count
//
else if (Count == Maximum_Resolution_In_Percent) {
digitalWrite(Pin, HIGH);
Count = 0;
}
// Bump count by 1
//
Count += 1;
}

// Subroutines
//
int DoCalcs (byte Resolution, byte DutyCycle)
{
// Set up the high count
return (Duty_Cycle_In_Percent / (100 / Maximum_Resolution_In_Percent));
}

// Main loop
// This does nothing at present, but can add other stuff to this
// depending on how hard the timer is being driven....
//
void loop()
{
while (1);
{
// do other stuff here
}
}

@SelfOnlyPath:
Yes we know. However, sometimes it is desirable - for example for PCB layout purposes - to be able to use a user-assigned pin. In some cases that will allow you to use a single-sided board instead of double, or a double-sided board in place of 4 layers. We take a rather holistic approach to such things and this is only one of several libraries - each with a specific purpose. We chose to list this one because it is the most flexible for users not familiar with the timers on these chips.

@SelfOnlyPath:
Points taken. Frankly, if we needed jitter-free PWM we'd either use a dedicated chip (i.e. the uM-PWM) or drive an interrupt with a high precision crystal at the MCU's highest priority level. Personally I'd never rely on a software timer inside an MCU for highly time-critical PWM - no matter what anyone suggests. A nice 10Mhz crystal running through a /10 would give you a nice clean and (if temperature controlled) highly predictable reference signal that is ns accurate with a small correction lookup table (if needed).

If you want high precision, jitter free, you have to - in my humble view - look outside the MCU software counters at any rate. As noted hereinbefore, jitter is the least of our problems; we have up to 1.5% tolerance - well within the MCU's software capabilities for our timeframes (down to 1/125th of a second).

@SelfOnlyPath:
Thanks for your expert help. All the best.

Hi all. I've implemented Sebastian Vik & cmaglie's timer code. I've got it working as is, with the LED. And I also tried a Serial.println and that works fine too.

However after transplanting the code to something more meaty (RGB Pixel light string) - the TC3_Handler only executes once.

The LEDs driver is using the hardware SPI channel - could it be anything to do with that? To rule out a clash of interrupts with the LED driver, I tried TC4/5 then TC0, but I still only get the first frame.

Any ideas? I'll post a video of the lights, if I get it working. (The code works otherwise - if I base the timer on time, it works fine)

I'll post the code, but you'd need the lights to see it:

(note the previous version of the code works without the timer)

Hi to all, I'm new to the arduino environment, I own an arduino due, I made some experiments with interrupts, apparently I cannot get the frequency higher than 250kHz, is this a known limit of the hardware ? I'm missing something, I read the datasheet and it states that TC_CMR_TCCLKS_TIMER_CLOCK1 is 84MHz / 2 = 41MHz so I was hoping that setting appropriately RA and RC (in my case 20 and 41 respectively) I would be able to acquire timer interrupts at 1MHz frequency.

Where am I wrong ?

Thanks in advance,
Giuseppe

I answer myself, I was wrong, with correct values the timer interrupt can be triggered at 1MHz.
The problem was that the analogWrite is too slow to go to that frequency, so I digged into the arduino/sam7core sources and found how to use the SAM library directly, the code (much fast) that I wrote is:

// This function to configure a Timer was given some replies above, I changed only the used CLOCK.
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_CLOCK1);
  uint32_t rc = VARIANT_MCK/2/frequency; //2 because we selected TIMER_CLOCK1 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);
}

volatile boolean l;

void TC3_Handler()
{
  TC_GetStatus(TC1, 0);
  dacc_write_conversion_data(DAC0, (l = !l)*4095);
}

void setup() {
  Serial.begin(115200);
  pinMode(DAC0, OUTPUT);
  analogWriteResolution(12);  
  pmc_enable_periph_clk(DACC_INTERFACE_ID);
  dacc_reset(DACC_INTERFACE);
  dacc_set_transfer_mode(DACC_INTERFACE, 0);
  dacc_set_power_save(DACC_INTERFACE, 0, 0);
  dacc_set_timing(DACC_INTERFACE, 0x0, 1, 0x0);

  dacc_set_analog_control(DACC_INTERFACE, DACC_ACR_IBCTLCH0(0x02) |
				       DACC_ACR_IBCTLCH1(0x02) |
				       DACC_ACR_IBCTLDACCORE(0x01));
  dacc_disable_trigger(DACC_INTERFACE);
  dacc_set_channel_selection(DACC_INTERFACE, 0);
  dacc_enable_channel(DACC_INTERFACE, 0);

  // 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, 1000000);
}

Thanks to all,
Giuseppe Stanghellini

1 Like

what does this compile to ?

dacc_write_conversion_data(DAC0, (l = !l)*4095);

why not l = ~l;

with l initialised to 4095 -it should be a lot faster.

Duane B

rcarduino.blogspot.com

DuaneB:
what does this compile to ?

dacc_write_conversion_data(DAC0, (l = !l)*4095);

why not l = ~l;

with l initialised to 4095 -it should be a lot faster.

Duane B

rcarduino.blogspot.com

Good point! Nevertheless the code was timing correctly at 1MHz. Checked with a picoscope,

Giuseppe

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

How to change it to micro seconds?

Thanks in advance

My classmate and I have written a timer library that encapsulates cmaglie's "Black Magic" code and allows the user to pass their own function that they want to be executed by the timer as a function pointer. I have included an example program that shows how this is used. It also cleanly allows the user to start multiple timers for multiple functions in the same sketch. I would appreciate any suggestions, criticism, comments, etc.

Thanks!

ARMtimer.zip (2.76 KB)

Hi SomeRandomGuy.

That looks pretty good to me! I've already implemented two timers in my own code - one for lights and the other to debounce/read some buttons. I think that your library will make things much neater. I'll try it next weekend.

I had a chance to use your timer, SomeRandomGuy. It works perfectly, and has tidied up my code somewhat.

Thanks for the feedback! Good to know that it is working well. If anyone thinks of some useful additions or improvements, I would be happy to add those in.

Thanks for taking a look at it.

Thanks alot for all the activity on this post. This is the first time I used timers and all I could find were explainations on how to do it on the atmega328. I read the datasheet but couldnt make up how to do it on the due, so this helped alot!

Nice library - thank you for sharing it.

One idea: it would be nice if startTimer() picked the clock source with the least error, given the desired frequency (credit for this idea goes to RCArduino - see comments in RCArduino: Arduino Due DDS - Part 1 - Sinewaves and Fixed Point Maths).

I hacked together some code to demonstrate it. Caveat: I'm an arduino newb.

/*
 * pick clock that provides the least error for specified frequency.
 */
uint8_t pickClock(uint32_t frequency, uint32_t& retRC)
{
	/*
	Timer		Definition
	TIMER_CLOCK1	MCK/2
	TIMER_CLOCK2	MCK/8
	TIMER_CLOCK3	MCK/32
	TIMER_CLOCK4	MCK/128
	*/

	struct {
		uint8_t flag;
		uint8_t divisor;
	} clockConfig[] = {
		{ TC_CMR_TCCLKS_TIMER_CLOCK1, 2 },
		{ TC_CMR_TCCLKS_TIMER_CLOCK2, 8 },
		{ TC_CMR_TCCLKS_TIMER_CLOCK3, 32 },
		{ TC_CMR_TCCLKS_TIMER_CLOCK4, 128 }
	};

	int clkId = 3;
	int bestClock = 3;
	float bestError = 1.0;

	do {
		float ticks = (float) VARIANT_MCK / (float) frequency / (float) clockConfig[clkId].divisor;
		float error = abs(ticks - round(ticks));
		if (abs(error) < bestError) {
			bestClock = clkId;
			bestError = error;
		}
	} while (clkId-- > 0);

	float ticks = (float) VARIANT_MCK / (float) frequency / (float) clockConfig[bestClock].divisor;
	retRC = (uint32_t) round(ticks);
	return clockConfig[bestClock].flag;
}

void startTimer(Tc *tc, uint32_t channel, IRQn_Type irq, uint32_t frequency, volatile void (*function)())
{
	pmc_set_writeprotect(false);
	pmc_enable_periph_clk((uint32_t)irq);

	uint32_t rc = 0;
	uint8_t clock = pickClock(frequency, rc);
	TC_Configure(tc, channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | clock);

	... remainder of startTimer() unchanged ...

}

Thanks for the idea BKM! I have added it to the library along with several other major improvements that should completely remove any IRQ references from the sketches and make it much cleaner and more user friendly. I will attach a link to my github repo tomorrow night once I get to test it a little bit.