Снова о автономной Arduino-метеостанции на батарейках

    Еще донедавна мне не удавалось найти в Интернете любительскую метеостанцию с питанием от батареек. Я имею ввиду бытовую автономную метеостанцию с измерениями параметров в помещении, на улице и отображением информации на дисплее метеостанции. Любители не заморачиваются на этой проблеме, а питают свои автономные девайсы от солнечных батарей, аккумуляторов и т.п. Уточню — проблема касается только одного из узлов метеостанции — базы, а первые проекты малогабаритных беспроводных автономных выносных датчиков на Ардуино появились 10 лет назад. Вместе с тем, промышленные устройства такого плана — бытовые метеостанции, комнатные термостаты годами работают от пары батареек АА и этот факт является той целью, которой хотелось бы достичь.


    Это было донедавна. Несколько дней назад меня поразил очередной проект @Berkseo, как поражают все его проекты: "Беспроводная мини погодная станция с e-paper экраном на батарейках". Тут все на уровне промышленного продукта. Удивляет единственное — в устройстве нет внешнего датчика.


    Полтора года назад разместил статью Автономная метеостанция на контроллере ATMEGA328P и питанием от батареек с беспроводным выносным датчиком. Хотя прототип и выполнял свои функции, но имел серьезные недостатки — слишком малая частота обновления данных и большое энергопотребление.


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



    Что сделано:


    Датчики DHT22 и DS18B20, которые использовались в предыдущем проекте, заменены энергосберегающим модулем — это датчик температуры и влажности HTU21D. Период измерений, отправки/приема данных уменьшен с 15 мин до 53,5 сек. Сделан переход на устойчивую частоту работы контроллера (8 МГц) при напряжении питания ниже 3 В. Для уменьшения объемов занимаемой памяти в скетчах использованы некоторые функции С/С++. И главное, принципиально изменен алгоритм передачи пакетов с выносного датчика и алгоритм приема этих пакетов базой метеостанции. Теперь для обеспечения надежного приема пакетов с выносного датчика в нем формируется и отправляется с интервалом около 0,3 сек не один, а три пакета с данными о параметрах воздуха на улице и состоянии батареек. Только после отправки третьего пакета контроллер в. датчика вместе с периферией уходит в сон. База метеостанции уходит спать после приема одного из 6-ти пакетов с выносного датчика и просыпается за полсекунды до поступления очередной серии пакетов с выносного датчика.


    Метеостанция состоит из двух автономных узлов с питанием от двух батареек AA: базы и выносного датчика. Назовем их для простоты анализатором (по-другому – база) и беспроводным в.датчиком (выносным датчиком).


    Анализатор, построен на контроллере ATMEGA328P, измеряет температуру и влажность (датчик температуры и влажности HTU21D) в помещении, а также измеряет и анализирует величину напряжения питания узла, которое обеспечивают две батарейки АА 1,5 В. На контроллер также поступает сигнал с приемника LoRa, который по эфиру принимает информацию с выносного датчика. Вся инфа с контроллера выводится на ЖК-дисплей NOKIA 5110.


    В в.датчике, тоже собранном на контроллере ATMEGA328P, измеряется температура и влажность воздуха на улице (модуль HTU21D), а также напряжение питания выносного узла, организованного на двух батарейках АА 1,5 В. Передатчик LoRa этого узла передает инфу о температуре, влажности и состоянии батарейки на анализатор. С в.датчика выполняется отправка 3 пакетов с интервалом около 0,3 сек, затем контроллер ATMEGA328P, передатчик LoRa и модуль HTU21D для экономного расходования заряда батареек переводятся в режим сна. Измерения и отправка данных с в.датчика выполняется с циклом несколько меньше 1-ой минуты.


    Работа анализатора построена по следующему алгоритму:


    Вначале, при включении обоих узлов метеостанции, контроллер анализатора подает команды на измерение температуры и влажности внутри помещения и выводит эти параметры на дисплей, затем устанавливает приемник LoRa в режим прослушивания эфира. После приема сигнала с в.датчика и успешной дешифрации принятых данных контролер подает команду на повторное измерение температуры, влажности и выводит инфу в полном объеме на экран. Затем анализатор уходит в сон, просыпаясь примерно за полсекунды до планируемого поступления сигнала с в.датчика. Приняв и дешифровав один из трех пакетов с в.датчика, повторно выполняет свои измерения, выводит информацию на экран и снова уходит спать. Если по каким-то причинам сигнал с в.датчика отсутствует около одной минуты (например, сели батарейки), что по времени соответствует отправке 6 пакетов с в.датчика, анализатор проводит измерения только в помещении, изредка сканируя эфир: а вдруг в.датчик появился в эфире?! Это сделано для того, чтобы постоянно работающий на прием модуль LoRa не посадил за короткое время батарейки анализатора.


    Для сборки устройства понадобятся радиодетали:


    1. Контроллер ATMEGA328P-PU — 2 шт.
    2. Датчик влажности и температуры HTU21D/SHT21/Si7021 — 2 шт.
    3. ЖК-дисплей NOKIA 5110 — 1 шт.
    4. Приемник-передатчик LoRa Rа-01 — 2 шт.
    5. Макетная плата (стеклотекстолит), монтажные провода, батарейки АА, кварцевые резонаторы 8 МГц, резисторы, конденсаторы, другие мелочи.

    Ориентировочная стоимость компонентов по ценам AliExpress примерно $25.


    Для работы с контроллерами ATMEGA328P в качестве программатора я использую плату Arduino UNO. На Youtube есть хорошее видео по установке загрузчика и загрузки скетчей в контроллер ATMEGA328P с помощью платы Arduino UNO.


    На этот раз мы не будем устанавливать новые фьюзы программой SinaProg, а воспользуемся, на мой взгляд, более универсальным способом — созданием новых конфигураций плат в платформе Arduino IDE.


    В новые контроллеры надо установить загрузчик Arduino as ISP и надо учитывать то, что контроллеры ATMEGA328P поступают в продажу с заводской настройкой фьюз для мониторинга (контроля) напряжения питания не ниже 2,7 В. Мы же будем работать от батареек, напряжение на которых при разряде может быть ниже установленного заводского порога 2,7 В, и с кварцем 8 МГц. Установим загрузчик и изменим фьюзы под наши условия, используя в качестве программатора плату Arduino UNO, в такой последовательности:


    1. Найти по адресу c:\Program Files\Arduino\hardware\arduino\avr\ файл boards.txt и открыть его текстовом редакторе с форматированием, например, AkelPad.
    2. Дополнить файл блоком, который приведен под спойлером, и сохранить файл.

      блок установок 1
      ##############################################################

      amega.name=Mega Low (8 MHz, >1.8V)

      amega.upload.tool=avrdude
      amega.upload.protocol=arduino
      amega.upload.maximum_size=32256
      amega.upload.maximum_data_size=2048
      amega.upload.speed=57600

      amega.bootloader.tool=avrdude
      amega.bootloader.low_fuses=0xFF
      amega.bootloader.high_fuses=0xDA
      amega.bootloader.extended_fuses=0xFE
      amega.bootloader.unlock_bits=0x3F
      amega.bootloader.lock_bits=0x0F
      amega.bootloader.file=optiboot/optiboot_atmega328.hex

      amega.build.mcu=atmega328p
      amega.build.f_cpu=8000000L
      amega.build.board=AVR_UNO
      amega.build.core=arduino
      amega.build.variant=standard

    3. В плату Arduino UNO загрузить скетч ArduinoISP.ino из примеров платформы Arduino IDE (Файл –> Примеры –> ArduinoISP).
    4. Собрать схему (плата Arduino UNO, контроллер ATMEGA328P, кварц 16 МГц) для установки в контроллер загрузчика ArduinoISP (инструкции – тут), подключить ее компьютеру и записать в контроллер бутлоадер Arduino as ISP.
    5. Заменить кварц в схеме 16 МГц на 8 Мгц. В меню ИНСТРУМЕНТЫ выбрать из списка плату Mega Low (8 MHz, >1.8V), которая появилась в меню после дополнения файла boards.txt новым блоком, выбрать тут же Программатор: “Arduino as ISP” и, нажав Записать загрузчик изменить фьюзы и другие установки в контроллере.
    6. Далее загружаем в контроллер необходимый скетч, используя ту же схему, что и для установки загрузчика (п.4), через Скетч –> Загрузить через программатор.

    Выносной датчик


    В.датчик построен на контроллере ATMEGA328P. В нем осуществляется прием данных с HTU21D по протоколу I2C, измерение и анализ величины напряжения питания узла и управление передатчиком LoRa.


    скетч в.датчика
    /*
       Снова о автономной Arduino-метеостанции на батарейках, выносной датчик
       https://habr.com/ru/post/544936/
    */
    
    #include <avr/io.h>
    #include <util/delay.h>
    
    #include <SPI.h>
    #include <LoRa.h>
    #include <LowPower.h>
    #include <Wire.h>
    #include <avr/power.h>
    #include "HTU21D.h"
    
    #define VccHTU 8  //питание и подтяжка HTU21D (pin 14 AtMega328P, D8)
    HTU21D myHTU21D;
    float Tout; // температура
    int Hout;  // влажность
    
    unsigned int sleepCounter, sleepCounter0; // счетчик, задающий время сна
    int pct;  //счетчик числа пакетов перед уходом в сон
    String messageOut; // LoRa-сообщение
    float BatOut; // напряжение батареек
    const int batteryPin = A0; // pin 23 (Atmega328P), к которому подключена батарея для измерения напряжения
    const float typVbg = 1.132; //калибровочная константа, 1.0 - 1.2
    
    int counter = 0;
    // измерение опорного напряжения
    float readVcc() {
      byte i;
      float result = 0.0;
      float tmp = 0.0;
    
      for (i = 0; i < 1; i++) {
        // Read 1.1V reference against AVcc
        // set the reference to Vcc and the measurement to the internal 1.1V reference
    #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
        ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
        ADMUX = _BV(MUX5) | _BV(MUX0);
    #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
        ADMUX = _BV(MUX3) | _BV(MUX2);
    #else
        // works on an Arduino 168 or 328
        ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    #endif
    
        _delay_ms(3); // Wait for Vref to settle
        ADCSRA |= _BV(ADSC); // Start conversion
        while (bit_is_set(ADCSRA, ADSC)); // measuring
    
        uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH
        uint8_t high = ADCH; // unlocks both
    
        tmp = (high << 8) | low;
        tmp = (typVbg * 1023.0) / tmp;
        result = result + tmp;
        _delay_ms(5);
      }
      return result;
    }
    
    void Measurement () {
      // измерение температуры и влажности
      Hout = myHTU21D.readHumidity();
      Hout = 62;  //delete!
      float Tout_p = myHTU21D.readTemperature();
      Tout = 0.1 * int(Tout_p * 10 + 0.5);  //округление до десятых
      // измерение напряжения батареек
      BatOut = 0.1 * int(readVcc() * 10 + 0.5);
      if (BatOut < 2.2) {
        BatOut = 0.0;
      } else {
        BatOut = 2.2;
      }
    }
    
    void SendMessage () {
      // отправка данных (температура, влажность, состояние батареек)
      if (BatOut > 2.1) {
        messageOut = String(Tout) + "#" + String(Hout) + "$" + String("BGood");
      }
      else {
        messageOut = String(Tout) + "#" + String(Hout) + "$" + String("BLow");
      }
    
      LoRa.beginPacket();
      LoRa.print(messageOut);
      LoRa.endPacket();
    }
    
    void setup() {
      Serial.begin(9600);
      Serial.println("Power ON");
      analogReference(DEFAULT);
    
      pinMode(VccHTU, OUTPUT);
      digitalWrite(VccHTU, 1);
      _delay_ms(200);
      myHTU21D.begin();
    
      int counter = 0;
      while (!LoRa.begin(433E6) && counter < 10) {
        Serial.println("Не удалось найти LoRa-передатчик!");
        counter++;
        _delay_ms(500);
      }
      LoRa.setTxPower(4); //мощность передатчика, 2...20 дБ
      LoRa.setSyncWord(0xF3);
    }
    
    void loop() {
      digitalWrite(VccHTU, 1);
      if (pct < 3)
      { // измерения, отправка пакетов
        Serial.println(messageOut);
        Measurement ();
        SendMessage ();
      } else {// измерения, отправка пакета и длительный сон
        Serial.println(messageOut);
        Serial.println("sleep ...");
        Measurement ();
        SendMessage ();
        for (sleepCounter = 6; sleepCounter > 0; sleepCounter--)
        {
          digitalWrite(VccHTU, 0);
          digitalWrite(VccHTU, 1);
          LoRa.sleep ();
          LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
        }
        pct = 0;
      }
      pct++;
      if (pct >= 3) pct = 3; //защита от переполнения счетчика
    }
    
    int main() {
      init();
      setup();
    
      for (;;) {
        loop();
      }
    }

    Электрическая схема в.датчика:



    Питание и подтяжка выводов модуля HTU21D осуществляется с пина 14 контроллера ATMEGA328P. Это сделано для того, чтобы программно обнулить питание HTU21D и перевести этот датчик в режим низкого энергопотребления во время сна.


    В в.датчике формируется и отправляется с интервалом около 0,3 сек три пакета с данными о температуре и влажности на улице и состоянии батареек. Если напряжение на батарейках выше установленного порога (2,2 В), то в коде пакета присутствует BGood, а ниже — BLow. После отправки третьего пакета контроллер в.датчика вместе с периферией уходят в сон. Цикл отправки серий пакетов — 53,5 сек.


    Анализатор


    Мозг анализатора – контроллер ATMEGA328P. Он принимает сигналы с датчика HTU21D по протоколу I2С и по SPI взаимодействует с приемником LoRa и дисплеем NOKIA 5110.


    скетч анализатора
    /*
       Снова о автономной Arduino-метеостанции на батарейках, анализатор
       https://habr.com/ru/post/544936/
    */
    
    #include <avr/io.h>
    #include <util/delay.h>
    
    #include <SPI.h>
    #include <LoRa.h>
    #include <LowPower.h>
    #include "HTU21D.h"
    #include <LCD5110_Graph.h>
    
    #define VccHTU 8  //питание и подтяжка HTU21D(pin 14 AtMega328P, D8)
    HTU21D myHTU21D;
    float Tin; // температура в помещении
    int Hin;  // влажность в помещении
    
    LCD5110 myNokia(3, 4, 5, 6, 7);
    extern uint8_t SmallFont[];
    extern uint8_t MediumNumbers[];
    
    float BatIn = 0; // напряжение батареи
    const int batteryPin = A0; // pin 23(Atmega328P), к которому подключена батарея для измерения напряжения
    const float typVbg = 1.132; //калибровочная константа, 1.0 - 1.2
    
    unsigned int sleepCounter;  //счетчик, задающий время сна
    
    int r; //счетчик циклов прослушивания эфира
    int mlc;  //счетчик циклов работы без в.датчика
    
    String LoRaData, Tout_str, Hout_str, BatIn_str, BatOut_str;
    
    // измерение напряжения батареек
    float readVcc() {
      byte i;
      float result = 0.0;
      float tmp = 0.0;
    
      for (i = 0; i < 1; i++) {
        // Read 1.1V reference against AVcc
        // set the reference to Vcc and the measurement to the internal 1.1V reference
    #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
        ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
        ADMUX = _BV(MUX5) | _BV(MUX0);
    #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
        ADMUX = _BV(MUX3) | _BV(MUX2);
    #else
        // works on an Arduino 168 or 328
        ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
    #endif
    
        _delay_ms(3); // Wait for Vref to settle
        ADCSRA |= _BV(ADSC); // Start conversion
        while (bit_is_set(ADCSRA, ADSC)); // measuring
    
        uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH
        uint8_t high = ADCH; // unlocks both
    
        tmp = (high << 8) | low;
        tmp = (typVbg * 1023.0) / tmp;
        result = result + tmp;
        _delay_ms(5);
      }
      return result;
    }
    
    void Measurement() {
      float Tin0;
      // измерение напряжения батареи:
      BatIn = readVcc();
      // измерение температуры  и влажности в помещении
      Hin = myHTU21D.readHumidity();
      // Hin = 58; // delete!
      float Tin_p = myHTU21D.readTemperature();
      Tin = 0.1 * int(Tin_p * 10 + 0.5);  //округление до десятых
      //  Tin = 21.4; // delete!
    }
    
    void draw() {
      myNokia.enableSleep();
      myNokia.clrScr();
    
      //Tin
      char chr_Tin [5];
      String Tin_str = String(Tin);
      myNokia.setFont(SmallFont);
      myNokia.print("            C", LEFT, 0);
      myNokia.print("In", LEFT, 8);
      myNokia.setFont(MediumNumbers);
      Tin_str.toCharArray(chr_Tin, 5); //количество знаков+1
      myNokia.print(String(chr_Tin), CENTER, 0);
    
      //Tout
      char chr_Tout [5];
      myNokia.setFont(SmallFont);
      myNokia.print("            C", LEFT, 16);
      myNokia.print("Out", LEFT, 24);
      myNokia.setFont(MediumNumbers);
      Tout_str.toCharArray(chr_Tout, 5);
      myNokia.print(String(chr_Tout), CENTER, 16);
    
      // Hin, Hout
      char chr_Hout [5];
      Hout_str.toCharArray(chr_Hout, 4);
      myNokia.setFont(MediumNumbers);
      myNokia.print(String(Hout_str), RIGHT, 32);
      myNokia.setFont(SmallFont);
      myNokia.print("    In Out", LEFT, 40);
      myNokia.print("      %", LEFT, 32);
      myNokia.setFont(MediumNumbers);
      myNokia.print(String(Hin), LEFT, 32);
      myNokia.setFont(SmallFont);
    
      // Battery Level
      if (BatIn < 2.2) {
        myNokia.setFont(SmallFont);
        myNokia.print("Bat", LEFT, 0);
      }
    
      if (BatOut_str == "BLow") {
        myNokia.setFont(SmallFont);
        myNokia.print("Bat", LEFT, 16);
      }
    
      myNokia.disableSleep();
      _delay_ms(5);
    }
    
    void drawStart() {
      myNokia.enableSleep();
      myNokia.clrScr();
    
      //Tin
      char chr_Tin [5];
      String Tin_str = String(Tin);
      myNokia.setFont(SmallFont);
      myNokia.print("            C", LEFT, 0);
      myNokia.print("In", LEFT, 8);
      myNokia.setFont(MediumNumbers);
      Tin_str.toCharArray(chr_Tin, 5); //количество знаков+1
      myNokia.print(String(chr_Tin), CENTER, 0);
    
      // Battery Level
      if (BatIn < 2.2)
      {
        myNokia.setFont(SmallFont);
        myNokia.print("Bat!", RIGHT, 28);
      }
    
      //Hin
      myNokia.setFont(SmallFont);
      myNokia.print("         %", LEFT, 18);
      myNokia.print("In", LEFT, 28);
      myNokia.setFont(MediumNumbers);
      myNokia.print(String(Hin), CENTER, 18);
    
      //No signal!
      myNokia.setFont(SmallFont);
      myNokia.print("Out - - -", CENTER, 40);
    
      myNokia.update();
    
      myNokia.disableSleep();
      _delay_ms(5);
    }
    
    void setup() {
      Serial.begin(9600);
    
      pinMode(VccHTU, OUTPUT);
      digitalWrite(VccHTU, 1);
      Serial.println("Power ON!");
      analogReference(DEFAULT);
    
      // инициализация дисплея
      myNokia.InitLCD();
    
      myNokia.setFont(SmallFont);
      myNokia.clrScr();
      myNokia.print(">>>>>", CENTER, 20);
      myNokia.update();
      _delay_ms(1000);
      myNokia.setFont(SmallFont);
      myNokia.clrScr();
      myNokia.print("))-->", CENTER, 20);
      myNokia.update();
    
      if (!LoRa.begin(433E6)) {
        Serial.println("Ошибка загрузки LoRa-приемника!");
        while (1);
    
        myNokia.setFont(SmallFont);
        myNokia.clrScr();
        myNokia.print(" ->  ->", CENTER, 20);
        myNokia.update();
      }
    
      // Диапазон для синхрослова – между "0-0xFF".
      LoRa.setSyncWord(0xF3);
      Serial.println("Прослушивание эфира. Ожидание пакета с в.датчика ...");
    
      myHTU21D.begin();
      Measurement();
      drawStart();
      digitalWrite(VccHTU, 0);
      _delay_ms(1000);
    
      myNokia.clrScr();
      myNokia.print("Waiting", CENTER, 10);
      myNokia.print("Message from", CENTER, 22);
      myNokia.print("OUTSIDE", CENTER, 34);
      myNokia.update();
    }
    
    void loop() {
      r++;
      digitalWrite(VccHTU, 1);
      if (r < 600)  // 8 MHz;
      {
        mlc = 0;
        // Прослушивание эфира, прием, дешифрация, если сигнал с в.датчика принят,
        // то измерения в помещении, вывод инфы на экран и - в спячку.
        {
          int packetSize = LoRa.parsePacket();
          if (packetSize) {
            while (LoRa.available()) {
              LoRaData = LoRa.readString();
            }
            int pos1 = LoRaData.indexOf('#');
            int pos2 = LoRaData.indexOf('$');
            Tout_str = LoRaData.substring(0, pos1);
            Hout_str = LoRaData.substring(pos1 + 1, pos2);
            BatOut_str = LoRaData.substring(pos2 + 1, LoRaData.length());
    
            if ((LoRaData).substring(pos1, pos1 + 1) == "#") {
              Serial.println("Принято, декодировано! r = " +  String(r));
              r = 0;
              Measurement();
              draw();
              digitalWrite(VccHTU, 0);
              // sleepCounter = 49; 16 MHz
              // sleepCounter = 48; 8 MHz
              for (sleepCounter = 48; sleepCounter > 0; sleepCounter--)
              {
                digitalWrite(VccHTU, 1);
                LoRa.sleep ();
                LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);
              }
            }
          }
        }
      } else {
        r = 600;
        if (mlc < 250) //4 часа, время работы без датчика
        {
          Serial.println("Работа без в.датчика.");
          LoRa.sleep ();
          Measurement();
          drawStart();
          digitalWrite(VccHTU, 0);
    
          for (sleepCounter = 6; sleepCounter > 0; sleepCounter--)
          {
            digitalWrite(VccHTU, 1);
            LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
          }
          mlc++;
        } else {
          r = 0;
          mlc = 0;
        }
      }
      _delay_ms(110); 
    }
    
    int main() {
      init();
      setup();
    
      for (;;) {
        loop();
      }
    }

    Работа анализатора начинается в setup'e с инициализации модулей, измерения параметров воздуха, анализа напряжения на батарейках и вывода этой инфы на дисплей. Далее уже в loop'e прослушивается эфир приемником LoRa. После приема и дешифрации сигнала с в.датчика повторно проводятся измерения, анализа напряжения на батарейках и вывод измеренной и принятой инфы на дисплей. Выполнив эту работу все элементы схемы уходят поспать примерно на полсекунды меньше, чем период отправки пакетов с в.датчика. В следующем цикле контроллер просыпается и включает приемник приблизительно за 0,5 сек до ожидаемого прихода сигнала с в.датчика. Таким образом, контроллер и периферия анализатора работают около полсекунды с периодом (циклом) меньше минуты (53,5 сек). Если радиосигнал с в.датчика не поступает на приемник анализатора на протяжении приблизительно одной минуты (время, достаточное для приема одного из 6 пакетов), то анализатор переходит в режим работы без в.датчика на 4 часа, измеряя параметры воздуха и оценивая состояние батареек только в помещении с индикацией на дисплее этих данных. Период обновления данных в режиме работы без в. датчика — 56,7 сек. В конце четырехчасового цикла работы анализатора без в.датчика он прослушивает эфир: а вдруг в.датчик снова в эфире?




    Для перевода модуля HTU21D в режим низкого энергопотребления во время сна его питание также, как и в в.датчике, организовано с контроллера ATMEGA328P (пин 14).


    В целом, на дисплее анализатора видна такая картинка:



    Дисплей из-за низкого разрешения и малого размера экрана плотно забит символами. Эта картинка смотрелась бы намного лучше на современном дисплее с электронными чернилами. В будущем в своих проектах буду использовать e-paper дисплей.


    Ресурс батареек и другое


    Для расчета срока работы батареек понадобится время и потребляемый ток во время выполнения работы (операционное время) и сна. Операционное время и рабочий ток измерялись с использованием тестовых скетчей, идея которых взята отсюда. Архив с тестовыми скетчами и данными хронометража лежит здесь.


    Рабочий ток измерялся с использованием тех же тестовых скетчей. Для исключения разрывов цепи питания или значительного увеличения величины выходного сопротивления батареек можно использовать шунт 3,9...5,6 Ом и параллельно подключенный к нему цифровой мультиметр с механическим переключением в режиме вольтметра на диапазоне 2000 мкВ. Это критично при измерении потребления тока сна анализатора, поскольку разрыв питания или значительное ограничение тока приводят к циклическому ресету анализатора. Да и выносной датчик может переходить в постоянный рестарт. По мере возможности необходимо проверять ток потребления разными способами на разных диапазонах шкал прибора и с батарейками, которые планируется использовать, притом, обязательно без вывода результатов на монитор порта Ардуино. Невыполнение этих правил сказались на результатах измерений тока в предыдущем моем посте на тему метеостанции — в одних случаях они занижены, в других — завышены.


    Результаты измерений сведены в таблицу:


      в.датчик анализатор
    Операционное время функции измерений параметров воздуха, состояния батареек 0,25 сек 0,39 сек
    Операционный ток функции измерений параметров воздуха, состояния батареек 3,4 мА 3,5 мА
    Операционное время функции передачи/приема сигнала 42 мсек 83 мсек
    Операционный ток функции передачи/приема сигнала 30,0 мА

    (4 дБ)


    11,5 мА
    Ток сна 10 мкА 190 мкА

    Что бросается в глаза, глядя на эту таблицу. Операционный ток передачи сигнала — 30,0 мА при мощности передатчика LoRa 4 дБ. Для сравнения, заявленный ток передачи для модуля nRF24L01 13,5 мА. Вывод очевиден: надо переходить на nRF24L01.


    Средний ток потребления по данным таблицы в. датчика 0,13 мА. Емкости батареек типа АА GP Litium для выносного датчика должно хватить на 2,5 года.


    Средний ток потребления анализатора 0,27 мА. Ресурс батареек АА GP Litium в анализаторе — 1,2 года. Для беспроводного комнатного термостата Computherm Q7RF, например, срок действия батареек  около 1 года. Неплохо! Конечно, хотелось бы сравнить с базой промышленной метеостанции, но производители этого товара не приводят в комплекте документов ресурса батареек.


    Материал, который раньше находился на этом месте, в развернутом виде вынесен сюда: На распутье — Ардуино, Cи или Ассемблер? Заходите!


    Не буду скромничать, полученный результат — энергопотребление прототипа на уровне промышленных образцов, меня радует, а совершенству нет предела…


    И, наконец, искренне благодарю AlexanderS, который донес до меня идею виртуальной шкалы времени или синхронизации, а также других участников обсуждения статьи «Автономная метеостанция на контроллере ATMEGA328P и питанием от батареек с беспроводным выносным датчиком» за предложения, конструктивную критику и замечания.


    Спасибо, кто дочитал. Всем — отличного иммунитета во времена ковида и не только.


    Ссылки по теме


    Узел беспроводного датчика с низким энергопотреблением


    Беспроводная мини погодная станция с e-paper экраном на батарейках


    Автономная метеостанция на контроллере ATMEGA328P и питанием от батареек с беспроводным выносным датчиком


    Превращаем Arduino в полноценный AVRISP программатор


    LoRa и сон


    Узнайте о битах конфигурации ATmega328P и о том, как использовать их с внешним кварцевым резонатором


    Калькулятор фьюзов AVR


    Почему многие не любят Arduino

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 44

      +3

      Коль уж с Arduino уходим на C/asm, возникает резонный вопрос: может уже "гулять на все" и с самой atmega уйти на что-нибудь специально созданное для низкого потребления? STM8L или STM32L например.

        0
        Коль уж с Arduino уходим на C/asm, возникает резонный вопрос: может уже «гулять на все» и с самой atmega уйти на что-нибудь специально созданное для низкого потребления? STM8L или STM32L например.

        Спасибо, но Вы переоцениваете мои возможности.
        Для меня программисты, работающие на низкоуровневых языках типа Ассемблера — небожители и, как оказалось, на Ардуино тоже можно решать не тривиальные задачи.
          0

          А причем тут асм. STM32 также програмируются на Си как и большинство мк в мире. Более того ARM Asm куда более сложен чем AVR, и имхо лучше без сильной необходимости в него не лезть.
          Также в сети полно страшилок про то, что stm32 мегасложные.
          Но это уже пару лет как не так, благодаря кодогенератору CubeMX все настройки можно за 10 минут выставить и контроллер заведётся. А потом к этому всему обращаться через HAL функции (почти как в ардуине).
          Ну и на крайняк stm32 blue pill eсть в ардуине.
          P. S. HAL при всей моще имеет тот же минус что и ардуина (хоть и гибче) — много универсального кода == много больше места. С другой стороны при 100-200К памяти нет так и страшно.
          P. P. S. Тема использования Cuba и HAL холиварная, но если вы с ардуины на Си съехали то разберётесь.

          +2
          Atmega вполне себе низкопотребляющий, если правильно пользоваться. Если хочется выжать максимум то atmel picopower. Но в данном случае бессмысленно экономить пикоамперы, контроллер тут далеко не на первом месте в потреблении энергии.
            +2

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

            0
            Экран E-ink 2.13 + Контроллер NRF52 < 2мкА потребления в режиме сна
            Либо взять E-ink + Zigbee как на Xiaomi термометрах
              0
              С Zigbee придется же ещё полноценный шлюз организовывать? Это всё проект сильно удорожает.
                0
                Ну да. Как и с Mysensors
                Готовый USB брелок или модуль к малинке стоит порядка 2-х тысяч
                Можно взять SOC на алишке за 500р и спаять самому
                Зато сяоминые, акваровские и др. устройства работаю готовые
                  0
                  С брелком-то ещё хуже всё будет — он же просто функцию координатора играет. Это надо будет разворачивать какую-то систему автоматизации и его туда интегрировать. У автора, кажется, задачи сильно проще)
                0
                Экран E-ink 2.13 + Контроллер NRF52 < 2мкА потребления в режиме сна
                Либо взять E-ink + Zigbee как на Xiaomi термометрах

                Дисплей 2.13 будет маловат для комнатного термостата и метеостанции в одном. В списке желаний на Али уже несколько месяцев e-paper дисплей 3,7 дюйма.
                NRF52832 уже в пути.
                +2
                Снизить энергопотребление в режиме сна можно, если питание внешних модулей коммутировать p-канальным полевиком или даже просто запитать их от одного или нескольких пинов GPIO микроконтроллера. Максимальный ток 20мА на пин.

                VelocidadAbsurda
                Коль уж с Arduino уходим на C/asm, возникает резонный вопрос: может уже «гулять на все» и с самой atmega уйти на что-нибудь специально созданное для низкого потребления? STM8L или STM32L например.

                В ATMega328PA нет ничего плохого. Это уже pico-power контроллер. У неё достаточно возможностей отключать периферию и уходить в глубокий сон с потреблением около 1мкА.
                В данном случае, все равно большую часть потребления уходит на прием и передачу данных. Если датчик находится недалеко, метров 30 до базы, то я бы поставил лучше NRF24L01. У него низкое потребление и высокая скорость передачи. А это значит, что в активном состоянии его нужно держать очень короткое время.

                Объем флеш-памяти, занимаемой в коде в Ардуино — 12968 байт, на С — 5954 байта и оценочно на Ассемблере не больше 200 байт.

                Думаю, что все дело тут в функции println, которая притягивает библиотечную функцию printf. А в ней, в зависимости от настроек, может быть либо только самая базовая функциональность, либо дополнительные параметры форматирования, вещественные числа и т.п.
                В любом случае, большой объем занятой флеш памяти не обязательно означает, что она вся будет исполняться. Чаще всего это просто мертвый груз.
                Также переписывание с С на ассемблер не имеет почти никакого смысла. Зато имеет смысл порой избавиться от стандартной функции printf() и написать свою, если вы выводите, например, только целые числа.
                  0
                  Снизить энергопотребление в режиме сна можно, если питание внешних модулей коммутировать p-канальным полевиком или даже просто запитать их от одного или нескольких пинов GPIO микроконтроллера. Максимальный ток 20мА на пин.

                  Питание периферии с коммутацией транзистором или от пинов контроллера у меня практически во всех проектах.
                  В nRF24L01+ требуется повторная инициализация контроллера радиомодуля. Инициализация несовместима с командами библиотеки LowPower.h, которые задают у меня время сна.
                  Короче, чтобы восстановить настройки радиомодуля после сна надо сделать Reset контроллера, который ним управляет. У меня — это atmega328p. При ресетах с интервалом минута ресурса флеш-памяти хватит на несколько суток.
                  Возможно я заблуждаюсь, еще поиграюсь с nRF24L01+ уж очень низкое у него энергопотребление.
                  Также переписывание с С на ассемблер не имеет почти никакого смысла. Зато имеет смысл порой избавиться от стандартной функции printf() и написать свою, если вы выводите, например, только целые числа

                  Перед отправкой данных с десятыми-сотыми умножаю на 100, а дальше — дело техники.
                    +1
                    При ресетах с интервалом минута ресурса флеш-памяти хватит на несколько суток.

                    При чем ресурс флеш-памяти к ресету? Ее ресурс "расходуется" только при перепрошивке. Или у вас бутлоадер при каждом сбросе перепрошивает контроллер?

                  0
                  Для максимальной экономии лучше отключать потребителей наподобие этого примера:
                  image
                  habr.com/ru/post/386735
                    0

                    А почему нет кода на ассемблер, откуда эти "оценочные" оценки? Очень интересно посмотреть, как Вы уместили в 200 байт шрифты для экранчика и преобразование данных с датчиков.

                      0
                      А почему нет кода на ассемблер, откуда эти «оценочные» оценки? Очень интересно посмотреть, как Вы уместили в 200 байт шрифты для экранчика и преобразование данных с датчиков.

                      Работал с кодом термометра-гидрометра на ATtiny13. Получил объем занимаемой флеш-памяти 40 байт. Навскидку увеличил это число в 5 раз. Подредактирую эту цифру, если будут аргументы.
                      Вот код:
                      термометр-гидрометр на Ассемблере
                      // *********************************************
                      // *** Simple digital thermometer/hygrometer ***
                      // *********************************************
                      // *** (c) SD, 14.03.2016 ***
                      // *********************************************

                      // Based on ATtiny13, AM2303 and MAX7219

                      // **************
                      // *** Clocks ***
                      // **************

                      // MCU clock frequency is 9.6MHz (internal oscillator)
                      // Timer frequency is 75KHz = 9.6MHz/128
                      // (13.3 us between interrupts)

                      #define SKIPNEXT1W (PC + 2)
                      #define DS(var) Y + var - _dataStart

                      // ************
                      // *** Pins ***
                      // ************

                      // MAX7219 output pins
                      .equ MAX_DIN = 0
                      .equ MAX_CS = 1
                      .equ MAX_CLK = 4

                      // AM2302 input pin
                      .equ AM2302_PIN = 3

                      // MAX7219 registers
                      .equ MAX_DECODE = 0x09
                      .equ MAX_INTENSITY = 0x0A
                      .equ MAX_SCANLIMIT = 0x0B
                      .equ MAX_SHUTDOWN = 0x0C
                      .equ MAX_DISPTEST = 0x0F

                      // Temperature measurement state register
                      // Bits 0 - 2 define the byte number being received
                      // Bit 3 is set when there are valid data received
                      // Bits 4 - 7 define the current receiver state
                      .def R_TS = R0

                      // Temperature measurement tick
                      .def R_TT = R1

                      // Temperature data register
                      .def R_TD = R2

                      // Temperature measurement states
                      .equ TMS_NONE = 0x00 // TMS_NONE - do nothing an wait until
                      // somebody changes the state
                      .equ TMS_START = 0x10 // Start of the measurement cycle
                      .equ TMS_ST_LOW = 0x20 // Initial low signal is being sent
                      // (1 ms = 75 timer ticks)
                      .equ TMS_WRSP_LOW = 0x30 // Initial low signal has been sent,
                      // waiting for the response low signal
                      .equ TMS_WRSP_HIGH = 0x40 // Response low signal has been received,
                      // waiting for the response high signal
                      .equ TMS_W1ST_BIT_LOW = 0x50 // Waiting for the first bit low signal
                      .equ TMS_WBIT_HIGH = 0x60 // Waiting for the bit high signal
                      .equ TMS_WBIT_LOW = 0x70 // Waiting for the bit low signal
                      .equ TMS_WHIGH = 0x80 // Waiting for the final high signal

                      // Timer 100Hz tick counter
                      // (counts upwards from 0 to 255)
                      .def R_TICK100 = R3

                      // Timer 16bit 75KHz tick counter
                      // (counts downwords from 749 to 0)
                      .def R_TICKL = R4
                      .def R_TICKH = R5

                      // ************
                      // *** Data ***
                      // ************

                      .dseg
                      _dataStart: // Data start label

                      tempData: .byte 5 // Data, received from the AM2302 sensor
                      displayData: .byte 4 // Decimal printing result

                      .equ DATA_BUF_SIZE = 8 // AM2302 data buffer size in samples
                      // (each sample is 4 bytes)

                      dataBuffer: .byte DATA_BUF_SIZE*4

                      .cseg
                      .org 0

                      // *** Interrupts ***

                      // Reset Handler
                      rjmp start

                      // IRQ0 Handler
                      reti

                      // PCINT0 Handler
                      reti

                      // Timer0 Overflow Handler
                      rjmp timerOvfl

                      // EEPROM Ready Handler
                      reti

                      // Analog Comparator Handler
                      reti

                      // Timer0 CompareA Handler
                      rjmp timerCompA

                      // Timer0 CompareB Handler
                      reti

                      // Watchdog Interrupt Handler
                      reti

                      // ADC Conversion Handler
                      reti

                      // Table to convert decimal digit into 7-segment code
                      hexTable:
                      .db 0b01111110, 0b00110000, 0b01101101, 0b01111001
                      .db 0b00110011, 0b01011011, 0b01011111, 0b01110010
                      .db 0b01111111, 0b01111011

                      start:
                      cli
                      ldi R16, RAMEND
                      out (SPL), R16

                      // Init watchdog (4s interval)
                      wdr
                      ldi R16, (1 << WDCE) | (1 << WDE)
                      out (WDTCR), R16
                      ldi R16, (1 << WDE) | (1 << WDP3)
                      out (WDTCR), R16

                      // Init registers
                      ldi YL, low (_dataStart)
                      ldi YH, high (_dataStart)
                      clr R_TS
                      clr R_TT
                      clr R_TICKL
                      clr R_TICKH
                      clr R_TICK100

                      // Init ports
                      out (PORTB), R_TS
                      ldi R16, (1 << MAX_DIN) | (1 << MAX_CS) | (1 << MAX_CLK)
                      out (DDRB), R16

                      // Init LED driver
                      // Set all digits to "-"
                      ldi XL, 0b00000001
                      ldi XH, 1
                      init1:
                      rcall maxWriteWord
                      cpi XH, 9
                      brne init1

                      // Set control registers
                      ldi XL, 0 // Decode
                      rcall maxWriteWord
                      ldi XL, 4 // Intensity
                      rcall maxWriteWord
                      ldi XL, 7 // Scan limit
                      rcall maxWriteWord
                      ldi XL, 1 // Shutdown
                      rcall maxWriteWord
                      ldi XH, 0x0F
                      ldi XL, 0 // Display test
                      rcall maxWriteWord

                      // Init timer for 1 interrupt each 128 CPU cycles
                      ldi R16, 127
                      out (OCR0A), R16
                      ldi R16, 0b00000110
                      out (TIMSK0), R16
                      ldi R16, 0b00000001
                      out (TCCR0B), R16

                      // First part of the initialization is done.
                      // Enable interrupts
                      sei

                      // Wait 2 sec (while AM2302 initialize itself)
                      // with little animation
                      ldi XH, 1
                      ldi XL, 0
                      init2:
                      ldi R16, 25
                      rcall wait100Hz
                      rcall maxWriteWord
                      cpi XH, 9
                      brne init2

                      // R6 will contain the number of
                      // measurement values received
                      clr R6

                      // R7 will contain the number of
                      // continious errors
                      clr R7

                      loop:
                      // Reset watchdog timer
                      wdr

                      // Initiate measurement
                      ldi R16, TMS_START
                      mov R_TS, R16

                      loop1:
                      // Wait for the TMS_NONE state
                      // which indicates that the measurement
                      // is done
                      sleep

                      mov R16, R_TS
                      andi R16, 0xF0
                      brne loop1

                      // Do we have the valid data?
                      sbrs R_TS, 3
                      loop_error1:
                      rjmp loop_error

                      // Check control sum of the received data
                      ldd R16, DS (tempData)
                      ldd ZL, DS (tempData + 1)
                      add R16, ZL
                      ldd ZL, DS (tempData + 2)
                      add R16, ZL
                      ldd ZL, DS (tempData + 3)
                      add R16, ZL
                      ldd ZL, DS (tempData + 4)
                      cp R16, ZL
                      brne loop_error1

                      // We have valid new measurement data,
                      // reset error count
                      clr R7

                      // Move up data in the buffer
                      // and count the sum at the same time.
                      // R12:R13 will contain the humidity value and
                      // R14:R15 the temperature value
                      clr R12
                      clr R13
                      clr R14
                      clr R15
                      ldi ZL, low (dataBuffer + (DATA_BUF_SIZE - 2)*4)
                      ldi ZH, 0
                      buf1:
                      ldd R16, Z + 0
                      ldd R17, Z + 1
                      std Z + 4, R16
                      std Z + 5, R17
                      add R12, R16
                      adc R13, R17

                      ldd R16, Z + 2
                      ldd R17, Z + 3
                      std Z + 6, R16
                      std Z + 7, R17
                      add R14, R16
                      adc R15, R17

                      subi ZL, 4
                      cpi ZL, low (dataBuffer - 4)
                      brne buf1

                      // Add new humidity value to the buffer
                      // and to the sum
                      ldd R16, DS (tempData + 1)
                      ldd R17, DS (tempData)
                      std DS (dataBuffer + 0), R16
                      std DS (dataBuffer + 1), R17
                      add R12, R16
                      adc R13, R17

                      // Add new temperature value to the buffer
                      // and to the sum
                      ldd R16, DS (tempData + 3)
                      ldd R17, DS (tempData + 2)

                      // Check for a negative value
                      and R17, R17
                      brpl buf2

                      // Convert negative temperature to the 2's
                      // complement form
                      clr ZL
                      andi R17, 0x7F
                      neg R16
                      sbc ZL, R17
                      mov R17, ZL

                      buf2:
                      std DS (dataBuffer + 2), R16
                      std DS (dataBuffer + 3), R17
                      add R14, R16
                      adc R15, R17

                      // Divide the humidity and temperature
                      // sum values by 8 (by shifting them right
                      // three times)
                      ldi R16, 3
                      buf3:
                      asr R15
                      ror R14
                      asr R13
                      ror R12
                      dec R16
                      brne buf3

                      // Do we have 8 full measurements?
                      mov R16, R6
                      cpi R16, 7

                      // If so, use the average values from
                      // the buffer
                      breq buf4

                      // Otherwise use the latest measurement
                      ldd R12, DS (dataBuffer + 0)
                      ldd R13, DS (dataBuffer + 1)
                      ldd R14, DS (dataBuffer + 2)
                      ldd R15, DS (dataBuffer + 3)
                      inc R6

                      buf4:
                      // Print out values

                      // *** Humidity ***
                      movw X, R12
                      rcall printDecX

                      ldi XH, 1
                      ldd XL, DS (displayData + 3)
                      rcall maxWriteWord

                      ldd XL, DS (displayData + 2)
                      ori XL, 0x80
                      rcall maxWriteWord

                      ldd XL, DS (displayData + 1)
                      rcall maxWriteWord

                      ldd XL, DS (displayData)
                      rcall maxWriteWord

                      // *** Temperature ***
                      movw X, R14

                      // Check for a negative value
                      and XH, XH
                      brpl buf5

                      // Calculate the absolute value
                      clr ZL
                      neg XL
                      sbc ZL, XH
                      mov XH, ZL

                      buf5:
                      rcall printDecX

                      ldi XH, 5
                      ldd XL, DS (displayData + 3)
                      rcall maxWriteWord

                      ldd XL, DS (displayData + 2)
                      ori XL, 0x80
                      rcall maxWriteWord

                      ldd XL, DS (displayData + 1)
                      rcall maxWriteWord

                      // If temperature is negative
                      // write the minus sign to the first digit
                      // (temperatures of -100.0 and below
                      // are not supported anyway)
                      ldd XL, DS (displayData)
                      and R15, R15
                      brpl SKIPNEXT1W
                      ldi XL, 1
                      rcall maxWriteWord

                      loop2:
                      // Wait for 1 sec
                      ldi R16, 100
                      rcall wait100Hz

                      // And repeat
                      rjmp loop

                      loop_error:
                      // An error had occured.
                      // Increment error count
                      inc R7

                      // Do we have 3 or more errors in a row?
                      mov R16, R7
                      cpi R16, 3

                      // No? Just do nothing
                      brne loop2

                      // Prevent error count from growing
                      dec R7

                      // Display error
                      ldi ZL, low (errText*2)
                      ldi ZH, high (errText*2)
                      rcall maxWrite8Bytes
                      rjmp loop2

                      errText:
                      // "Sn Error"
                      .db 0b00000101, 0b00011101, 0b00000101, 0b00000101
                      .db 0b01001111, 0b00000000, 0b00010101, 0b01011011

                      // **********
                      // Waits given number (R16) of 100Hz ticks
                      // Uses: Z
                      wait100Hz:
                      // Enable sleep
                      ldi ZL, 0b00100000
                      out (MCUCR), ZL

                      mov ZL, R_TICK100
                      w100:
                      sleep
                      mov ZH, R_TICK100
                      sub ZH, ZL
                      cp ZH, R16
                      brcs w100
                      ret

                      // Timer interrupt

                      timerOvfl:
                      timerCompA:
                      push R16
                      in R16, (SREG)
                      push R16
                      push ZL
                      push ZH

                      // Receive AM2303 data
                      rcall am2302proc

                      // Decrement current 75KHz tick
                      ldi R16, 1
                      sub R_TICKL, R16
                      brcc timerRet
                      sub R_TICKH, R16
                      brcc timerRet

                      // Initialize 75KHz tick value
                      ldi ZL, low (750 - 1)
                      ldi ZH, high (750 - 1)
                      movw R_TICKL, Z

                      // Increment current 100Hz tick
                      inc R_TICK100

                      timerRet:
                      pop ZH
                      pop ZL
                      pop R16
                      out (SREG), R16
                      pop R16
                      reti

                      // **************
                      // *** AM2302 ***
                      // **************

                      amStart:
                      // Send the start low signal.
                      // Switch corresponding PORTB pin to output
                      // (there is already 0 in the PORTB register)
                      sbi (DDRB), AM2302_PIN
                      ldi R16, TMS_ST_LOW
                      rjmp amSetState

                      amStartLow:
                      // Initial start low signal is being sent.
                      // Wait for 75 ticks
                      cpi R16, 75
                      brne amNone

                      // Switch PORTB pin back to input
                      cbi (DDRB), AM2302_PIN
                      ldi R16, TMS_WRSP_LOW

                      // Do not check AM2303 input pin at this tick
                      // since it's possible that it has not recovered
                      // from the low state yet.
                      rjmp amSetState

                      amWRespLow:
                      // Waiting for the response low signal
                      sbrc ZH, AM2302_PIN
                      ret

                      ldi R16, TMS_WRSP_HIGH
                      rjmp amSetState

                      amWRespHigh:
                      // Waiting for the response high signal
                      sbrs ZH, AM2302_PIN
                      ret

                      ldi R16, TMS_W1ST_BIT_LOW
                      rjmp amSetState

                      amW1StBitLow:
                      // Waiting for the first bit low signal
                      sbrc ZH, AM2302_PIN
                      ret

                      // Get ready to receive the first bit
                      ldi R16, 1
                      mov R_TD, R16

                      // Set new state and reset the byte counter
                      ldi ZL, TMS_WBIT_HIGH
                      rjmp amSetState2

                      amBitHigh:
                      sbrs ZH, AM2302_PIN
                      ret

                      // If the bit low signal was there too long
                      // (longer than 5 ticks (5*13.3 = 66.5us)
                      // something went wrong)
                      cpi R16, 6
                      brcc amResetState

                      ldi R16, TMS_WBIT_LOW
                      rjmp amSetState

                      am2302proc:
                      // First, check for the TMS_NONE state.
                      // In this case just do nothing to
                      // not waste MCU cycles.
                      mov ZL, R_TS
                      andi ZL, 0xF0

                      cpi ZL, TMS_NONE
                      breq amNone

                      // Increment receiver tick
                      inc R_TT

                      // If we are waiting for too long,
                      // something went wrong, reset the state
                      breq amResetState

                      // Save the current tick into a more
                      // convenient register
                      mov R16, R_TT

                      // Get input signal
                      in ZH, (PINB)

                      // Branch depending on the current state.
                      // Check for TMS_WBIT_LOW first since it
                      // has the longest service routine
                      cpi ZL, TMS_WBIT_LOW
                      breq amBitLow

                      cpi ZL, TMS_START
                      breq amStart

                      cpi ZL, TMS_ST_LOW
                      breq amStartLow

                      cpi ZL, TMS_WRSP_LOW
                      breq amWRespLow

                      cpi ZL, TMS_WRSP_HIGH
                      breq amWRespHigh

                      cpi ZL, TMS_W1ST_BIT_LOW
                      breq amW1StBitLow

                      cpi ZL, TMS_WBIT_HIGH
                      breq amBitHigh

                      cpi ZL, TMS_WHIGH
                      breq amWHigh

                      amResetState:
                      // In case of an error, reset state to
                      // the default TMS_NONE
                      ldi R16, TMS_NONE

                      amSetState:
                      // Preserve the current byte number
                      mov ZL, R_TS
                      andi ZL, 0x07
                      or ZL, R16

                      amSetState2:
                      mov R_TS, ZL

                      // Clear receiver tick counter
                      clr R_TT

                      amNone:
                      ret

                      amBitLow:
                      sbrc ZH, AM2302_PIN
                      ret

                      // The high bit signal was too long?
                      cpi R16, 8
                      brcc amResetState

                      // Store input bit (inverted, since cpi produces
                      // inverted result in the carry flag)
                      cpi R16, 4
                      rol R_TD

                      // Initally we set R_TD to 1, so when all 8
                      // bits are received, the carry flag will be set
                      // indicating that a full byte has been received.
                      // Otherwise, receive the next bit
                      ldi R16, TMS_WBIT_HIGH
                      brcc amSetState

                      // We have the full byte. Invert it
                      com R_TD

                      // Save it
                      mov ZL, R_TS
                      andi ZL, 0x07
                      subi ZL, low (-tempData)
                      ldi ZH, high (tempData)
                      st Z+, R_TD

                      // Did we receive all 5 bytes?
                      cpi ZL, low (tempData + 5)
                      ldi R16, TMS_WHIGH
                      breq amSetState

                      // OK, receive the next byte.
                      // Increment the byte counter
                      inc R_TS

                      // Initialize R_TD
                      ldi R16, 1
                      mov R_TD, R16

                      ldi R16, TMS_WBIT_HIGH
                      rjmp amSetState

                      amWHigh:
                      sbrs ZH, AM2302_PIN
                      ret

                      cpi R16, 6
                      brcc amResetState

                      // We received everything. Set
                      // the state to TMS_NONE and set
                      // the data validity bit
                      ldi R16, 0x08
                      mov R_TS, R16
                      ret

                      // *********

                      /*
                      // Write data from Z
                      // Uses R16 - R19, X, Z
                      maxWriteData:
                      lpm XH, Z+
                      tst XH
                      brne SKIPNEXT1W
                      ret
                      lpm XL, Z+
                      rcall maxWriteWord
                      rjmp maxWriteData

                      maxInit:
                      .db MAX_DECODE, 0
                      .db MAX_INTENSITY, 4
                      .db MAX_SCANLIMIT, 7
                      .db MAX_SHUTDOWN, 1
                      .db MAX_DISPTEST, 0
                      .db 0, 0

                      maxTest:
                      .db 0, 0b00011101, 0b00010101, 0b00010000, 0b00011100, 0b00111101, 0b00000101, 0b01110111
                      */

                      // Writes 8 bytes from (Z) (program memory)
                      // to MAX7219
                      // Uses R16 - R19, X, Z
                      maxWrite8Bytes:
                      ldi XH, 0x01

                      mw8b1:
                      lpm XL, Z+
                      rcall maxWriteWord
                      cpi XH, 9
                      brne mw8b1
                      ret

                      // Write word X (XL = data, XH = address) to MAX2719
                      // Uses R16 - R19, X
                      maxWriteWord:
                      // Set all pins to zero
                      in R17, (PORTB)
                      andi R17, ~((1 << MAX_DIN) | (1 << MAX_CS) | (1 << MAX_CLK))
                      out (PORTB), R17

                      ldi R19, (1 << MAX_CLK)

                      mov R16, XH
                      rcall mww1

                      mov R16, XL
                      rcall mww1

                      // Set LOAD(CS) to high thus writing all 16 bits into
                      // MAX register
                      sbi (PORTB), MAX_CS

                      // Increment MAX register number
                      inc XH
                      ret

                      mww1:
                      ldi R18, 8

                      mww2:
                      bst R16, 7
                      bld R17, MAX_DIN
                      out (PORTB), R17

                      lsl R16
                      dec R18

                      // Create clock impulse by toggling clock output twice
                      out (PINB), R19
                      out (PINB), R19

                      brne mww2
                      ret

                      // *********

                      printDecX:
                      ldi ZH, low (1000)
                      ldi R16, high (1000)
                      rcall pdx

                      // Change zero digit to empty space
                      cpi ZL, 0b01111110
                      brne SKIPNEXT1W
                      ldi ZL, 0
                      std DS (displayData), ZL

                      ldi ZH, 100
                      ldi R16, 0
                      rcall pdx

                      // If this digit is zero and the first
                      // digit is empty (i.e. it was zero too)
                      // change this digit to empty space
                      ldi R16, 0b01111110
                      eor R16, ZL
                      ldd ZH, DS (displayData)
                      or R16, ZH
                      brne SKIPNEXT1W
                      ldi ZL, 0
                      std DS (displayData + 1), ZL

                      ldi ZH, 10
                      ldi R16, 0
                      rcall pdx
                      std DS (displayData + 2), ZL

                      mov ZL, XL
                      rcall pdx3
                      std DS (displayData + 3), ZL

                      // Clear carry flag to indicate that
                      // no error occurred
                      clc
                      ret

                      pdx:
                      ldi ZL, 0
                      pdx1:
                      sub XL, ZH
                      sbc XH, R16
                      brcs pdx2

                      cpi ZL, 9
                      breq pdxOverflow
                      inc ZL
                      rjmp pdx1

                      pdx2:
                      add XL, ZH
                      adc XH, R16

                      pdx3:
                      subi ZL, -low (hexTable << 1)
                      ldi ZH, high (hexTable << 1)
                      lpm ZL, Z
                      ret

                      pdxOverflow:
                      // Set carry flag to indicate error
                      sec

                      // Pop return address out of the stack
                      // so we can return to the caller of printDecX
                      pop R16
                      pop R16
                      ret

                        0
                        Аргументы:
                        — Датчик другой
                        — Экран другой
                        Приведённый код не имеет смысла применительно к описанному проекту.
                          +2
                          Вы свой код собрать пробовали?

                          ATtiny13 memory use summary [bytes]:
                          Segment Begin End Code Data Used Size Use%
                          ---------------------------------------------------------------
                          [.cseg] 0x000000 0x0002f4 738 18 756 1024 73.8%
                          [.dseg] 0x000060 0x000089 0 41 41 64 64.1%
                          [.eseg] 0x000000 0x000000 0 0 0 64 0.0%

                          Assembly complete, 0 errors. 0 warnings


                          Видимо, разницы между 40 байтами и 738 байтами совсем нет. И это мы еще никак знакогенератор на несколько шрифтов для экранчика не трогали.
                          На уровне таких «оценок» и фраз
                          Код на Ассемблере уменьшает размер памяти на порядки. Соответственно пиковое потребление падает в сотни раз.
                          адекватность всего остального в статье вызывает крайне высокие сомнения. Не очень понимаю, как размер кода влияет на энергоэффективность. Настоящий специалист может и в пустом цикле крутить на полную мощность ядра.
                            –1
                            Ну, если Вы не понимаете как размер кода влияет на энергопотребление, тогда о чем говорить. Посмотрите, пожалуйста, техописание комнатного термостата стр 11 — это оттуда: Напряжение питания 2 x 1.5V алкальных батарейки (LR6 AA), Потребляемая мощность 1.3 mW, Срок действия батареек ~ 1 год.
                            Вряд ли прибор прошит в коде Ардуино или у Вас есть сомнения? )

                            Далее, для примера, один из тестовых скетчей и короткий фрагмент таблицы с результатами теста. Таких таблиц с записями на 1300-1500 строк и тестовых скетчей у меня десяток. В ближайшее время размещу их в каком-нибудь облаке, чтобы рассеять сомнения. Моя статья и так перегружена, поэтому не стал не размещать тестовые скетчи, таблицы, видео (гифки) с измерениями тока и т.п.

                            тестовый скетч
                            /*скетч для измерения операционного времени приемником LoRa и макc. операц. тока
                            операционное время 0,083сек (83 мсек)
                            операционный ток 11.5 mA (Vbat=3V, 8 MHz)
                            */

                            #include <avr/io.h>
                            #include <util/delay.h>

                            #include <SPI.h>
                            #include <LoRa.h>
                            #include <LowPower.h>
                            #include "HTU21D.h"
                            #include <LCD5110_Graph.h>

                            String LoRaData;

                            void setup() {
                            Serial.begin(38400);
                            Serial.println("Power ON!");

                            int counter = 0;
                            while (!LoRa.begin(433E6) && counter < 10) {
                            Serial.println("Не удалось найти LoRa-передатчик!");
                            counter++;
                            _delay_ms(500);
                            }

                            // Диапазон для синхрослова – между "0-0xFF".
                            LoRa.setSyncWord(0xF3);
                            Serial.println("Прослушивание эфира. Ожидание пакета с в.датчика ...");

                            _delay_ms(2000);
                            }

                            void loop() {
                            int packetSize = LoRa.parsePacket();
                            Serial.println("Прослушивание эфира.");
                            if (packetSize) {
                            while (LoRa.available()) {
                            LoRaData = LoRa.readString();
                            }
                            int pos1 = LoRaData.indexOf('#');
                            if ((LoRaData).substring(pos1, pos1 + 1) == "#")
                            {
                            Serial.println("Принято, декодировано!");
                            }
                            }
                            }

                            int main() {
                            init();
                            setup();

                            for (;;) {
                            loop();
                            }
                            }


                            таблица
                            10:53:29.744 -> Tin=21.90 Hin=41 BatIn=3.31 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:30.144 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:30.544 -> Tin=21.90 Hin=41 BatIn=3.27 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:30.944 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:31.304 -> Tin=21.90 Hin=41 BatIn=3.31 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:31.704 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:32.104 -> Tin=21.80 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:32.504 -> Tin=21.90 Hin=41 BatIn=3.31 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:32.904 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:33.304 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:33.704 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:34.064 -> Tin=21.90 Hin=41 BatIn=3.31 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:34.464 -> Tin=21.80 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:34.866 -> Tin=21.90 Hin=41 BatIn=3.31 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:35.266 -> Tin=21.90 Hin=41 BatIn=3.31 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:35.666 -> Tin=21.90 Hin=41 BatIn=3.31 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:36.066 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:36.466 -> Tin=21.90 Hin=41 BatIn=3.27 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:36.866 -> Tin=21.90 Hin=41 BatIn=3.32 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:37.226 -> Tin=21.80 Hin=41 BatIn=3.31 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:37.626 -> Tin=21.90 Hin=41 BatIn=3.29 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:38.026 -> Tin=21.90 Hin=41 BatIn=3.29 Tout=00.00 Hout=00 BatOut=BGood
                            10:53:38.426 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood


                            Если уж сильно захочется посмотреть видео на тему потребления, тогда сюда. Тут подобных гифок хватает.

                            Я к тому, что отношусь к проблеме серьезно, а Вы делаете обобщающие выводы по одной неточности. Успехов!
                              0
                              То есть, вопрос с размером кода уже замылился? Думаю, что смысла доказывать и про энергопотребление тогда нет. Вам удачи и новых чудесных открытий!
                                0
                                если Вы не понимаете как размер кода влияет на энергопотребление

                                Если и влияет, то очень косвенно. Практически — никак, ни на пиковое, ни на среднее.
                                  0
                                  Готов поспорить: размер кода > количество операций > энергопотребление. Ведь очевидно, что на две операции затраты меньше, чем, допустим, на 10 таких же. Что легче: вытащить 2 ведра воды из колодца или 10?
                                    0
                                    2 операции, выполняющиеся непрерывно в цикле, или 100 операций, выполняющиеся однократно раз в 10 секунд. По Вашей же аналогии — вытащить 2 ведра из колодца одним махом или по 1 литру в день за 20 дней.
                                    Если же говорить о более продвинутых контроллерах с настраиваемой частотой, DMA и т.д., то примеров можно привести множество.
                                      0
                                      Читаем еще раз :
                                      … Ведь очевидно, что на две операции затраты меньше, чем, допустим, на 10 таких же.

                                      И неважно, как они выполняются — одним махом или не одним.
                                        0
                                        Вы не признаете режимы энергосбережения микроконтроллеров? :) Ведь совершенно неочевидно, что код с меньшим объемом будет более энергоэффективным. Можете проверить сами — напишите прошивку, в которой функция main() будет содержать только строку:
                                        while (1);
                                        и сравните объем кода и энергопотребление этой прошивки и Вашей прошивки метеостанции из статьи. А если хотите еще большего контраста, то напишите не пустой бесконечный цикл, а бесконечный цикл с отправкой пары байт через радиоканал без всяких powerdown и sleep :)
                              0
                              Вот код:

                              Как-то совсем не похоже на 40 байт кода. Одна только таблица прерываний и таблица преобразования в 7-сегментное отображение уже тянут на 30 байт. И это только самое начало всей портянки кода.
                                +1
                                Да, вы правы! Дикая описка и выводы соответственно.
                            0
                            Не всегда понимаю чем руководствуются когда ставят тип переменной int если в эту переменную максимум что прилетает или записывается в самом начале в виде константы нечто в пределах 0-255.
                              0
                              Учту на будущее. Как-то не было проблем до сих пор. Спасибо!
                              +1
                              Первый раз слышу про «спячку» BM*280 от дёрганья питания. Мануал вполне однозначен: при сбросе/подаче питания датчик в режиме «sleep» — 0.1uA потребление, дальше — командуем по I2C.
                              Ардуиновские библиотеки, ЕМНИП, переключают датчик в «normal» режим и обратно есс-но не отключают. А это — измерения раз в секунду. «Правильный» ход — поправить используемую библиотеку (точнее запись в регистр ctrl_meas) для работы в «forced» режиме или написать свою обёртку. Я писал свою, чтобы читать сразу все данные за один запрос по I2C.

                              Javian, Крутовато для поделок :) Использовал похожий приём — супервизор на примерно 2.1В в качестве источника прерываний и полевик на отключение себя от батарейки. Дальше спим без watchdog-ов, пока не сработает IRQ0, на остатках конденсатора подключаем батарейку обратно. Идея долго бродила, но оформилась после чтения комментов сайта hallard.me про Ultra Low Power Node.

                              ЗЫ: прошелся по ссылке и нашел себя же во втором комменте. Идём на следующий круг )))
                                0

                                Ардуиновские библиотеки (конкретно от Adafruit) умеют выставлять режим датчика

                                0

                                немного не понял зачем Лора? выносной блок на км удалён? хотя Лора интересно, я не рискнул работать на 433- очень много помех, решил делать на 868 на 1276. интересно, что мне не удалось добиться потребления в режиме передачи, как в даташитах, спит, да 2мка или что то на грани чувствительности мультиметра. Что с реальным потреблением у Ра01?

                                  +1
                                  А почему бы и не Лора? Ra-01 это SX1278. В sleep там наноамперы какие-то. И с помехоусточивостью всё отлично.
                                    0
                                    А почему бы и не Лора?

                                    Поддерживаю. Да и мощности в пределах квартиры достаточно 4 дБ.
                                  +2
                                  Ассемблер даст выигрыш в размере кода ну может процентов 10 по сравнению с Си. Это если проект не большой, сотня-две байт. Если проект больше, то выигрыш в размере кода стремится к нулю, а в больших проектах может и Си скомпилировать меньший код за счёт лучшей оптимизации. В общем не тот это случай чтобы париться с асмом. Не пользуйтесь printf(), float, string и что там ещё у вас многожрущее. И самый главный выигрыш — нафиг ардуину, пишите на обычном си.
                                    0
                                    Ассемблер даст выигрыш в размере кода ну может процентов 10 по сравнению с Си. Это если проект не большой, сотня-две байт. Если проект больше, то выигрыш в размере кода стремится к нулю, а в больших проектах может и Си скомпилировать меньший код за счёт лучшей оптимизации. В общем не тот это случай чтобы париться с асмом. Не пользуйтесь printf(), float, string и что там ещё у вас многожрущее. И самый главный выигрыш — нафиг ардуину, пишите на обычном си.

                                    Спасибо! Коротко, внятно и однозначно.
                                    0
                                    Добавлю немного своего экспириенса. Использую датчики построенные на довольно близкой базе: Mega328P@12MHz, Ra-01, HTU21D, TPS61291, 2xAAA (щелочные «мизинчики»). Связь информационным пакетом в одну сторону на хост, пакет 11 байт, мощность 14дБм, диапазон 433МГц. Дальше режим приёма длительностью 10 символов для получения возможного управляющего пакета от хоста. Везде где можно сон в моде PowerDown. Не нужную периферию отключать. Никаких повторных пересылок я не делаю, наоборот, используется адаптивный интервал, чтобы не флудить избыточными данными. Прошивка объёмом 9412 байт собрана с использованием выпотрошенных и отученных либ от ардуины. Самые старые элементы питания живут уже 15 месяцев, умирать пока не спешат. Больше времени с последнего апгрейда HW+FW просто не прошло. Потерь и повреждений данных я не фиксировал ни разу (отдельные случаи потерь за весь период, конечно, возможны, на общей картине не видно). Радиоканал работает на 25+ метров с несколькими деревянными перекрытиями, стенами, стальной крышей на пути, сигнал LoRa хороший и стабильный. Узкополосный сигнал в диапазоне 2.4ГГц (всевозможные nRF24L01+) ничего подобного и близко не показал. Но да, хост у меня не на батарейках, там режим приёма постоянный.
                                      0
                                      В статье добавил ссылку на архив с набором тестовых скетчей для измерений времени выполнения и тока операций.
                                      Результаты измерений, как и раньше, — в таблице статьи.
                                        0
                                        Несколько лет как сделал «умную квартиру». Сейчас уже вторая квартира. Для климатических датчиков использую связку Arduino Mini Pro 8Мгц nRF24L01+ ds18b20 am2320 и все это на аккумуляторе 16340. Библиотеку для связи использую mysensors. Никаких хитростей типа отключения питания датчикам или радиомодулю не использую. Так же не хочу заменять на что то ds18b20 по тому что ему верю больше всех (проводил испытания разных). Хотя часто читаю что он якобы не пригоден для батарейного питания. Код примитивен. Передаю данные раз в 5 минут. При этом датчики живут полгода — год на одном аккумуляторе легко. Все кроме уличного. Уличный кроме того что морозится так еще находится дальше всех от базы. С ним постоянные танцы. Если уличный питать батарейками — то мороз по ним бьет прямо сразу. Я к чему это все — достаочно поставить один аккумулятор 18650 в базу и перехать на 8 мгц и больше никаких танцев по моему не надо. Ну это если надо устройство работающее от аккумулятора а не стремится повторить энергопотребление промышленных устройств.
                                        Еще заметка с опытом выяснилось что сам модуль nRF24L01+ надо подбирать. Есть модули которые спят с током 6мА а есть которые с 0.05мА (ниже я уже не измеряю). BM*280 и правда надо усыплять програмно. Вешал его питание на ногу микроконтроллера. Так вот даже опустив ногу в ноль датчик умудрялся есть аккумулятор. Так что его нужно принудительно усыплять. Ну а точнее перевести в режим единичного измерения.
                                          0
                                          При этом датчики живут полгода — год на одном аккумуляторе легко. Все кроме уличного. Уличный кроме того что морозится так еще находится дальше всех от базы. С ним постоянные танцы. Если уличный питать батарейками — то мороз по ним бьет прямо сразу.

                                          А почему бы не вывести за пределы помещения тонким кабелем только датчик ds18b20, а остальное оставить в помещении. В промышленных образцах реализовано именно такое решение.
                                            0
                                            ну во первых на улице не только ds18b20 но еще датчик влажности и давления. т.е получится что почти все надо будет оставить на улице а в квартиру тащить только батарейки и может быть радиомодуль. странное решение выдет. к тому же не красивый проводок и где то установленная коробка с батарейками. жена не оценит такую красоту
                                              0
                                              … к тому же не красивый проводок и где то установленная коробка с батарейками. жена не оценит такую красоту

                                              Тоже есть варианты:
                                              • Красивый многожильный проводок. Плюс красивая коробочка для жены. Замечу, красивая коробочка — это работа посложнее. Эстетику разделения проводка на два — оставить на суд голубей.
                                              • По результатам своих наблюдений внести корректировку температуры в скетч и оставить только am2320.
                                              • Побороть недоверие, разобравшись в его причинах.
                                              • Продолжать гробить не дешевые аккумуляторы, оставив все как есть.

                                                0
                                                Причины недоверия — я тестировал разные датчики. Кстати на самом деле am2320 весьма нормально показывает. Но веры все равно больше ds18b20. Когда я устал от того что десяток термометров стоящих радом (и иногда и в одном корпусе) показывает совершенное разное я купил поверенный термометр ВИТ и уже всех сравнивал с ним. Влажность кстати как оказалось am2320 тоже показывает весьма хорошо. Но у них опять же есть такое что конкретные экземпляры немного отличаются.
                                                Сейчас повесил за окно датчик уже на здоровом аккумуляторе. Работает стабильно )
                                                даже на narodmon вытащил свои данные.
                                            0
                                            Автономная метеостанция на батарейках. Цена 4$ на Али.
                                            esp8266.ru/forum/threads/tlsr8251-lcd-termometr-lywsd03mmc-xiaomi-bluetooth-termometr.5263

                                            Only users with full accounts can post comments. Log in, please.