mbed totally freezes when using AnalogIn::read in an interrupt

09 Jan 2012

Hi everyone,

I was investigating a bug in my program and finally found it after lots of head scratching. It turns out the reason is that you cannot safely call read() on AnalogIn from an interrupt, such as a Ticker. When you do, and a main program happens to be inside the read() call as well, then mbed totally freezes - both the main program flow and all the mbed interrupts (the ones used by Ticker, Timeout, etc.) stop.

Here is the simplest program I've written to reproduce it:

#include "mbed.h"

AnalogIn in(p15);
DigitalOut led1(LED1), led2(LED2);
Ticker ticker1, ticker2;

void read_analog_in() {
    float v = in.read();
}

void test_led2() {
    led2 = !led2;
}

int main() {
    ticker1.attach(&read_analog_in, 0.001);
    ticker2.attach(&test_led2, 0.5);
    
    while(1) {
        led1 = 1;
        wait(0.05);
        led1 = 0;
        wait(0.05);
        read_analog_in();
    }
}

The programs makes LED1 blink once/sec from main program, and LED2 10 times/sec from ticker2. It will work for a couple of seconds and then blinking of both LEDs will stop. If you increase the ticker1 frequency (to say 0.0001) it will only work for a fraction of second after reset, and if you decrease it to 0.01 it will work much longer but it will eventually freeze as well after a minute or so. LED1 will always be off when it freezes, while LED2 state will be random. So clearly, main program is inside a call to read() while the ticker1 interrupt arrives, causes a second call to read() and it totally freezes mbed.

My question: Is it a bug or a feature? If so, is this documented anywhere and are there any other functions in mbed library with the same limitation (basically not being re-entrant)?

09 Jan 2012

I think its the "shared" call to read_analog_in(). First call in IRQ handler and second call in while loop. Why do you do this?

09 Jan 2012

If you declare 'float v' as a global 'volatile float v' (outside the interrupt) then you can use the value 'v' in your while loop and you do not need to call read_analog_in.

09 Jan 2012

Rene Greiner wrote:

I think its the "shared" call to read_analog_in(). First call in IRQ handler and second call in while loop. Why do you do this?

I've written that this way to reproduce the problem of the AnalogIn implementation in a simplest possible program. You can substitute the second call to read_analog_in, with say "printf("%f", in.read());" - the result will be the same. It does not matter where you store the result of in.read() or if you use it at all - here is the modified version:

#include "mbed.h"

AnalogIn in(p15);
DigitalOut led1(LED1), led2(LED2);
Ticker ticker1, ticker2;

void read_analog_in() {
    float v = in.read();
}

void test_led2() {
    led2 = !led2;
}

int main() {
    ticker1.attach(&read_analog_in, 0.001);
    ticker2.attach(&test_led2, 0.5);
    
    while(1) {
        led1 = 1;
        wait(0.05);
        led1 = 0;
        wait(0.05);
        printf("%f\n", in.read());
    }
}

In my actual application, I'm using AnalogIn in an relatively low frequency interrupt (50Hz) to convert read analog value to a PWM signal, while in my loop I'm reading it to send it over the USB serial. Occasionally, it caused freezes which were very frustrating and for a long time I couldn't understand the reason, which the program above shows.

For now my workaround is to read the AnalogIn in main loop as frequently as possible and store the results in a global variable. Then in an interrupt I use this last read value. This is less than ideal, because I'm doing lots of computation and serial output in main loop, which means it executes only at around 20Hz. This means the read analog value is up to 50ms out-of-date when I'm using it in an interrupt. I've also tried another approach to sample the AnalogIn regularly using a seperate Ticker (for example with 1ms period) - this turned out to be unacceptable, as the overhead of calling in.read() caused massively fluctuating delay in other tickers' handlers, which results in lots of jitter in some signals I generate in them.

09 Jan 2012

I think you did not understand how an interrupt works.

Imagine your while(1) loop calls in.read() and p15 is read out. At the same time your ticker interrupt fires and calls in.read() a second time while the first call did not return. If the timer has finished reading out p15 and returns, the state of your previous call to in.read() is not the same as it was before the ticker has fired. As Gert mentioned you should read out p15 only once within the irq handler and save the value within a volatile variable you can then print out in your main loop.