Скрыть объявление
На нашем форуме недоступен просмотр изображений для неавторизованных пользователей. Если Вы уже зарегистрированы на нашем форуме, то можете войти. Если у Вас еще нет аккаунта, мы будем рады, если Вы к нам присоединитесь. Зарегистрироваться Вы можете здесь.

Делюсь опытом Использование RTC-памяти и режим глубокого сна.

Тема в разделе "Раздел для начинающих", создана пользователем Andrey L, 26 фев 2019.

  1. Andrey L

    Andrey L Новичок

    Сообщения:
    62
    Симпатии:
    4
    С одной стороны хочу поделиться своим "опытом", а с другой стороны, может быть у меня есть ошибка, которую я не заметил, хоть скетч и работает.

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

    Зачем надо использовать RTC?
    ESP8266 выходит из режима "глубокого сна" путём "перезагрузки", а следовательно код выполняется заного, да и старые значения переменых теряются, даже внутренние счётчики millis() и micros() сбрасываются.

    Для хранения некоторых данных можно воспользоваться RTC памятью, но надо помнить, что и эта память сбрасывается, когда ESP будет полностью отключена от сети.

    Так же размер RTC-памяти окраничена 512 байтами и советуется объявлять там типы по 4 байт. (Это я не полностью понимаю, поскольку с переменной типа bite это тоже работает.)

    Что надо дополнительно учитывать при работе с RTC?
    При подачи питания на модуль, нельзя рассчитывать, что в RTC будет хранится одни "нули", старых данных там тоже не будет. А следовательно надо научиться определять хранятся ли там наши данные или какие-то случайные "шумы".

    Для этого используется дополнительная функция для подчёта проверочной суммы. Но и это магическое число для проверки тоже будет храниться в RTC памяти. В примере эта функция называется calculateCRC32().

    Структура для RTC
    Как я понимаю, отдельные переменные можно хранить в RTC-памяти, но для этого надо строго следить за тем какие данные по какому адресу хранятся, а так же чтобы они не пересекались.

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

    Код
    Код (раскрыть)
    Код (Text):
    1. /**
    2. * Структура, которая будет хранится в RTC-памяти.
    3. * byte counter - счётчик запусков программы
    4. * unsigned long RunningTime - счётчик примерного времени в миллисекундах
    5. *
    6. * uint32_t crc32 - хранилище проверочной суммы, ОБЯЗАТЕЛЬНО должно стоять вконце структуры (ограничение моей функции по проверке этой суммы).
    7. *
    8. * Так же следим за общим размером структуры, её размер не должен превышать 512 байт. (Физический размер RTC-памяти.)
    9. */
    10. struct {
    11.   byte counter;               // 1 byte
    12.   unsigned long RunningTime;  // 4 byte
    13.  
    14.   uint32_t crc32;             // 4 byte
    15. } rtcData;
    16.  
    17.  
    18. // Время в микросекундах, на которое мы отправляем наш модуль "спать".
    19. // 1e6=10^6 - мультипликатор, который превращает секунды в микросекунды.
    20. #define sleepTime 10*1e6
    21.  
    22.  
    23. // Мультипликатор, который переводит микросекунды в миллисекунды
    24. #define mikroToMillis 1/1e3
    25.  
    26.  
    27. void setup() {
    28.   // Без "большого серийного брата" во время тестов никак нельзя.
    29.   Serial.begin(115200);
    30.   Serial.println(""); // Первая строчка вывода - абра-кадабра
    31.   Serial.println("Hallo!!!"); // Привет-привет
    32.  
    33.   // Считываем данные из RTC-памяти.
    34.   if (ESP.rtcUserMemoryRead(0, (uint32_t*) &rtcData, sizeof(rtcData))) {
    35.     Serial.println("Прочёл!!!");
    36.     Serial.println(rtcData.RunningTime);
    37.     Serial.println(rtcData.counter);
    38.   } else {
    39.     Serial.println("Паника! Прочесть не получилось.");
    40.   }
    41.  
    42.   // Проверяем проверочную сумму
    43.   if (rtcData.crc32 != calculateCRC32((uint8_t*) &rtcData)) {
    44.     // Если проверочные суммы не сошлись, то на модуль только что подали питание,
    45.     // и мы сохраняем "нулевые" значения.
    46.     rtcData.RunningTime = 0;
    47.     rtcData.counter = 0;
    48.     // Обязательно перед записью пересчитываем проверочную сумму
    49.     rtcData.crc32 = calculateCRC32((uint8_t*) &rtcData);
    50.     // Сохраняем нашу структуру в RTC-память
    51.     ESP.rtcUserMemoryWrite(0, (uint32_t*) &rtcData, sizeof(rtcData));
    52.  
    53.     Serial.println("Проверочные суммы не сошлись, все данные сбросили.");
    54.   } else {
    55.     Serial.println("Проверочные суммы сошлись.");
    56.   }
    57.  
    58. }
    59.  
    60.  
    61. void loop() {
    62.   // Увеличиваем время счётчика.
    63.   // sleepTime (время "сна" в микросекундах)
    64.   // * mikroToMillis (переводит микросекунды в миллисекунды)
    65.   // + millis() не забываем добавить время, которое прошло с момета запуска программы
    66.   // ещё можно добавить некое число на выполнение следующих команд до того, как модуль "заснёт"
    67.   rtcData.RunningTime += sleepTime * mikroToMillis + millis();
    68.   // Увеличиваем счётчик
    69.   rtcData.counter++;
    70.   // Не забываем перед записью обновить проверочную сумму
    71.   rtcData.crc32 = calculateCRC32((uint8_t*) &rtcData);
    72.   // Сохраняем нашу структуру в RTC-памяти
    73.   ESP.rtcUserMemoryWrite(0, (uint32_t*) &rtcData, sizeof(rtcData));
    74.  
    75.   // Идём "спать"
    76.   ESP.deepSleep(sleepTime);
    77. }
    78.  
    79.  
    80. /**
    81. * Магическая функция по подсчёту проверочной суммы нашей структуры.
    82. * Основа её взята из примера ESP8266/RTCUserMemory.
    83. */
    84. uint32_t calculateCRC32(const uint8_t *data) {
    85.   // Обрабатываем все данные, кроме последних четырёх байтов,
    86.   // где и будет храниться проверочная сумма.
    87.   size_t length = sizeof(rtcData)-4;
    88.  
    89.   uint32_t crc = 0xffffffff;
    90.   while (length--) {
    91.     uint8_t c = *data++;
    92.     for (uint32_t i = 0x80; i > 0; i >>= 1) {
    93.       bool bit = crc & 0x80000000;
    94.       if (c & i) {
    95.         bit = !bit;
    96.       }
    97.       crc <<= 1;
    98.       if (bit) {
    99.         crc ^= 0x04c11db7;
    100.       }
    101.     }
    102.   }
    103.   return crc;
    104. }


    Вкатце что делает программа
    1. setup()
      Читаем, что хранится в RTC памяти.
      - Если данные не проходят валидацию, то мы эти данные "обнуляем"
    2. loop()
      Увеличиваем счётчики,
      пересчитываем новую проверочную сумму,
      сохраняем всё.
      Идём спать.

    Альтернатива RTC
    Внутренняя файловая система, работать можно через библиотеку "FS.h" (SPIFFS). Эта система как код скетча энергонезависимая, где можно хранить данные и без питания. Но для неё надо отдельно выделять память от 4МБ модуля.
     
    Последнее редактирование модератором: 27 фев 2019
  2. Сергей_Ф

    Сергей_Ф Moderator Команда форума

    Сообщения:
    2.168
    Симпатии:
    226
    @Andrey L если используете режим сна, то loop должен быть пустым!
     
  3. Andrey L

    Andrey L Новичок

    Сообщения:
    62
    Симпатии:
    4
    @Сергей_Ф Спасибо, учту.

    Но следующая версия программы должна просыпаться по таймеру или при нажатой кнопке, запускать ВЕБ-сервер, и после 10 минут после последнего запроса к ВЕБ-серверу вновь "засыпать". Пока я думаю как это можно сделать при помощи одной кнопки.
     
  4. Сергей_Ф

    Сергей_Ф Moderator Команда форума

    Сообщения:
    2.168
    Симпатии:
    226
    @Andrey L нет проблем, смотрите статус просыпания. А кнопку повесьте на chip_en - это обеспечит другой код при просыпания.
    Тут loop использовать будет логично для веб сервера.
     
  5. nikolz

    nikolz Гуру

    Сообщения:
    4.736
    Симпатии:
    452
    надо учитывать, что просыпание по сигналу chip-en приводит к обнулению памяти RTC (здесь называемой TRC)
     
  6. Сергей_Ф

    Сергей_Ф Moderator Команда форума

    Сообщения:
    2.168
    Симпатии:
    226
    Это да. Иначе можно обойти, если допустимо двойное нажатие на кнопку подключенную к RST. Или кнопку от RST подключить к одному из gpio и считывать состояние пина при старте. Но тут без дополнительных схемных элементов для развязки RST и gpio не обойтись.
     
  7. tretyakov_sa

    tretyakov_sa Moderator Команда форума

    Сообщения:
    982
    Симпатии:
    151
    Вы можете определить, как ESP стартовала, и если это включение по питанию, то в памяти точно нет данных.
    ESP.getResetReason();
     
  8. nikolz

    nikolz Гуру

    Сообщения:
    4.736
    Симпатии:
    452
    на самом деле, согласно документации,
    включение питания предполагает подачу High на RST и потом с задержкой на CH, что приводит к очистке RTC.
     
  9. Andrey L

    Andrey L Новичок

    Сообщения:
    62
    Симпатии:
    4
    Да, я ошибся когда RTC неправильно назвал, но первое сообщение я уже изменить не могу, только теги подправил.

    Так же я нашёл ESP.getResetReason(); , но с этим мне надо будет отдельно поэксперементировать.

    Что же касается "chip_en", то я нашёл, где этот пин на ESP-модуле находится. Но где он на моём NodeMCU и зачем он нужен, я так и не понял.
     
  10. Сергей_Ф

    Сергей_Ф Moderator Команда форума

    Сообщения:
    2.168
    Симпатии:
    226
    на вашем NodeMCU он уже притянут +3.3В резистором. Пин находится рядом с RST, называется EN (если китайцы не ошиблись, конечно). При низком уровне на нём esp будет вЫключена и будет потреблять очень мало, при высоком - включена.
     
  11. Andrey L

    Andrey L Новичок

    Сообщения:
    62
    Симпатии:
    4
    @Сергей_Ф Спасибо, нашёл, сейчас испробую и отпишусь.
     
  12. Andrey L

    Andrey L Новичок

    Сообщения:
    62
    Симпатии:
    4
    В соседней ветке я выяснил какие "статусы просыпания" получатюся при замыкании пинов "RST" и "EN" с "GND", Вы это имели ввиду?

    А то я сейчас пытаюсь замыкать пины "RST" и "EN" с одним из пинов "D", но тут у меня ничего не получается (лишь иногда "Hardware Watchdog").

    Вообще-то я хотел, чтобы при нажатии на кнопку модуль "просыпался" (помимо таймера), а если модуль при нажатии на эту же кнопку не был в режиме "сна", то он бы ещё на неё как-нибудь смог реагировать.
     
  13. Сергей_Ф

    Сергей_Ф Moderator Команда форума

    Сообщения:
    2.168
    Симпатии:
    226
    Если модуль спал - код один, если нет - другой. Никаких проблем. Правда все через reset.
    ESP8266 Deep Sleep with Arduino IDE
     
  14. Сергей_Ф

    Сергей_Ф Moderator Команда форума

    Сообщения:
    2.168
    Симпатии:
    226
    а что должно получаться? Вы должно понимать что и для чего соединяете и поддержать это как аппаратно, так и программно. Вы же, судя по написанному, не понимаете и используете метод тыка. Тут так не пройдет.
     
  15. Andrey L

    Andrey L Новичок

    Сообщения:
    62
    Симпатии:
    4
    Надеялся, что при Digitalwrite(LOW) или же Pinmode(input) пин будет притягиваться к "GND".

    Да, если честно, половину ответов мне я не понимаю.
     
  16. Сергей_Ф

    Сергей_Ф Moderator Команда форума

    Сообщения:
    2.168
    Симпатии:
    226
    Он притягивается, на время работы модуля. Но не во сне.
    А это с какой стати? Это вход. Но опять на время работы.
     
  17. Andrey L

    Andrey L Новичок

    Сообщения:
    62
    Симпатии:
    4
    @Сергей_Ф Спасибо.
    Моя ошибка была в том, что железу надо было давать время на "выполнение", чего я не делал. Вот у меня и с Digitalwrite(LOW) не получилось. После чего, я и решился попробовать с Pinmode(input).
     

Поделиться этой страницей