Search code examples
stringmemoryavravr-gccwinavr

Leaving a data array (Font) in FLASH - PROGMEM in AVR GCC


Ahhh, PROGMEM, pointers, pointers to pointers, addresses of pointers... My head boggles.

I have a data array for the font in question

const uint8_t dejaVuSans9ptBitmaps[] = 
{
    /* @0 ' ' (5 pixels wide) */
    0x00, /*          */
    0x00, /*          */
...

to which I have added PROGMEM

const uint8_t dejaVuSans9ptBitmaps[] PROGMEM =

This is referenced in another structure like so;

const FONT_INFO dejaVuSans9ptFontInfo = {
   13,
   ' ',
   '~',
   dejaVuSans9ptDescriptors, 
   dejaVuSans9ptBitmaps,
};

The structure is defined as;

typedef struct {
   const uint8_t           height;       
   const uint8_t           startChar;    
   const uint8_t           endChar;      
   const FONT_CHAR_INFO*   charInfo;    
   const uint8_t*          data;         
} FONT_INFO;

Am I correct in assuming this needs to change to;

typedef struct {
   const uint8_t           height;       
   const uint8_t           startChar;    
   const uint8_t           endChar;      
   const FONT_CHAR_INFO*   charInfo;    
   const PGM_P             data;         
} FONT_INFO;

When I do so, it complains that

warning: pointer targets in initialization differ in signedness

For this particular line in the FONT_INFO variable;

const FONT_INFO dejaVuSans9ptFontInfo = {
    13,
    ' ',
    '~',
    dejaVuSans9ptDescriptors, 
--> dejaVuSans9ptBitmaps, <--
};

It is then drawn using the function;

void drawString(uint16_t x, uint16_t y, uint16_t color, const FONT_INFO *fontInfo, char *str) {
    ...
    drawCharBitmap(currentX, y, color, &fontInfo->data[charOffset], charWidth, fontInfo->height);
    ...

Which finally draws the glyph;

void drawCharBitmap(const uint16_t xPixel, const uint16_t yPixel, uint16_t color, const uint8_t *glyph, uint8_t cols, uint8_t rows) {
   ...
      if (glyph[indexIntoGlyph] & (0X80)) drawPixel(currentX, currentY, color);
   ...

I am in over my head :/ Can anyone give me some direction? I have spent hours trying to use PGM_P, and pgm_read_byte etc to no avail - I always get garbage on the screen.

Save me!


Solution

  • OK, I think I understand what is going on here.

    First, const uint8_t* data is a pointer to the data stored in PROGMEM.

    In the function void drawString(uint16_t x, uint16_t y, uint16_t color, const FONT_INFO *fontInfo, char *str) we pass a pointer to fontInfo.

    To continue, the following is important to understand;

    fontInfo.data
    (*ptr_to_fontInfo).data
    ptr_to_fontInfo->data
    

    are all the same. So ptr_to_fontInfo->data returns the data (not address)

    Then using the & operator, we pass the 'address of' this data to the next function

    drawCharBitmap(currentX, y, color, 
      &fontInfo->data[charOffset], charWidth, fontInfo->height)
    

    This address is stored in the declared pointer variable unint8_t *glyph here;

    void drawCharBitmap(const uint16_t xPixel, const uint16_t yPixel, 
      uint16_t color, const uint8_t *glyph, uint8_t cols, uint8_t rows)
    

    Keeping this in mind;

    int *ptr;
    int a;
    
    ptr = &a;
    

    Then glyph now points to the same address as fontInfo->data[charOffset].

    The next thing to know is;

    a[b] in C is just a fancy way for writing *(a + b)

    So glyph being a pointer, when used like this glyph[indexIntoGlyph], it is the same as *(glyph + indexIntoGlyph), and the dereferencing operator * means we get the data at that address.

    From there, we can use the pgm rules as wex described;

    If the variable is in PROGMEM, you use pgm_read_byte() as a replacement for the dereference operator *. For "normal" variables in RAM you can always write *(&a) instead of just a to return the value of the variable a; so to return a 8-bit wide variable from progmem you write pgm_read_byte(&x).

    Hope this explanation is correct and helps people (newbies like me!) understand it a bit better.