Использование 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
Кто-нибудь знает, как решить эту проблему?
@András Ujvári, 👍0
Обсуждение3 ответа
Очень большой отказ от ответственности: код в этом ответе не тестировался, и, кроме того, я никогда не играл с объектами 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
Правильный синтаксис для ссылки на элемент структуры: [имя_структуры].[имя_члена]. Изменение lastThing
в строках 8 & 13 в savedValues.lastThing
исправляет «необъявленную» ошибку.
лучше вообще не использовать тип 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
- Arduino EEPROM сохраняет старые данные после прошивки новой программой
- Как увеличить срок службы EEPROM?
- Запустить код один раз после программирования
- Сохранение EEPROM после программирования на Arduino uno
- Не получается читать из / писать в EEPROM
- Сохранить данные перед выключением
- Как прочитать данные, хранящиеся в EEPROM, хотя он выключен на основе простого кода, который я написал?
- Чтение и сравнение данных из Arduino Uno EEPROM
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