GyverLibs / GyverDB

Простая база данных для Arduino
MIT License
7 stars 1 forks source link

Получение некорректных значений из поля БД #2

Open aleta2020 opened 3 days ago

aleta2020 commented 3 days ago

Здравствуйте! Столкнулся с проблемой: периодически при чтении полей БД, возвращаются "кривые" данные. Стал искать источник - удалил из скетча все лишнее. Пока зафиксировал проявление в виде скетча на основе примера из библиотеки Settings: Пример "выбросов" из монитора:

``` 14:34:23.535 -> kk::slider: 25 14:34:23.582 -> kk::slider: 1073933810 14:34:23.678 -> kk::slider: 70 14:37:02.767 -> kk::slider2: 20 14:37:02.815 -> kk::slider2: 1073933810 14:37:02.861 -> kk::slider2: 30 14:37:02.861 -> kk::slider2: 80 14:37:02.909 -> kk::slider2: 90 14:37:02.956 -> kk::slider2: 70 14:37:02.956 -> kk::slider2: 1073933810 14:37:03.051 -> kk::slider2: 10 ```

Скетч:

``` #include // Пример (на основе идущего к библиотеке Settings "demo_data"), показывающий "порчу" значений полей БД, привязанных к виджетам. // Чтобы проявилось, нужно интенсивно подвигать слайдером - в Serial появляются значения явно превышающие заданный диапазон. // Аналогичные "выбросы" проявляются и на других виджетах, но отловить их сложнее. // Ошибка проявляется на ESP32S2 (S2 mini) - тестировалось на нескольких платах из разных партий. // Вид в мониторе: /* 14:33:03.159 -> kk::slider2: 100 14:33:03.206 -> kk::slider2: 80 14:33:03.251 -> kk::slider2: 60 14:33:03.298 -> kk::slider2: 1073933810 14:33:03.392 -> kk::slider2: 30 */ // Тестировался скетч на ESP WROOM 32 и на WEMOS D1 mini - таких "выбросов" замечено не было // Тестировалось с питанием от USB и с внешним питанием +5В #define WIFI_SSID "aaaa" #define WIFI_PASS "aaaa" #ifdef ESP8266 #include #else #include #endif #include #include GyverDBFile db(&LittleFS, "/data2.db"); #include SettingsGyver sett("My Settings", &db); DB_KEYS( kk, slider, slider2, sel, conf, btn); bool cfm_f = false; void build(sets::Builder& b) { { sets::Group g(b, "database"); b.Slider(kk::slider, "slider:", 0, 100, 5); b.Slider(kk::slider2, "slider2:", 10, 100, 10); b.Select(kk::sel, "", "foo;bar;test"); if (b.Button(kk::btn)) Serial.println("btn 0"); } if (b.Button("Confirm")) cfm_f = true; if (b.Confirm(kk::conf, "Confirm")) { Serial.println(b.build.value); } // actions ----------- добавленный блок if (b.build.isAction()) { switch (b.build.id) { case kk::slider: Serial.print("kk::slider: "); Serial.println(db[kk::slider].toInt()); // <<<< - искажение проявляется здесь - пробовал и сырые данные выводить и преобразовывать к типам break; case kk::slider2: Serial.print("kk::slider2: "); Serial.println(db[kk::slider2].toInt()); // <<<< - искажение проявляется здесь - пробовал и сырые данные выводить и преобразовывать к типам break; } } } void update(sets::Updater& u) { if (cfm_f) { cfm_f = false; u.update(kk::conf); } } void setup() { Serial.begin(115200); Serial.println(); // ======== WIFI ======== // STA WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASS); uint8_t tries = 20; while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); if (!--tries) break; } Serial.println(); Serial.print("Connected: "); Serial.println(WiFi.localIP()); // ======== SETTINGS ======== sett.begin(); sett.onBuild(build); sett.onUpdate(update); // ======== DATABASE ======== #ifdef ESP32 LittleFS.begin(true); #else LittleFS.begin(); #endif db.begin(); db.init(kk::slider, 33); db.init(kk::slider2, 0); db.init(kk::sel, 1); } void loop() { sett.tick(); } ```

Тестировал на разных платах из разных партий:

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

db[kk::slider] - причем в таком виде по команде Serial.println(db[kk::slider]) на ESP32S2 выдается значение поля, а на D1 mini - всегда большое число (видимо адрес)... db[kk::slider].toInt() db[kk::slider].toString() db[kk::slider].toText()

Искажаются разные поля: отлавливал цифровые поля и bool.

Технические данные:

GyverLibs commented 3 days ago

Может есть какой нибудь минимальный пример, где чисто пишется и читается чисто бд с одной записью? S2 у меня нет, могу заказать

aleta2020 commented 3 days ago

извините, не понял, что значит "чисто пишется и читается чисто бд с одной записью"? Сделать циклическое чтение поля до "ошибки"?

GyverLibs commented 3 days ago

Нет, взять бд, записать в ячейку значение и увидеть что записался мусор

aleta2020 commented 3 days ago

В том-то и дело, что записывается и хранится то, что записывалось, а при чтении периодически происходит искажение значения. При повторном чтении прочитается, как правило, правильное значение. Вот такой скетч получился: в цикле читаю значение поля, проверяю на границы и вывожу в монитор, если выходит за диапазон.

``` /* Проверка корректности чтения полей из БД */ #include #include #include GyverDBFile db(&LittleFS, "/data2.db"); DB_KEYS( kk, slider, slider2, conf); uint32_t err = 0; // читаем сюда значение поля из БД int i = 0; // счетчик циклов const int ii = 20; // сколько строк ошибок выводить? void setup() { Serial.begin(115200); while (!Serial) { } delay(1000); Serial.println("\nПроверка корректности чтения полей из БД: выводим первые "+String(ii)+" строк ошибок"); // ======== DATABASE ======== #ifdef ESP32 LittleFS.begin(true); #else LittleFS.begin(); #endif db.begin(); // инициируем db.init(kk::slider, 33); // считаем, что диапазон от 0 до 100 db.init(kk::slider2, 99); // считаем, что диапазон от 0 до 100 db.init(kk::conf, true); // 0 или 1 delay(1000); // Выводим для контроля из БД Serial.println("\n******Значения ДО анализа ошибок******"); Serial.print("db[kk::slider].toInt()= \t"); Serial.println(db[kk::slider].toInt()); Serial.print("db[kk::slider2].toInt()= \t"); Serial.println(db[kk::slider2].toInt()); Serial.print("db[kk::conf].toInt()= \t\t"); Serial.println(db[kk::conf].toInt()); Serial.println("**************************************\n"); } void loop() { if (i <= ii) { // Читаем и выводим в Serial в случае искаженного значения err = db[kk::slider].toInt(); if (err > 100 || err < 0) { Serial.println("Ошибка чтения поля slider, некорректное значение " + String(err)); i++; } err = db[kk::slider2].toInt(); if (err > 100 || err <0) { Serial.println("Ошибка чтения поля slider2, некорректное значение " + String(err)); i++; } // bool err = db[kk::conf].toInt(); if (err != 1 && err != 0) { Serial.println("Ошибка чтения поля conf, некорректное значение " + String(err)); i++; } if (i == ii) { // Выводим для контроля из БД Serial.println("\n****Значения ПОСЛЕ цикла ошибок*****"); Serial.print("db[kk::slider].toInt()= \t"); Serial.println(db[kk::slider].toInt()); Serial.print("db[kk::slider2].toInt()= \t"); Serial.println(db[kk::slider2].toInt()); Serial.print("db[kk::conf].toInt()= \t\t"); Serial.println(db[kk::conf].toInt()); Serial.println("************************************\n"); i++; } } } ```

У меня выдает такие результаты:

``` 16:40:50.513 -> 16:40:50.513 -> Проверка корректности чтения полей из БД: выводим первые 20 строк ошибок 16:40:51.558 -> 16:40:51.558 -> ******Значения ДО анализа ошибок****** 16:40:51.558 -> db[kk::slider].toInt()= 45 16:40:51.558 -> db[kk::slider2].toInt()= 60 16:40:51.558 -> db[kk::conf].toInt()= 1 16:40:51.558 -> ************************************** 16:40:51.558 -> 16:40:51.558 -> Ошибка чтения поля conf, некорректное значение 1073917418 16:40:51.558 -> Ошибка чтения поля conf, некорректное значение 1073917418 16:40:51.939 -> Ошибка чтения поля slider, некорректное значение 1073917418 16:40:52.225 -> Ошибка чтения поля conf, некорректное значение 1073917418 16:40:52.321 -> Ошибка чтения поля conf, некорректное значение 1073917418 16:40:52.321 -> Ошибка чтения поля conf, некорректное значение 1073917418 16:40:52.896 -> Ошибка чтения поля slider2, некорректное значение 1073917418 16:40:52.991 -> Ошибка чтения поля conf, некорректное значение 1073917418 16:40:53.466 -> Ошибка чтения поля conf, некорректное значение 1073917418 16:40:53.561 -> Ошибка чтения поля conf, некорректное значение 1073917418 16:40:53.753 -> Ошибка чтения поля slider, некорректное значение 1073917418 16:40:54.042 -> Ошибка чтения поля conf, некорректное значение 1073917418 16:40:54.042 -> Ошибка чтения поля conf, некорректное значение 1073917418 16:40:54.279 -> Ошибка чтения поля conf, некорректное значение 1073917418 16:40:54.420 -> Ошибка чтения поля slider, некорректное значение 1073917418 16:40:54.420 -> Ошибка чтения поля slider2, некорректное значение 1073917418 16:40:54.420 -> Ошибка чтения поля conf, некорректное значение 1073917418 16:40:54.420 -> Ошибка чтения поля conf, некорректное значение 1073917418 16:40:54.565 -> Ошибка чтения поля conf, некорректное значение 1073917418 16:40:54.611 -> Ошибка чтения поля conf, некорректное значение 1073917418 16:40:54.611 -> 16:40:54.611 -> ****Значения ПОСЛЕ цикла ошибок***** 16:40:54.611 -> db[kk::slider].toInt()= 45 16:40:54.611 -> db[kk::slider2].toInt()= 60 16:40:54.611 -> db[kk::conf].toInt()= 1 16:40:54.611 -> ************************************ 16:40:54.611 -> ```
aleta2020 commented 3 days ago

Для информации: S2 mini платы такого вида:

image

GyverLibs commented 2 days ago

вижу багу, проявляется только на S2 =) Минимальный код

#include <Arduino.h>
#include <GyverDB.h>
GyverDB db;

void setup() {
    Serial.begin(115200);
    while (!Serial);
    delay(500);

    db.init(0, 50);
    db.init(1, 50);

    db.dump(Serial);
}

void loop() {
    static int i;
    i++;

    int v;

    v = db[0];
    if (v != 50) {
        Serial.print(i);
        Serial.print(": 0 ");
        Serial.println(v);
    }

    v = db[1];
    if (v != 50) {
        Serial.print(i);
        Serial.print(": 1 ");
        Serial.println(v);
    }
}

Сбой происходит рандомно через несколько тысяч чтений.... image

GyverLibs commented 2 days ago

подозреваю что проблема в самом ядре, т.к. отключение прерываний на период чтения полностью решает проблему. На плате программный USB, возможно он что то портит в рантайме и это беда

noInterrupts();
v = db[1];
interrupts();
aleta2020 commented 1 day ago

Спасибо за информацию. Полазил по форумам - ничего похожего не нашел. Неужели никто на этом чипе с подобным не сталкивался? Это значит, что может "портиться" и какой-то другой функционал или память? А всё noInterrupts() не обернешь... Проверил выборочно предыдущие версии ядра ESP32 - такая же ситуация :-(

GyverLibs commented 1 day ago

Не знаю, потыкал - пока не заметил каких то закономерностей, очень странное поведение

aleta2020 commented 1 day ago

Закономерности нет: на разных платах - разная интенсивность. Даже на той же плате - после rst может частота "выбросов" измениться. "Обвешал" резисторами, конденсаторами и уменьшил мощность передатчика (как предлагают на форумах) - не помогает. :-( У меня в скетче еще запрос делается к сайту - периодически при коде 200 получаю в отладке "оборванный" json. Причем "обрывается" всегда в разных местах. Короткий текст успевает "проскочить", а длинный - обрывается. Но бывает и вообще пустой. Вот и думаю теперь: не из-за этого ли? Не разбирались: на какой операции происходит "искажение"?

GyverLibs commented 1 day ago

Такое ощущение, что по адресу читаются некорректные данные, как такое возможно - хз. WiFi отключен

aleta2020 commented 1 day ago

я так понимаю - возвращается значение адреса? причем одного и того же. И смещение выводимого адреса от адреса db одинаковое:

12:45:10.285 -> DB dump: 2 entries (24 bytes)
12:45:10.285 -> 00. 0x0 [Int]: 50
12:45:10.285 -> 01. 0x1 [Int]: 50
12:45:10.285 -> Адрес db: 1073488848
12:45:13.321 -> 3955: 0 1073921922
12:45:13.321 -> Разница: 433074
12:45:13.942 -> 121357: 0 1073921922
12:45:13.942 -> Разница: 433074
12:45:15.087 -> 352307: 0 1073921922
12:45:15.087 -> Разница: 433074
12:45:15.087 -> 353008: 0 1073921922
12:45:15.087 -> Разница: 433074
12:45:15.324 -> 391102: 0 1073921922

адрес db: Serial.print("Адрес db: "); Serial.println((uint32_t)&db);

разница: Serial.print("Разница: "); Serial.println(v-(uint32_t)&db);

Вопрос: в скетче v объявлено как int - почему при выводе v на печать такое число большое? Ведь int (без учета знака) 0… 65 535

GyverLibs commented 1 day ago

Адрес объекта db никакой роли не играет, данные хранятся вообще в другом месте.

v-(uint32_t)&db

Эта разность вообще ничего не значит

int (без учета знака) 0… 65 535

Нет, размер int зависит от платформы, на еспшках это 32 бит

aleta2020 commented 21 hours ago

Если в примере перед v = db[1]; поставить delay(1);, то некорректные данные вроде как исчезают вообще. "Вроде" - т.к. ждать долго приходится :-) , а delay заметно тормозит код. Пробовал delayMicroseconds(1000); - тоже почти работает, а вот при delayMicroseconds(500); появляются выбросы почти как без задержки. В общем "костыль" сомнительный, но для данного кода существенно снижает вероятность ошибки...

Без delay на 2,4 млн. зафиксирована 41 ошибка (на 90 млн. 1436 ошибок) - существенно быстрее крутится - проще мерить. При delayMicroseconds(500); на 2,4 млн. циклов 33 ошибки. При delayMicroseconds(1000); на 2,5 млн. циклов 1 ошибка.

Код:

``` #include #include GyverDB db; void setup() { Serial.begin(115200); while (!Serial); delay(500); db.init(0, 50); db.init(1, 50); db.dump(Serial); } void loop() { static int i; static int e; // кол-во ошибок i++; int v; v = db[0]; if (v != 50) { Serial.print(i); Serial.print(": 0 "); Serial.println(v); e++; } delayMicroseconds(1000); // <<----------------------------------!!!!!! задержка перед чтением v = db[1]; if (v != 50) { Serial.print(i); Serial.print(": 1 "); Serial.println(v); e++; } if (i % 10000 == 0) { Serial.println("Прошло циклов: " + String(i) + " ошибок: " + String(e)); // кол-во циклов для контроля } } ```
GyverLibs commented 20 hours ago

Запрещать прерывания выгоднее тогда