Const uint8_t asked, how to display an image which may vary?

Hello,

I want to program a TFT LCD screen but I have a problem displaying a bitmap that changes image.
I will try to explain better.

This function is used to display a bitmap:
drawBitmap(int, int, const uint8_t&, int, int, int, unsigned int)

I would like the image to be able to vary by having only this line of code because I need to display an image, always in the same place, the same size but whose content can change.

Since it requires a const uint8_t, I don't know how to do it.

I would like something that would look like this:

variable varImage = ImageLocatedInFlashMemoryWithPROGMEM; //possibility to change this variable afterwards

drawBitmap(30, 30,  varImage, 64, 64, BLACK,  WHITE)

How do I get that?

if your bitmap is in flash storage (PROGMEM attribute) then you can't change it. In order to change something it needs to be in SRAM and not declared as const

the fact that the function's signature says const uint8_t& does no mean you need to pass a const data reference but that the function won't modify it.

so define

uint8_t aBitmap[] = {151,146,242,146,151,0,255,0};  // 8x8 bitmap 

and call the display

drawBitmap(0, 0,  aBitmap, 8, 8, BLACK,  WHITE); // assuming the first parameters are (x,y) and the other (w,h)

Thank you.

It actually works.
But if I don't have enough memory in SRAM and want to store in FLASH memory, there is no way to do that?

A piece of code that refers for example directly to the const uint8_t PROGMEM?

if that works it means the drawBitmap() does not expect data to be in PROGMEM.

What library are you using? may be they offer a different function for data in PROGMEM.

When I do this it doesn't work, it shows a white square with black blurs:

uint8_t bitmapA[] = {151,146,242,146,151,0,255,0};
uint8_t actualBitmap[] = {bitmapA};
---------
tft.drawBitmap(0, 0,  actualBitmap, 8, 8, BLACK,  WHITE);

or
uint8_t actualBitmap[] = bitmapA;
the compiler writes:
initializer fails to determine size of 'actualBitmap'

I use all this libraries:

#include <avr/pgmspace.h>

#include <TouchScreen.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <MCUFRIEND_kbv.h>   // Hardware-specific library

Try

uint8_t* actualBitmap = bitmapA;

The drawBitmap function in the Adafruit library is overloaded to take a const uint8_t from PROGMEM, or a uint8_t that is not const from SRAM.

Indeed if that is the library, it is documented in source code

It works. Thank you.

So no way to store images in PROGMEM, call one in SRAM, and display it in a bitmap?

I don't understand what you mean by that.

  • You could have many bitmap in Flash/PROGMEM and your code could decide which one to display
  • You could have bitmap(s) in SRAM and your code could mess around with the pixels if you want to and display
  • you could select a bitmap in Flash/PROGMEM, load it up in SRAM, mess around with the pixels if you want to and display

everything is possible :slight_smile:

How can I do this?

if the data is not in far memory use memcpy_P to copy the content of the flash memory into SRAM. if it's in far memory us memcpy_PF())

const uint8_t aFlashBitmap[] PROGMEM = {151, 146, 242, 146, 151, 0, 255, 0}; // 8x8 bitmap
const size_t aFlashBitmapSize = sizeof aFlashBitmap;

uint8_t aSRAMBitmap[aFlashBitmapSize];

void setup() {
  Serial.begin(115200); Serial.println();
  
  // load bitmap from Flash into SRAM
  memcpy_P(aSRAMBitmap, aFlashBitmap, aFlashBitmapSize);

  // print the SRAM array
  for (auto &&v : aSRAMBitmap) {Serial.print(v); Serial.write(' ');}
  Serial.println();
}

void loop() {}

Could you explain why you need it in SRAM?

1 Like

Thank you so much ! It works as I want it!

Could you explain why you need it in SRAM?

If I use dozens of pages with the same layout and multiple images per page, the code will be cleaner.

Kind of like this:

// different images are stored with PROGMEM
const size_t aFlashBitmapSize = sizeof aFlashBitmap;

uint8_t actualLogo[aFlashBitmapSize];

int brand =1;
---------
if(brand==1){
     memcpy_P(actualLogo, logoA, aFlashBitmapSize);
     memcpy_P(actualLogo2, logoB, aFlashBitmapSize);
     //etc
}

tft.drawBitmap(30, 30,  actualLogo1, 64, 64, BLACK,  WHITE);
tft.drawBitmap(30, 150,  actualLogo2, 64, 64, BLACK,  WHITE);
//etc

if you don't modify the data, you don't need to copy it in memory, instead you could keep a pointer to the data

here is an example:

const uint8_t aFlashBitmap1[] PROGMEM = {151, 146, 242, 146, 151,  0, 170, 85}; // 8x8 bitmap
const uint8_t aFlashBitmap2[] PROGMEM = {174, 170,  74,  74,  78,  0, 170, 85}; // 8x8 bitmap
const size_t aFlashBitmapSize = sizeof aFlashBitmap1;

const uint8_t * logo;

void dumpData() {
  Serial.print("Current LOGO : ");
  for (size_t i = 0; i < aFlashBitmapSize; i++) {
    Serial.print(pgm_read_byte(logo + i));
    Serial.write(' ');
  }
  Serial.println();
}


void setup() {
  Serial.begin(115200); Serial.println();

  logo = aFlashBitmap1;
  dumpData();

  logo = aFlashBitmap2;
  dumpData();
}

void loop() {}

the logo pointer will be first pointing at aFlashBitmap1 and then at aFlashBitmap2.
➜ This way you don't have a memory impact besides keeping a few pointers around. It's way more efficient.

you could also have a 2D array indexed by the page number for the pointers, so this way you would just do

tft.drawBitmap(30, 30,  logos[brand][0], 64, 64, BLACK,  WHITE);
tft.drawBitmap(30, 150,  logos[brand][1], 64, 64, BLACK,  WHITE);`

and no messing around with the ifs

Thanks anyway, but I don't think it's my level .
Adapting a pointer for my case or creating arrays is still too difficult for me :cry: .

the pointer is as easy as copying the data...
instead of doing

if(brand==1){
     memcpy_P(actualLogo, logoA, aFlashBitmapSize);
     memcpy_P(actualLogo2, logoB, aFlashBitmapSize);
}

you just do

if(brand==1){
     actualLogo1 =  logoA;
     actualLogo2 =  logoB;
}

with actualLogo1 and actualLogo2 defined as pointers

const uint8_t * actualLogo1;
const uint8_t * actualLogo2;

instead of being arrays


regarding the 2D array, assume you have 10 bitmaps in EEPROM

const uint8_t aFlashBitmap01[] PROGMEM = {151, 146, 242, 146, 151,  0, 170, 85}; // 8x8 bitmap
const uint8_t aFlashBitmap02[] PROGMEM = {174, 170,  74,  74,  78,  0, 170, 85}; // 8x8 bitmap
const uint8_t aFlashBitmap03[] PROGMEM = {151, 146, 242, 146, 151,  0, 170, 85}; // 8x8 bitmap
const uint8_t aFlashBitmap04[] PROGMEM = {174, 170,  74,  74,  78,  0, 170, 85}; // 8x8 bitmap
const uint8_t aFlashBitmap05[] PROGMEM = {151, 146, 242, 146, 151,  0, 170, 85}; // 8x8 bitmap
const uint8_t aFlashBitmap06[] PROGMEM = {174, 170,  74,  74,  78,  0, 170, 85}; // 8x8 bitmap
const uint8_t aFlashBitmap07[] PROGMEM = {151, 146, 242, 146, 151,  0, 170, 85}; // 8x8 bitmap
const uint8_t aFlashBitmap08[] PROGMEM = {174, 170,  74,  74,  78,  0, 170, 85}; // 8x8 bitmap
const uint8_t aFlashBitmap09[] PROGMEM = {151, 146, 242, 146, 151,  0, 170, 85}; // 8x8 bitmap
const uint8_t aFlashBitmap10[] PROGMEM = {174, 170,  74,  74,  78,  0, 170, 85}; // 8x8 bitmap

and you have 5 pages, page 1 using bitmap 1 and 2, page 2 using bitmap 3 and 4 etc

you would define a 2D array of pointers like this

const uint8_t * pageLogos[][2] = {
  {aFlashBitmap01, aFlashBitmap02},  // page 1 (is at index 0)
  {aFlashBitmap03, aFlashBitmap04},  // page 2 (is at index 1
  {aFlashBitmap05, aFlashBitmap06},  // page 3 (is at index 2)
  {aFlashBitmap07, aFlashBitmap08},  // page 4 (is at index 3)
  {aFlashBitmap09, aFlashBitmap10},  // page 5 (is at index 4)
};

and if you have a pageIndex variable that can vary between 0 and 4, you would do

byte pageIndex = 3; // for example, so this is page #4
...
tft.drawBitmap(30,  30, pageLogos[pageIndex][0], 64, 64, BLACK,  WHITE);
tft.drawBitmap(30, 150, pageLogos[pageIndex][1], 64, 64, BLACK,  WHITE);
1 Like

Thank you so much !

I understood now. Your explanation and your code are perfect.
I will use the 2D array. It allows me to do exactly what I wanted :grinning:.
Thanks to you, I understand better how 2D arrays work and how to use pointers.
My code will be much cleaner and take up less space :white_check_mark:.

great :wink:
glad it helped

Why the double '&&'?

it's a c++ "detail" because I'm lazy and this way it works all the time regardless if you have an rvalue or lvalue and you don't have to think about it. :slight_smile: (in case the iterator's operator* for the type returns a proxy type by value like if the array was of vector type)

in the case here, it's a simple array just one & or not & at all would be enough (because we have a very simple type in the array)

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