Как стать автором
Обновить

Беспроводной датчик протечки воды на nRF52832, DIY проект

Время на прочтение 8 мин
Количество просмотров 18K
Приветствую всех читателей раздела «DIY или Сделай сам» на Habr! Сегодня хочу рассказать об очередном своем проекте, эта статья будет о датчике протечки воды на батарейном питании. Как и в предыдущих проектах, это устройство работает на микроконтроллере nRF52832. Есть три версии этого датчика, во всех трех версиях используются готовые модули с nRF52832, в этой статье речь пойдет о средней версии в котором используется модуль YJ-17103 от HOLYIOT.



Детектор жидкости реализован на микросхеме SN74LVC1G00 | Даташит. Кратко опишу схемное решение и принцип работы. Электрод №1 датчика подключен к земле, электрод №2 датчика подключен к ножкам A и В микросхемы SN74LVC1G00 через резистор 100Oм, так же к этой линии подведено 3.3в через резистор 1М, так же в схему добавлена емкость. Когда контакта с жидкостью нет на ножках микросхемы A и В логическая единица, соответственно на ножке Y подключенной к ножке МК (програмно настроенной на детектирование прерывания через встроенный компоратор) логический ноль. Как только произойдет контакт с жидкостью и на ножках A и B будет низкий уровень, то сигнал на ножке Y микросхемы SN74LVC1G00 так же инвертируется, что вызовет прерывание, которое в свою очередь выведет МК из сна. В дальнейшем микросхема SN74LVC1G00 возможно будет заменена на микросхему SN74LVC1G14 | Даташит, а возможно и не будет :). Детектирование жидкости с ножки МК через встроенный компоратор не планируется.

Как и все другием мои проекты, этот тоже является Arduino проектом и как и все проекты за последний год(примерно) этот так же сделан под проект Mysensors. Как и в других своих статьях, немного затрону тему Mysensors и в этой статье.

Mysensors это сообщество разработчиков програмного обеспечения с открытым исходным кодом. Данный протокол разработан сообществом для создания радио и проводных сетей. Первоначально проект разрабатывался для платформы Arduino. Стандартная Mysensors сеть состоит из гейта(шлюза), ретранстяторов и конечных устройств(ноды). В одной сети может быть до 254 устройств, каждое из устройств может быть оснащено до 254-мя сенсорами, датчиками, исполнительными узлами. Работа сети, обработка данных, выполнение сценариев и взаимодействие в другими устройствами осуществляется с помощью контроллера УД. Некоторые из контроллеров(Мажордомо) поддерживают работу с несколькими сетямии Mysensors(мультигейтовость), соответственно сетей может быть намного больше одной управляемых одним контроллером.

Поддерживаемые аппаратные платформы: Linux / Raspberry Pi / Orange Pi | ATMega 328P | ESP8266 | ESP32 | nRF5x(Cortex M0, M4) | Atmel SAMD, используемое в Arduino Zero (Cortex M0) | Teensy3(MK66FX1M0VMD18) | STM32F1.

Поддерживаемые радиопередатчики: NRF24L01 | RFM69 | RFM95 (LoRa) | nRF5x

Поддерживаемый проводной тип связи: RS485

Поддерживаемые типы связи между гейтом и контроллером: MQTT | Serial USB | WiFi | Ethernet | GSM

Вернемся к датчику протечки. Устройство работает от батареек CR2430, CR2450 или CR2477. Потребление во сне составляет менее 3мкА. Скорость передачи — 250Kbps, 10-15ms. Энергопотребление в момент передачи составляет не более 8мА. Теоретически срок работы на одной батарейке примерно равен сроку саморазряда батарейки. На практике все конечно менее радужно, так как есть процедура регистрации, презентации, периодическая отправка уровня заряда, так что срок работы от одной батарейки скорее ближе к значению — срок саморазряда/2 :). Питание осуществляется напрямую от батарейки, контроль уровня заряда батарейки производится непосредственно с пина VDD. В датчике установлен RGB LED для индикации регистрации датчика в сети, для индикации сервисных режимов и для индикации детектирования протечки. Естественно светодиод может не использоваться вообще или использоваться частично.

Плату устройства была сделана для дальнейшего ее изготовления по методу ЛУТ. Поэтому из нюансов такого варианта это увеличенная ширина трасс, увеличенные расстояния между трассами, увеличенные площадки под межслойные переходы(для более удобного сверления отверстий), отсутствие заливки пустых областей из-за небольшой площади платы. Позже был сделан вариант для заказа на производстве.



Корпус устройства был спроектирован из двух частей. Верхняя крышка с местами для крепления платы и нижняя часть(ванночка) с 2 отверстиями под стальные контактные винты(герметизация возможна силиконовым герметиком под шляпку винтов или не требуется) и двумя трубками под кнопки (сброс и режимы) на плате. Печать выполнялась на SLA 3D принтере ANICUBIC PHOTON. После печати была выполнена обработка наждачной бумагой 320 и 1000 для подгонки стыков крышки и дна корпуса.





Фотографии датчика















Код тестовой программы
wl_standart_test.ino

bool button_flag;
bool send_flag;
bool detection;
bool nosleep;
byte timer;
bool AckG;
bool AckB;
bool AckL;
bool PRESENT_ACK;
bool flag_lq;
unsigned long SLEEP_TIME = 172800000; //48 hours
//unsigned long SLEEP_TIME = 3600000; //1 hour
unsigned long oldmillis;
unsigned long newmillis;
unsigned long interrupt_time;
unsigned long SLEEP_TIME_W;
uint16_t currentBatteryPercent;
uint16_t batteryVoltage = 0;
uint16_t battery_vcc_min = 2300;
uint16_t battery_vcc_max = 3000;

int16_t linkQuality;

#define MY_DISABLED_SERIAL
#define MY_RADIO_NRF5_ESB
#define MY_RF24_PA_LEVEL (NRF5_PA_MAX)
//#define MY_PASSIVE_NODE
#define MY_NODE_ID 86
#define MY_PARENT_NODE_ID 0
#define MY_PARENT_NODE_IS_STATIC
#define MY_TRANSPORT_UPLINK_CHECK_DISABLED
#define INTR_PIN 3 //(PORT0,  gpio 5)
#include <MySensors.h>
// see https://www.mysensors.org/download/serial_api_20
#define W_L_SENS_CHILD_ID 0
#define LINK_QUALITY_CHILD_ID 253
MyMessage sensMsg(W_L_SENS_CHILD_ID, V_VAR1);
//MyMessage voltMsg(CHILD_ID_VOLT, V_VOLTAGE);


void preHwInit() {
  pinMode(POWER_PIN, OUTPUT);
  digitalWrite(POWER_PIN, HIGH);
  wait(3000);
  pinMode(RED_LED, OUTPUT);
  digitalWrite(RED_LED, HIGH);
  pinMode(GREEN_LED, OUTPUT);
  digitalWrite(GREEN_LED, HIGH);
  pinMode(BLUE_LED, OUTPUT);
  digitalWrite(BLUE_LED, HIGH);
  pinMode(PIN_BUTTON, INPUT);
  pinMode(W_L_SENS, INPUT);

  //pinMode(24, OUTPUT);
  //pinMode(20, OUTPUT);
}

void before()
{
  NRF_POWER->DCDCEN = 1;
  NRF_UART0->ENABLE = 0;
  digitalWrite(BLUE_LED, LOW);
  sleep(50);
  digitalWrite(BLUE_LED, HIGH);
}

void presentation() {
  sendSketchInfo("EFEKTA ST WL Sensor", "1.1");
  present(W_L_SENS_CHILD_ID, S_CUSTOM, "SWITCH STATUS");
  present(LINK_QUALITY_CHILD_ID, S_CUSTOM, "LINK_QUALITY");
}

void setup() {
  digitalWrite(BLUE_LED, LOW);
  wait(100);
  digitalWrite(BLUE_LED, HIGH);
  wait(200);
  digitalWrite(BLUE_LED, LOW);
  wait(100);
  digitalWrite(BLUE_LED, HIGH);
  lpComp();
  detection = false;
  SLEEP_TIME_W = SLEEP_TIME;
  wait(100);
  sendBatteryStatus();
  wait(100);
  send(sensMsg.set(detection), 1);
  wait(2000, 1, V_VAR1);
}

void loop() {
  if (nosleep == 0) {
    oldmillis = millis();
    sleep(SLEEP_TIME_W);
  }

  if (detection) {
    if (digitalRead(PIN_BUTTON) == 1 && button_flag == 0 && digitalRead(W_L_SENS) == 0) {
      //back side button detection
      button_flag = 1;
      nosleep = 1;
    }
    if (digitalRead(PIN_BUTTON) == 1 && button_flag == 1 && digitalRead(W_L_SENS) == 0) {
      digitalWrite(GREEN_LED, LOW);
      wait(10);
      digitalWrite(GREEN_LED, HIGH);
      wait(50);
    }
    if (digitalRead(PIN_BUTTON) == 0 && button_flag == 1 && digitalRead(W_L_SENS) == 0) {
      nosleep = 0;
      button_flag = 0;
      digitalWrite(GREEN_LED, HIGH);
      lpComp_reset();
    }

    if (digitalRead(W_L_SENS) == 1 && digitalRead(PIN_BUTTON) == 0) {
      //sens detection
      newmillis = millis();
      interrupt_time = newmillis - oldmillis;
      SLEEP_TIME_W = SLEEP_TIME_W - interrupt_time;
      send(sensMsg.set(detection), 1);
      wait(3000, 1, V_VAR1);
      if (AckG == 1) {
        while (timer < 10) {
          timer++;
          digitalWrite(BLUE_LED, LOW);
          wait(20);
          digitalWrite(BLUE_LED, HIGH);
          wait(30);
        }
        timer = 0;
        AckG = 0;
        wait(200);
      } else {
        while (timer < 10) {
          timer++;
          digitalWrite(RED_LED, LOW);
          wait(20);
          digitalWrite(RED_LED, HIGH);
          wait(30);
        }
        timer = 0;
        send(sensMsg.set(detection), 1);
        wait(3000, 1, V_VAR1);
        if (AckG == 1) {
          while (timer < 10) {
            timer++;
            digitalWrite(BLUE_LED, LOW);
            wait(20);
            digitalWrite(BLUE_LED, HIGH);
            wait(30);
          }
          timer = 0;
          AckG = 0;
        } else {
          while (timer < 10) {
            timer++;
            digitalWrite(RED_LED, LOW);
            wait(20);
            digitalWrite(RED_LED, HIGH);
            wait(30);
          }
          timer = 0;
        }
        lpComp_reset();
      }
    }

    if (SLEEP_TIME_W < 60000) {
      SLEEP_TIME_W = SLEEP_TIME;
      sendBatteryStatus();
    }
  }
  else {
    //if (detection == -1) {
    SLEEP_TIME_W = SLEEP_TIME;
    sendBatteryStatus();
  }
}


void receive(const MyMessage & message) {
  if (message.type == V_VAR1) {
    if (message.sensor == W_L_SENS_CHILD_ID) {
      if (mGetCommand(message) == 1) {
        if (message.isAck()) {
          AckG = 1;
        } else {

        }
      }
    }
  }
  if (message.type == I_BATTERY_LEVEL) {
    if (message.sensor == 255) {
      if (mGetCommand(message) == 3) {
        if (message.isAck()) {
          AckB = 1;
        } else {

        }
      }
    }
  }
  if (message.type == V_VAR1) {
    if (message.sensor == 255) {
      if (mGetCommand(message) == 1) {
        if (message.isAck()) {
          AckL = 1;
        } else {

        }
      }
    }
  }
}


void sendBatteryStatus() {
  wait(100);
  batteryVoltage = hwCPUVoltage();
  wait(20);

  if (batteryVoltage > battery_vcc_max) {
    currentBatteryPercent = 100;
  }
  else if (batteryVoltage < battery_vcc_min) {
    currentBatteryPercent = 0;
  } else {
    currentBatteryPercent = (100 * (batteryVoltage - battery_vcc_min)) / (battery_vcc_max - battery_vcc_min);
  }
  sendBatteryLevel(currentBatteryPercent, 1);
  wait(3000, C_INTERNAL, I_BATTERY_LEVEL);
  if (AckB == 1) {
    AckB = 0;
    flag_lq = 1;
  } else {  
    sendBatteryLevel(currentBatteryPercent, 1); 
    wait(3000, C_INTERNAL, I_BATTERY_LEVEL); 
    if (AckB == 1) {
      AckB = 0;
      flag_lq = 1;
    }
  }
  //send(powerMsg.set(batteryVoltage), 1);
  //wait(2000, 1, V_VAR1);

  //sleep(10000); //
  if (flag_lq == 1) {
    linkQuality = calculationRxQuality();
    wait(50);
    sendSignalStrength(linkQuality, 1);
    wait(2000, 1, V_VAR1);
    if (AckL == 1) {
      AckL = 0;
    } else {
      sendSignalStrength(linkQuality, 1);
      wait(2000, 1, V_VAR1);
      if (AckL == 1) {
        AckG = 0;
      }
    }
    flag_lq = 0;
  }
}


void lpComp() {
  NRF_LPCOMP->PSEL = INTR_PIN;
  NRF_LPCOMP->ANADETECT = 1;
  NRF_LPCOMP->INTENSET = B0100;
  NRF_LPCOMP->ENABLE = 1;
  NRF_LPCOMP->TASKS_START = 1;
  NVIC_SetPriority(LPCOMP_IRQn, 15);
  NVIC_ClearPendingIRQ(LPCOMP_IRQn);
  NVIC_EnableIRQ(LPCOMP_IRQn);
}

void s_lpComp() {
  if ((NRF_LPCOMP->ENABLE) && (NRF_LPCOMP->EVENTS_READY)) {
    NRF_LPCOMP->INTENCLR = B0100;
  }
}

void r_lpComp() {
  NRF_LPCOMP->INTENSET = B0100;
}

#if __CORTEX_M == 0x04
#define NRF5_RESET_EVENT(event)                                                 \
  event = 0;                                                                   \
  (void)event
#else
#define NRF5_RESET_EVENT(event) event = 0
#endif


void lpComp_reset () {
  s_lpComp();
  detection = false;
  NRF_LPCOMP->EVENTS_UP = 0;
  r_lpComp();
}

//****************************** very experimental *******************************


bool sendSignalStrength(const int16_t level, const bool ack)
{
  return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_SET, V_VAR1,
                          ack).set(level));
}
int16_t calculationRxQuality() {
  int16_t nRFRSSI_temp = transportGetReceivingRSSI();
  int16_t nRFRSSI = map(nRFRSSI_temp, -85, -40, 0, 100);
  if (nRFRSSI < 0) {
    nRFRSSI = 0;
  }
  if (nRFRSSI > 100) {
    nRFRSSI = 100;
  }
  return nRFRSSI;
}

//****************************** very experimental *******************************


extern "C" {
  void LPCOMP_IRQHandler(void) {
    detection = true;
    NRF5_RESET_EVENT(NRF_LPCOMP->EVENTS_UP);
    NRF_LPCOMP->EVENTS_UP = 0;
    MY_HW_RTC->CC[0] = (MY_HW_RTC->COUNTER + 2);
  }
}

MyBoardNRF5.h

#ifndef _MYBOARDNRF5_H_
#define _MYBOARDNRF5_H_

#ifdef __cplusplus
extern "C"
{
#endif // __cplusplus


#define PINS_COUNT (32u)
#define NUM_DIGITAL_PINS (32u)
#define NUM_ANALOG_INPUTS (8u)
#define NUM_ANALOG_OUTPUTS (8u)

#define PIN_LED1 (27)
#define PIN_LED2 (25)
#define PIN_LED3 (26)
#define RED_LED (PIN_LED1)
#define GREEN_LED (PIN_LED2)
#define BLUE_LED (PIN_LED3)

#define PIN_BUTTON (14)
#define W_L_SENS (8)

#define POWER_PIN (7)

#define PIN_SERIAL_RX (12)
#define PIN_SERIAL_TX (11)


#ifdef __cplusplus
}
#endif

#endif


nRF52832 программно настроен на работу в режиме пониженного энергопотребления (DC-DC Mode), Вывод МК из сна по сигналу от микросхемы SN74LVC1G00 настроен через внутренний компаратор LPCOMP. Устройство так же имеет тактовую кнопку для реализации сервисных режимов, таких как привязка устройства, обнуление устройства и т.п. Кнопка заведена на ту же ножку МК что и детектор протечки. Обе линии разделены диодами Шоттки. Микросхема SN74LVC1G00 в режиме мониторинга ничего не потребляет. Управление питанием микросхемы осуществляется с ножки МК.

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

Видео с демонстрацией работы датчика протечки


GitHub проекта
(гербер файлы, софт, модели корпуса, список компонентов)

Место где всегда с радостью помогут всем кто хочется познакомиться с MYSENSORS (установка плат, работа с микроконтроллерами nRF5 в среде Arduino IDE, советы по работе с протоколом mysensors, обсуждение новых авторских проектов — телеграмм чат @mysensors_rus.
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+34
Комментарии 18
Комментарии Комментарии 18

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн