Arduino Mega cannot run programs that need over 70kB of memory [solved]

Hi all,

I have been creating bitmaps on my colour OLED display with an Arduino Mega 2560 board. I recently encountered an problem where the OLED screen is appears completely black when the required program memory exceeds 70 kB.

The Arduino Mega 2560 has about 254 kB of flash memory so it should not have issues running the program. To determine whether the issue originates from the Arduino board or OLED display, I wrote a program shown below

#include <SPI.h>
#include <Adafruit_SSD1351.h>
#include <Adafruit_GFX.h>
#include "Drawing_Grayscale.h"  // This header file contains the PROGMEM arrays of all the bitmaps

Adafruit_SSD1351 ucg = Adafruit_SSD1351(128,128,&SPI,47,48);  // SCL = 52, SDA = 51, DC = 48, CS = 47

void setup() {
  ucg.begin();
  ucg.setRotation(1);
  pinMode(16,OUTPUT); // Blinking LED to verify if the issue is from the display or Arduino Mega 2560
}

void loop() {
  ucg.drawGrayscaleBitmap(0,0,landscape,128,128);
  digitalWrite(16,HIGH);
  delay(100);
  digitalWrite(16,LOW);
  ucg.drawGrayscaleBitmap(0,0,mono,128,128);
  delay(100);
  digitalWrite(16,HIGH);
  ucg.drawGrayscaleBitmap(0,0,lego,128,128);
  delay(100);
  digitalWrite(16,LOW);
  ucg.drawGrayscaleBitmap(0,0,gray,128,128);
  delay(100);
}

Output message:

Sketch uses 76634 bytes (30%) of program storage space. Maximum is 253952 bytes.
Global variables use 239 bytes (2%) of dynamic memory, leaving 7953 bytes for local variables. Maximum is 8192 bytes.

When the program is uploaded, the LED does not flash and the SSD1351 screen appears black. If I comment out one of the 'drawGrayscaleBitmap' commands, the program runs as expected and the LED flashes. What could be causing this unusual problem?

Check out this post: How to get the best out of this forum - Using Arduino / Installation & Troubleshooting - Arduino Forum It explains how to properly format a question.

Your screenshot of code is not useful to those trying to help.

Please read the above post and consider editing your post to include the actual code with code tags </>

Using more than 64K of PROGMEM on the mega gets a bit complex.
You have to explicitly tell the compiler to put your data in the upper part of program memory, so that it does not displace other data stored in PROGMEM, then use slightly different instruction to access the data because it is in the far section of PROGMEM instead of the near section. Since you are using the Adafruit_GFX library to draw the bitmap, it will also require writing a modified drayGrayscaleBitmap function that accesses the far program memory (this is actually easy to do).

Unfortunately I don't have time tonight to write some sample code, will try to get on here tomorrow evening if noone else has helped you out by then.

Does the drawGrayscaleBitmap() function work correctly with the SSD1351? That is a 16-bit color display, and the function takes an 8-bit monochrome bitmap.

I have tried the function. It does not work properly since it produces an image which has very low contrast and shades of blue. In a question that I asked, someone told me that my display was not designed to work with bitGrayscaleBitmap().

From the AVR library in the website: avr-libc: <avr/pgmspace.h>: Program Space Utilities, I can see that there are 'far' counterparts for each of the program memory access functions. Do I just add need the suffix '_far' to the source code in the modified function?

As a start. You also can't treat the address as a "char *". "byte *" or, indeed, any kind of pointer, since the address won't fit into 16 bits (the size of a 'pointer'). There is a type designed for 'far' pointers and it boils down to an unsigned long.

Ok, have a look at this simulation, compiled with the IDE the sketch uses 37% of the program memory of a Mega.

You do need to use the 'far' counterparts of the progmem access functions. Additionally pgm_get_far_address() has to be used at run-time to get the actual physical address of the data, because the compiler is limited to 16-bit addresses and cannot determine where in memory the data will be stored.

The most important part is that the compiler has to be told to store the data at the upper part of program memory, at memory locations above the sketch code and any other data stored in program memory. This is done by specifying a compiler attribute in the array declaration. To make it easier I define PROGMEM_FAR as the needed command:

#define PROGMEM_FAR __attribute__((section(".fini7")))

The compiler is also unable to properly use array indexes for data stored this way, so it is necessary to calculate the position of the array elements in the sketch.

1 Like

The test sketch I used for the Wokwi simulation

//rgbwheel bitmap from Adafruit_ImageReader_Library

#include "testimage.h"
#include <SPI.h>
#include <Adafruit_GFX.h>
#include "Adafruit_ILI9341.h"

#define TFT_DC 49
#define TFT_CS 53

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

uint_farptr_t rgbwheel0_farptr;
uint_farptr_t rgbwheel1_farptr;

void setup() {
  rgbwheel0_farptr = pgm_get_far_address(rgbwheel0);
  rgbwheel1_farptr = pgm_get_far_address(rgbwheel1);

  Serial.begin(9600);
  Serial.println("ILI9341 Test!");

  tft.begin();
  drawRGBBitmap_FP(0,  0, rgbwheel0_farptr, 128, 64);
  drawRGBBitmap_FP(0, 64, rgbwheel1_farptr, 128, 64);
  drawRGBBitmap_FP(56, 128, pgm_get_far_address(rgbwheel2), 128, 64);
  drawRGBBitmap_FP(56, 192, pgm_get_far_address(rgbwheel3), 128, 64);
  drawRGBBitmap_FP(112, 256, pgm_get_far_address(rgbwheel4), 128, 64);
}

void loop() {
}

//Modified function from Adafruit_GFX library

/**************************************************************************/
/*!
   @brief   Draw a PROGMEM-resident 16-bit image (RGB 5/6/5) at the specified
   (x,y) position. For 16-bit display devices; no color reduction performed.
    @param    x   Top left corner x coordinate
    @param    y   Top left corner y coordinate
    @param    bitmap  byte array with 16-bit color bitmap
    @param    w   Width of bitmap in pixels
    @param    h   Height of bitmap in pixels
*/
/**************************************************************************/
void drawRGBBitmap_FP(int16_t x, int16_t y, const uint_farptr_t bitmap,
                      int16_t w, int16_t h) {
  tft.startWrite();
  for (int16_t j = 0; j < h; j++, y++) {
    for (int16_t i = 0; i < w; i++) {
      tft.writePixel(x + i, y, pgm_read_word_far(bitmap + sizeof(uint16_t) * (j * w + i)));
    }
  }
  tft.endWrite();
}

And the start of the image array declarations:

#ifndef _TESTIMAGE_H_
#define _TESTIMAGE_H_

#define PROGMEM_FAR __attribute__((section(".fini7")))

const uint16_t rgbwheel0[128 * 64] PROGMEM_FAR = {
  0XC618, 0XC618, 0XC618, 0XC618, 0XC618, 0XC618, 0XC618, 0XC618,
  0XC618, 0XC618, 0XC618, 0XC618, 0XC618, 0XC618, 0XC618, 0XC618,
  0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF,
  0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF, 0XFFFF, 0XF7BE, 0XEF7D,
  0XAD75, 0XAD55, 0XA534, 0XA514, 0X9CF3, 0X9CD3, 0X94B2, 0X9492,

The images are RGB565 16-bit color 128x128 pixel bitmaps. Each image is split into two arrays, because a single array would need 32768 bytes, one byte more than the maximum allowable array size on a Mega. The first image is the original, the subsequent images are slightly altered so that it is obvious that they are different.

1 Like

The same question was asked two weeks ago: Arduino stucked- how to command to utilize of FULL MEM to display
It triggers an avalanche of problems. It is doable with your own code, but when a library is involved, then the library might not be able to deal with it.
An easy solution is to make your project with a newer board that has more memory.

I tried the example by david_2018, and yeah, that works :smiley:
If you only use it for bitmap images, then the solution by david_2018 is good :heavy_check_mark:

Thanks for writing the code for me. Did you make up this definition yourself? I was only able to find information about additional program memory from forums.

Is it true that the Arduino Due does not require reference to far program space? I have read a stack exchange forum where someone was asking how to adapt far PROGMEM code from the Mega to the Arduino Due.

See these "issues":

I ran across the reference for the attribute in the forum, the #define is just a common way of making it a bit more convenient to use.

I'm not familiar with the Due. You may want to move to a board with more memory, as you have seen it takes a considerable amount of memory to store images, you will quickly run out on a Mega. The Adafruit_ImageReader library can be used to display images stored on an SD card, that would be another possibility.

The reason for needing to use PROGMEM is that the processor being used has the flash memory (progmem) and dynamic memory (ram) in separate address spaces. This means that the same numeric memory address can refer to either a location in program memory or a location in ram, and different processor instructions are needed when accessing those memory locations. If the processor has the flash memory and dynamic memory in the same address space, then there is no need for PROGMEM because every memory address is unique and can be accessed without special instructions.

Please don't buy the Due. It has a few issues and it is little to none maintained.
The ESP32 is actively maintained by the manufacturer of the ESP. It is a "compatible" board, it is not a official Arduino board.
The Raspberry Pi Pico is also a good option.

1 Like

If it is true, then I will skip it. My biggest project so far is an animated image which used just 49% of the memory from the Arduino Mega 2560. I will probably keep using my current board until it reaches a point where my projects need nearly all of the available flash memory.

I am having a similar problem with a program that needs to load 80K of font definitions into PROGMEM, in 8K chunks. If I only load 64K, it works; if I load 72K or the full 80K, it won't run.
The fix seems simple, but now I have to dig through someone elses library functions and write a version that can work with FAR memory, assuming the source code is available.

My thanks to this forum, and the respondents to this thread, for the information and solution.

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