Приветствую всех читателей 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)


  1. osmanpasha
    12.06.2019 18:43

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


    1. Berkseo Автор
      12.06.2019 18:47

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


      1. osmanpasha
        12.06.2019 23:48

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


      1. wholeman
        13.06.2019 10:35

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


        1. Berkseo Автор
          13.06.2019 10:45

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


          1. wholeman
            13.06.2019 10:52

            Извиняюсь — не вполне понятно выразился. Вопрос был не о наличии шлюза (оно понятно), а об использовании режима совместимости вместо BLE.


  1. enjoyneering
    12.06.2019 18:43

    Проясните пожалуйста, mysensors это протокол типа MQTT?


    1. Berkseo Автор
      12.06.2019 18:46

      типа зиг би или ble mesh скорее, конечно сильно притянуто. Сеть централизованная, но транспортный уровень автоматически строит маршруты…


      1. enjoyneering
        12.06.2019 19:15

        понятно. на ваш взгляд что лучше mysensors или MQTT?


        1. Berkseo Автор
          12.06.2019 19:18

          У меня сначала были esp-шки и mqtt, теперь mysensors и потихоньку осваиваю зигби


          1. enjoyneering
            12.06.2019 21:46

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


            1. Berkseo Автор
              12.06.2019 22:02

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


        1. sav13
          13.06.2019 05:28

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


          1. olartamonov
            13.06.2019 11:41

            Вопрос корректный, ответ некорректный.

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


            1. sav13
              13.06.2019 12:22

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


              1. olartamonov
                14.06.2019 07:54

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

                Не должна.

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


                Чтобы соединиться с MQTT-брокером, понадобится MQTT-брокер, а железка, на которой он работает, может всё так же не иметь ни Wi-Fi, ни Ethernet. Ничего другого не понадобится.


  1. igrushkin
    13.06.2019 10:01

    а как Вы паяли вме280? Я вот не решаюсь, предпочитаю модули


    1. qoj
      13.06.2019 13:01

      Мне вот тоже интересно. Я паял модуль и судя по всему перегрел датчик, т.к. данные с него начали приходить не совсем корректные, да и вести себя стал тоже странно. А тут прям на плату запаян.


      1. Berkseo Автор
        13.06.2019 13:03

        Довольно просто, развожу немного флюс геля, немного паяльной пасты, ставлю датчик, зажимаю обратным пинцетом, другим пинцетом подправляю позицию, включаю фен, как разогреется быстро круговыми движениями греем, примерно около 5 сек. всё


        1. igrushkin
          13.06.2019 15:03

          хм, даже на 2х0805 нужно секунд 10


      1. igrushkin
        13.06.2019 15:01

        а как вы умудрились модуль перегреть?


        1. qoj
          13.06.2019 15:06

          Пару раз перепаивал с места на место, а модуль у меня для I2C, плата очень маленькая. Ещё на ней отвалился конденсатор пока паял, и припой склеил ноги резисторной сборки, пришлось восстанавливать, а фена у меня нет.


          1. igrushkin
            13.06.2019 15:13

            а) надо фен б) прикрывать плату специальной лентой


  1. A__D
    13.06.2019 10:35

    mysku.ru/blog/diy/73421.html
    Копипаста.


    1. wholeman
      13.06.2019 10:50
      +1

      Из правил «Не следует копировать на «Хабр» тексты, опубликованные другими людьми на других ресурсах, но можно копировать собственные тексты, если они не нарушают правила ресурса.» Кажется, раньше было требование, чтобы текст не был где-то опубликован до Хабра. Впрочем, формально и оно выполнено: здесь пост появился в 18:07, там — в 20:14.