again: InterruptIn too slow ??? !!!

26 May 2011

After browsing more than 30pages of poststs about interrupts I found some posts dealing with the same problem but none of them with a reply containing a solution or clarification of the problem I decided to pick up the topic again. Hopefully this time somebody can give me a hint whats going wrong. Otherwise I will have to drop the mbed for my required task, going ahead with a simple 16bit 20Mhz Pic which does the job flawlessly

My task: A soft coded frequency divider aka: a 1-100kHz square wave input on a gpio (using InterruptIn) gets divided by a factor of 2-500 (accomplished by a counter loop) and output to another gpio. Furthermore a rotary encoder is used to set the divisor on the fly, a lcd is used to display the divisor.

See the complete code for reference in reply to this post

My problem: Actually up to input frequencies of about 70kHz everything works fine as predicted Above that up to closely 100kHz I seem to loose interrupts, aka the output frequency is lower than it should be. Above 100kHz the output suddenly drops to half the predicted frequency.

At the max frequency of 100kHz I figured out to have 10us time or roughly 1000 clock cycles until the next interrupt occurs.

Believing the documentaton, interrupt latency is no more than 12 clock cycles (120ns) and having verified that the code in my interrupt handling routine does not consume more than 40 cycles (400ns) at the most, I end up with app. 50 cycles or 0.5us for the whole interrupt handling, which is a factor of 20 away from the min. period of 10us at 100kHz. (there should be plenty time, more than 9us waiting for the next interrupt!!!)

In reality the whole routine (entering interrupt, executing handler, reentering main)seems to take somewhere between 5 to 10 us!!!

Whats going on here?? (some little dwarfs munchning up my interrupts? ;-) Hopefully somebody here in this forum has an idea what's going wrong. Otherwise I'll begin to hate my beloved mbed which is causing me sleeplessnights at the moment ;-)

Reagards to all Tom

26 May 2011

main.cpp

#include "mbed.h"
#include "TextLCD.h"
#include "RotaryEncoder.h"

Ticker timer;
Timer t;
InterruptIn event(p10);
DigitalOut flop(p22);
DigitalOut led(LED1);
volatile int divider, newdiv;
volatile int count;

TextLCD lcd(p15, p16, p17, p18, p19, p20, TextLCD::LCD16x1);

RotaryEncoder re(p12, p11, 2, 500, 50);

void atint() {
    t.start();
    count++;
    if (count == divider/2){
        flop = !flop;
        }
    if (count >= divider) {
        count = 0;
        flop = !flop;
    }
}
void attim() {
    newdiv = re.getVal();
    led =!led;
    if (newdiv != divider) {
        divider = newdiv;
        lcd.cls();
        lcd.printf("Teiler: %d     ", divider);
    }
}


int main() {
    count = 0;
    divider = 50;
    lcd.printf("Teiler: %d     ", divider);
    event.rise(&atint);
    timer.attach(&attim,0.1);
    while (1) {
    }
}
26 May 2011

The Mbed librarys are great at getting going out of the box and for a large part most projects can use them without an issue. But you have to remember, in many places the Mbed librarys have to abstract out certain concepts. This abstract comes at a price, speed. So, can we remedy this? Yes. Lets break down your program and look at ways to make it go faster.

We'll start with those DigitalOuts. Because a DigitalOut takes a PinName in it's constructor, the class itself has to "work out" which pin it is when you want to set/clr/read it. That takes time. Is there a quicker way? Yes, but you loose your abstract. But that's no big deal. Now, there are always many ways to skin a cat as they say. But I'll jusy show one I use that I find convienent.

First, import this library into your project:-

Import librarySimpleIOMacros

Simple to use macros to manipulate GPIOs quickly.

You might be thinhing "oh not another library" but all it does in fact is give you a header file IOMacros.h that do just that, make available a buch of easy to use macros. So, coming back to your program, lets start by making those DigitalOuts go faster. I'll add comments into the code to show the changes.

#include "mbed.h"
#include "TextLCD.h"
#include "RotaryEncoder.h"
#include "IOMacros.h"

Ticker timer;
Timer t;
InterruptIn event(p10);
DigitalOut flop(p22);
DigitalOut led(LED1);
volatile int divider, newdiv;
volatile int count;

TextLCD lcd(p15, p16, p17, p18, p19, p20, TextLCD::LCD16x1);

RotaryEncoder re(p12, p11, 2, 500, 50);

void atint() {
    t.start();
    count++;
    if (count == divider/2) {
        p22_TOGGLE; // Use macro instead of flop = !flop;
    }
    if (count >= divider) {
        count = 0;
        p22_TOGGLE; // Use macro instead of flop = !flop;
    }
}
void attim() {
    newdiv = re.getVal();
    LED1_TOGGLE; // Use macro instead of led =!led;
    if (newdiv != divider) {
        divider = newdiv;
        lcd.cls();
        lcd.printf("Teiler: %d     ", divider);
    }
}


int main() {
    count = 0;
    divider = 50;
    lcd.printf("Teiler: %d     ", divider);
    event.rise(&atint);
    timer.attach(&attim,0.1);
    while (1) {
    }
}

The next step is to remove that InterruptIn as again it suffers from the abstraction problem. Removing this is more involved but once you read through it it should be fairly obvious what we are doing.

#include "mbed.h"
#include "TextLCD.h"
#include "RotaryEncoder.h"
#include "IOMacros.h"

Ticker timer;
Timer t;
// InterruptIn event(p10);
DigitalOut flop(p22);
DigitalOut led(LED1);
volatile int divider, newdiv;
volatile int count;

TextLCD lcd(p15, p16, p17, p18, p19, p20, TextLCD::LCD16x1);

RotaryEncoder re(p12, p11, 2, 500, 50);

// Function prototype/forward declaration for ISR.
void atint();

/** EINT3_IRQHandler
 */
extern "C" void EINT3_IRQHandler __irq (void) {

    // The "event" is connected to pin p10 which is LPC1768 P0_1
    // so lets trap that and ignore all other GPIO interrupts.    
    // Test for IRQ on Port0.
    if (LPC_GPIOINT->IntStatus & 0x1) {
	// If P0_1/p10 rises, call atint()
        if (LPC_GPIOINT->IO0IntStatR & (1 << 1))  atint();
    }

    // Clear this and all other possible GPIO generated interrupts as they don't concern us.    
    LPC_GPIOINT->IO2IntClr = (LPC_GPIOINT->IO2IntStatR | LPC_GPIOINT->IO2IntStatF);
    LPC_GPIOINT->IO0IntClr = (LPC_GPIOINT->IO0IntStatR | LPC_GPIOINT->IO0IntStatF);
}

void event_irq_init(void) {
    // Use macro to set p10 as an input.
    p10_AS_INPUT; 
    // Enable P0_1/p10 for rising edge interrupt generation.
    LPC_GPIOINT->IO0IntEnR |= (1UL << 1);
    // Enable the interrupt.
    NVIC_EnableIRQ(EINT3_IRQn);
}

void atint() {
    t.start();
    count++;
    if (count == divider/2) {
        p22_TOGGLE; // Use macro instead of flop = !flop;
    }
    if (count >= divider) {
        count = 0;
        p22_TOGGLE; // Use macro instead of flop = !flop;
    }
}
void attim() {
    newdiv = re.getVal();
    LED1_TOGGLE; // Use macro instead of led =!led;
    if (newdiv != divider) {
        divider = newdiv;
        lcd.cls();
        lcd.printf("Teiler: %d     ", divider);
    }
}


int main() {
    count = 0;
    divider = 50;
    lcd.printf("Teiler: %d     ", divider);
    event_irq_init();
    // event.rise(&atint);  No need for this as we have our own ISR to cal atint().
    timer.attach(&attim,0.1);
    while (1) {
    }
}

Finally, just a word of advice. I haven't actually tried or compiled the above, I don't have your setup to test it, so treat it as "psuedo code" to work from as an example. The main point here is that when you are using a PIC @ 20MHz your code would probably look something similar to the above (ie rather less abstraction and more direct use of the peripherals). If you start using the Mbed in the same way as you would a PIC then it's time to start comparing things :)

You can take the abstraction further by writing you own Tickers and Timers. However, I don't think you'll see massive improvements in speed in that department. But you can always play as you like :)

Also, you can use NVIC_SetPriority() to ensure that your atint() ISR gets called with a higher priority than the attim() function. I suggest you have a search of the forums and Google on the vectored interrupt controller the LPC1768/Cortex-M3 uses.

Hope this helps. Let us know how you get on.

(ps, you may find "divider = divider >> 1" a little faster than divider/2. But then I would use unsigned int versions assuming you don't need negative numbers)

26 May 2011

One last piece of advice. When using other librarys (such as RotaryEncoder and TextLCD.h) it's always worth having a look in their code to see if there are conflicts. For example, it occured to me that RotaryEncoder might have used InterruptIns for it's pins. Turns out it doesn't but if it had your use of EINT3 ISR here would have broken that library (it wouldn't have worked). You have to be careful when you start doing "neat things" directly with the peripherals to ensure you don't break other things that previously worked fine.

I didn't look in the TextLCD but that maybe another area of conflict.

RotaryEncoder also uses DigitalIns. That's more abstraction (more time). You might like to "roll your own" using the macros. But then you loose the ability to have someone else maintain your library and fix bugs, etc. It's a trade off against ease of use and time you want to invest yourself into your project.

You may also find these two articles of use:-

http://mbed.org/users/AjK/notebook/regarding-interrupts-use-and-blocking

http://mbed.org/users/AjK/notebook/getting-closer-to-the-hardware

26 May 2011

Hi Andy, thanx for the quick reply!

and thank you for your detailed comments on my code, actually I was expecting that the abstraction of the hardware functions through the mbed library is responsible for the weaker performance, but i did not think it would slow down things by a factor of 10.

Anyway, I will implement your suggested changes and do some testing.

Yes, I have read your posts already, btw. they are written brilliantly and helped me a lot undstaning the issues involved with interrupts.

I agree with you introducing new components using also interrupts could interfere with the main interrupt using a small time frame of 10us. But in my case the routine attim() does not do any harm during normal operation except flashing a led. If you noticed the rest (lcd write) is only executed if the encoder changed the value.

For that instant I accept the encoder to interfere with the main interrupt. There should be no interference if the encoder is left as it is (no encoder interrupt -> no divider change -> no lcd update). Thats actually the running mode.

Just for interest: The initial program did not have the enconder nor the lcd, the timing on the Interuptin was the same, so I suspect the slow down is caused by the wrapping functions InterruptIn and DigitalOut themselves, as you mention in your reply.

Ok let's get hands on and do some further testing I'll let you know the results

26 May 2011

If you think about it, look at my code for ISR EINT3:-

    if (LPC_GPIOINT->IntStatus & 0x1) {
	// If P0_1/p10 rises, call atint()
        if (LPC_GPIOINT->IO0IntStatR & (1 << 1))  atint();
    }

Now, the Mbed library code isn't public but in order to do what it does it must do something similar to this pusedo code:-

   #include "FunctionPointer.h"
    
    FunctionPointer fp_r[2][32];
    FunctionPointer fp_f[2][32];
    uint32_t mask;
    
    /* Test for IRQ on Port0. */
    mask = 1;
    if (LPC_GPIOINT->IntStatus & 0x1) {
        for (int i = 0; i < 32; i++) {
            if (LPC_GPIOINT->IO0IntStatR & mask) {
                fp_r[0][i].call();                
                LPC_GPIOINT->IO0IntClr = mask;
            }
            if (LPC_GPIOINT->IO0IntStatF & mask) {
                fp_f[0][i].call();
                LPC_GPIOINT->IO0IntClr = mask;
            }
            mask = mask << 1
        }
    }

    /* Test for IRQ on Port2. */
    mask = 1;
    if (LPC_GPIOINT->IntStatus & 0x4) {
        for (int i = 0; i < 32; i++) {
            if (LPC_GPIOINT->IO2IntStatR & mask) {
                fp_r[1][i].call();                
                LPC_GPIOINT->IO2IntClr = mask;
            }
            if (LPC_GPIOINT->IO2IntStatF & mask) {
                fp_f[1][i].call();
                LPC_GPIOINT->IO2IntClr = mask;
            }
            mask = mask << 1
        }
    }

Now, that's not perfect, it's just an idea of how to get the sort of functionality InterruptIn does. They probably do something more efficient than this but at the of of the day when EINT3 fires you still have to look at the registers and then find if an InterruptIn PinName is bound to that. So in this case the abstraction can add a lot of time to the ISR.

In mine we look for a specific bit being set as we know where the "event" signal is connected (p10), don't care about any other bits and just go and look for it immediately. So hand crafting the code to your needs will always be to some degree faster than an abstracted version. It all depends on your needs. For the most part, the convenince of the library outweighs the time you spend yourself (and the potential for conflict with other librarys, as soon as you start doing hand coding like this you have to be on your guard, for example taking EINT3 probably makes Mbed's InterruptIn totally disfuctional and any librarys that use it, playing with TIMER3 breaks Mbed's wait API lib Ticker/Timer/wait(), etc etc. Librarys built using Mbed libs break as the house of cards fall down).

Swings and roundabouts. But once you start down the road of hand crafting the code at this level you just have to pay closer attention to detail and look out for the ripple effect in sub-librarys.

27 May 2011

Really brilliant how simple you describe things for a newbie like me! if you ever write a book about the mbed I will buy it immediately. Unfortunately I have not had time so far to test the modified version, but I am hooked now and can't wait to get back to play with the mbed.

29 May 2011

Hello Andy! :-)))

Your advice did the trick! With the custom irq routine I can go as high as 250 kHz for the input frequency! Thank you very much!

30 May 2011

Hi Thomas,

Given your original problem was:-

Thomas Lunzer wrote:

My problem: Actually up to input frequencies of about 70kHz everything works fine as predicted Above that up to closely 100kHz I seem to loose interrupts, aka the output frequency is lower than it should be. Above 100kHz the output suddenly drops to half the predicted frequency.

then 250kHz looks like and improvement! Well done :)

regards, Andy

24 Aug 2012

I'm wondering if there is any possiblity of using the ahrdware xcounters on the lpc chip. Reading its spec it seems to have the capability to count directly from inputs on several pins. Not sure where this is implemented though.

In the meantime I've been playing with counting freqeuency using interruptin too. I only need a fairly low 8-10kHz reading but require reasonable accuracy (+/-1Hz). I'm using the following code and find that as long as I don't try to include an lcd printf in the frequency determining step it generally goes ok However I have to update my lcd at a lower rate than I update the freqeuency reading. In case anyones interested here's the code.

Here's my code

#include "mbed.h"
#include "TextLCD.h"

InterruptIn button(p5);
DigitalOut led(LED1);
DigitalOut flash(LED4);

TextLCD lcd(p15, p16, p17, p18, p19, p20); // rs, e, d4-d7

Ticker counter;
Ticker display;

int count=0,frequency=0;

void countup()
{

    count++;
}
// do stuff every second - calc the freqeuency of the input signal and update global frequency variable.
void freq() {
    frequency=count;
    count=0;
}
// update the display - run once every 2 secs
void disp(){

        lcd.locate(0,1);
        lcd.printf("%d Hz ",frequency);
}

int main()
{
    button.rise(&countup);  // attach the address of the flip function to the rising edge

    counter.attach(&freq, 1.0);
    display.attach(&disp, 2);
    
    lcd.locate(0,0);
    lcd.printf("Frequency");
    while(1) {           // wait around, interrupts will interrupt this!
    
        

    }
}