I'm using the DuinoWitchery LCD library (https://github.com/duinoWitchery/hd44780) in a PlatformIO Arduino project with CLion.
The following code works if I stick it in main.cpp:
// near top of class...
const PROGMEM uint8_t decimalDigit[10][8] = {
{0x07, 0x05, 0x05, 0x05, 0x07, 0x18, 0x18}, // .0
{0x02, 0x02, 0x02, 0x02, 0x02, 0x18, 0x18}, // .1
{0x07, 0x01, 0x07, 0x04, 0x07, 0x18, 0x18}, // .2
{0x07, 0x01, 0x07, 0x01, 0x07, 0x18, 0x18}, // .3
{0x5, 0x05, 0x07, 0x01, 0x01, 0x18, 0x18}, // .4
{0x07, 0x04, 0x06, 0x01, 0x06, 0x00, 0x18, 0x18}, // .5
{4, 4, 7, 5, 7, 0, 0x18, 0x18}, // .6
{7, 1, 1, 1, 1, 0, 0x18, 0x18}, // .7
{7, 5, 7, 5, 7, 0, 0x18, 0x18}, // .8
{7, 5, 7, 1, 1, 0, 0x18, 0x18}, // .9
};
hd44780_I2Cexp lcd(0x27);
// ... snip ...
void setup() {
for (int x=0; x<8; x++)
lcd.createChar((uint8_t)x, decimalDigit[x]);
// ...snip...
}
... but if I move the code into another class, like:
Renderer.h
class Renderer : BaseRenderer {
public:
virtual void renderTo(hd44780* lcd) override;
// ... snip ...
private:
const PROGMEM uint8_t decimalDigit[10][8] = {
{0x07, 0x05, 0x05, 0x05, 0x07, 0x18, 0x18}, // .0
{0x02, 0x02, 0x02, 0x02, 0x02, 0x18, 0x18}, // .1
{0x07, 0x01, 0x07, 0x04, 0x07, 0x18, 0x18}, // .2
{0x07, 0x01, 0x07, 0x01, 0x07, 0x18, 0x18}, // .3
{0x5, 0x05, 0x07, 0x01, 0x01, 0x18, 0x18}, // .4
{0x07, 0x04, 0x06, 0x01, 0x06, 0x00, 0x18, 0x18}, // .5
{4, 4, 7, 5, 7, 0, 0x18, 0x18}, // .6
{7, 1, 1, 1, 1, 0, 0x18, 0x18}, // .7
{7, 5, 7, 5, 7, 0, 0x18, 0x18}, // .8
{7, 5, 7, 1, 1, 0, 0x18, 0x18}, // .9
};
}
Renderer.cpp
void Renderer::renderTo(hd44780lcd* lcd) {
for (int x=0; x<8; x++)
lcd->createChar((uint8_t)x, decimalDigit[x]);
// ...snip...
}
... the custom characters end up with garbage data.
I think I might be breaking some esoteric rule that governs where you're allowed to put a const PROGMEM uint8_t[]
, and causing the array declared in Renderer.h to end up in SRAM. But I'm not sure how to confirm it OR fix it.
note: the for/next loop to create the chars is just for the sake of illustration & to demonstrate that something that works in main.setup() fails in Renderer.renderTo(). In real life, the sequence of characters getting rendered to the LCD owns one of the 8 custom characters & redefines it as necessary to be the decimal point and tenths value.
2 Answers 2
The PROGMEM
qualifier can only be applied to statically-allocated
constant data. When you move the decimalDigit
array inside the class,
it becomes a data member, i.e. you have one copy of the array per
instance... in RAM. In this case, PROGMEM
does not work. My compiler
says:
warning: ‘__progmem__’ attribute ignored [-Wattributes]
If you want to keep the array inside the class, it must be given the
static
qualifier. It then becomes statically-allocated class data (not
instance data):
// Renderer.h:
class Renderer : BaseRenderer {
public:
virtual void renderTo(hd44780* lcd) override;
// ... snip ...
private:
static const PROGMEM uint8_t decimalDigit[10][8];
};
// Renderer.cpp:
const PROGMEM uint8_t Renderer::decimalDigit[10][8] = {
{0x07, 0x05, 0x05, 0x05, 0x07, 0x18, 0x18}, // .0
{0x02, 0x02, 0x02, 0x02, 0x02, 0x18, 0x18}, // .1
// ... snip ...
};
Of course, putting the array within the class is not an obligation. If it is going to be private anyway, the outside world (the files that include Renderer.h) does not need to know about its existence.
OK, I ended up blindly stumbling over the solution, even though at this moment I have no idea why it actually works.
I moved the const PROGMEM uint8_t decimalDigit[10][8] = {...}
declaration from Renderer.h to the almost-top of Renderer.cpp, so it sits between #include "Renderer.h"
and constructor:
#include "Renderer.h"
const PROGMEM uint8_t decimalDigit[10][8] = {
{0x07, 0x05, 0x05, 0x05, 0x07, 0x18, 0x18}, // .0
{0x02, 0x02, 0x02, 0x02, 0x02, 0x18, 0x18}, // .1
{0x07, 0x01, 0x07, 0x04, 0x07, 0x18, 0x18}, // .2
{0x07, 0x01, 0x07, 0x01, 0x07, 0x18, 0x18}, // .3
{0x5, 0x05, 0x07, 0x01, 0x01, 0x18, 0x18}, // .4
{0x07, 0x04, 0x06, 0x01, 0x06, 0x00, 0x18, 0x18}, // .5
{4, 4, 7, 5, 7, 0, 0x18, 0x18}, // .6
{7, 1, 1, 1, 1, 0, 0x18, 0x18}, // .7
{7, 5, 7, 5, 7, 0, 0x18, 0x18}, // .8
{7, 5, 7, 1, 1, 0, 0x18, 0x18}, // .9
};
Renderer::Renderer(int col, int row, int customCharNumber) : BaseRenderer(col, row) {
assignCustomChar(customCharNumber);
}
For now, I just have to chalk it up as 'black magic', because some subsequent experiments have forced me to question and doubt everything I ever thought I understood about how #include directives work.
-
1That's not black magic. You just took it out of the class definition. If the class needs it as a member, then pass in a pointer or reference. As it is written now, the decimalDigit array is no longer a part of the class. It is existing at global scope.Delta_G– Delta_G2023年07月17日 03:15:34 +00:00Commented Jul 17, 2023 at 3:15
-
1It's nothing to do with black magic or #includes. An array of constants is, by definition, not going to change. Therefore there is no point in including it inside a class, unless marked as
static
, for scoping purposes.2023年07月17日 10:39:57 +00:00Commented Jul 17, 2023 at 10:39
static const
?