ПлатформаIO и константа PROGMEM

progmem platformio

Я использую библиотеку DuinoWitchery LCD (https://github.com/duinoWitchery/hd44780) в PlatformIO Arduino проект с CLion.

Следующий код будет работать, если я вставлю его в main.cpp:

// почти лучший в классе...
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);

// ... отрезок ...

void setup() {
    for (int x=0; x<8; x++)
       lcd.createChar((uint8_t)x, decimalDigit[x]);
    // ...отрезать...
}

... но если я перенесу код в другой класс, например:

Renderer.h

class Renderer : BaseRenderer {
public:
    virtual void renderTo(hd44780* lcd) override;
    // ... отрезок ...
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...
}

... пользовательские символы в конечном итоге оказываются мусорными данными.

Я думаю, что я, возможно, нарушаю какое-то эзотерическое правило, определяющее, где разрешено помещать const PROGMEM uint8_t[], и вызываю массив, объявленный в Renderer. h, чтобы оказаться в SRAM. Но я не знаю, как это подтвердить ИЛИ исправить.


примечание: цикл for/next для создания символов предназначен только для иллюстрации & чтобы продемонстрировать, что то, что работает в main.setup(), не работает в Renderer.renderTo(). В реальной жизни последовательность символов, отображаемых на ЖК-дисплее, содержит один из 8 пользовательских символов & переопределяет его по мере необходимости как десятичную точку и значение десятых долей.

, 👍2

Обсуждение

Поможет ли вам сделать элемент данных static const?, @Edgar Bonet

Я почти уверен, что первый работает только потому, что он все равно находится в оперативной памяти, потому что createChar не поддерживает PROGMEM., @KIIV

Я думаю, что библиотека DuinoWitchery LCD специально для этого подходит. Я буквально скопировал код, который работал в main.setup(), из примера кода библиотеки., @Bitbang3r

Подтверждение: hd44780::createChar(uint8_t location, const uint8_t*charmap) АБСОЛЮТНО рассматриваетcharmap как указатель на PROGMEM. Я воспользовался возможностью CLion найти источник метода и отредактировал его, вставив несколько операторов Serial.print() в createChar, чтобы посмотреть, что происходит. Значения, которые он получает с помощью pgm_read_byte(), определенно НЕ являются значениями в моем массиве. Итак, похоже, моя теория о том, что decimalDigit[10][8] попадает в SRAM, вероятно, верна. Я до сих пор понятия не имею, почему и как это исправить :-(, @Bitbang3r

Я также только что проверил, что массив decimalDigit[][] в Renderer.h ОПРЕДЕЛЕННО попадает в SRAM (временно взломав createChar(), чтобы принудительно рассматривать его как указатель на SRAM, а не как указатель на флэш-память). Итак... СЕЙЧАС большой вопрос: что мне нужно сделать, чтобы массив Renderer.h оказался в PROGMEM, а не в SRAM?, @Bitbang3r

Уберите стол из класса. Держите это в проге. Дайте классу ссылку или указатель на него., @Delta_G

Хорошо, я не заметил ссылку на библиотеку и нашел не ту. Вы определили его как константный член класса, поэтому теоретически вы можете изменить его в списке инициализаторов конструкторов, что означает, что это «код времени выполнения» и, следовательно, заканчивается в ОЗУ., @KIIV


2 ответа


Лучший ответ:

4

Квалификатор PROGMEM можно применять только к статически выделенным постоянные данные. Когда вы перемещаете массив decimalDigit внутри класса, он становится элементом данных, т.е. у вас есть одна копия массива на каждый экземпляр... в оперативной памяти. В этом случае PROGMEM не работает. Мой компилятор говорит:

warning: ‘__progmem__’ attribute ignored [-Wattributes]

Если вы хотите сохранить массив внутри класса, ему необходимо присвоить Квалификатор static. Затем они становятся статически выделенными данными класса (не данные экземпляра):

// Renderer.h:
class Renderer : BaseRenderer {
public:
    virtual void renderTo(hd44780* lcd) override;
    // ... отрезок ...
private:
    static const PROGMEM uint8_t decimalDigit[10][8];
};

// Рендерер.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
    // ... отрезок ...
};

Разумеется, размещение массива внутри класса не является обязательным. Если в любом случае это будет конфиденциально, внешний мир (файлы, которые включить Renderer.h) не обязательно знать о его существовании.

,

1

Хорошо, я вслепую наткнулся на решение, хотя на данный момент понятия не имею, почему оно на самом деле работает.

Я переместил объявление const PROGMEM uint8_t decimalDigit[10][8] = {... из Renderer.h в почти верхняя часть Renderer.cpp, поэтому он находится между #include "Renderer.h" и конструктором:

#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);
}

На данный момент мне просто приходится называть это «черной магией», потому что некоторые последующие эксперименты заставили меня подвергнуть сомнению и усомниться во всем, что я когда-либо думал, что понимаю, о том, как работают директивы #include.

,

Это не черная магия. Вы просто исключили это из определения класса. Если классу он нужен как член, передайте указатель или ссылку. Как сейчас написано, массив decimalDigit больше не является частью класса. Он существует в глобальном масштабе., @Delta_G

Это не имеет ничего общего с черной магией или #includes. Массив констант по определению не изменится. Поэтому нет смысла включать его в класс, если только он не помечен как «статический» для целей обзора., @Nick Gammon