Problems with Memory Pointers to mbed NXP1768 Peripherals

20 Feb 2010

Can anybody provide some suggestions here? I am trying a simple read of the A/D registers and the mbed hangs. I have tried several iterations working with pointers and everything works except as shown below:

long * A2DControlReg    = (long *) 0x40034000; // no problem here

long lTmp = 12345678; // no problem here

*(long *)0x10000000 = 0xFACECAFE; // no problem here

lTmp = *A2DControlReg;  // mbed hangs here. Won't go on to any code after this statement.

Does anybody have any suggestions as to what the problem is here?

RSH

21 Feb 2010

Hi Russ,

I'd love to answer this one as it pretty much sums up the annoyance of programming micros and why we started building mbed!

Short answer: you didn't enable power to the ADC.

Long answer: that's just the answer.

I wanted to expand on this, as there are a number of traps this code demonstrates which I hope by pointing them out may save others time in the future, and also make it easier for people to be helped by others. Lets go on a journey...

First up, the code posted isn't what you actually observed the problem on; it is just a snippit. This makes reproduction of the problem harder. So for example, I copied the snippit in to a program with an led enabled after the hanging assignment, built and downloaded it to an mbed:

#include "mbed.h"

DigitalOut myled(LED1);

int main() {
    long * A2DControlReg    = (long *) 0x40034000; // no problem here
    long lTmp = 12345678; // no problem here
    *(long *)0x10000000 = 0xFACECAFE; // no problem here
    lTmp = *A2DControlReg;  // mbed hangs here. Won't go on to any code after this statement.

    while(1) {
        myled = !myled;
        wait(0.2);
    }
}
So what happens? Led starts flashing! As someone trying to quickly help out, the response would have been "works for me!".

The reality is of course it didn't *really* work, but the report was "Won't go after this statement", and it obvioulsy did as the LED came on, so the problem is dismissed. Not good for anyone. This is one reason we're trying to work out the best way to build in a publishing mechanism and have a consistent platform; it makes problems reproducable.

So what happened? Why doesn't it work for you and does for me? Well, it's something quite subtle. The hint is in the compiler messages:

Whilst only a warning and therefore your program compiles fine, this is a first clue. The compiler is saying the value ITmp is never used; i.e. no-one cares what it is. And because no-one cares, the compiler will naturally not bother ever calculating it. This is called Dead Code Elimination, and is just one of the ways a compiler can really help optimise code.

So now we see the differences in behaviour is caused by code *outside* the snippit posted. The code obvioulsy used ITmp at some later point, and hence that code executed; for me, the compiler didn't bother. This really sums up many "i'm stumped" bugs; the problem is often not where you think it is; posting just a snippit shows the bit you've sweated over for hours before looking for help. But if you've spent that long on it, the problem might be elsewhere!

So a related problem now shows itself; the register pointer definitions. What if I wrote to a register that was a UART FIFO? We'd never read it again, so ther compiler would just not bother. This is where the volatile keyword comes in. If you mark a variable as volatile, the compiler will always generate the associated memory access. It basically says, "something else which you can't see can change or be impacted by this variable". So in this example, we want to say A2DControlReg is a pointer to a volatile variable; lets try that:

#include "mbed.h"

DigitalOut myled(LED1);

int main() {
    volatile long * A2DControlReg    = (long *) 0x40034000; // no problem here
    long lTmp = 12345678; // no problem here
    *(long *)0x10000000 = 0xFACECAFE; // no problem here
    lTmp = *A2DControlReg;  // mbed hangs here. Won't go on to any code after this statement.

    while(1) {
        myled = !myled;
        wait(0.2);
    }
}
Ok, now it successfully hangs(!!). One thing that I just realised which made me smile is there is a nice comment on the line I modified :)

Register definitions are a real source of pain and mistakes; is the base address and offset correct, is it volatile, is the register size correct. I'd always recommend using the pre-defined CMSIS register structures wherever possible. For the LPC1768, see:

Here is an example of the same code using the CMSIS register definitions:

#include "mbed.h"

DigitalOut myled(LED1);

int main() {
    uint32_t lTmp = LPC_ADC->ADCR; 

    while(1) {
        myled = !myled;
        wait(0.2);
    }
}
Note, I also changed "long" to a more explicit stdint "uint32_t", as we know it is a 32-bit register value.

It still hangs correctly :)

Finally, the real problem. If we look at the ADC setup instructions for the LPC1768 (Chapter 29 of the LPC1768 user manual), it states we need to power up the device before we read from it. So lets power that up:

#include "mbed.h"

DigitalOut myled(LED1);

int main() {
    LPC_SC->PCONP |= (1 << 12);
    uint32_t lTmp = LPC_ADC->ADCR; 

    while(1) {
        myled = !myled;
        wait(0.2);
    }
}
And the flashing lights are back!

You should be good to go now Russ, and hopefully this breakdown might help many more of you spot some of the tricky mistakes that are common when you decide to deep dive down to the metal.

Simon