Беспроводной датчик температуры, влажности и атмосферного давления на nRF52832

    Приветствую всех читателей Habr! Сегодняшняя статья будет о датчике температуры, влажности и атмосферного давления c длительным сроком работы от одной батарейки. Датчик работает на микроконтроллере nRF52832 (даташит). Для получения температуры, влажности и атмосферного давления использован сенсор BME280 — даташит. Датчик работает от батареек CR2430/CR2450/CR2477. Потребление в режиме передачи составляет 8мА, в режиме сна 5мкА. Итак, обо всем попорядку.


    Это Arduino проект, программа написана в Arduino IDE, Для работы сенсора BME280 использована библиотека Adafruit Industries — гитхаб сенсоры | гитхаб BME280. Для работы с платами nRF52832 в Arduino IDE использован проект Sandeep Mistry — гитхаб. Передача данных на контролер Умного Дома осуществляется по протоколу Mysensors — гитхабплаты | протокол|библиотека.

    Плата датчика разрабатывалась в программе Диптрейс. Размеры платы 36.8мм Х 25мм.



    Перечень используемых компонентов
    • С1 — cap0603 100nF
    • C2 — cap0603 100nF
    • R1 — res0603 332
    • R2 — res0603 85b
    • R3 — res0603 113
    • R4 — res0603 912
    • R5 — res0603 113
    • R6 — res0603 512
    • R7 — res0603 512
    • RGBL1 — led0805 rgb
    • SWD — PPHF 2x3 6p 1.27mm
    • U1 — YJ-16048 nRF52832
    • U2 — BME280
    • CONNECT — тактовая кнопка KLS7-TS5401
    • RESET — тактовая кнопка KLS7-TS5401
    • Держатель батареи KW-BS-2450-2-SMT
    • Переключатель dsc0012


    Плата заказывалась через сайт jlcpcb.com — 2$ за 5 штук в любом цвете.





    Ссылка на архив с гербер файлами

    Датчик работает по протоколу Mysensors. Добавить любое устройство в сеть Mysensors достаточно просто. Давайте разберемся на примере этого датчика, пояснения по коду самого датчиком BME280 я опущу, там ничего не меняется при работе с сети Mysensors.

    #define MY_DEBUG // Вывод дебага
    #define MY_RADIO_NRF5_ESB // Выбор типа радиопередатчика(возможные варианты rfm69, rfm95, nrf24l01, nrf51-52)
    #define MY_RF24_PA_LEVEL (NRF5_PA_MAX) // выбор уровня радиосигнала
    #define MY_DISABLED_SERIAL // отключение сериала
    #define MY_PASSIVE_NODE // режим работы ноды(устройство в сети mysensors), PASSIVE означает что отключены все процедуры контроля транспортного уровня, обновление по воздуху, шифрование, безопасность
    #define MY_NODE_ID 1  // ручное назначение идентификатора ноды
    #define MY_PARENT_NODE_ID 0 // ручное назначение идентификатора гейта или ретранслятора
    //#define MY_PARENT_NODE_IS_STATIC // атрибут PARENT_NODE отвечаюший за отключение функционала автоматического перестроения маршрута
    //#define MY_TRANSPORT_UPLINK_CHECK_DISABLED // отключение контроля работоспособности транспортного уровня
    
    #include <MySensors.h> // - библиотека MySensors
    
    #define TEMP_CHILD_ID 0 // назначение идентификатора сенсора температуры
    #define HUM_CHILD_ID 1 // назначение идентификатора сенсора влажности
    #define BARO_CHILD_ID 2 // назначение идентификатора сенсора атмосферного давления
    #define CHILD_ID_VOLT 254 // назначение идентификатора сенсора заряда батареи
    
    MyMessage voltMsg(CHILD_ID_VOLT, V_VOLTAGE);  // инициализация сообщения для сенсора заряда батареи
    MyMessage temperatureMsg(TEMP_CHILD_ID, V_TEMP);  // инициализация сообщения для сенсора температуры
    MyMessage humidityMsg(HUM_CHILD_ID, V_HUM);  // инициализация сообщения для сенсора влажности
    MyMessage pressureMsg(BARO_CHILD_ID, V_PRESSURE);  // инициализация сообщения для атмосферного давления
    
    Презентация датчиков и сенсоров в контролер умного дома:

      sendSketchInfo("BME280 Sensor", "1.0");  // Название датчика, версия ПО
      present(CHILD_ID_VOLT, S_MULTIMETER, "Battery");  // Презентация сенсора заряда батареи, тип сенсора, описание
      present(TEMP_CHILD_ID, S_TEMP, "TEMPERATURE [C or F]");  // Презентация сенсора температуры, тип сенсора, описание
      present(HUM_CHILD_ID, S_HUM, "HUMIDITY [%]");  // Презентация сенсора влажности, тип сенсора, описание
      present(BARO_CHILD_ID, S_BARO, "PRESSURE [hPa or mmHg]");  // Презентация сенсора атмосферного давления, тип сенсора, описание
    

    Передача данных на контролер умного дома:
    send(voltMsg.set(batteryVoltage));  // отправка данных о заряде батарейки в mW
    sendBatteryLevel(currentBatteryPercent);   // отправка данных о заряде батарейки в %
    send(temperatureMsg.set(temperature, 1));  // отправка данных о температуре, точность 1 знак после запятой
    send(humidityMsg.set(humidity, 0));  // отправка данных о влажности, точность 0 знаков после запятой
    send(pressureMsg.set(pressure, 0));  // отправка данных о давлении, точность 0 знаков после запятой
    


    Ардуино код программы
    #include <Wire.h>
    #include <SPI.h>
    #include <Adafruit_Sensor.h>
    #include <Adafruit_BME280.h>
    //#define MY_DEBUG
    #define MY_RADIO_NRF5_ESB
    #define MY_RF24_PA_LEVEL (NRF5_PA_MAX)
    #define MY_DISABLED_SERIAL
    #define MY_PASSIVE_NODE
    #define MY_NODE_ID 1
    #define MY_PARENT_NODE_ID 0
    //#define MY_PARENT_NODE_IS_STATIC
    //#define MY_TRANSPORT_UPLINK_CHECK_DISABLED
    
    #include <MySensors.h>
    
    bool sleep_flag;
    bool metric = true;
    bool last_sent_value;
    
    uint16_t currentBatteryPercent;
    uint16_t lastBatteryPercent = 1000;
    uint16_t battery_vcc_min = 2150;
    uint16_t battery_vcc_max = 2950;
    uint16_t batteryVoltage;
    uint16_t battery_alert_level = 25;
    
    uint32_t default_sleep_time = 60000;
    uint32_t SLEEP_TIME;
    uint32_t newmillisforbatt;
    uint32_t battsendinterval = 3600000;
    
    float tempThreshold = 0.5;
    float humThreshold = 5;
    float presThreshold = 1;
    float pres_mmThreshold = 1;
    float temperature;
    float pressure;
    float pressure_mm;
    float humidity;
    float lastTemperature = -1;
    float lastHumidity = -1;
    float lastPressure = -1;
    float lastPressure_mm = -1;
    
    #define SEALEVELPRESSURE_HPA (1013.25)
    Adafruit_BME280 bme;
    
    #define TEMP_CHILD_ID 0
    #define HUM_CHILD_ID 1
    #define BARO_CHILD_ID 2
    #define CHILD_ID_VOLT 254
    
    MyMessage voltMsg(CHILD_ID_VOLT, V_VOLTAGE);
    MyMessage temperatureMsg(TEMP_CHILD_ID, V_TEMP);
    MyMessage humidityMsg(HUM_CHILD_ID, V_HUM);
    MyMessage pressureMsg(BARO_CHILD_ID, V_PRESSURE);
    
    
    void preHwInit() {
      pinMode(21, INPUT);
      pinMode(25, OUTPUT);
      digitalWrite(25, HIGH);
      pinMode(26, OUTPUT);
      digitalWrite(26, HIGH);
      pinMode(27, OUTPUT);
      digitalWrite(27, HIGH);
    }
    
    
    void before() {
      NRF_POWER->DCDCEN = 1;
      NRF_NFCT->TASKS_DISABLE = 1;
      NRF_NVMC->CONFIG = 1;
      NRF_UICR->NFCPINS = 0;
      NRF_NVMC->CONFIG = 0;
      if (NRF_SAADC->ENABLE) {
        NRF_SAADC->TASKS_STOP = 1;
        while (NRF_SAADC->EVENTS_STOPPED) {}
        NRF_SAADC->ENABLE = 0;
        while (NRF_SAADC->ENABLE) {}
      }
      pinMode(BLUE_LED, OUTPUT);
      pinMode(RED_LED, OUTPUT);
      digitalWrite(BLUE_LED, HIGH);
      digitalWrite(RED_LED, HIGH);
      digitalWrite(27, LOW);
    }
    
    void setup()
    {
      digitalWrite(27, HIGH);
      bme_initAsleep();
      wait(100);
      sendBatteryStatus();
      wait(100);
    }
    
    
    void presentation()  {
      sendSketchInfo("EFEKTA BME280 Sensor", "1.2");
      present(CHILD_ID_VOLT, S_MULTIMETER, "Battery");
      present(TEMP_CHILD_ID, S_TEMP, "TEMPERATURE [C or F]");
      present(HUM_CHILD_ID, S_HUM, "HUMIDITY [%]");
      present(BARO_CHILD_ID, S_BARO, "PRESSURE [hPa or mmHg]");
    }
    
    
    void loop() {
      wait(10);
      bme.takeForcedMeasurement();
      wait(100);
      sendData();
      if (millis() - newmillisforbatt >= battsendinterval) {
        sleep_flag = 1;
        sendBatteryStatus();
      }
      if (sleep_flag == 0) {
        sleep(SLEEP_TIME);
        sleep_flag = 1;
      }
    }
    
    
    void blinky(uint8_t pulses, uint8_t repit, uint8_t ledColor) {
      for (int x = 0; x < repit; x++) {
        if (x > 0) {
          sleep(500);
        }
        for (int i = 0; i < pulses; i++) {
          if (i > 0) {
            sleep(100);
          }
          digitalWrite(ledColor, LOW);
          wait(20);
          digitalWrite(ledColor, HIGH);
        }
      }
    }
    
    
    void sendBatteryStatus() {
      wait(20);
      batteryVoltage = hwCPUVoltage();
      wait(2);
    
      if (batteryVoltage > battery_vcc_max) {
        currentBatteryPercent = 100;
      }
      else if (batteryVoltage < battery_vcc_min) {
        currentBatteryPercent = 0;
      }
      else {
        if (lastBatteryPercent == 1000) {
          currentBatteryPercent = (100 * (batteryVoltage - battery_vcc_min)) / (battery_vcc_max - battery_vcc_min) + 5;
        } else {
          currentBatteryPercent = (100 * (batteryVoltage - battery_vcc_min)) / (battery_vcc_max - battery_vcc_min) - 5;
        }
      }
      sendBatteryLevel(currentBatteryPercent);
      wait(100);
      if (lastBatteryPercent < battery_alert_level) {
      blinky(3, 1, RED_LED);
      }
      else {
        blinky((last_sent_value == true ? 2 : 1), 1, BLUE_LED);
      }
      sleep_flag = 0;
      newmillisforbatt = millis();
    }
    
    
    void sendData() {
      temperature = bme.readTemperature();
      wait(20);
      humidity = bme.readHumidity();
      wait(20);
      pressure = bme.readPressure() / 100.0F;
      if (!metric) {
        temperature = temperature * 9.0 / 5.0 + 32.0;
      } else {
        pressure = pressure * 0.75006375541921;
      }
      CORE_DEBUG(PSTR("MY_TEMPERATURE: %d\n"), (int)temperature);
      CORE_DEBUG(PSTR("MY_HUMIDITY: %d\n"), (int)humidity);
      CORE_DEBUG(PSTR("MY_PRESSURE: %d\n"), (int)pressure);
      
        if (abs(temperature - lastTemperature) >= tempThreshold) {
          send(temperatureMsg.set(temperature, 1));
          lastTemperature = temperature;
          sleep(1000);
        }
    
        if (abs(humidity - lastHumidity) >= humThreshold) {
          send(humidityMsg.set(humidity, 0));
          lastHumidity = humidity;
          sleep(1000);
        }
    
        if (abs(pressure - lastPressure) >= presThreshold) {
          send(pressureMsg.set(pressure, 0));
          lastPressure = pressure;
          sleep(1000);
        }
      sleep_flag = 0;
    }
    
    
    void bme_initAsleep() {
      if (! bme.begin(&Wire)) {
        while (1);
      }
      bme.setSampling(Adafruit_BME280::MODE_FORCED,
                      Adafruit_BME280::SAMPLING_X1, // temperature
                      Adafruit_BME280::SAMPLING_X1, // pressure
                      Adafruit_BME280::SAMPLING_X1, // humidity
                      Adafruit_BME280::FILTER_OFF   );
      wait(1000);
    }
    


    Корпус для датчика разрабатывался в 3Д редакторе:



    Напечатан был на 3D принтере ANYCUBIC FOTON, использовалась смола белого цвета этого же производителя, толщина слоя была выбрана средняя — 50микрон. Время печати корпуса и крышки 3 часа.








    Сеть MySensors в которой работает датчик обменивается данными с системой умного дома Мажордомо. Зарегистрированный датчик в модуле Майсенсорс Мажордомо выглядит так:




    Видео с тестом



    Для желающих сделать себе такой же в статье даны ссылки на всё необходимое.

    Место где всегда с радостью помогут всем кто хочется познакомиться с MYSENSORS (установка плат, работа с микроконтролерами nRF5 в среде Arduino IDE, советы по работе с протоколом mysensors — @mysensors_rus
    Поделиться публикацией

    Комментарии 25

      0

      А что на другом конце BLE?

        0
        Там не ble, режим совместимости с nrf24l01, на другом конце шлюз.
          0

          Понятно, спасибо. Интересная статья.

            +2
            А почему предпочли такое решение, ведь устройств с поддержкой BLE намного больше, и использование этого протоколу сделало бы датчик значительно универсальнее?
              0

              потому что не все могут работать в режиме совместимости с аутентичным нордиковским нрф24l01

                +1
                Извиняюсь — не вполне понятно выразился. Вопрос был не о наличии шлюза (оно понятно), а об использовании режима совместимости вместо BLE.
          0
          Проясните пожалуйста, mysensors это протокол типа MQTT?
            –1
            типа зиг би или ble mesh скорее, конечно сильно притянуто. Сеть централизованная, но транспортный уровень автоматически строит маршруты…
              0
              понятно. на ваш взгляд что лучше mysensors или MQTT?
                0
                У меня сначала были esp-шки и mqtt, теперь mysensors и потихоньку осваиваю зигби
                  0

                  А почему отказались от ESP8266 и перешли на nRF52832?

                    0

                    я не отказывался, просто тема не батарейная.

                  +1
                  Вопрос некорректный.
                  MQTT работает поверх TCP/IP, а это значит должна быть плата с Ethernet или WiFi.
                  Для батарейных устройств это ИМХО избыточно.
                  Но если сильно нужно, то есть Mysensors MQTT Gateway — на ардуине с Ethernet либо ESP8266/32
                    +1
                    Вопрос корректный, ответ некорректный.

                    Во-первых, есть MQTT-SN, во-вторых, можно поднять MQTT over 6LoWPAN over BLE, например.

                      0
                      MQTT-SN работает поверх UDP. Что принципиально это меняет на транспортном уровне?
                      Теоретически можно пустить любой протокол верхнего уровня через любой транспорт, только чтобы соединиться с MQTT брокером понадобиться шлюз.

                        +1
                        То, что фраза «а это значит должна быть плата с Ethernet или WiFi» неверна.

                        Не должна.

                        только чтобы соединиться с MQTT брокером понадобиться шлюз


                        Чтобы соединиться с MQTT-брокером, понадобится MQTT-брокер, а железка, на которой он работает, может всё так же не иметь ни Wi-Fi, ни Ethernet. Ничего другого не понадобится.
              0
              а как Вы паяли вме280? Я вот не решаюсь, предпочитаю модули
                0
                Мне вот тоже интересно. Я паял модуль и судя по всему перегрел датчик, т.к. данные с него начали приходить не совсем корректные, да и вести себя стал тоже странно. А тут прям на плату запаян.
                  0
                  Довольно просто, развожу немного флюс геля, немного паяльной пасты, ставлю датчик, зажимаю обратным пинцетом, другим пинцетом подправляю позицию, включаю фен, как разогреется быстро круговыми движениями греем, примерно около 5 сек. всё
                    0
                    хм, даже на 2х0805 нужно секунд 10
                    0
                    а как вы умудрились модуль перегреть?
                      0
                      Пару раз перепаивал с места на место, а модуль у меня для I2C, плата очень маленькая. Ещё на ней отвалился конденсатор пока паял, и припой склеил ноги резисторной сборки, пришлось восстанавливать, а фена у меня нет.
                        0
                        а) надо фен б) прикрывать плату специальной лентой
                  –1
                  mysku.ru/blog/diy/73421.html
                  Копипаста.
                    +1
                    Из правил «Не следует копировать на «Хабр» тексты, опубликованные другими людьми на других ресурсах, но можно копировать собственные тексты, если они не нарушают правила ресурса.» Кажется, раньше было требование, чтобы текст не был где-то опубликован до Хабра. Впрочем, формально и оно выполнено: здесь пост появился в 18:07, там — в 20:14.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое