Interrupt Service Routine Questions (Arduino)

Submitted by Josephk on Sat, 03/27/2010 - 11:33

Printer-friendly versionPrinter-friendly version

I have been trying to figure out how to get my Arduino to do *a thing* at a rate that I have fairly precise and variable control over.  I believe that the best way to do this is with an interrupt service routine that can call a function x number of times per second.  The problem is, I can't seem to wrap my head around how to make this happen. Here is my specific example:

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

 long incoming;  //a variable to read 10bit ADC at pin 0
 int outgoing;  //8 bit value to write to the 8 outputs on Port D

 void setup() {
  DDRD = 0xff; // set PORTD to all outputs
 }

 void loop() {
   incoming = analogRead(0);  //get 10 bit sample value
   outgoing = incoming >> 2;  //right shift by two for 8 bit sample value
   PORTD = outgoing;          //write channels 0 - 7 simultaneously on Port D with the value of "outgoing"
 }

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

 

What I would like to do is put all of my loop activity into a function that I can call at rates that are definable, accurate, and able to be changed over time.  Any thoughts on how to do this or pointers to good ISR tutorials.  Has anyone else had success programing with ISRs on the Arduino?

Comments

7 comments posted
for what it's worth...

 This was my first introduction to timers in Arduino....

interface.khm.de/index.php/lab/experiments/arduino-realtime-audio-processing/

Not exactly what you are looking for, but related given that it's about audio.

Robb

Posted by rdrink on Mon, 05/10/2010 - 16:33
Interrupt Service Routine Questions (Arduino)

I have not done interrupts on the AVR, but I have done them on the 'HC11's, PICs, and a 386 running MS-DOS. They're more alike than not, so this is what I know.

An interrupt is a function call that comes from hardware. An interrupt "interrupts" the normal flow of a program. Interrupts are a very useful tool. They're easiest to understand by looking at an example.

To get an interrupt working on an MCU, you need a few things. For our example, we'll look at  an MCU's hardware timers. Hardware timers are counters which are driven from the MCU's clock. They run freely without processor effort. The timers in an MCU are as accurate as the system clock. When using a crystal in the clock oscillator, accuracy can be a few parts per million. Hardware timers can be used to schedule events in the main program, create accurate waveforms including PWM, and do accurate measuments of external pulses. They are highly configurable. Their special function registers contain count values and trigger values which, in some cases, be read from and written to like variables. They also can fire off interrupts when an event like a counter overflow occurs.  Although the timers don't require CPU cycles to run, using the hardware timers in a program requires effort from the processor. That effort is what we're discussing here.

For that we need:

  • An available hardware timer in the chip. The ATMega168 has three (numbered 0, 1, and 2).
  • You need to write a function called an  Interrupt Service Routine, ISR, to hook into the hardware. The ISR is the function where your job gets done. It's a function, much like any other function, except different. (That phrasing was a bit of humor to gloss over a bit of unrevealed complexity. More on that later, perhaps.) In the flow of events around an interrupt, the MCU stores what it's doing when an interrupt occurs and then calls the ISR function. It saves its place in the main program so that it can return to that after the ISR call is done. The hook connecting your ISR function to the timer hardware in the chip is made simply by naming your function with the special name given in the manual that comes with your compiler. Using avr-gcc with a '168 chip, an ISR attached to a timer 2 overflow is required to have the special name ISR(TIMER_2_OVF_vect) . This particular interrupt is used to make something in code happen at controllable, precise intervals. The compiler will see this in your source code and give that function special treatment.
  • You need enough time to do the job in your ISR  function. Your ISR is competing for time away from the main program, as well as other ISR's (like the one for the serial port). ISR's should be very, very short.  The cleanest way to use a simple ISR in the presence of the Arduino code is to limit yourself to doing , at most,  a few if/then type things, incrementing or decrementing a few counter variables, or maybe doing a bit of i/o. It's a bad sign to have while or for statements, and tragicomical to have a delay(). In an ISR, you want to get in, get done, and get out fast.

The Arduino uses timer interrupts for delay() and pwm, and also uses time-sensitive interrupts for handling the serial port. This makes it a bit challenging to use a hardware timer. People do this, but the complication is that the AVR has three interrupt-capable hardware timers and the Arduino has them all spoken for.  If you want to use a timer, you have to steal it from somebody.

Quoting from an excellent article on this subject at http://www.uchobby.com/index.php/2007/11/24/arduino-interrupts/
 
    * Timer0 (System timing, PWM 5 and 6)
      Used to keep track of the time the program has been running. The millis() function to return the number of
      milliseconds since the program started using a global incremented in the timer 0 ISR. Timer 0 is also used for
      PWM outputs on digital pins 5 and 6.
    * Timer1 (PWM 9 and 10)
      Used to drive PWM outputs for digital pins 9 and 10.
    * Timer2 (PWM 3 and 11)
      Used to drive PWM outputs for digital pins 3 and 11.
 
While all the timers are used only Timer0 has an assigned timer ISR. This means we can hijack Timer1 and/or Timer2 for our uses. The PWM function on some of the I/O pins will be affected as a result however. If you plan to use PWM you need to know what is affected. I chose to use timer 2 so PWM pins 3 and 11 will be affected.

And then he shows how to do it. It's a great article.

The way the OP's question is posed sounds like a request for a "real-time" system. What real time means varies with who you're talking to, but the best definition I've heard says that a real-time system has a guaranteed response time. That time can be nanoseconds or minutes. It really depends on the application. The method you choose to implement your solution will be easier or harder depending on the demands. If your case can stand a few milliseconds of response variation, it's easy to poll elapsed time using millis(). If that's not fast enough, you will need to use the AVR timer interrupts.

Two last things to know about using interrupts on an MCU.

There is latency in servicing an interrupt. A stack is a special section of memory that stores bytes like a cafeteria tray dispenser -- last in, first out. The processor has to "push onto the stack" all the variables and the current location of the program' s execution associated with the main program. At the end of the ISR, the "return from interrupt" restores the normal main program from the stack and resumes execution where it left off. In the article referenced above, the author finds that an ISR which toggles an i/o pin, reads a counter, and adds two ints used 45% of the processor when firing off every 20 microseconds. That's a lot of interrupts, it's true, but the 45% figure shows the cost of doing an interrupt.  

The other big issue is what to do when two or more interrupts want to be serviced at the same time. An ISR can can be written to either lock out other interrupts from being serviced during its own execution, or it can allow being interrupted itself. Your choice. If you have a long ISR that locks out other ISR's by making them wait, you might for instance, miss some input on the serial port. If too many interrupts are running at once, they get in each other's way. Time sensitve actions won't happen on schedule, and in the worst case the stack can overflow causing your program to crash. The number of ways interrupts can be prioritized and how much freedom the programmer has to make things work in an ideal way depends on the chip and the compiler. The AVR and GCC combo used in the Arduino seem pretty good this way. Regardless, the programmer has to be clever and vigilant to be sure things don't step on each other.

The takeaway is to keep your ISR's as short and as infrequent as possible, and you'll probably be okay.

To josephk: I'd need to know more about what you're doing, particularly the timing requirements, to be more specific about your needs.

-e

 

Posted by Ed_B on Sat, 03/27/2010 - 18:53
.a bit more specific.

Thank you for the speedy and thorough response.  I read the Uc hobby article over and over before writing my original post, but there are still parts of it that I can't seem to grasp.  I have printed it out on actual paper now and am making notes and drawing flow charts.  I will understand.  I am going to add in what I learned from your response (excellent contextualizing of the latency issue), and try to come up with a solution.

HERE is what I am doing:

I am sampling audio (with an added DC offset) that is coming into analogPin0.  I then take that sample and shift it to get the 8 MSBs. Then I write it directly to PORTD (one bit on each digital out 0-7 in parallel).  This is a building block that I have been using in different projects and experiments.

HERE is what I want to be doing:

What I want to do in my ISR is make that AnalogRead, use 8 of the 10bits (or only sample 8, if I can figure out how to do that), and write that to PORTD.  I do it in my main loop now, but I don't have the accuracy or precision that I feel I need.  I want an ISR that takes one argument: "samplingFrequency" so that I can change the freq dynamically in the software.  Then the main code can just keep track of passing time so that it can change the sampling rate after specified durations.  I believe the uC hobby test was running at 50kHz.  That would be a great limit, but I could do with a lower ceiling.

Thanks again for the help, I am getting closer by the minute.
JK

Posted by Josephk on Sun, 03/28/2010 - 20:22
.a bit more specific.

For a historical reference, I'm including these diagrams on an 8-bit A-to-D converter with a parallel output. Why, back in the day, these were the cat's pajamas. This one is a TLC0820 from Texas instruments. 

And here is its internal diagram. It is not an MCU and it is not programmable. It has an analog input pin and eight data lines that carry a data byte to an MCU.

I'm including this because it sounds like what you're building with an MCU. The difference being that an A/D conversion is started on the TLC0820 by pulsing its READ pin. The data byte appears some number of microseconds later on the eight output lines. The sampling interval is the rate of polling the RD pin.

I'm not suggesting you use this, but if this is effectively what you're making, it starts a discussion on specifics of your code. 

Also, I don't think the maximum sample rate of the AVR A/D converter goes very far into the audio range. Its a very slow converter. I'll look it up, though. You wouldn't want to try doing analog conversions faster than the chip can process them.

One last question. How fast/often does the output byte need to change? Connected to this question is the idea that fast-changing waveforms going into  slow A/D converters give whacky output numbers. A nice little analog sallen-key low-pass filter in front of the A/D pin will make things more predictable. But I'm rambing now.

Posted by Ed_B on Mon, 03/29/2010 - 00:58
That is what I am making. The

That is what I am making.

The Arduino reference says the sample rate of the ADC is about 10kHz.  I run audio in to the pin with a 5kHz brickwall filter applied in software (Nyquist rate for 10kHz) and a DC offset in hardware (so the pin sees both sides of the AC waveform).  This gives me the distortion that I expect when I play the output through an AD558 8-bit DAC (for testing only...I am not making that straight of an A-to-D-to-A thing).  The main loop of the code is:

long incoming;  //a variable to read 10bit ADC at pin 0
int outgoing;  //8 bit value to write to the 8 outputs on Port D

void setup() {
 DDRD = 0xff; // set PORTD to all outputs

void loop() {
  incoming = analogRead(0);   //get 10 bit sample value at about 10kHz max
  outgoing = incoming >> 2;    //right shift by two for 8 bit sample value
  PORTD = outgoing;              //write channels 0 - 7 simultaneously on Port D with the value of "outgoing"
}

The result of this sounds comparable to the same audio file downsampled to 8-bit/10kHz in software. (at least in my memory. I did the monolithic DAC comparison test a long time ago).

So, I could just use an ADC chip, but I would still need a way to program my precise timing changes, right?  I am comfortable with an ADC chip, I just haven't considered it yet because I feel that I would still need a programmable stable frequency, which I think requires the use of a hardware timer on an MCU.  Also, it is true that I was hoping to discover some hidden computational headroom for doing *some things with bits* that are yet to be determined.

A Project Example:

In one project using this scheme I have eight speakers (each driven by the DC of one bit through a UN2803A at equal power) at distances from F that are equal to d1(MSB)=F+1, d2=F+2, d3=F+4, d4=F+8,...d8=F+128. (these distances are derived from measuring the distance at which SPL of the speaker is -6db from the next nearest speaker) This is to create a very crude DAC in which F is a perceptual "Focal point" where the SPL generated by each speaker creates an aggregate that somewhat represents the original signal. The result, as you might imagine, is not so great.  The bits aren't timed, for one thing, so the result is not doing a good job of clearing all those harmonics introduced by the square waves that even my crappy R/2R ladder was able to easily smooth because they aren't arriving at the same time.  Another thing is that the space is not a 130 foot anechoic chamber, so all other sounds as well as reflections of the sounds I am generating contribute to the distortion.  But all of that's okay for now.  What I need is a flexible system for breaking up audio into bits so that I can compose changes in sampling rate.  I am interested in the neat illusion that the "Spacialized DAC" concept is suggesting, but I am currently more interested in being able to compose for a time domain that includes Sampling rate along side rhythm, pitch, and timbre.  I think this means my priority is to have precise control over a flexible sampling rate (I would prefer it go higher than 10kHz).   I think I should get an ADC and see if I can piece together some control code.  I think I still need to use a programmable timer on an interrupt though.....or Hold On!... it just occured to me that I could use an oscillator (like the 74c14) with a digital pot in its timing circuit as a clock to drive an ADC.  This would do the work of clocking really fast, and I could change the clock by adjusting the digital pot at a much slower event timing level through SPI from my MCU. 

But I'm still gonna figure out how to write a timer driven ISR.

 

Posted by Josephk on Mon, 03/29/2010 - 13:38
That is what I am making. The

I had a had a hard time understanding what you're up to. So I spent some effort and found this on Wikipedia. In acoustics, the sound pressure of a spherical wavefront radiating from a point source decreases by 50% as the distance r is doubled, or measured in dB it decreases by 6.02 dB.

So by powereing your speakers equally, but spacing them from the listener at a distance that maps the sound-halving aginst the bit position in the amplitude byte, you're summing the amplitudes to produce an instsantaneous SPL (looked that one up, too) proportonal to the numeric value of the sample byte? At least I tried... I guess I'll have to hear it to understand.

Hows about I just post a little timer interrupt generator for you to play with. I'll have to find one or write one. There's other stuff going on, so it' ll take a couple of days. Check back then.

HH
-e

 

 

Posted by Ed_B on Mon, 03/29/2010 - 22:57
Wow, Yes!

That describes precisely what I have been attempting.  Can I use that?

Also, thanks for all the help so far.  This is not an urgent feature, it is just the next step in an ongoing project.  Having a timer interrupt generator to play with is what I would really like.  I can't figure out how to use the uC Hobby code for what I am trying to do.  One of the posts commentors, "tat", posted a code that is very similar to what I am describing, but I'm still a not quite able to make sense of it.  I think it is a conceptual problem of not calling the ISR function in the main loop, but I guess that is the point.  I think I'm about to figure this out....

Posted by Josephk on Tue, 03/30/2010 - 11:28