NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » Wrong results on Screen (Using arrays): NaN

March 11, 2010
by Shanytc
Shanytc's Avatar

Now... I'm well familiar with C/C++ programming, but this is too weird, I guess it is avr-gcc specific data types problem?

Consider the following:

int main(){
    float temps[7][2] = {
        {19.9,0.0 },
        {20.0,21.9},
        {22.0,23.9},
        {24.0,26.9},
        {27.0,28.9},
        {29.0,29.9},
        {30.0,0.0 }
    };

    for(i=0; i<7; i++){
        for(j=0; j<2; j++){
            fprintf_P(&lcd_stream, PSTR("%.2f,"), temps[i][j] );
        }
   }
}

I get on my screen: NaN,NaN,NaN,NaN,NaN,NaN...etc but when I do this:

fprintf_P(&lcd_stream, PSTR("%.2f,"), temps[0][0] );
fprintf_P(&lcd_stream, PSTR("%.2f,"), temps[0][1] );
...
...
fprintf_P(&lcd_stream, PSTR("%.2f,"), temps[6][1] )

it does print the data on the screen..

what gives?! :-)

March 11, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi Shanytc,

The problem you are experiencing stems from a quirk in embedded programming regarding statically initialized arrays. There is a pretty good discussion about it in this thread.

The basic issue is that an MCU is not like a computer, on a computer both the program and the local variables on the stack are held on RAM. On an MCU there is a separate memory space for the program (flash) and your stack and heap (RAM). You need to explicitly put your static arrays in the flash memory space and read from them by using PROGMEM. Or you can just build the array element by element in RAM using local variables.

Why it works as expected in the second case you described is actually a neat problem. It shouldn't work. I'm going to give you and others a little bit to think about it. See if you can come up with the answer.

Humberto

March 11, 2010
by bretm
bretm's Avatar

(I don't mean to side-track the discussion, but saying the MCU is not like a computer seems a bit misleading. It absolutely is a computer. It's just this kind of computer instead of this kind.)

Back on track--a third option which that thread mentions (instead of PROGMEM or using local variables) is to make sure the array data actually makes it into RAM in the first place, by adding "-j .data" to the avr-objcopy command in the makefile. But RAM is a much more limited commodity on Atmegas, and the compiler will add code to copy the array from program memory to data memory during initialization, so your array actually ends up in program memory AND data memory. So avoiding PROGMEM doesn't buy you much unless the values in the array will be changing during program execution.

March 12, 2010
by Shanytc
Shanytc's Avatar

Humberto, Thanks.

Well, I guess, at compile the address of temps[0][0] is known, and since the .data (if i'm correct) is also included during compile time (linker adds the address) than jumping to the .data section where the initialized array reside can be done.

However, when using temps[i][j], there is no known address using compile time, the MCU will just try to access &temps array but its address is unknown during run time.

I have tried:

float temps[7][2] PROGMEM = {
    {19.9,0.0 },
    {20.0,21.9},
    {22.0,23.9},
    {24.0,26.9},
    {27.0,28.9},
    {29.0,29.9},
    {30.0,0.0 }
};

However that did not work either.

adding the -j .data switch to the avr-objcopy, did do the trick in the end.

March 12, 2010
by bretm
bretm's Avatar

Ooops, I was trying to discourage -j .data. :-) In addition to PROGMEM you have to use pgm_read_float(&temps[i][j]) to get the data back out of the array.

March 12, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi Shanytc,

Yep, you got the right idea. You were seeing the behaviour when you hard coded the numbers because of a compiler optimization. When you hard coded temps[0][0], at compile time the compiler knows what that value is. Since it sees that it is a local variable, and doesn't see it changing within the function, it just relpaces temps[0][0] with 19.9. So in the compiled code there was no lookup into the array, it just

fprintf_P(&lcd_stream, PSTR("%.2f,"), 19.9 );

It is always a good thing to keep in mind that the compilers can take some freedoms while optimizing your code, and while they do a great job most of the time it can sometimes lead to some unexpected results.

I would also like to echo bretm's point. In an environment where memory is scarce you want to be aware of where your data is and where you are pulling it from. Explicitly using PROGMEM nad pgm_read good practice.

Happy coding! I look forward to reading more about your projects on the forums.

Humberto

March 13, 2010
by Shanytc
Shanytc's Avatar

Humberto,

Yeah everything is looking good now, however, it seems that when defining as a global (outside the main() ) as all PROGMEM variables should, they automatically become CONSTANTS ??? that means I can't change their values?! eh?

#include <avr/pgmspace.h>
double temps [7][2] PROGMEM = 
{
    {0.0 ,19.9},
    {20.0,21.9},
    {22.0,23.9},
    {24.0,26.9},
    {27.0,28.9},
    {29.0,29.9},
    {0.0 ,30.0 }
};

in main(){
    int select=1;
    double x;
    x=pgm_read_float(&(temps[(select-1)][0])); // works great!
    *(&(temps[(select-1)][1]))+=0.1; // **this for some reason hangs the cpu**
}

I don't remember reading in the docs (file:///C:/WinAVR-20090313/doc/avr-libc/avr-libc-user-manual/pgmspace.html) that global progmem variables become const

March 13, 2010
by bretm
bretm's Avatar

PROGMEM data is stored in Flash memory which is only updatable a page at a time, and only a limited number of times (at least 10,000). The compiler does not generate any code to write to it, and pgmspace.h only contains read functions.

If you want to update memory frequently you need to use SRAM, and use "-j .data", and omit PROGMEM, and don't use pgm_read_xxxx. SRAM can be updated a byte at a time and an unlimited number of times.

March 13, 2010
by Shanytc
Shanytc's Avatar

Thanks bretm,

I guess than that there isn't much use for a PROGMEM array if it can't be written to it normally :-) The best would to create a local array and copy from the PROGREM array into the local array for the first initialization (in case those bytes never change).

that seems to be working just fine!

March 13, 2010
by bretm
bretm's Avatar

You don't have to write any extra code to copy from Flash to SRAM on initialization. The compiler automatically includes the code to do that if you don't use PROGMEM. You just have to use "-j .data" to get it into the .hex file first.

There are many cases where you want read-only arrays. For example, the font data that turns characters into shapes for the LED scroller project, or sound samples, or conversion tables that would be too slow to calculate at run-time, etc.

December 22, 2010
by Icyhot
Icyhot's Avatar

I know there is something simple I'm missing here.

I declare a variable like this

int lookup[5][4] PROGMEM = { {0,0,512,1024}, {0,30,60,90}, {500,50,80,110}, {1500,70,100,130}, {2000,90,115,170}

and I was trying to call it with something like this

lookupvalue = pgm_read_float(&(lookup[row][col]));

nothing returns that is correct or useful.

I have tried removing -j .text as well as adding -j .text -j .data

before I go creating a headache for myself and changing more I thought I'd let someone else try to line me out.

December 22, 2010
by bretm
bretm's Avatar

You are using pgm_read_float which expects the address of a float. You have an array with int values so you need pgm_read_word or whatever the correct funtion is for two-byte integers.

December 22, 2010
by Icyhot
Icyhot's Avatar

thanks bretm!

Post a Reply

Please log in to post a reply.

Did you know that the Timer/Counter modules on the microcontroller can be configured to output a PWM (Pulse Width Modulation) signal? Learn more...