I have a rather complex application which controls the number of cups of coffee that are drawn. After different numbers the water tank has to be filled or the coffee grounds has to be discarded.
This application performs well for several years and now I want to add a fan to dry the coffee grounds to prevent mold, which now forms after several days.
The available space in flash is almost eaten up (around 100 bytes left after several loops of optimization).
Randomly bitmaps (smileys) are displayed, the bitmaps are 392 byte in size each, I have 7 of them.
They are stored as follows:
//const unsigned char Sm1Dimension[] PROGMEM = {56, 56};
const unsigned char Smiley1[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x00, 0x00,
0x03, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0xff, 0x00,
... (cut out several lines of bytes)
0xfe, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xc0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00
};
const unsigned char Smiley2[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x3f, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0xff, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x00,
... (cut out several lines of bytes)
0x0f, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xe0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
... and so on until
const unsigned char Smiley7[] PROGMEM = {...};
In the first approach I selected the to be displayed bitmap by means of a switch..case construct which works perfectly. The Adafruit GFX library accepts the pointers and displays the bitmaps as expected.
Below the part of working code:
switch(cSmileySelect) // select Smiley
{
case 1:
pSmileyAdresse = Smiley1;
break;
case 2:
pSmileyAdresse = Smiley2;
break;
case 3:
pSmileyAdresse = Smiley3;
break;
case 4:
pSmileyAdresse = Smiley4;
break;
case 5:
pSmileyAdresse = Smiley5;
break;
case 6:
pSmileyAdresse = Smiley6;
break;
case 7:
pSmileyAdresse = Smiley7;
break;
default:
pSmileyAdresse = Smiley1;
break;
}
// void drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t width, int16_t height, uint16_t color);
display.drawBitmap((128 - cSmileyBreite) / 2, 0 + (64 - cSmileyHoehe) / 2, pSmileyAdresse,
(unsigned int)cSmileyBreite, (unsigned int)cSmileyHoehe, WHITE);
After again experiencing memory shortage when adding the logic to drive a fan, I found, that this switch..case construct uses up plenty of flash.
I tried to replace the switch..case by means of mathematics to derive the pointer to the bitmap in flash, but I get no result.
My test are as so:
pSmileyAdresse = (uint8_t*)(uint16_t)((Smiley7) + (2352 - (((cSmileySelect - 1) % 7) * (sizeof(Smiley7)) )));
The magic number 2352 is the size of the complete array of 7 smileys, the bitmaps are stored in descending order (smiley7 at the lowest address).
My expectation was, adding the size of each Smiley bitmap should derive the address of the next smiley, but to no avail.
Next approach was, to build up an array of pointers to the smileys…
const unsigned char* Smileys[] = {
&Smiley1[0], &Smiley2[0], &Smiley3[0], &Smiley4[0], &Smiley5[0], &Smiley6[0], &Smiley7[0], &Smiley13_bw[0]
};
…and get the address of the selected smiley by this:
pSmileyAdresse = (const uint8_t*)(Smileys[cSmileySelect - 1][0]);
This might have worked, but this small addition to my code swells the code in flash by 3166 byte, busting the maximum by ~2000 byte.
Why this?
And lastly:
Is there a way of deriving the PROGMEM addresses of the smileys by simple mathematics, not busting the flash limit?
Thanks!
Harry S is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
7
void drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t width, int16_t height, uint16_t color);
AdafruitGFX offers two overloaded methods for drawBitmap()
. This particular method allows you to pass in a pointer to a bitmap
that is located in the RAM, as you can see from the source code, it doesn’t read the data from the PROGMEM, but simply get a byte with b = bitmap[j * byteWidth + i / 8];
. So if you are calling the method with the function signature, data need to be first copy into a ram buffer and calling the function with the pointer to ram buffer, obviously that would take up to the RAM memory of the size of Smileyx.
The correct API to call is the second overloaded method:
void Adafruit_GFX::drawBitmap(int16_t x, int16_t y, const uint8_t bitmap[], int16_t w, int16_t h, uint16_t color)
Noticed that the subtle difference is the const uint8_t bitmap[]
and the source code indicated that it read a byte directly from the PROGMEM with:
b = pgm_read_byte(&bitmap[j * byteWidth + i / 8]);
This would take only 1 byte of RAM to store the b
instead of the size of Smiley1. To use this method, all it needs is to directly pass in the Smiley1
as:
display.drawBitmap(x, y, Smiley1, w, h, color);
3
The initial question arose when I was on search for code optimization to free flash memory for further enhancing the program by additional functions. I back then found, that commenting out the switch…case construct, I used to get the adress of the to be displayed bitmap, frees a considerable amount of flash space.
But I missed the fact, that the now no longer referenced bitmaps are optimized away by the linker.
So I was mislead into thinking, the switch…case construct uses up so much space and has to be replaced by something more efficient.
In the end, supported by @Maximilian and @hcheung, I found, that every approach leads to the same memory shortage as the initial switch…case construct.
Finally: No solution but gain in knowledge 🙂
Harry S is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.