trouble animating multiple image arrays on a microOLED (memory issue?)

First post here, apologies in advance for forum hygiene.

I have the SparkFun microOLED breakout and am trying to get 20 frames of looping animation on it.

Initially, I defined each frame as an array like this:

uint8_t frame1 [] = { 0x00, 0x00, ... 0x00 };

and it looks as I expect.

I can fit about 3 of these frames on the dynamic memory, which isn't enough for a good animation. So I read up on PROGMEM and moved the frame arrays to read-only program memory where everything fits:

const PROGMEM uint8_t frame1 [] = { 0x00, 0x00, ... 0x00 };

But when I try to display those frames, the screen just looks like a glitch.

I have tried unsuccessfully to figure out how to create a buffer so I can move those arrays in PROGMEM to dynamic memory. Or maybe I'm not understanding how to use SparkFun's MicroOLED library well? Or maybe that's not even the issue? Hoping someone here might point me the right way. I've attached my .ico here.

I tremendously appreciate your help!

MicroOLED_rotating3D.ino (52.2 KB)

1 Like

I believe your problem is that you are defining arrays in PROGMEM ok but not correctly accessing them.

oled.drawBitmap(animation[i]);//call the drawBitmap function and pass it the array from above

animation[] is in PROGMEM.

Look here PROGMEM - Arduino Reference under the title "Arrays of strings" to see an example.

Thanks for taking a look @6v6gt

I have combed over that PROGMEM documentation page and took another look on your recommendation. Still not getting the results I expect.

Specifically, I define each frame like this as a global variable:

const PROGMEM uint8_t frame1 [] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0xF0, 0xF8, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
  0xE0, 0xF0, 0xF8, 0xFC, 0xFC, 0xFF, 0xFF, 0xFF, 0xFE, 0xFC,
  0xF8, 0xF8, 0xE0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0,
  0xF0, 0x7C, 0x3E, 0x1F, 0x07, 0x03, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x07, 0x0F, 0x3F, 0x7E,
  0xF8, 0xF0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F,
  0xFF, 0xFF, 0xFF, 0x3F, 0x7F, 0xFF, 0xFF, 0xFF, 0x7F, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x7F,
  0xFF, 0xFF, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x38, 0x3F, 0x1F,
  0x00, 0x00, 0x00, 0x08, 0x3F, 0x3F, 0x30, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00
};

Then I follow the reference page and create an array of these frames as in the example code. I also create the buffer as instructed:

// create an array of each frame
const uint8_t *const animation[] PROGMEM = {
  frame1, frame2, frame3, frame4, frame5, frame6, frame7, frame8, frame9, frame10,
  frame11, frame12, frame13, frame14, frame15, frame16, frame17, frame18, frame19, frame20
};

// get the size of the animation array, 1-indexed
int animationSize = sizeof(animation) / sizeof(animation[0]) + 1;

uint8_t buffer[384];

Then this is the function showing how I try to call the variables back into dynamic memory. Using the syntax in the documentation.

void animateFunction()
{

  for (int i = 1; i <= animationSize; i++) {

    strcpy_P(buffer, (char *)pgm_read_word(&(animation[i])));
    
    oled.clear(ALL);//clear the screen before we draw our image
    oled.drawBitmap(buffer);//call the drawBitmap function and pass it the array from above
    oled.display();//display the image

      Serial.print("frame ");
      Serial.println(i);

    delay(setDelay);

  };
}

If anyone can spot a syntax error or some bigger issue, I would be extremely grateful!

Instead of getting into the complication of defining an array of pointers, just use a two dimensional array, it will be much easier to access from PROGMEM that way. Also, don't use strcpy when you don't have an actual null-terminated string, your frame arrays are better handled as bytes, and do the copying using a for loop.

void animateFunction() {
  for (int i = 0; i < animationSize; i++) {
    for (unsigned int j = 0; j < sizeof(animation[0]) / sizeof(animation[0][0]); j++) {
      buffer[j] = pgm_read_byte(&(animation[i][j]));
    }

    oled.clear(ALL);//clear the screen before we draw our image
    oled.drawBitmap(buffer);//call the drawBitmap function and pass it the array from above
    oled.display();//display the image

    Serial.print("frame ");
    Serial.println(i + 1);

    delay(setDelay);
  }
}

I don't have the actual hardware to test it on, but try the attached modified version of your sketch.

MicroOLED_rotating3D.ino (54.1 KB)

Likewise. I don't have your hardware. But I did create the animation on a 128x64 OLED.

  1. Code is easier to read if you let the dog see the rabbit.
  2. I moved the frame data to a local sketch tab called "frames.h"
  3. I created an array of pointers to Flash
  4. I calculated the number of elements in the array
  5. Your Frame data is actually organised as raw page data and not as a bitmap
#include <SPI.h>  // Include SPI if you're using SPI
//#include <SFE_MicroOLED.h>  // Include the SFE_MicroOLED library
#include <Adafruit_SSD1306.h>         // https://github.com/adafruit/Adafruit_SSD1306

//I have edited for MY display
#define PIN_RESET 8  // Connect RST to pin 9 (req. for SPI and I2C)
#define PIN_DC    9  // Connect DC to pin 8 (required for SPI)
#define PIN_CS    10 // Connect CS to pin 10 (required for SPI)
#define DC_JUMPER 0
// Also connect pin 13 to SCK and pin 11 to MOSI

//MicroOLED oled(PIN_RESET, PIN_DC, PIN_CS);
Adafruit_SSD1306 oled(128, 64, &SPI, PIN_DC, PIN_RESET, PIN_CS, 8000000);

// set animation frame rate (1000ms / frames-per-second)
int setDelay = 1000 / 30;

#include "frames.h"     //data in local sketch tab

// create an array of pointers to each frame
const uint8_t *animation[]  = {
    frame1, frame2, frame3, frame4, frame5, frame6, frame7, frame8, frame9, frame10,
    frame11, frame12, frame13, frame14, frame15, frame16, frame17, frame18, frame19, frame20
};

// get the number of frames in the animation array
int animationSize = sizeof(animation) / sizeof(animation[0]);

void setup()
{
    oled.begin();
    oled.display();
    delay(2000);//pause for the splash screen
}

void animateFunction()
{
    uint8_t *buf = oled.getBuffer();  //find OLED buffer in SRAM memory
    int w = 64, h = 48;  //your frame dimensions
    int ssd1306width = 128; //my OLED width.  your microOLED = 64
    // copy the raw page data from Flash to the buffer
    for (int i = 0; i < animationSize; i++) { //index is zero based
        for (int page = 0; page < h / 8; page++) {
            memcpy_P(buf + ssd1306width * page, animation[i] + w * page, w);
        }
        oled.display();//display the image
        delay(setDelay);
    }
}

I suggest that you study the statements and then try it with your pin wiring, 64x48 OLED and library.
Hint. Your library has a different name for finding the SRAM address:

    uint8_t *getScreenBuffer(void);

Then change the setDelay value to 1. This shows how fast you could run it.

David.

Thank you David and David! (My name is also David, by the way...)

I tried your edit, david_2018, and it works exactly as I had hoped! Your one-dimensional array definitely gets the job done. And your grasp of reading from program memory is what saved the day. Here's a quick .gif showing the animation working (the animation is much smoother than the gif suggests.)

Thanks also, david_prentice. Definitely cleaner to move the frame data to another tab, too. I'll do that.