Problem with timer interrupts on Arduino Uno

I am trying to generate sine waveforms with Arduino Uno using timer interrupts.

Basically, I have a pre-calculated array of 100 integers that holds values of a whole sine wave period. Every dt seconds a timer interrupt occurs, and the ISR (interrupt service routine) outputs a single sine value. The process is repeated, so in the end I get a sine wave on my oscilloscope.

At some point I realized that it's impossible to set a timer interrupt for a value less than 20 µs, so I made a series of experiments to show what I get (I am sorry for the quality of pictures): Imgur album with oscillograms

And, of course, the source code of the program I wrote: main.ino waveform.h

So I tried setting different OCR1A values for timer1 (16-bit timer) to get different timer interrupt values. I also ran two different kinds of experiments:

  • Sine wave. Analog signal is generated using DAC8512 (12-bit DAC), and the data is sent to it from Arduino over SPI with a setValue() inline function I wrote;
  • [Square wave. I simply invert square wave polarity every dt µseconds.

As you can see from the pictures above, for a dt of 20 µs the sine wave period is wider than expected. I expect to see the period: T= 100 samples * 20 µs = 2000 µs = 2 ms, but I get a period of approx. 2.2 µs.

The square wave at dt = 20 µs looks like a trapezoid, no matter whether I use 1x or 10x probe.

However, if I increase dt to 25 µs, the pictures start to look fine - there's no frequency deviation anymore for sine wave, and square wave looks more like a square wave.

If I take a value even lower than 20 µs (i.e. 5 µs; not shown in the pictures), I get almost the same results as for 20 µs. Square wave seems to have a pulse width of approx. 18 µs, and sine wave period is also wider than expected

Even if I don't use a DAC for square wave and simply use digitalWrite, it still works no faster than 20 µs, so it's not a DAC limitation.

In the end I want to generate a 200 Hz sine wave, and I need no less than 720 points per period for that (0.5 degrees precision). And so I get: T = 1/f = 1/200 Hz = 5 ms; dt = T / samples = 5 ms / 720 = 6.94 µs

This means that I need to set dt equal to 5 or 6 if I want to get the precision right. Not to mention that I need to modulate this signal, as well as perform some complex calculations (solving system of linear differential calculations in loop() ), and 5 µs limit leaves me almost no time for such calculations, since (for Uno): time_per_instruction = 1/freq = 1 / 16 MHz = 62.5 ns; number_of_instructions = time/(time_per_instruction) = 5 µs / 62.5 ns = 80. And I have 10 equations in the system, each with multiplications, dozens of them.

So I have a few questions about this problem:

  1. Am I running into some sort of a timer1 limitation? What if I use a different timer (timer0, timer2)? Is there any way to see a 5 µs long pulse from Arduino Uno?
  2. How much time I lose when I use SPI for data transfer? Is there any way to optimize it?
  3. Will it help to improve performance if I replace my DAC with R2R ladder (at the cost of number of pins used, of course)?
  4. Is it a better idea to use Arduino Due instead, with its analog outputs? Is it possible to perform a 5 µs interrupt on Due?

You seem to be calling a function setValue() from within your ISR but I don't see the code for that function - is it a function that is appropriate to call from within an ISR?

...R

You seem to be calling a function setValue() from within your ISR but I don't see the code for that function - is it a function that is appropriate to call from within an ISR?

setValue is here, on line 114

inline void setValue(unsigned int value) {  
  SPI.transfer(value >> 8);
  SPI.transfer(value & 0xff);    
}

It used to have much more code, but then I optimized it and also defined it as "inline". I did it because I originally planned to use it for two different waveform generators, and I needed some common way to send data over SPI.

chryssalid:
setValue is here, on line 114

Sorry - I missed that - I think I assumed that was part of loop().

I’m not all that familiar with SPI - should one call it from within an ISR?

The answer must be NO is SPI itself uses interrupts.

Another concern is how long the SPI call takes - does it make the ISR take too long?

…R

Another concern is how long the SPI call takes - does it make the ISR take too long?

I looked at the source code of SPI module, and it's actually very simple:

byte SPIClass::transfer(byte _data) {
  SPDR = _data;
  while (!(SPSR & _BV(SPIF)))
    ;
  return SPDR;
}

I am not sure how many instructions it takes, but I assume that generating 16 pulses for serial clock and serial data may take up to 62.5 ns * 16 = 1000 ns = 1 µs

So surely it may introduce some error. To avoid that, I do the following trick in the ISR: I first pull LD down (to output data from the DAC), and then transfer the NEXT point over SPI. By the next timer interrupt the data is already there, so the first thing I do in the ISR is pulling LD down to output a point almost immediately (it takes one instruction or so to write data to a pin, right?)

But even without the SPI, digitalWrite fails to write data faster than every 20 µs. When I set timer counter to 5 µs, the pulse is 20 µs long anyway. Why does this happen?

digitalWrite() is very slow because it has to figure out which PIN you mean. If you need speed use direct port manipulation.

...R

I agree about direct port manipulation. Also, if you want accurate timing of your sine wave, avoid using interrupts. Interrupts are somewhat unpredictable in the response time.

I understand the point about direct port manipulation, thanks for pointing out.

However:

Also, if you want accurate timing of your sine wave, avoid using interrupts. Interrupts are somewhat unpredictable in the response time.

What other options do I have? What if I need to perform some calculations in loop(), which need to be interrupted every dt µsecs?

If you need to do something else while generating a signal, then you will have to live with the unpredictable timing of interrupts. It is only a few CPU cycles -- the data sheet describes this in detail.

However, with direct port manipulation, as I understand, I cannot transfer serial data to DAC. The only option I have is to output values as a parallel code; in that case, I need to build a DIY DAC (R2R-ladder). Is it a good idea?

If I use a different board (i.e. Due), is there a chance to achieve better performance in terms of time between interrupts? Another thing is, I can simplify my project by using Due's DACs instead of external DAC I was using.

chryssalid: However, with direct port manipulation, as I understand, I cannot transfer serial data to DAC.

I don't understand what you don't understand ! You can do anything with port manipulation that you can do with digitalWrite() - just faster.

Can you explain in simple terms what exactly you are trying to do?

My sense of it is that you have an array of numbers (which probably represent the amplitude of a sine wave - but to the Arduino they are just numbers) which you want to transmit to something at regular intervals.

If I am correct I have a few questions Is the interval between transmission of successive numbers always the same? If it is not always the same, how does it vary? What is the interval (or the range of intervals if it varies)? Do you want to be able to send the data with one interval (or range) now and with a different interval on a different occasion?

I also believe the thing you want to send the data to would like it sent using SPI - but let's leave that to one side for a moment.

...R

My sense of it is that you have an array of numbers (which probably represent the amplitude of a sine wave - but to the Arduino they are just numbers) which you want to transmit to something at regular intervals.

Yes, exactly. And since with Uno I can only output HIGH or LOW, and PWM is not quite suitable for this task, I am using an external DAC for this. And since this DAC takes serial code at its input, I used SPI, but let's put this aside for a moment.

Is the interval between transmission of successive numbers always the same? If it is not always the same, how does it vary?

It is strictly the same and it must not vary

What is the interval?

dt = 5 or 6 µs

Do you want to be able to send the data with one interval (or range) now and with a different interval on a different occasion?

Definitely not.

I need to output a modulated 200 Hz sine wave. I have precision limits: the time step dt between two consecutive points should be equal to at least 0.5 degrees, and since the whole period is 360 degress, I need to store 720 points (minimum).

The 200 Hz sine wave period is: T = 1/f = 1/200 Hz = 5 ms;

And, knowing how much time the whole period takes, and how many points I need to output, I can get dt: dt = T / samples = 5 ms / 720 = 6.94 µs (maximum)

This means that I need to set dt equal to 5 or 6 µs if I want to get precision right. Even if I set timer for 5 µs interrupts, in reality every interrupt occurs every 20 µs or so, which puzzles me a lot.

So when I realized that timer interrupts work precisely only for values bigger than 20 µs, I ran a series of experiments to check whether I am wrong or not (first post). And so far I didn't manage to see at least a 5 µs long square pulses

OK. That makes things much clearer for me. I find it easier to think about sending 720 * 200 = 144,000 bytes per second = 1,152,000 bits per second.

I suspect you are running up against the limits of what is possible on a 16MHz Arduino.

In 5 usecs an Arduino can only execute 80 instructions and you want to be able comfortably to output 8 bits of data in that time.

I would have to do a considerable amount of reading to figure out how SPI works (it's a while since I looked at it) - for example I can't remember if there are start and stop bits etc. Also I can't remember what are the clock speed limits - but I think it can be very fast.

One thing is sure you certainly don't have enough time to use interrupts or a Timer or call a Library.

I suspect it would be possible to write code that behaves like SPI (as far as the external device is concerned) and which would send 8 bits within 80 clock cycles. Obviously the Arduino could do nothing else while that is going on. You may have to disable interrupts which may also screw up other parts of the Arduino system.

If I get time later I will try to refresh my knowledge of SPI.

I have now done a bit of reading. I had forgotten that SPI is a built-in feature of the Atmega chip. Consequently I think you should be able to use the SPI system directly to send data - all you have to do is initialize the appropriate registers (before you send anything) and when you write a byte to the SPDR register it will be sent automatically. This should make the coding easier but it does not affect the overall timing constraints.

...R

Ok, the SPI problem is relatively clear.

Let's assume that I am not using SPI at all. Is 5 µs timer interrupt possible on Uno, with this Atmega chip? If not, is it possible to get such short interrupts with a different microcontroller (i.e. Due)?

chryssalid: Ok, the SPI problem is relatively clear.

Let's assume that I am not using SPI at all. Is 5 µs timer interrupt possible on Uno, with this Atmega chip? If not, is it possible to get such short interrupts with a different microcontroller (i.e. Due)?

I think you misunderstood what I was trying to say - sorry for rambling.

I believe you can use SPI, but not the SPI library (in case if steals precious cpu cycles)

I don't believe it is possible to get the accuracy you want with interrupts, but it should probably be attainable without them.

The time it takes the Arduino to process code is very precise and repeatable when it is NOT interrupted.

The code I am thinking of is something like this pseudo code

setup the SPI registers
interrupts off
for n  from 0 to max  number of samples {
    SPDR = sample[n]
    some rubbish code to waste the right amount of time until the FOR should repeat
      (perhaps some inline assembler NOPs)
}
interrupts on

You will need to do some practical tests to figure out how much "rubbish code" is needed to get the right timing

...R

For absolutely critical timing and short procedures, the usual approach is to "unroll" all loops and just use direct port manipulation and fixed-address array indexing. Essentially, there is a one-to-one correspondence between a line of code and 1 or 2 machine instructions. This is usually done in assembly language, but a C program can be written to approximate that.

Here is an example of such an in-line program, where an ATmega chip acts as an RFID tag. https://bitbucket.org/hudson/rfid/src/0ef4c3636f0a/avrfid2.c?at=default