Interrupts vs polling

Hi, I am working on a project that has 40 sensors connected to the strings on a piano. I need to read and respond to their data very quickly. I have seen information on polling with the arduino, and some on using interrupts, but not that much.

I am new to the Arduino, but I have worked with real time embedded systems on a motorola microprocessor, so I am quite familiar with working with interrupts.

Can anyone tell me how well they work with the arduino? What limitations exist? Can one use reentrant code with the arduino?

Thanks!

I avoid reentrant code where possible because the SRAM is pretty tight and it is easy to run out, overwrite part of the stack, and fail mysteriously. I don't need that happening when some odd combination of interrupts occurs. That said, you could sei() inside your interrupt handler and that would allow a reentrant interrupt, hopefully not the one you are already handling.

I tend to use interrupts when precise timing is required, for instance if I am sending bits to modulate a radio and need the timing to be accurate I will program a timer to make a periodic interrupt at the right time. Or the USB handling code I use watches for the leading edge of the first bit with an interrupt.

Be aware: C and C++ interrupt handlers have something like a 70 clock cycle overhead to push the pop all the registers that are available to the API. If you are doing something small and very frequent you might want to do it in assembler o avoid that.

Aside from the two pin level interrupts, you need to go to the <avr/interrupt.h> functions to code the handlers. See avr-libc: <avr/interrupt.h>: Interrupts

For your application, I think you are sensing piano strings to see if they have been hit, I think polling would be best. You only have a couple of interrupts pins, so you'd still have to scan the strings looking for which one made the interrupt. Millisecond level accuracy is plenty for music, heck, I'm a bass player, you'd have to wait 20 milliseconds for the first cycle of my note to complete.

So, if you were using, say three 16:1 muxes your code might look like this...

for( int i = 0; i < 16; i++) {  // going down to 0 would save 1 cycle/loop
  digitalWrite( muxAddr0, i&1);      // 5uS, note all three muxes are on the same address lines
  digtialWrite( muxAddr1, (i>>1)&1); // 5uS, that way we can do the expensive addressing once
  digitalWrite( muxAddr2, (i>>2)&1); // 5uS
  digitalWrite( muxAddr3, (i>>3)&1); // 5uS
  m[i] = digitalRead( mux0); // 5uS
  m[i+16] = digitalRead( mux1); // 5uS
  m[i+32] = digitalRead( mux2); // 5uS
}

You could scan 48 inputs in about 600 microseconds. Plenty fast for music. You will also need to budget some time for doing something with the data. If you are sending it back to the host over the serial port, then be aware the serial output functions are blocking. If a byte hasn't finished being sent then you will have to wait when you try to send the next one.

I suppose I could put an interrupt based serial output class into the playground. I use if for debugging when I don't want to disrupt the timing too badly. I haven't suggested putting it in the main code because you end up needing SRAM space for buffering which is in tight supply.

Thank you. That helps a lot. I also haven't worked with instruments before so your sense of what kind of timing they require is very helpful.

One question though, does the serial io have to be blocking? Is that all that's available?

The standard serial methods shipped are blocking on the output and nonblocking on the input. The first character out doesn't block, but if you write a second one before the first is done it will block.

It is easy enough to watch the interrupt for the uart data register and have a little fifo. I'll post an example, but it is gravely flawed and I am away from my test gear for a week or so and won't be able to polish it.

  • This example code probably does not handle overflow correctly. It probably fails if you overrun it.
  • This example does not use the begin() method the same way as the normal serial. It takes the precomputed rate register value which saves a bunch of long arithmetic in the object code. This is confusing enough that I do it wrong every time. I should rename that method.
  • This example uses different functions for the print functions. It keeps the object size down.
  • This example has never been used with incoming data, I wonder if that interrupt goes off for any kind of incoming data.
  • This example is set for a 128 byte output buffer, it may run you out of SRAM. You might want to shrink it a lot.
  • On the plus side, it has been checked on both mega8 and mega168s and is has printp() method for printing program space strings.

The flawed but perhaps useful SerialOut.h file...

#ifndef SERIAL_OUT_IS_IN
#define SERIAL_OUT_IS_IN

#include <avr/io.h>

class SerialOut {
  protected:
    char buffer[128];
    uint8_t head;
    uint8_t tail;
    static SerialOut *instance;
  public:
    SerialOut();
    void begin(uint16_t rateRegister);
    void print(const char *c);
    void printp(const char *c);
    void println();
    void print(char c);
    void SerialOut::printhex( uint16_t v);
    void SerialOut::printhex( uint8_t v);
    static void xmit(void);
    void xmitChar(void);
    static inline uint16_t rate(unsigned long baud) { return (F_CPU / 16 + baud / 2) / baud - 1; }
};

#endif

The flawed but perhaps useful SerialOut.cpp file

#include "SerialOut.h"

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>

SerialOut *SerialOut::instance = 0;
SerialOut::SerialOut() : head(0), tail(0)
{
}

void SerialOut::begin(uint16_t rateRegister)
{
    instance = this;
#if defined(__AVR_ATmega168__)
    UBRR0H = rateRegister >> 8;
    UBRR0L = rateRegister;

    // enable rx and tx
    //sbi(UCSR0B, RXEN0);
    UCSR0B |= _BV(TXEN0);

    // enable interrupt on complete reception of a byte
    //sbi(UCSR0B, RXCIE0);
#else
    UBRRH = rateRegister >> 8;
    UBRRL = rateRegister;

    // enable rx and tx
    //sbi(UCSRB, RXEN);
    UCSRB |= _BV(TXEN);

    // enable interrupt on complete reception of a byte
    // sbi(UCSRB, RXCIE);
#endif
}


#if defined(__AVR_ATmega168__)
SIGNAL(SIG_USART_DATA)
#else
SIGNAL(SIG_UART_DATA)
#endif
{
    SerialOut::xmit();
}

void SerialOut::print( const char *c)
{
    while( *c != 0) print(*c++);
}

void SerialOut::printp( const char *c)
{
    char ch;

    for (;;) {
        ch = pgm_read_byte( c++);
        if ( !ch) return;
        print(ch);
    }
}

void SerialOut::println()
{
    print('\r');
    print('\n');
}

static const char PROGMEM p[] = "0123456789abcdef";

void SerialOut::printhex( uint8_t v)
{
    print( (char)pgm_read_byte( p + (v>>4)));
    print( (char)pgm_read_byte( p + (v&0x0f)));
}

void SerialOut::printhex( uint16_t v)
{
    printhex( (uint8_t) (v>>8));
    printhex( (uint8_t) (v&0xff));
}

void SerialOut::print( char c)
{
    buffer[tail++] = c;
    if ( tail >= 128) tail = 0;
#if defined(__AVR_ATmega168__)
    UCSR0B |= _BV(UDRIE0);
#else
    UCSRB |= _BV(UDRIE);
#endif
}

void SerialOut::xmit()
{
    if ( !instance) return;
    instance->xmitChar();
}

void SerialOut::xmitChar( )
{
    if ( head != tail) {
#if defined(__AVR_ATmega168__)
        UDR0 = buffer[head++];
#else
        UDR = buffer[head++];
#endif    
        if ( head >= 128) head = 0;
    }
  
    if ( head == tail) {
#if defined(__AVR_ATmega168__)
        UCSR0B &= ~_BV(UDRIE0);
#else
        UCSRB &= ~_BV(UDRIE);
#endif
    }  
}

An example of using he flawed but perhaps useful SerialOut class

#include <SerialOut.h>
#include <avr/pgmspace.h>

SerialOut debug;

void setup()
{
  debug.begin( SerialOut::rate(57600));  // see how we precompute the rate at compile time?
  pinMode(1,OUTPUT);  // See how we set the pin to SPACE and wait for it to take effect
  digitalWrite(1,HIGH);
  delay(1);
  debug.printp(PSTR("Hello\r\n"));  // See how we print a string from program space
}

Again thank you so much! I've only begun working with this, but am really looking forward to it.