Pointer to program space

Go To Last Post
10 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Forum, 

 

First post but been reading for a long time. Trying to learn more about how to use the pgmspace function in AVR-GCC and have gotten stuck on the pointers. As a test i have written a small program that places that uses a function to place an array in program space and returns a pointer to the array. Another function then reads the value. I've read through the other topics on the subject on the forum but can't seem to understand the logic, some pointers in the right direction would be very much appriciated.

  1 #include <avr/io.h>
  2 #include <avr/pgmspace.h>
  3 #include <stdlib.h>
  4 
  5 PGM_P init_table();
  6 void read_table(PGM_P wt);
  7 
  8 PGM_P init_table()
  9 {
 10         static const uint8_t wt[] PROGMEM = {0x35,0x45,0x55};
 11         PGM_P adr = pgm_read_ptr(wt) ;
 12         return adr;
 13 }
 14 
 15 
 16 void read_table(PGM_P wt)
 17 {
 18         uint8_t value;
 19         uint8_t x;
 20         for(x = 0; x < 2; x++)
 21                 value = (uint8_t)pgm_read_word(wt);
 22 }
 23 
 24 int main()
 25 {
 26 
 27         PGM_P wt;
 28         wt = init_table();
 29         read_table(wt);
 30 }
 31 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You don't specify what needs clarifying, or the expected behaviour of your code, but there are a couple of issues:

 

Line 11: pgm_read_ptr() doesn't do what you think it does. As it stands, your code is effectively doing:

PGM_P adr = *(PGM_P *)wt;

You might as well replace lines 11 and 12 with:

return wt;

Not withstanding the fact that the compiler will probably optimise away the read_table() function's contents, it has a couple of issues.

  1. pgm_read_word() will read 16-bits, yet the array is comprised of 8-bit elements.
  2. You're not advancing the pointer for each iteration of the loop.

 

Program memory pointers, for the most part, can be treated like any other pointer. The only difference being you need to use the accessor macros from <pgmspace.h> to dereference them. In most cases, you don't even need to care about near versus far PGM space addresses; in my experience the toolchain does a good job of placing all data declared this way as close as possible to the start of program Flash. In addition, most AVRs don't have enough Flash for it even to be an issue. So 'near' pointers are usually all you need to care about.

 

Steve

 

Maverick Embedded Technologies Ltd. Home of wAVR and Maven.

wAVR: WiFi AVR ISP/PDI/uPDI Programmer.

Maven: WiFi ARM Cortex-M Debugger/Programmer

https://www.maverick-embedded.co...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Unless you are using C++ then PROGMEM and pgm_read_*() are now largely replaced by __flash so I would concentrate on learning about that if I were you. The advantage of the __flash modifier is that the need to LPM any such data is inherent so there is no need for explicit pointer dereference functions like pgm_read_*(). In effect:

#include <avr/pgmspace.h>

const uint8_t data[] PROGMEM = { 0x55, 0xAA, 0xB7, 0x3F };

int main(void) {
    uint8_t i;
    DDRB = 0xFF;
    while(1) {
        for (i = 0; i < 4; i++) {
            PORTB = pgm_read_byte(&data[i]);
        }
    }
}

now becomes the far easier to read:

const __flash uint8_t data[] = { 0x55, 0xAA, 0xB7, 0x3F };

int main(void) {
    uint8_t i;
    DDRB = 0xFF;
    while(1) {
        for (i = 0; i < 4; i++) {
            PORTB = data[i];
        }
    }
}

You may still use <avr/pgmspace.h> for some of the other functionality it delivers (like PSTR() etc) but you no longer need pgm_read_*().

 

As to "fixing" what you have now. Remember that (in AVR land) a pointer is nothing more than a 16 bit number. It can just be an integer value like 12345. Or it could be the address of something in RAM like &RAM_var. Or it could be the address of something in flash &PGM_var. Or it might even be the address of something in EEPROM such as &EE_var. But they are all just 16 bit numbers. 

 

The key thing is when you come to DEREFERNCE the pointer. You might use:

PORTB = *(uint8_t *)12345;
PORTB = *(&RAM_var); // and yes this is just like PORTB = RAM_var ;-)
PORTB = pgm_read_byte(&PGM_var);
PORTB = eeprom_read_byte(&EE_var);

but it could be the actual value in all of those is 12345. It's only when you read through the pointer that the memory space it is pointing to becomes relevant. As such you can just pass them round like any 16 bit pointer. For example you might find "void *" useful as it does not care what the pointer is actually pointing at. It's only at the point you then come to read through it that the method of dereference becomes important.

 

So this:

  8 PGM_P init_table()
  9 {
 10         static const uint8_t wt[] PROGMEM = {0x35,0x45,0x55};
 11         PGM_P adr = pgm_read_ptr(wt) ;
 12         return adr;
 13 }

could, for example, become:

  8 void * init_table()
  9 {
 10         static const uint8_t wt[] PROGMEM = {0x35,0x45,0x55};
 12         return &wt;
 13 }

Or, if you want to retain knowledge of the width of the type it points to but not the memory space you might use:

  8 const uint8_t * init_table()
  9 {
 10         static const uint8_t wt[] PROGMEM = {0x35,0x45,0x55};
 12         return &wt;
 13 }

Now I assume that:

 16 void read_table(PGM_P wt)
 17 {
 18         uint8_t value;
 19         uint8_t x;
 20         for(x = 0; x < 2; x++)
 21                 value = (uint8_t)pgm_read_word(wt);
 22 }

was actually supposed to be something more like:

 16 void read_table(PGM_P wt)
 17 {
 18         uint8_t value;
 19         uint8_t x;
 20         for(x = 0; x < 2; x++)
 21                 value = (uint8_t)pgm_read_word(wt[x]);
 22 }

(so that [x] indexes off the base pointer applying a 0, 1, 2 offset for each of the three reads?).  Also why are you using pgm_read_word() and then casting it down to a byte (uint8_t) ? Surely line 21 is supposed to be:

 21                 value = pgm_read_byte(wt[x]);

You don't need the cast because pgm_read_byte (the right function to use to read bytes) returns uint8_t anyway. This also just reads one location using LPM. When you used the _word function it was reading two bytes with two LPMs then you were discarding one of these with the cast.

 

In the code "wt" is only being treated as a pointer to flash rather then RAM or EEPROM because you happen to dereference it with pgm_read_byte(). So it could be passed around again as a completely anonymous type of pointer (void * / const uint8_t *) because it's the pgm_read_byte() that finally puts an interpretation of which address space it is pointing to

 16 void read_table(const uint8_t * wt)
 17 {
 18         uint8_t value;
 19         uint8_t x;
 20         for(x = 0; x < 2; x++)
 21                 value = pgm_read_byte(wt[x]);
 22 }

But, like I say, all this is fairly irrelevant as you should look at using __flash which has now been the replacement for PROGMEM for several years. One interesting thing you will find when you make the transition is that you will not only get __flash (effectively a pointer that "knows" it must be dereferenced using LPM) but you will also come across __memx which are not only "large pointers" (so go beyond the 16 bits that can only access 64K) but of their 24 bits just 23 are used for memory addressing (so 8MB range) but 1 bit is used to say whether the pointer is to RAM or __flash so you can then write routines that can handle either type of data. Imaging a string printing routine that does not care whether the string itself is in RAM or __flash for example!

 

The following is a potential implementation of your code in #1 using __flash:

  1 #include <avr/io.h>
  3 #include <stdlib.h>
  4 
  5 const __flash uint8_t * init_table();
  6 void read_table(const __flash uint8_t *);
  7 
  8 const __flash uint8_t * init_table()
  9 {
 10         static const __flash uint8_t wt[] = {0x35,0x45,0x55};
 12         return wt;
 13 }
 14 
 15 
 16 void read_table(const __flash uint8_t * wt)
 17 {
 18         volatile uint8_t value;
 19         uint8_t x;
 20         for(x = 0; x < 2; x++)
 21                 value = wt[x];
 22 }
 23 
 24 int main()
 25 {
 26 
 27         const __flash uint8_t * wt;
 28         wt = init_table();
 29         read_table(wt);
 30 }

If you get sick of typing:

const __flash uint8_t *

you could invent something like:

#define FLASH_P const __flash uint8_t *

to mimic the previous PGM_P.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Steve, Clawson, 

Thanks a lot! This was exactly what I needed, I now feel pointed in the right directions. Will review your comments in detail this evening and revise my code.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

Unless you are using C++ then PROGMEM and pgm_read_*() are now largely replaced by __flash

 

As someone once said, every day's a school day. Is this something specific to Atmel Studio? I don't see that modifier in avr-libc.

 

Steve

Maverick Embedded Technologies Ltd. Home of wAVR and Maven.

wAVR: WiFi AVR ISP/PDI/uPDI Programmer.

Maven: WiFi ARM Cortex-M Debugger/Programmer

https://www.maverick-embedded.co...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

scdoubleu wrote:
As someone once said, every day's a school day. Is this something specific to Atmel Studio? I don't see that modifier in avr-libc.
The reason it's not in AVR-LibC documentation (like the pgm stuff which is a messy "add on" in the C library to support AVR) is that __flash is now part of the compiler itself. So the place it is documented is in the compiler user manual:

 

https://gcc.gnu.org/onlinedocs/gcc/Named-Address-Spaces.html

 

Note the point it makes and the link it gives to:

 

https://gcc.gnu.org/onlinedocs/gcc/AVR-Built-in-Functions.html#AVR-Built-in-Functions

 

and see that __builtin_avr_flash_space() is useful in that it returns -1 if the __memx pointer contains a RAM address so this is how, in a routine using __memx that wants to handle flash data different to RAM data, you can tell whether the address is a RAM or a flash one. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

and see that __builtin_avr_flash_space() is useful in that it returns -1 if the __memx pointer contains a RAM address so this is how, in a routine using __memx that wants to handle flash data different to RAM data, you can tell whether the address is a RAM or a flash one. 

 

Oh, that's handy. Looks like it's been a feature for quite some time too. Guess I've been using the Old Ways for too long. Thanks for the tip!

 

Steve

Maverick Embedded Technologies Ltd. Home of wAVR and Maven.

wAVR: WiFi AVR ISP/PDI/uPDI Programmer.

Maven: WiFi ARM Cortex-M Debugger/Programmer

https://www.maverick-embedded.co...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Do note the point about C++ though. The team that develop avr-g++ (or rather just GNU g++ in general) will not admit non standard compliant extensions. So for C++ (and that includes "Arduino") everything should be done using the old PROGMEM approach.

 

(having said that you can have a project that mixes .c and .cpp and put all the LPM based stuff in .c to benefit from __flash/__memx).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Clawson, this was extremely helpful! 

 

A follow-up question: your example code compiled just fine but I can't seem to get get value to read in avr-gdb. 

Within the init_table function:

(gdb) print wt
$14 = "5EU"
(gdb) print &wt
$15 = (const uint8_t (*)[3]) 0x800178

within the main function

(gdb) print wt
$19 = (const uint8_t *) 0x800178 ""

Address is correct but according to avr-gdb the pointer is pointing at nothing. Is this something obvious I'm missing about how to work with __flash ? Might be just be an issue with the avr-gdb simulator as well.   

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Usually an 800000 offset means  "RAM" but I wonder if has simply assumed RAM and this really means flash address 0x178