Записать struct в ЕЕПРОМ
- Войдите на сайт для отправки комментариев
Сб, 25/05/2019 - 05:34
Ребята, прошу помощи, весь мозг сломал. Есть такой код
#include <EEPROM.h>
#include "EEPROMAnything.h"
struct apartment_t {
byte number;
uint32_t tlfn[5];
uint32_t card[5];
bool inService;
} aprt;
void setup() {
Serial.begin(57600);
Serial.println(sizeof(aprt));
delay(2000);
for (int i = 0; i < 1024; i++) {
EEPROM.update(i, 255);
}
for (byte i = 0; i < 20; i++) { // Тут я набиваю поля какими-то данными
aprt.number = i + 1;
for (byte x = 0; x < 5; x++) {
aprt.tlfn[x] = 642000000 + x * i; // генерирую некое число, просто для того, чтобы что-то записать
aprt.card[x] = 100000000 + x * i; // то же самое, что и выше, просто генерация числа
}
EEPROM_writeAnything(aprtAddress(i), aprt);
Serial.println(aprtAddress(i));
}
for (byte i = 0; i < 20; i++) {
EEPROM_readAnything(aprtAddress(i), aprt);
Serial.print((String)aprt.number + ";");
for (byte x = 0; x < 5; x++) {
Serial.print((String)aprt.tlfn[x] + ";");
Serial.print((String)aprt.card[x] + ";");
}
Serial.println(aprt.inService == true ? "yes" : "no");
Serial.println(aprtAddress(i));
}
}
void loop() {
// put your main code here, to run repeatedly:
}
uint16_t aprtAddress(uint16_t num) {
return num * sizeof(aprt);
}
содержимое EEPROMAnything.h
#include <EEPROM.h>
#include <Arduino.h> // for type definitions
template <class T> int EEPROM_writeAnything(uint16_t ee, const T& value)
{
const byte* p = (const byte*)(const void*)&value;
unsigned int i;
for (i = 0; i < sizeof(value); i++)
EEPROM.write(ee++, *p++);
return i;
}
template <class T> int EEPROM_readAnything(uint16_t ee, T& value)
{
byte* p = (byte*)(void*)&value;
unsigned int i;
for (i = 0; i < sizeof(value); i++)
*p++ = EEPROM.read(ee++);
return i;
}
Проблема заключается в том, что при записи, все выводится правильно. А вот если очистку ЕЕПРОМ и запись закоментировать, то первые 10 записей читает кашу, а потом остальные нормально, числа в полях совпадают.
Второй день вылечить не могу. В чем моя ошибка?
unsigned int всюду заменил на uint16_t, потому как готовлюсь схожий код использовать с 24C512 и там таких записей дофига.
Кстати, если по ходу подскажите как организовать поиск по полю aprt.number и по массиву полей aprt.card[x] , я был бы очень признателен.
На глаз ничего особенного не заметил, кроме использования EEPROM с нулевого адреса. Раньше с этим была проблема, потом её производитель исправил, но мы не знаем исправили ли её китайские производители клонов, потому предпочитаю адреса меньше 16 не использовать.
Запустил Вашу программу и не разобрался в выоде :(
Переделал программу, чтобы вывод был более читабельным, заодно поставил не с нулевого адреса и выбросил EEPROMAnything - нафига это надо, когда тоже самое есть в штатной библиотеке, только лучше сделано?
В итоге получилась вот такая программа
#include <EEPROM.h> const uint16_t StartAddress = 16; struct apartment_t : public Printable { byte number; uint32_t tlfn[5]; uint32_t card[5]; bool inService; size_t printTo(Print& p) const { size_t res = p.print(".number=") + p.println(number); for (byte x = 0; x < 5; x++) { res += p.print(".tlfn[") + p.print(x) + p.print("]=") + p.print(tlfn[x]) + p.print("; "); res += p.print(".card[") + p.print(x) + p.print("]=") + p.println(card[x]); } res += p.print(".inService=") + p.println(inService ? "yes" : "no"); return res + p.print("---------------------"); } } aprt; uint16_t aprtAddress(const uint8_t num) { return StartAddress + num * sizeof(aprt); } void setup() { Serial.begin(57600); Serial.println(sizeof(aprt)); delay(2000); for (int i = 0; i < 1024; i++) EEPROM.update(i, 255); for (byte i = 0; i < 20; i++) { // Тут я набиваю поля какими-то данными aprt.number = i + 1; for (byte x = 0; x < 5; x++) { aprt.tlfn[x] = 642000000 + x * i; // генерирую некое число, просто для того, чтобы что-то записать aprt.card[x] = 100000000 + x * i; // то же самое, что и выше, просто генерация числа } EEPROM.put(aprtAddress(i), aprt); Serial.print("@ address = "); Serial.println(aprtAddress(i)); Serial.println(aprt); } Serial.println("======================================"); for (byte i = 0; i < 20; i++) { EEPROM.get(aprtAddress(i), aprt); Serial.print("@ address = "); Serial.println(aprtAddress(i)); Serial.println(aprt); } } void loop(void) {}Выдаёт во это (до "=======" - то, что записываем, а после - то, что читаем).
Проблемы не вижу. Если я чего-то не понял и она есть, поясните пожалуйста в чём она (укажите номера строк и суть проблемы).
И да, кстати, мозг не ломайте. Как поётся в классической песне: "есть только две вещи, способные сломать мозг нормальному парню" и ардуины среди этих вещеё нет :))))
На глаз ничего особенного не заметил, кроме использования EEPROM с нулевого адреса. Раньше с этим была проблема, потом её производитель исправил, но мы не знаем исправили ли её китайские производители клонов, потому предпочитаю адреса меньше 16 не использовать.
Первый раз слышу об этом, а что за проблема то была? Всегда использовал с 0го адреса флеши сторублёвых клонов с алиэкспресса, никогда проблем небыло и нет.
Первый раз слышу об этом, а что за проблема то была?
Вот с сайта производителя. Они описывают проблему и говорят, что в "Newer parts" они её решили.
На глаз ничего особенного не заметил, кроме использования EEPROM с нулевого адреса. Раньше с этим была проблема, потом её производитель исправил, но мы не знаем исправили ли её китайские производители клонов, потому предпочитаю адреса меньше 16 не использовать.
Запустил Вашу программу и не разобрался в выоде :(
Переделал программу, чтобы вывод был более читабельным, заодно поставил не с нулевого адреса и выбросил EEPROMAnything - нафига это надо, когда тоже самое есть в штатной библиотеке, только лучше сделано?
В итоге получилась вот такая программа
Печать в примере не самоцель, а только лишь для отладки. Спасибо конечно, почерпнул некоторые интересности для себя.
Похоже, что проблема не в коде. Мне попалась плата NANO с битым ЕЕПРОМ. (абсолютно новая, твою ж мать!)
Работает и мой код, и ваш. Странно то, что первые 60 байт пишутся нормально, потом покоцанные области, потом снова нормально. Первый раз такое встретил. ДВА ДНЯ ЖИЗНИ В ТОПКУ!!! Обидно шо ппц.
Может подскажите функциию, или библиотеку, которая могла бы с приемлемой скоростью искать совпадения входной переменной с полями aprt.card[x] и\или aprt.number?
Дело в том, что аналогичный код будет работать с 24С512 и искать придется между несколькими сотнями записей, соответственно тисячей полей aprt.card[x]
Вводных мало. Записи-то хоть отсортированные?
В смысле "отсортированные"?
Начальный адрес первой - известен, потом пишутся подряд, как в моем примере, общее количество таких записей фиксированно, то-есть тоже известно, длинна записи тоже известна.
Общая логика - RFID ридер читает карту, полученный 4-х байтный номер нужно сопоставить с записанными aprt.card[x] и открыть общую дверь. То есть нужно пробежаться по всем записям структуры, и по всем полям aprt.card[x] в них, и проверить совпадет ли пришедший номер с каким нибудь из записанных.
второй поиск - по введенному числу позиционироваться на запись с совпавши с числом aprt.number и вычитать в массив из него поля aprt.tlfn[x].
Нужен грамотный пример - как максимально эффективно, по скорости работы, организовать поиск в такой импровизированной БД. Не хотелось бы держать гостя под дверьми более секунды, пока ардуино будет лопатить несколько тисяч таких записей, соответственоо несколько тысяч записанных карт. Учитывая тот факт, что ОЗУ у нас очень мало, ничего вразумительного в голову особо не приходит.
В смысле "отсортированные"?
Нужен грамотный пример - как максимально эффективно, по скорости работы, организовать поиск
Чтобы максимально бстро искать данные нужно хранить не как попало, а отсортированными. ОТсортированные, это в смысле сначала идёт самый маленький, потом побольше, потом ещё поболье, а самый большой - последний.
Если Вы будете так хранить, то можно эффективно искать (гуглите "метод половинного деления") - время поиска пропорционально логарифму по основанию 2 от количества записей. Если же будете хранить как попало - то только линейный поиск (сравнивать с каждым по очереди) - время поиска пропорционально количеству записей..
Интересно, Atmel предполагал, что их МК будут в качестве серверов БД использовать?
Интересно, Atmel предполагал, что их МК будут в качестве серверов БД использовать?
:D Смешно, да...
Короче, по быстрому нарисовал такое
uint16_t searchCard(uint32_t _card) { uint16_t i = 0; while (i < countAprts) { EEPROM.get(aprtAddress(i), aprt); for (byte x = 0; x < countCards; x++) { if (aprt.card[x] == _card) { return aprt.number; } } i++; } return 0; }делает поиск в двадцати записах ~3мс, (это во внутренней ЕЕПРОМ) при количестве записей 20. Экстраполируя, предположу, что на 600 записей потратило бы около 90 мс. Вроде устаривает. (надеюсь на внешней ЕЕПРОМ скорость резко не упадет, в любом случае - реакция системы в +-500 мс кажется приемлемой).
Кстати, никто не знает, что работает быстрее while или for?
ПС. Битой ЕЕПРОМ оказалась у всех, последних купленных мной, НАНО. Покупал у локального продавца, не из Китая. В понедельник буду предъявлять. Но сам факт не радует ниразу.
UPDATE: Мда, на внешней ЕЕПРОМ 24С512, на частоте i2c в 400кГц (больше - микруха ЕЕПРОМ виснет) скорость поиска между 256 записями почти ровно 2 секунды. Не приемлемо. Буду думать.
Чтобы максимально бстро искать данные нужно хранить не как попало, а отсортированными. ОТсортированные, это в смысле сначала идёт самый маленький, потом побольше, потом ещё поболье, а самый большой - последний.
Если Вы будете так хранить, то можно эффективно искать (гуглите "метод половинного деления") - время поиска пропорционально логарифму по основанию 2 от количества записей. Если же будете хранить как попало - то только линейный поиск (сравнивать с каждым по очереди) - время поиска пропорционально количеству записей..
Если массив статичный, то - да.
А если изменяемый, следует оценивать баланс:
Отсортированный: время вставки O(N), время поиска O(log(N)).
Неотсортированный: время вставки O(1), ремя поиска O(N).
Так что, возможно, целесобразно применять вместо массива, скажем, бинарное дерево, тогда и вставка и поиск O(log(N)).
Хотя, для 20 элементов ни одно, ни другое, ни тем более третье - не актуально.
UPDATE: Мда, на внешней ЕЕПРОМ 24С512, на частоте i2c в 400кГц (больше - микруха ЕЕПРОМ виснет) скорость поиска между 256 записями почти ровно 2 секунды. Не приемлемо. Буду думать.
Можно SPI-вариант попробовать:
https://www.st.com/en/memories/standard-spi-eeprom.html
ИлИ FRAM. Она читает/пишет на скорости интерфейса, как утверждает datasheet.
https://www.cypress.com/documentation/datasheets/fm25v20-2-mbit-256-k-8-serial-spi-f-ram
Хотя, для 20 элементов
У ТС мелькали слова про сотни и тысячи.
Короче, как оказалось, библиотека, которую я использовал для работы с AT24C512C, а именно AT24C1024.h (указана во многих плейэроунд на оф.сайте и во многих проектах, что странно) не умеет писать\читать данные в страничном режиме (современные ЕЕПРОМ серии 24Сххх поддерживают такой режим, размер внутренней старницы 64, для С256, до 128 для С512-1024).
В результате родил такое:
/* создать закладку EEPROM24Cxxx.h, подключить #include "EEPROM24Cxxx.h" Подтягивающие, к питанию, резисторы на А4-А5 лучше ставить 2кОм, а не 4к7, как в даташитах указано. */ #include <Arduino.h> // for type definitions #include <Wire.h> #define deviceAddress 0x50 // Адресс микросхемы по умолчанию, А0-А2 подключены к GND (или висят в воздухе, что не желательно). #define pageSize 128 // смотрите даташит на свою микросхему 24Схххх, размер страницы может быть как 64, так и 128 /* Физический буффер i2c шины в Ардуино равен 32 байта, и увеличить мне не удалось, даже исправляя библиотеку Wire, поэтому, воспользоваться прелестями чтения, 64 или 128 байт за раз, не получится. К тому же, увеличение буффера до 128 байт было бы слишком расточительно для ОЗУ ардуино. Поэтому, читать будем кусками по 32 байта. */ template <class T> void EEPROM_get(uint16_t eeaddress, T& value) { unsigned int num_bytes = sizeof(value); byte* p = (byte*)(void*)&value; byte countChank = num_bytes / 32; byte restChank = num_bytes % 32; uint16_t addressChank = 0; for (byte i = 0; i < countChank; i++) { addressChank = eeaddress + 32 * i; Wire.beginTransmission(deviceAddress); Wire.write((int)(addressChank >> 8)); Wire.write((int)(addressChank & 0xFF)); Wire.endTransmission(); Wire.requestFrom(deviceAddress, (num_bytes <= 32 ? num_bytes : 32)); while (Wire.available()) *p++ = Wire.read(); } if (restChank > 0) { Wire.beginTransmission(deviceAddress); Wire.write((int)((addressChank + 32) >> 8)); Wire.write((int)((addressChank + 32) & 0xFF)); Wire.endTransmission(); Wire.requestFrom(deviceAddress, restChank); while (Wire.available()) *p++ = Wire.read(); } } template <class T> void EEPROM_put(uint16_t eeaddress, const T& value) { const byte* p = (const byte*)(const void*)&value; byte i = 0, counter = 0; uint16_t address; byte page_space; byte page = 0; byte num_writes; uint16_t data_len = 0; byte first_write_size; byte last_write_size; byte write_size; // Calculate length of data data_len = sizeof(value); // Calculate space available in first page page_space = int(((eeaddress / pageSize) + 1) * pageSize) - eeaddress; // Calculate first write size if (page_space > 16) { first_write_size = page_space - ((page_space / 16) * 16); if (first_write_size == 0) { first_write_size = 16; } } else { first_write_size = page_space; } // calculate size of last write if (data_len > first_write_size) { last_write_size = (data_len - first_write_size) % 16; } // Calculate how many writes we need if (data_len > first_write_size) { num_writes = ((data_len - first_write_size) / 16) + 2; } else { num_writes = 1; } i = 0; address = eeaddress; for (page = 0; page < num_writes; page++) { if (page == 0) { write_size = first_write_size; } else if (page == (num_writes - 1)) { write_size = last_write_size; } else { write_size = 16; } Wire.beginTransmission(deviceAddress); Wire.write((int)((address) >> 8)); Wire.write((int)((address) & 0xFF)); counter = 0; do { Wire.write((byte) *p++); i++; counter++; } while ((counter < write_size)); Wire.endTransmission(); address += write_size; // увеличиваем адрес для следующего буфера delay(5); // задержка нужна для того, чтобы ЕЕПРОМ успела зарядить ячейки памяти (смотрите даташит своего устройства) } }а это проверочный скетч
#include "EEPROM24Cxxx.h" #include <EEPROM.h> #include <Wire.h> const byte countCards = 20; const byte countTlfns = 10; const uint16_t countAprts = 500; uint32_t cardToSearch = 100009462; unsigned long uTime; struct apartment_t { int16_t number; uint32_t tlfn[countTlfns]; uint32_t card[countCards]; bool inService; } aprt; void setup() { Wire.begin(); Serial.begin(115200); Wire.setClock(400000L); //Serial.println(sizeof(aprt)); //Serial.println(sizeof(conf)); delay(500); byte result = 0; Serial.print(F("Writing data to 24C512...")); uTime = millis(); ///* result = 0; for (uint16_t i = 0; i < countAprts; i++) { aprt.number = i + 1; for (byte x = 0; x < countTlfns; x++) { aprt.tlfn[x] = 642000000 + x * i; } for (byte y = 0; y < countCards; y++) { aprt.card[y] = 100000000 + y * i; } EEPROM_put(aprtAddress(i), aprt); result = i % 10; if (result == 0)Serial.print(F("#")); result = i % 250; if (result == 0)Serial.println(); } Serial.println(); Serial.println((String)F("Writed in mS ") + (String)(millis() - uTime)); while (!Serial.available()); // в активном окне монитора порта просто нажать Энтер для продолжения выполнения скетча //*/ for (uint16_t i = 0; i < countAprts; i++) { EEPROM_get(aprtAddress(i), aprt); Serial.print((String)aprt.number + ";"); for (byte x = 0; x < countTlfns; x++) { Serial.print("+" + (String)conf.preTel + (String)aprt.tlfn[x] + ";"); } for (byte y = 0; y < countCards; y++) { Serial.print((String)aprt.card[y] + ";"); } Serial.println(aprt.inService == true ? "yes" : "no"); } uTime = millis(); Serial.println((String)F("Apartment: ") + (String)searchCard(cardToSearch)); Serial.println((String)F("Found in mS ") + (String)(millis() - uTime)); Serial.println(sizeof(aprt)); } void loop() { // put your main code here, to run repeatedly: } uint16_t aprtAddress(uint16_t num) { return 100 + (num * (sizeof(aprt))); } uint16_t searchCard(uint32_t _card) { // Wire.setClock(800000L); // Это работает, переключает "на лету" частоту, НО! LCD1602 с I2C на частотах выше 400кГц виснет в усмерть, вешая шину напрочь. for (uint16_t i = 0; i < countAprts; i++) { EEPROM_get(aprtAddress(i), aprt); for (byte x = 0; x < countCards; x++) { if (aprt.card[x] == _card) { return aprt.number; } } } // Wire.setClock(400000L); // return 0; }В результате, скорость записи 500 структур, размером 123 байта, занимает около 24 секунд. А поиск отрабатівает за 2.2 секунды. Многовато таки. Можно ускорить в два раза установив частоту шины 800кГц или 1МГц, но дохнет LCD1602 c i2c модулем, увешивая шину намертво. Нужно в проекте экран менять. Но в общем, результат меня вроде устраивает. Если поднять частоту (нужно ставить резисторы ниже 2кОм - пилу искажает, у меня сейчас 3кОм стоят, и екран поменять) то реакция системы находится в приемлемых 1-1.2 секунды.
Вывод
Apartment: 499
Found in mS 2240
Может кто-то из грамотных глянет код и немного причешет (особенно запись, я этот код где-то на github выдернул и подправил, но похоже его можно причесать, если не ускорить, то хотя бы оптимизировать память), может еще есть идеи по увеличению скорости чтения, запись вроде в норме, и больше не разгониш. Или хотя бы оценку даст?
LCD высадить на SoftWire?
Идея хорошая, вот только пугает тот факт, что LCD_1602_RUS.h сильно завязана на Wire.h, много лопатить ( я уже молчу о том, что чел не удосужился украинские и беларуские буквы привинтить, что ИМХО не комильфо конечно, еще и это делать), а ковырять либы мне скила не хватает, пока надеюсь. Да и с этими экспериментами работа над проектом встала как вкопанная, пока я шишки набиваю и скилы качаю. Помог бы кто с экраном.
UPDATE: Украинские Її, Єє Іі и апостроф в либу добавил. Смотрю ног у меня только две и осталось, ги... как раз для софтверного Wire. Остановился на этой либе http://wiki.seeedstudio.com/Arduino_Software_I2C_user_guide/. Завтра пилить буду. Не знаю получится ли.
Ну так это для пользы дела шишки и потеря времени. У кого мало времени - просто покупает готовое.
У меня висит LCD на SoftWire. Ничего в этой ситуации драматичного кроме скорости нет )) Да и её хватает, если 30 FPS на дисплей не гнать.
ты еще учти, что у Wire внутренний буфер на чтение/запись 32 байта всего, включая адрес ячейки, начиная с которой ты хочешь писать.
Так учел ведь, в комментариях к коду даже прописал. Поэтому читаю фреймами по 32 байта в цикле, а потом остаток дочитываю, если он есть, а пишу фреймами по 16 байт. Точнее не пишу, а набиваю ЕЕПРОМку байтами до размера ее страницы памяти, а потом разом всю страницу и пишу.