Инициализировать объект с константами PROGMEM

У меня есть класс со свойством const char *:

    class A
    {
    public:
        const PROGMEM char* text;
    };

    void setup()
    {
        // A a{"Hello World!"};
        // A a{PSTR("Hello World!")};

        A a;
        a.text = PSTR("Hello World!");

        Serial.begin(9600);
        delay(30);
        Serial.println(a.text);
    }

    void loop()
    {
    }

Я хотел бы сэкономить немного оперативной памяти, используя PROGMEM.

Как тогда инициализировать объект a?

Очевидно, что так быть не может:

    A a{PSTR("Hello World!")};

Это будет сделано:

    A a;
    a.text = PSTR("Hello World!");

Однако мне нужно передать строку конструктору.

, 👍0

Обсуждение

a.text = F("Hello World!"); делает то, что вы хотите?, @dandavis

@dandavis, это не так. вы упустили, что a.text не является const __FlashStringHelper*, а const char*, @zhekaus


2 ответа


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

2

Жаль, что gcc поддерживает квалификатор __flash только в режиме C, не в C++, поэтому вместо этого мы должны использовать PROGMEM. В отличие от __flash, который определяет переменную точно так же, как const, атрибут PROGMEM имеет только эффект при выделении места для переменной. После того, как распределение выполнено, компилятор забывает об атрибуте. В частности, заявление например

const PROGMEM char* text;

не выделяет флэш-память, поэтому генерирует предупреждение

warning: ‘__progmem__’ attribute ignored [-Wattributes]
     const PROGMEM char* text;
                         ^

Таким образом, вы можете забыть об атрибуте при объявлении указателя, так как нет такой вещи, как «указатель на PROGMEM». Вы просто используете const char * вместо этого.

Теперь вторая проблема заключается в том, что Serial.println() что касается указателя выше, это обычный const char *, поэтому он будет интерпретировать его как адрес в оперативной памяти и печатать мусор. Если ты хочешь Serial.println(), чтобы знать, что вы даете ему адрес во флэш-памяти, вы должен предоставить ему указатель const __FlashStringHelper*.

Вот решение, которое я предлагаю. Протестировано на Uno-совместимой плате:

class A
{
public:
    A(const char* s)
        : text(reinterpret_cast<const __FlashStringHelper *>(s)) {}
    const __FlashStringHelper* text;
};

void setup()
{
    A a{PSTR("Hello World!")};
    Serial.begin(9600);
    Serial.println(a.text);
}

void loop(){}

Обновление: увидев последнюю версию ответа Юрая, я должен сказать что я с ним согласен. Так как мы используем API Arduino, это делает больше смысла для конструктора принимать const __FlashStringHelper* и чтобы вызывающая сторона использовала макрос F().

,

ваше решение работает отлично! большое спасибо!, @zhekaus


3

const char* text; — это указатель на константу, а не указатель на константу (char * const text — это указатель на константу). Таким образом, вы можете назначить указатель на постоянный массив символов в const char* text; даже указатель на массив в PROGMEM.

Компилятор не знает разницы между указателем PROGMEM и указателем в SRAM. Вы должны правильно работать в коде с указателем на PROGMEM.

поэтому удалите PROGMEM из const PROGMEM char* text;


добавить конструктор для инициализации объекта.

A(const char* _text) {
  text = _text;
}

а затем

A a(PSTR("Hello World!"));

ИЗМЕНИТЬ:

вы можете использовать макрос F() Arduino и тип __FlashStringHelper, потому что он поддерживается Serial print и другими.

class A
{
public:
  A(const __FlashStringHelper* _text) {
    text = _text;
  }
  const __FlashStringHelper* text;
};

void setup()
{
    A a(F("Hello World!"));

    Serial.begin(9600);
    delay(30);
    Serial.println(a.text);
}

void loop()
{
}

Тип __FlashStringHelper позволяет отличить строки PROGMEM от массивов символов в SRAM..

,

Это не ответ на мой вопрос. Как хранить строки во флэш-памяти? Строки должны быть переданы конструктору., @zhekaus

непонятно, чего вы не знаете. я расширил ответ, @Juraj

Гм... Ваше решение не позволит использовать Serial.println. И добавление пользовательского конструктора тут ни при чем., @zhekaus

@zhekaus, да, твой вопрос объединил 3 проблемы. Эдгар получил их все, но верное решение состоит в том, чтобы использовать макрос F, а не PSTR, если это возможно. я расширил ответ, @Juraj