Displaying PROGMEM bitmap indirectly

Overall question regarding displaying a bitmap stored in PROGMEM indirectly.

Situation:
As an example, I would have a character that can move up down left and right, 4 bitmaps defined in progmem.
But this character can be happy or sad, so I have two sets of 4 bitmaps.
What I want to achieve is set the character to be either happy or sad and the bitmaps to follow accodingly.

What I considered or tried:
I can hard code this in, eveytime I draw a bitmap I have a switch/case which selects the correct bitmap.
However, as the actual code is far more complex (bigger set and more bitmaps) this gets quite big and messy.
I feel this should not be the way to do this.

I have defined variabels outside PROGMEM and copied the bitmaps for whatever set is active.
This works -> but as before, I have quite a few bitmaps and lose massive amounts of RAM
Basically this is not an option due to RAM limitations

What I want to achieve but cant seem to figure out:
I would want to just set a pointer to the active set of bitmaps, this way I do not have to buffer anything and just "lose" the RAM for the pointers (which should not be a lot anyways right?)
So I'd have 4 pointer and I would change these pointers on the fly based on the status (happy or sad)
What I can't seem to figure out is how to point to a bitmap in PROGMEM, the output is basically a mess
I'm made a simple scetch which should show happy and sad twice, but clearly I'm not doing this right as the 2nd line of icons are just junk.

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#include <avr/pgmspace.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

static const unsigned char PROGMEM happy[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000
};

static const unsigned char PROGMEM sad[] PROGMEM = {
  B00000000, B11000000,
  B00000000, B11000000,
  B00000000, B11000000,
  B00000000, B11100000,
  B00000000, B11100000,
  B00000000, B11111000,
  B00000000, B11111111,
  B00000000, B10011111,
  B00000000, B11111100,
  B00000000, B01110000,
  B00000000, B10100000,
  B00000000, B11100000,
  B00000000, B11110000,
  B00000000, B11110000,
  B00000000, B01110000,
  B00000000, B00110000
};

unsigned char* bmp;

void setup() {
  Serial.begin(9600);
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;); // Don't proceed, loop forever
  }
  delay(1000);
  Serial.println(F("Started"));
  
  display.clearDisplay();
  //first draw the bitmaps to see if those are OK
  display.drawBitmap(0,0,happy, 16, 16, 1);
  display.drawBitmap(30,0,sad, 16, 16, 1);
  //And then to try and duplicate the same thing by using a pointer to these PROGMEM bitmaps
  *bmp = &happy;
  display.drawBitmap(0,30,bmp, 16, 16, 1);
  *bmp = &sad;
  display.drawBitmap(30,30,bmp, 16, 16, 1);
  display.display();
  Serial.println(F("Final"));
}

void loop() {
}

Which Arduino board do you use ? An Arduino Uno ?
Those Adafruit libraries require a lot of RAM. If you add, for example, a SD library, then the RAM is fully used.

I don't know the GFX library very well, but I think that the display.drawBitmap() only accepts PROGMEM data. Perhaps it also accepts data in RAM.

When the Arduino Uno has a pointer, it is always a 16-bit pointer to RAM. A pointer can never point to Flash memory.
The compiler can be told that a pointer should be treated as something special. In a function that special pointer can be recognized by the compiler and a function for PROGMEM can be called.

This is a pointer to RAM:

unsigned char* bmp;

The PROGMEM page shows how to make an array of pointers: PROGMEM - Arduino Reference.
Those are 'const' pointers of course.
I don't understand why you want a pointer in RAM that points to one of the bitmaps in PROGMEM.
It is possible, but then you get this: reinterpret_cast<const __FlashStringHelper *>

Can you put all the pointers to all the bitmaps in a array, just as the example with the text in the PROGMEM page ?
You can put that array in RAM or in PROGMEM.

I don't know the GFX library very well, but I think that the display.drawBitmap() only accepts PROGMEM data. Perhaps it also accepts data in RAM.

It is an overloaded function.
From adafruit_GFX.cpp

void Adafruit_GFX::drawRGBBitmap(int16_t x, int16_t y, const uint16_t bitmap[],
                                 const uint8_t mask[], int16_t w, int16_t h) {
  int16_t bw = (w + 7) / 8; // Bitmask scanline pad = whole byte
  uint8_t byte = 0;
  startWrite();
  for (int16_t j = 0; j < h; j++, y++) {
    for (int16_t i = 0; i < w; i++) {
      if (i & 7)
        byte <<= 1;
      else
        byte = pgm_read_byte(&mask[j * bw + i / 8]);
      if (byte & 0x80) {
        writePixel(x + i, y, pgm_read_word(&bitmap[j * w + i]));
      }
    }
  }
  endWrite();
}

//(..)

void Adafruit_GFX::drawRGBBitmap(int16_t x, int16_t y, uint16_t *bitmap,
                                 uint8_t *mask, int16_t w, int16_t h) {
  int16_t bw = (w + 7) / 8; // Bitmask scanline pad = whole byte
  uint8_t byte = 0;
  startWrite();
  for (int16_t j = 0; j < h; j++, y++) {
    for (int16_t i = 0; i < w; i++) {
      if (i & 7)
        byte <<= 1;
      else
        byte = mask[j * bw + i / 8];
      if (byte & 0x80) {
        writePixel(x + i, y, bitmap[j * w + i]);
      }
    }
  }
  endWrite();
}

When the Arduino Uno has a pointer, it is always a 16-bit pointer to RAM. A pointer can never point to Flash memory.
The compiler can be told that a pointer should be treated as something special. In a function that special pointer can be recognized by the compiler and a function for PROGMEM can be called.

So if you pass the pointer to a function that treats it as such (which the first one does) all should be fine.

First of all, thanks for taking the time to reply!

I am indeed using an Uno, in the end a Pro Mini but that does not make much difference.

Using an array like you mentioned should actually be a great solution, as long as both are in PROGMEM it's no issue that they are constants.

Ive looked at the example you linked, tried to incorporate it.
The test code now looks like the code below, however the display is currently displaying no second line. (Completely empty, no junk pixels, nothing)
It shows the two BMP's when addressed directly but won't show anything from the buffer.
Serial indicates we reach the end of the setup and I have no errors when compiling.

Any suggestions on what I'm missing?

const char *const bmp_table[] PROGMEM = {happy, sad};
unsigned char buff[32];

void setup() {
  Serial.begin(9600);
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;); // Don't proceed, loop forever
  }
  delay(1000);
  Serial.println(F("Started"));
  
  display.clearDisplay();
  display.drawBitmap(0,0,happy, 16, 16, 1);
  display.drawBitmap(30,0,sad, 16, 16, 1);
 
  strcpy_P(buff, (char *)pgm_read_word(&(bmp_table[0])));
  display.drawBitmap(0,30,buff, 16, 16, 1);
  strcpy_P(buff, (char *)pgm_read_word(&(bmp_table[1])));
  display.drawBitmap(30,30,buff, 16, 16, 1);
  display.display();

  Serial.println(F("Final"));
}

void loop() {
}

Wouldn't it be easier to store the bitmaps in a 2 dimensional array ? (as long as they are the same size )

Or just using a lookup table as the reference suggests

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#include <avr/pgmspace.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

const unsigned char happy[] PROGMEM =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000
};

const unsigned char sad[] PROGMEM = {
  B00000000, B11000000,
  B00000000, B11000000,
  B00000000, B11000000,
  B00000000, B11100000,
  B00000000, B11100000,
  B00000000, B11111000,
  B00000000, B11111111,
  B00000000, B10011111,
  B00000000, B11111100,
  B00000000, B01110000,
  B00000000, B10100000,
  B00000000, B11100000,
  B00000000, B11110000,
  B00000000, B11110000,
  B00000000, B01110000,
  B00000000, B00110000
};


const unsigned char * const bmp_table[] PROGMEM = {happy, sad};

void setup() {
  Serial.begin(9600);
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;); // Don't proceed, loop forever
  }
  delay(1000);
  Serial.println(F("Started"));
 
  display.clearDisplay();
  //first draw the bitmaps to see if those are OK
  display.drawBitmap(0,0,bmp_table[0], 16, 16, 1);
  display.drawBitmap(30,0,bmp_table[1], 16, 16, 1);


  display.display();
  Serial.println(F("Final"));
}

void loop() {
}

This compiles without issue, and i don't see why it shouldn't work.
You could even create a struct to hold the size as well.

Hi Deva,

This is exactly what I wanted to achieve, many thanks already!

Can I take this a step further?

Instead of:

display.drawBitmap(0,0,bmp_table[0], 16, 16, 1);

I would define:

const unsigned char * const character_1[] PROGMEM = {sad, happy};
const unsigned char * const character_2[] PROGMEM = {happy, sad};
const unsigned char * const bmp_table[] PROGMEM = {character_1, character_2};

And use:

display.drawBitmap(0,0,bmp_table[0][1], 16, 16, 1);

Unfortunately this give me a error on the definition of bmp_table:
cannot convert 'const unsigned char* const*' to 'const unsigned char* const' in initialization

This is exactly what I wanted to achieve, many thanks already!

Ah so that works ?!

Unfortunately this give me a error on the definition of bmp_table:

hmm yes but i think you can do it like thisconst unsigned char * const bmp_table[][] PROGMEM = {{sad, happy},{happy, sad}};
Or possibly like this :const unsigned char * const* const bmp_table[] PROGMEM = {character_1, character_2}; (that compiles. now it would be a pointer to a pointer to the bitmap, and all pointers are explicitly declared as constant.)

const unsigned char * const* const bmp_table[] PROGMEM = {character_1, character_2};

This one compiles, and works as intended.

I cannot express how glad I'm for the help today.
I want to try and figure stuff out myself but this has given me headaches for 2 days now.

Time to move forward!

I want to try and figure stuff out myself but this has given me headaches for 2 days now.

Oh i can relate to that, slowly slowly i am getting the hang of this pointer stuff. (and PROGMEM for added nerve wrecking)

Now we are on the subject, maybe someone can explain to me what is going on.

So I've defined the array and if I draw the bitmap it shows perfectly.

display.drawBitmap(0, 0, bmp_table[0][0], 16, 16, 1);

What I did was define a global variable for character and animation

byte char_num = 0;
byte animation = 0;

And then use these for the drawBitmap function

  display.drawBitmap(0, 0, bmp_table[char_num][animation], 16, 16, 1);
  display.display();

  delay(1000);
  animation ++;
  display.drawBitmap(30, 0, bmp_table[char_num][animation], 16, 16, 1);
  display.display();

  delay(1000);
  char_num ++;
  animation --;
 
  display.drawBitmap(0, 30, bmp_table[char_num][animation], 16, 16, 1);
  display.display();

  delay(1000);
  animation ++;
  display.drawBitmap(30, 30, bmp_table[char_num][animation], 16, 16, 1);
  display.display();

Everything that the screen shows is noise, does not look like an icon, it does not work.

But if I change it to define the variable inside the function it works perfectly

  byte c = char_num;
  byte a = animation;
  
  display.clearDisplay();
  
  display.drawBitmap(0, 0, bmp_table[c][a], 16, 16, 1);
  display.display();

  delay(1000);
  a ++;
  display.drawBitmap(30, 0, bmp_table[c][a], 16, 16, 1);
  display.display();

  delay(1000);
  c ++;
  a --;

  display.drawBitmap(0, 30, bmp_table[c][a], 16, 16, 1);
  display.display();

  delay(1000);
  a ++;
  display.drawBitmap(30, 30, bmp_table[c][a], 16, 16, 1);
  display.display();

  Serial.println(F("Final"));

I'd like to understand what is going on :S

I think the problem in your first sketch is this:

    *bmp = &happy;

I think what you wanted to do is:

    bmp = &happy;

'bmp' is a pointer. Your original line says "store the address of 'happy' to the place where 'bmp' points. What you wanted to do was "set the pointer 'bmp' to point to 'happy'"

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.