Использование EEPROM в цикле ()

Я пытаюсь написать программу для Arduino Bluno Beetle, которая сохраняет строку в цикле. Вот мой код:

#include <EEPROM.h>
#include <EEPROMAnything.h>
struct config_t{
   String lastThing;
    } savedValues;
void setup(){
   EEPROM_readAnything(ADDR, savedValues);
   Serial.println(lastThing);
}
String readString="asd";
void loop() {
 //...
 lastThing=readString;
 EEPROM_writeAnything(ADDR, savedValues);
}

(Файл EEPROMAnything.h можно найти здесь: https://playground.arduino.cc /Code/EEPROMWriteAnything?action=sourceblock&num=1)

Код работает до 11 строки. Выдает там следующую ошибку:

'lastThing' was not declared in this scope

Кто-нибудь знает, как решить эту проблему?

, 👍0

Обсуждение

lastThing не является ни глобальной, ни локальной переменной. Это имя члена в структуре config_t. Что вы хотите сделать в цикле?, @Maximilian Gerhardt

Моя программа получает данные с мобильного телефона по блютусу и записывает их на OLED-экран. Мне нужна EEPROM, чтобы сохранить последнюю отправленную вещь, и при перезагрузке записать ее на экран., @András Ujvári

EEPROM имеет ограниченное количество операций записи, если вы будете записывать в нее в цикле, вы очень быстро исчерпаете все записи!, @esoterik

Ржу не могу. Помещение этой записи в цикл - быстрый способ исчерпать ваш eeprom., @PhillyNJ

«Текущая» версия библиотеки EEPROM (которая должна быть включена в IDE) имеет файл EEPROM.put, предназначенный для записи переменных любого типа. В качестве бонуса он записывает только в том случае, если данные отличаются, поэтому его можно использовать повторно, не изнашивая вашу EEPROM (настолько)., @user85471

См. https://www.arduino.cc/en/Tutorial/EEPROMPut., @user85471

он не будет записывать содержимое объекта String в EEPROM. объект String обрабатывает указатель на массив символов в куче. но он содержит только указатель., @Juraj


3 ответа


0

Очень большой отказ от ответственности: код в этом ответе не тестировался, и, кроме того, я никогда не играл с объектами String (мне не нравится динамическое размещение на arduino). Следовательно, в этом коде ожидаются ошибки. Если найдёте такие (например, не работает релокация, как я полагаю, или в функции чтения параметры должны передаваться другим способом) пишите в комментарии, чтобы я мог это исправить

РЕДАКТИРОВАТЬ: Поскольку были жалобы на это, я протестировал его и... Он работает. Функции чтения и записи работают, поэтому, в конце концов, кажется, что все исследования, которые я провел для этого ответа, были завершены и позволили мне правильно его реализовать


Как уже было сказано, lastThing — это не переменная, а поле в переменной savedValues. Таким образом, правильный синтаксис будет

lastThing=readString;

При этом ваш код не будет работать и имеет проблемы с производительностью. Во-первых, он использует метод EEPROM.write, который перезаписывает значение в eeprom, тем самым сокращая его жизнь, а затем вы сохраняете указатель на область памяти (4 байта), которую вы не хотите сохранять.

На мой взгляд, лучше сохранить "вручную":

// Записываем i в EEPROM, начиная с адреса addr.
// Возвращаем количество записанных байтов (2 если все ок)
uint16_t writeUInt16ToEeprom(uint16_t addr, uint16_t i)
{
    uint16_t currAddr = addr;

    // Только если места достаточно
    if ((currAddr + 2) <= E2END)
    {
        // Записываем 2 байта в eeprom
        EEPROM.update(currAddr++, i >> 8);
        EEPROM.update(currAddr++, i & 0xFF);
    }
    return (currAddr - addr);
}

// Записать s в EEPROM, начиная с адреса addr.
// Возвращаем количество записанных байтов
uint16_t writeStringToEeprom(uint16_t addr, String s)
{
    uint16_t currAddr = addr;

    uint16_t len = s.length();

    // Только если места достаточно
    if ((currAddr + len + 2) <= E2END)
    {
        // Записываем длину строки
        currAddr += writeUInt16ToEeprom(currAddr, len);

        // Записываем байты строки
        for (uint16_t i = 0; i < len; i++)
            EEPROM.update(currAddr++, s.charAt(i));
    }

    return (currAddr - addr);
}

// Читаем i из EEPROM, начиная с адреса addr.
// Возвращаем количество прочитанных байт (2 если все ок)
uint16_t readUInt16FromEeprom(uint16_t addr, uint16_t & i)
{
    uint16_t currAddr = addr;

    // Только если места достаточно
    if ((currAddr + 2) <= E2END)
    {
        i = EEPROM.read(currAddr++);
        i = i << 8 | EEPROM.read(currAddr++);
    }
    return (currAddr - addr);
}

// Читать s из EEPROM, начиная с адреса addr.
// Возвращаем количество прочитанных байтов
// S должен быть уже выделен при входе в функцию
uint16_t readStringFromEeprom(uint16_t addr, String & s)
{
    uint16_t currAddr = addr;

    uint16_t len = 0;
    s.remove(0);

    // Проверяем, достаточно ли места для длины
    if ((currAddr + 2) <= E2END)
        currAddr += readUInt16FromEeprom(currAddr, len);

    // Проверяем, достаточно ли места для всех символов
    if ((currAddr + len) <= E2END)
    {
        s.reserve(len); // Должен выделять только один раз, таким образом ускоряя

        for (uint16_t i = 0; i < len; i++)
            s.concat((char)(EEPROM.read(currAddr++)));
    }
    else
        currAddr = addr;

    return (currAddr - addr);
}


String lastThing;

void setup(){
    readStringFromEeprom(ADDR, lastThing);
    Serial.println(lastThing);
}

String readString="asd";
void loop() {
    //...
    lastThing=readString;
    writeStringToEeprom(ADDR, lastThing);
}

Примечание: вам также следует проверять значения, возвращаемые функциями, чтобы убедиться, что запись или чтение были успешными.

В данном случае я не использовал структуру, так как в вашем примере структура не используется. Если вам это действительно нужно, потому что, например, вы хотите сохранить еще и int, вы можете сделать что-то вроде этого:

struct config_t{
    String lastThing;
    uint16_t myIdx;
} savedValues;

uint16_t writeUInt16ToEeprom(uint16_t addr, uint16_t i)
...

uint16_t writeStringToEeprom(uint16_t addr, String s)
...

uint16_t readUInt16FromEeprom(uint16_t addr, uint16_t & i)
...

uint16_t readStringFromEeprom(uint16_t addr, String & s)
...

// Записать s в EEPROM, начиная с адреса addr.
// Возвращаем количество записанных байтов
uint16_t writeStructToEeprom(uint16_t addr, struct config_t s)
{
    uint16_t currAddr = addr;
    currAddr += writeStringToEeprom(currAddr, s.lastThing);
    currAddr += writeUInt16ToEeprom(currAddr, s.myIdx);
    return (currAddr - addr);
}

// Читать s из EEPROM, начиная с адреса addr.
// Возвращаем количество прочитанных байтов
// S должен быть уже выделен при входе в функцию
uint16_t readStructFromEeprom(uint16_t addr, struct config_t s)
{
    uint16_t currAddr = addr;

    uint16_t readVal = readStringFromEeprom(currAddr, s.lastThing);
    if (readVal == 0)
        return 0; 
    currAddr += readVal;

    uint16_t readVal = readUInt16FromEeprom(currAddr, s.myIdx);
    if (readVal == 0)
        return 0; 
    currAddr += readVal;

    return (currAddr - addr);
}

void setup(){
    readStructFromEeprom(ADDR, savedValues);
    Serial.println(lastThing);
}

String readString="asd";
void loop() {
    //...
    savedValues.lastThing = readString;
    savedValues.myIdx = 22;
    writeStructToEeprom(ADDR, savedValues);
}

Я думаю, что эту идею довольно легко понять. По сути, вы вручную пишете функцию для каждого типа, который хотите написать (например, writeUInt16ToEeprom), а затем используете эти «строительные блоки» для создания сложных функций (например, writeStructToEeprom). Для элементов, которые не имеют фиксированной ширины (Strings), вы сначала сохраняете длину, а затем количество байтов. Что я имею в виду под «фиксированной шириной», так это то, что во время компиляции вы знаете количество дочерних элементов. Например, у структуры есть два «дочерних элемента» (строка и целое число), а у строки неизвестное количество дочерних элементов (n символов).

Вы можете написать шаблонную функцию для всех целых чисел следующим образом:

// Записываем i в EEPROM, начиная с адреса addr.
// Возвращаем количество записанных байтов
template <class T> uint16_t writeIntegerToEeprom(uint16_t addr, T i)
{
    uint16_t currAddr = addr;

    // Только если места достаточно
    if ((currAddr + sizeof(T)) <= E2END)
    {
        // Записываем байты в eeprom
        for (uint8_t idx = 0; idx < sizeof(T); idx++)
        {
            EEPROM.update(currAddr++, (8*(i >> (sizeof(T) - 1 - idx))) & 0xFF);
        }
    }
    return (currAddr - addr);
}

// Читаем i из EEPROM, начиная с адреса addr.
// Возвращаем количество прочитанных байтов
template <class T> uint16_t readUInt16FromEeprom(uint16_t addr, T & i)
{
    uint16_t currAddr = addr;

    // Только если места достаточно
    if ((currAddr + sizeof(T)) <= E2END)
    {
        i = 0;
        for (uint8_t idx = 0; idx < sizeof(T); idx++)
        {
            i = i << 8 | EEPROM.read(currAddr++);
        }
    }
    return (currAddr - addr);
}

Вы можете использовать его для всех целочисленных типов, как вариант UInt16 выше. Не используйте его для классов или структур, так как в некоторых случаях (например, структура со строкой выше) он может не сохранить то, что вы хотите

,

но это в библиотеке EEPROIM как put() и get() для любой ссылки. https://github.com/arduino/ArduinoCore-avr/blob/master/libraries/EEPROM/src/EEPROM.h, @Juraj

@Juraj, вы говорите, что EEPROM put and get позволит поместить и получить структуру со строкой? Сомневаюсь. Вы можете использовать «положить» вместо самой последней реализации шаблона, но я не думаю, что остальная часть ответа должна измениться., @frarugi87

@downvoter: не могли бы вы объяснить, почему -1? Я думаю, что ответ соответствует теме вопроса (а не объявлению элемента) и, кроме того, решает основную проблему с реализацией OP, а именно тот факт, что он хочет сохранить строку, но на самом деле он этого не делает с его код, @frarugi87

Я минусовщик. Нет, вы не можете написать String с помощью put(), но ваше решение не элегантно и не проверено. И мы должны препятствовать использованию String., @Juraj

я разместил свой ответ, @Juraj

@Juraj Можете ли вы предложить, как я могу сделать его более «элегантным»? О непроверенном, вы проверяли мой код? Это работает? И, наконец, о струнах, они не "злые", это инструменты. Я согласен с тем, что по возможности их использование следует препятствовать (как я уже писал, я никогда не использовал их, поэтому я догадываюсь, как они работают, и проводил исследования, но у меня нет личного опыта), но иногда их нужно использовать., @frarugi87

«элегантным» будет эффективное использование библиотечных функций. вы предлагаете добавить в скетч избыточные функции, @Juraj

Давайте [продолжим это обсуждение в чате](https://chat.stackexchange.com/rooms/81536/discussion-between-frarugi87-and-juraj)., @frarugi87


1

Правильный синтаксис для ссылки на элемент структуры: [имя_структуры].[имя_члена]. Изменение lastThing в строках 8 & 13 в savedValues.lastThing исправляет «необъявленную» ошибку.

,

0

лучше вообще не использовать тип String и его нельзя сохранить в EEPROM. Вы можете сохранить копию внутренней c-строки строки с помощью strcpy(savedValues.lastThing, readString.c_str());

#include <EEPROM.h>

#define ADDR 512

struct config_t {
  char lastThing[32];
  int lastNum;
} savedValues;

void setup() {
  Serial.begin(115200);
  Serial.setTimeout(10);

  EEPROM.get(ADDR, savedValues);
  Serial.println(savedValues.lastThing);
  Serial.println(savedValues.lastNum);
}

void loop() {
  if (Serial.available()) {
    String readString = Serial.readStringUntil('|');
    int n = Serial.parseInt();

    strncpy(savedValues.lastThing, readString.c_str(), sizeof(savedValues.lastThing));
    savedValues.lastNum = n;
    EEPROM.put(ADDR, savedValues);

    // очистить остальную часть ввода (crlf)
    byte b;
    while (Serial.readBytes(&b, 1) > 0);
  }
}
,

В некоторых случаях необходимо использовать динамическое размещение. Лично я стараюсь этого чертовски избегать, так как это микроконтроллеры и у них мало памяти, но в некоторых случаях вы не можете. Затем вы добавили много допущений в код без выделения. Вы настолько хорошо знаете этот проект, что можете предположить, что строки разделены вертикальной чертой? Вы знаете, что строки никогда не превысят 32 символа, и вы настолько уверены, что даже не проверяете это? Вы знаете, что если пользователь напишет «Это действительно замечательный день :)», у вас будут всевозможные плохие поступки, верно?, @frarugi87

Я заменил strcpy на strncpy. Это тестовый скетч для EEPROM, а не удобная демонстрация обработки последовательного ввода., @Juraj