user330352
Published © Apache-2.0

Water level measuring - two solutions

To know how much water is left in a cistern to watering the garden, push the data to the Apple Homekit

IntermediateProtip10 hours2,813
Water level measuring - two solutions

Things used in this project

Hardware components

Adafruit LPS33HW
×1
Adafruit feather 32u4 RFM69 / Feather M0 RFM69
×1
optional adafruit TPL5111
×1
Raspberry Pi Zero
Raspberry Pi Zero
alternate a Pi 3B
×1
Adafruit RFM69 Breakout
×1
VL53L1X Time of Flight (ToF) Sensor Breakout
Pimoroni VL53L1X Time of Flight (ToF) Sensor Breakout
for the second, alternate solution
×1

Software apps and online services

PlatformIO IDE
PlatformIO IDE
Visual Studio Code Extension for Arduino
Microsoft Visual Studio Code Extension for Arduino

Story

Read more

Schematics

RFM69 on Raspberry

Fritzing

Code

Adafruit LPS35HW.h

C/C++
Driver
/*!
 *  @file Adafruit_LPS35HW.h
 *
 * 	I2C Driver for LPS35HW Current and Power sensor
 *
 * 	This is a library for the Adafruit LPS35HW breakout:
 * 	http://www.adafruit.com/products
 *
 * 	Adafruit invests time and resources providing this open source code,
 *  please support Adafruit and open-source hardware by purchasing products from
 * 	Adafruit!
 *
 *
 *	BSD license (see license.txt)
 */

#ifndef _ADAFRUIT_LPS35HW_H
#define _ADAFRUIT_LPS35HW_H

#include "Arduino.h"
#include <Adafruit_BusIO_Register.h>
#include <Adafruit_I2CDevice.h>
#include <Wire.h>

#define LPS35HW_I2CADDR_DEFAULT 0x5D ///< LPS35HW default i2c address
#define LPS35HW_INTERRUPT_CFG 0x0B   ///< Interrupt configuration register
#define LPS35HW_THS_P_L 0x0C         ///< Threshold pressure low byte
#define LPS35HW_THS_P_H 0x0D         ///< Threshold pressure high byte
#define LPS35HW_WHO_AM_I 0x0F        ///< Chip ID
#define LPS35HW_CTRL_REG1 0x10       ///< Control register 1
#define LPS35HW_CTRL_REG2 0x11       ///< Control register 2
#define LPS35HW_CTRL_REG3 0x12       ///< Control register 3
#define LPS35HW_FIFO_CTRL 0x14       ///< FIFO Control register
#define LPS35HW_REF_P_XL 0x15        ///< Reference pressure low byte
#define LPS35HW_REF_P_L 0x16         ///< Reference pressure mid byte
#define LPS35HW_REF_P_H 0x17         ///< Reference pressure high byte
#define LPS35HW_RPDS_L 0x18          ///< Offset pressure low byte
#define LPS35HW_RPDS_H 0x19          ///< Offset pressure high byte
#define LPS35HW_RES_CONF 0x1A        ///< Low power mode configuration
#define LPS35HW_INT_SOURCE 0x25      ///< Interrupt source
#define LPS35HW_FIFO_STATUS 0x26     ///< FIFO Status
#define LPS35HW_STATUS 0x27          ///< Status register
#define LPS35HW_PRESS_OUT_XL 0x28    ///< Pressure low byte
#define LPS35HW_PRESS_OUT_L 0x29     ///< Pressure mid byte
#define LPS35HW_PRESS_OUT_H 0x2A     ///< Pressure high byte
#define LPS35HW_TEMP_OUT_L 0x2B      ///< Temperature low byte
#define LPS35HW_TEMP_OUT_H 0x2C      ///< Temperature high byte
#define LPS35HW_LPFP_RES 0x33        ///< Low pass filter reset

/**
 * @brief Data rate options.
 *
 * Allowed values for `setDataRate`.
 */
typedef enum _data_rate {
  LPS35HW_RATE_ONE_SHOT, /**< ONE_SHOT: Put the sensor in a low power shutdown
                            mode that will only take a measurement when
                            `takeMeasurement` is called **/
  LPS35HW_RATE_1_HZ,     /** 1 hz  **/
  LPS35HW_RATE_10_HZ,    /** 10 hz  **/
  LPS35HW_RATE_25_HZ,    /** 25 hz  **/
  LPS35HW_RATE_50_HZ,    /** 50 hz  **/
  LPS35HW_RATE_75_HZ,    /** 75 hz  **/
} LPS35HW_DataRate;

/*!
 *    @brief  Class that stores state and functions for interacting with
 *            LPS35HW Current and Power Sensor
 */
class Adafruit_LPS35HW {
public:
  Adafruit_LPS35HW();
  boolean begin_I2C(uint8_t i2c_addr = LPS35HW_I2CADDR_DEFAULT,
                    TwoWire *wire = &Wire);
  boolean begin_SPI(uint8_t cs_pin, SPIClass *theSPI = &SPI);
  boolean begin_SPI(int8_t cs_pin, int8_t sck_pin, int8_t miso_pin,
                    int8_t mosi_pin);
  void reset(void);
  float readTemperature(void);
  float readPressure(void);
  void setDataRate(LPS35HW_DataRate new_rate);
  void takeMeasurement(void);
  void zeroPressure(void);
  void resetPressure(void);
  void setThresholdPressure(float threshold_pressure);
  void enableHighThreshold(void);
  void enableLowThreshold(void);
  bool highThresholdExceeded(void);
  bool lowThresholdExceeded(void);
  void enableInterrupts(bool active_low = false, bool open_drain = false);
  void disableInterrupts(void);
  void enableLowPass(bool extra_low_bandwidth = false);

  Adafruit_BusIO_Register *Config1, ///< BusIO Register for CONFIG_1
      *Config2,                     ///< BusIO Register for CONFIG_2
      *Config3,                     ///< BusIO Register for CONFIG_3
      *InterruptCfg,                ///< BusIO Register for INTERRUPT_CFG
      *InterruptStatus;             ///< BusIO Register for INTERRUPT_STATUS

private:
  bool _init(void);

  Adafruit_I2CDevice *i2c_dev;
  Adafruit_SPIDevice *spi_dev;
};

#endif

Adafruit LPS35HW.cpp

C/C++
Driver
/*!
 *  @file Adafruit_LPS35HW.cpp
 *
 *  @mainpage Adafruit LPS35HW I2C water resistant barometric pressure sensor
 *
 *  @section intro_sec Introduction
 *
 * 	I2C Driver for the LPS35HW I2C water resistant barometric pressure
 * sensor
 *
 * 	This is a library for the Adafruit LPS35HW breakout:
 * 	http://www.adafruit.com/products
 *
 * 	Adafruit invests time and resources providing this open source code,
 *  please support Adafruit and open-source hardware by purchasing products from
 * 	Adafruit!
 *
 *  @section dependencies Dependencies
 *
 *  This library depends on the Adafruit BusIO library
 *
 *  @section author Author
 *
 *  Bryan Siepert for Adafruit Industries
 *
 * 	@section license License
 *
 * 	BSD (see license.txt)
 *
 * 	@section  HISTORY
 *
 *     v1.0 - First release
 */

#include "Arduino.h"
#include <Wire.h>

#include "Adafruit_LPS35HW.h"

/*!
 *    @brief  Instantiates a new LPS35HW class
 */
Adafruit_LPS35HW::Adafruit_LPS35HW(void) {}

/*!
 *    @brief  Sets up the hardware and initializes I2C
 *    @param  i2c_address
 *            The I2C address to be used.
 *    @param  wire
 *            The Wire object to be used for I2C connections.
 *    @return True if initialization was successful, otherwise false.
 */
boolean Adafruit_LPS35HW::begin_I2C(uint8_t i2c_address, TwoWire *wire) {
  spi_dev = NULL;
  i2c_dev = new Adafruit_I2CDevice(i2c_address, wire);

  if (!i2c_dev->begin()) {
    return false;
  }

  return _init();
}

/*!
 *    @brief  Sets up the hardware and initializes hardware SPI
 *    @param  cs_pin The arduino pin # connected to chip select
 *    @param  theSPI The SPI object to be used for SPI connections.
 *    @return True if initialization was successful, otherwise false.
 */
boolean Adafruit_LPS35HW::begin_SPI(uint8_t cs_pin, SPIClass *theSPI) {
  i2c_dev = NULL;
  spi_dev = new Adafruit_SPIDevice(cs_pin,
                                   1000000,               // frequency
                                   SPI_BITORDER_MSBFIRST, // bit order
                                   SPI_MODE0,             // data mode
                                   theSPI);

  if (!spi_dev->begin()) {
    return false;
  }

  return _init();
}

/*!
 *    @brief  Sets up the hardware and initializes software SPI
 *    @param  cs_pin The arduino pin # connected to chip select
 *    @param  sck_pin The arduino pin # connected to SPI clock
 *    @param  miso_pin The arduino pin # connected to SPI MISO
 *    @param  mosi_pin The arduino pin # connected to SPI MOSI
 *    @return True if initialization was successful, otherwise false.
 */
boolean Adafruit_LPS35HW::begin_SPI(int8_t cs_pin, int8_t sck_pin,
                                    int8_t miso_pin, int8_t mosi_pin) {
  i2c_dev = NULL;
  spi_dev = new Adafruit_SPIDevice(cs_pin, sck_pin, miso_pin, mosi_pin,
                                   1000000,               // frequency
                                   SPI_BITORDER_MSBFIRST, // bit order
                                   SPI_MODE0);            // data mode

  if (!spi_dev->begin()) {
    return false;
  }

  return _init();
}

boolean Adafruit_LPS35HW::_init(void) {
  Adafruit_BusIO_Register chip_id = Adafruit_BusIO_Register(
      i2c_dev, spi_dev, ADDRBIT8_HIGH_TOREAD, LPS35HW_WHO_AM_I, 1);

  // make sure we're talking to the right chip
  if (chip_id.read() != 0xB1) {
    return false;
  }

  Config1 = new Adafruit_BusIO_Register(i2c_dev, spi_dev, ADDRBIT8_HIGH_TOREAD,
                                        LPS35HW_CTRL_REG1, 1);
  Config2 = new Adafruit_BusIO_Register(i2c_dev, spi_dev, ADDRBIT8_HIGH_TOREAD,
                                        LPS35HW_CTRL_REG2, 1);
  Config3 = new Adafruit_BusIO_Register(i2c_dev, spi_dev, ADDRBIT8_HIGH_TOREAD,
                                        LPS35HW_CTRL_REG3, 1);
  InterruptCfg = new Adafruit_BusIO_Register(
      i2c_dev, spi_dev, ADDRBIT8_HIGH_TOREAD, LPS35HW_INTERRUPT_CFG, 1);
  InterruptStatus = new Adafruit_BusIO_Register(
      i2c_dev, spi_dev, ADDRBIT8_HIGH_TOREAD, LPS35HW_INT_SOURCE, 1);

  reset();

  setDataRate(LPS35HW_RATE_10_HZ); // default in continuous at 10 hz
  // setup block reads
  Adafruit_BusIO_RegisterBits block_reads =
      Adafruit_BusIO_RegisterBits(Config1, 1, 1);
  block_reads.write(0x1);

  return true;
}

/**************************************************************************/
/*!
    @brief Resets the hardware. All configuration registers are set to
            default values, the same as a power-on reset.
*/
/**************************************************************************/
void Adafruit_LPS35HW::reset(void) {
  Adafruit_BusIO_RegisterBits reset =
      Adafruit_BusIO_RegisterBits(Config2, 1, 2);
  reset.write(1);
  while (reset.read() == true) {
    delay(1);
  }
}
/**************************************************************************/
/*!
    @brief Reads and scales the current value of the temperature register.
    @return The current temperature in degrees C
*/
/**************************************************************************/
float Adafruit_LPS35HW::readTemperature(void) {
  Adafruit_BusIO_Register temp = Adafruit_BusIO_Register(
      i2c_dev, spi_dev, ADDRBIT8_HIGH_TOREAD, LPS35HW_TEMP_OUT_L, 2);
  return (int16_t)temp.read() / 100.0;
}
/**************************************************************************/
/*!
    @brief Reads and scales the value of the pressure register.
    @return The current pressure in hPa, relative to the reference temperature
*/
/**************************************************************************/
float Adafruit_LPS35HW::readPressure(void) {
  Adafruit_BusIO_Register pressure = Adafruit_BusIO_Register(
      i2c_dev, spi_dev, ADDRBIT8_HIGH_TOREAD, LPS35HW_PRESS_OUT_XL, 3);
  int32_t raw_pressure = pressure.read();
  // perform sign extension for 24 bit number if needed
  if (raw_pressure & 0x800000) {
    raw_pressure = (0xff000000 | raw_pressure);
  }

  return (raw_pressure / 4096.0);
}

/**************************************************************************/
/*!
    @brief Takes a new measurement while in one shot mode.
*/
/**************************************************************************/
void Adafruit_LPS35HW::takeMeasurement(void) {
  Adafruit_BusIO_RegisterBits one_shot =
      Adafruit_BusIO_RegisterBits(Config2, 1, 0);
  one_shot.write(1);
  while (one_shot.read() == true) {
    delay(1);
  };
}

/**************************************************************************/
/*!
    @brief Sets the reference temperature to the current temperature. Future
            pressure readings will be relative to it until `resetPressure` is
            called.
*/
/**************************************************************************/
void Adafruit_LPS35HW::zeroPressure(void) {
  Adafruit_BusIO_RegisterBits zero_pressure =
      Adafruit_BusIO_RegisterBits(InterruptCfg, 1, 5);
  zero_pressure.write(1);
  while (zero_pressure.read() == true) {
    delay(1);
  };
}

/**************************************************************************/
/*!
    @brief Resets the reference pressure to zero so calls to `getPressure`
            are reported as the absolute value.
*/
/**************************************************************************/
void Adafruit_LPS35HW::resetPressure(void) {
  Adafruit_BusIO_RegisterBits pressure_reset =
      Adafruit_BusIO_RegisterBits(InterruptCfg, 1, 4);
  pressure_reset.write(1);
}

/**************************************************************************/
/*!
    @brief Sets the pressure threshold used by the high and low pressure
   thresholds
    @param threshold_pressure
            The threshold pressure in hPa, measured from zero
*/
/**************************************************************************/
void Adafruit_LPS35HW::setThresholdPressure(float threshold_pressure) {
  Adafruit_BusIO_Register threshold = Adafruit_BusIO_Register(
      i2c_dev, spi_dev, ADDRBIT8_HIGH_TOREAD, LPS35HW_THS_P_L, 2);
  threshold.write(threshold_pressure * 16);
}
/**************************************************************************/
/*!
    @brief Enables high pressure threshold interrupts.
*/
/**************************************************************************/
void Adafruit_LPS35HW::enableHighThreshold(void) {
  Adafruit_BusIO_RegisterBits high_thresh =
      Adafruit_BusIO_RegisterBits(InterruptCfg, 1, 0);
  high_thresh.write(0x1);
  Adafruit_BusIO_RegisterBits high_int_pin =
      Adafruit_BusIO_RegisterBits(Config3, 1, 0);
  high_int_pin.write(0x1);
}
/**************************************************************************/
/*!
    @brief Disables low pressure threshold interrupts.
*/
/**************************************************************************/
void Adafruit_LPS35HW::enableLowThreshold(void) {
  Adafruit_BusIO_RegisterBits low_thresh =
      Adafruit_BusIO_RegisterBits(InterruptCfg, 1, 1);
  low_thresh.write(0x1);
  Adafruit_BusIO_RegisterBits low_int_pin =
      Adafruit_BusIO_RegisterBits(Config3, 1, 1);
  low_int_pin.write(0x1);
}
/**************************************************************************/
/*!
    @brief Enables pressure threshold interrupts. High and low thresholds
          need to be enabled individually with `enableLowThreshold` and
          `enableHighThreshold`.
    @param active_low Polarity of interrupt pin, true for active low.
    @param open_drain
          Set to `true` to have the INT pin be open drain when active.
*/
/**************************************************************************/
void Adafruit_LPS35HW::enableInterrupts(bool active_low, bool open_drain) {
  Adafruit_BusIO_RegisterBits pin_mode =
      Adafruit_BusIO_RegisterBits(Config3, 2, 6);
  pin_mode.write((active_low << 1) | open_drain);

  Adafruit_BusIO_RegisterBits latch_enabled =
      Adafruit_BusIO_RegisterBits(InterruptCfg, 2, 2);
  latch_enabled.write(0x3);
}
/**************************************************************************/
/*!
    @brief Disables pressure threshold interrupts.
*/
/**************************************************************************/
void Adafruit_LPS35HW::disableInterrupts(void) {
  Adafruit_BusIO_RegisterBits enabled =
      Adafruit_BusIO_RegisterBits(InterruptCfg, 2, 2);
  enabled.write(0x0);
}
/**************************************************************************/
/*!
    @brief Enables the low pass filter with ODR/9 bandwidth
    @param extra_low_bandwidth
            Set to `true` to scale the bandwidth to ODR/20
*/
/**************************************************************************/
void Adafruit_LPS35HW::enableLowPass(bool extra_low_bandwidth) {
  Adafruit_BusIO_RegisterBits filter_config =
      Adafruit_BusIO_RegisterBits(Config1, 2, 2);
  filter_config.write(0x2 | (extra_low_bandwidth == true));
}

/**************************************************************************/
/*!
    @brief Returns the current state of the high pressure threshold interrupt.
    @return `true` if the high pressure threshold has been triggered since
          last checked.
*/
/**************************************************************************/
bool Adafruit_LPS35HW::highThresholdExceeded(void) {
  return (InterruptStatus->read() == 0b101);
}
/**************************************************************************/
/*!
    @brief Returns the current state of the low pressure threshold interrupt.
    @return `true` if the low pressure threshold has been triggered since
          last checked.
*/
/**************************************************************************/
bool Adafruit_LPS35HW::lowThresholdExceeded(void) {
  return (InterruptStatus->read() == 0b110);
}
/**************************************************************************/
/*!
    @brief Sets a new measurement rate
    @param new_rate
          The new output data rate to be set (ODR)
*/
/**************************************************************************/
void Adafruit_LPS35HW::setDataRate(LPS35HW_DataRate new_rate) {
  Adafruit_BusIO_RegisterBits data_rate =
      Adafruit_BusIO_RegisterBits(Config1, 3, 4);
  data_rate.write(new_rate);
}

Sensor Unit

C/C++
config for measuring
#include <RH_RF69.h>
#include <RHDatagram.h>
#include <Adafruit_LPS35HW.h>
#include <RTCZero.h>

#define POW_AIR A1 //A1
#define POW_WATER A2 //A2
#define VBATPIN  A7 // measuring battery
#define I2C_WATER 0x5C // LPS35HW Water i2c address
#define I2C_AIR 0x5D // LPS35HW AIR i2c address

/// Feather M0 w/Radio
#define RF69_FREQ 433.0
#define RFM69_CS      8
#define RFM69_INT     7
#define RFM69_RST     4

#define CLIENT_ADDRESS 11 // RHDatagram
#define SERVER_ADDRESS 1 // RHDatagram

RH_RF69 rf69(RFM69_CS, RFM69_INT);
RHDatagram manager(rf69, CLIENT_ADDRESS);
Adafruit_LPS35HW Water = Adafruit_LPS35HW();
Adafruit_LPS35HW Air = Adafruit_LPS35HW();

/* Create an rtc object */
RTCZero rtc;
const byte seconds = 00;
const byte minutes = 00;
const byte hours = 10;    
const byte day = 1;
const byte month = 1;
const byte year = 20;

uint8_t sensorVoltage;
int WaterPressure;
int AirPressure;

uint8_t ReadBattery() {
  digitalWrite(VBATPIN, HIGH);delay(10);
  float measuredvbat = analogRead(VBATPIN);
  digitalWrite(VBATPIN, LOW);
  measuredvbat *=2; // we divided by 2, so multiply back
  measuredvbat *=3.3; // Multiply by 3.3V, our reference voltage
  measuredvbat /= 1024; // convert to voltage
  // min ADC at 2 Volt = 621 (ignore 0,001)
  // max ADC at 4,2 Volt = 1303 (ignore 0,0009)
  if (measuredvbat > 4.2f) {measuredvbat = 4.2f;}
  if (measuredvbat < 2.4f) {measuredvbat = 2.4f;}
  uint8_t result = roundf(100*(measuredvbat-2.4f)/(4.2f-2.4f));
  return result; // value back in percent
 };


void setup() {
  //Serial.begin(115200);
  // Wait until serial port is opened
  //while (!Serial) { delay(10); }
  pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); // LED off
  pinMode(POW_WATER, OUTPUT);pinMode(POW_AIR, OUTPUT);
  pinMode(RFM69_RST, OUTPUT);
  digitalWrite(RFM69_RST, HIGH); delay(10);
  digitalWrite(RFM69_RST, LOW); delay(10);
  if (!manager.init()) {
    //Serial.println("init failed"); 
    while (1);}
  if (!rf69.setFrequency(RF69_FREQ)) 
  { 
    //Serial.println("setFrequency failed"); 
    }
  rf69.setTxPower(18, true);  // range from 14-20 for power, 2nd arg must be true for 69HCW
  // The encryption key has to be the same as the one in the server
  uint8_t key[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 
                    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
  rf69.setEncryptionKey(key);
};
void alarmMatch() {
  delay(10);
};
void loop() {
  digitalWrite(POW_WATER, HIGH);delay(500);
  digitalWrite(POW_AIR, HIGH);delay(500);
  if (!Water.begin(I2C_WATER)) {
    //Serial.println("Couldn't find Water chip");
    while (1);
  }
  if (!Air.begin(I2C_AIR)) {
    //Serial.println("Couldn't find Air chip");
    while (1);
  }
  Air.reset();Water.reset();
  sensorVoltage = ReadBattery();
  WaterPressure = roundf(Water.readPressure())+3; // "3" seems this sensor values differing, both sensors tested on same conditions.The "Water Sensor gives -3 hPa values
  AirPressure = roundf(Air.readPressure());
  digitalWrite(POW_WATER, LOW);
  digitalWrite(POW_AIR, LOW);
  char rpacket[60]; // max packet length RFM69 60 Byte
  int n = sprintf(rpacket, "{'Charge': %d,'AP': %d,'WP': %d}",sensorVoltage,AirPressure,WaterPressure);
  //Serial.printf("[%s] is a string %d chars long\n",rpacket,n);
  uint8_t radiopacket[n]; // reduce to the needed packet size 'n'
  memcpy(radiopacket, (const char*)rpacket, sizeof(rpacket));
  manager.sendto(radiopacket, sizeof(radiopacket), SERVER_ADDRESS);
  delay(50);
  rf69.sleep();
  rtc.begin(true);
  rtc.setTime(hours, minutes, seconds);
  rtc.setDate(day, month, year);
  rtc.setAlarmTime(13, 00, 00);
  rtc.enableAlarm(rtc.MATCH_HHMMSS);
  rtc.attachInterrupt(alarmMatch);
  rtc.standbyMode();

modified RTCZero.h

C/C++
RTCZero library
#include "Arduino.h"

typedef void(*voidFuncPtr)(void);

class RTCZero {
public:

  enum Alarm_Match: uint8_t // Should we have this enum or just use the identifiers from /component/rtc.h ?
  {
    MATCH_OFF          = RTC_MODE2_MASK_SEL_OFF_Val,          // Never
    MATCH_SS           = RTC_MODE2_MASK_SEL_SS_Val,           // Every Minute
    MATCH_MMSS         = RTC_MODE2_MASK_SEL_MMSS_Val,         // Every Hour
    MATCH_HHMMSS       = RTC_MODE2_MASK_SEL_HHMMSS_Val,       // Every Day
    MATCH_DHHMMSS      = RTC_MODE2_MASK_SEL_DDHHMMSS_Val,     // Every Month
    MATCH_MMDDHHMMSS   = RTC_MODE2_MASK_SEL_MMDDHHMMSS_Val,   // Every Year
    MATCH_YYMMDDHHMMSS = RTC_MODE2_MASK_SEL_YYMMDDHHMMSS_Val  // Once, on a specific date and a specific time
  };

  RTCZero();
  void begin(bool resetTime = false);

  void enableAlarm(Alarm_Match match);
  void disableAlarm();

  void attachInterrupt(voidFuncPtr callback);
  void detachInterrupt();
  
  void standbyMode();
  
  /* Get Functions */

  uint8_t getSeconds();
  uint8_t getMinutes();
  uint8_t getHours();
  
  uint8_t getDay();
  uint8_t getMonth();
  uint8_t getYear();
  
  uint8_t getAlarmSeconds();
  uint8_t getAlarmMinutes();
  uint8_t getAlarmHours();

  uint8_t getAlarmDay();
  uint8_t getAlarmMonth();
  uint8_t getAlarmYear();

  /* Set Functions */

  void setSeconds(uint8_t seconds);
  void setMinutes(uint8_t minutes);
  void setHours(uint8_t hours);
  void setTime(uint8_t hours, uint8_t minutes, uint8_t seconds);

  void setDay(uint8_t day);
  void setMonth(uint8_t month);
  void setYear(uint8_t year);
  void setDate(uint8_t day, uint8_t month, uint8_t year);

  void setAlarmSeconds(uint8_t seconds);
  void setAlarmMinutes(uint8_t minutes);
  void setAlarmHours(uint8_t hours);
  void setAlarmTime(uint8_t hours, uint8_t minutes, uint8_t seconds);

  void setAlarmDay(uint8_t day);
  void setAlarmMonth(uint8_t month);
  void setAlarmYear(uint8_t year);
  void setAlarmDate(uint8_t day, uint8_t month, uint8_t year);

  /* Epoch Functions */

  uint32_t getEpoch();
  uint32_t getY2kEpoch();
  void setEpoch(uint32_t ts);
  void setY2kEpoch(uint32_t ts);
  void setAlarmEpoch(uint32_t ts);

  bool isConfigured() {
    return _configured;
  }

private:
  bool _configured;

  void config32kOSC(void);
  void configureClock(void);
  void RTCreadRequest();
  bool RTCisSyncing(void);
  void RTCdisable();
  void RTCenable();
  void RTCreset();
  void RTCresetRemove();
};
#endif // RTC_ZERO_H

modified RTCZero.cpp

C/C++
RTCZero.cpp modified to use the interval oscillator
/*
  RTC library for Arduino Zero.
  Copyright (c) 2015 Arduino LLC. All right reserved.
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.
  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <time.h>
#include <RTCZero.h>

#define EPOCH_TIME_OFF      946684800  // This is 1st January 2000, 00:00:00 in epoch time
#define EPOCH_TIME_YEAR_OFF 100        // years since 1900
// Default date & time after reset
#define DEFAULT_YEAR    2000    // 2000..2063
#define DEFAULT_MONTH   1       // 1..12
#define DEFAULT_DAY     1       // 1..31
#define DEFAULT_HOUR    0       // 1..23
#define DEFAULT_MINUTE  0       // 0..59
#define DEFAULT_SECOND  0       // 0..59

voidFuncPtr RTC_callBack = NULL;

RTCZero::RTCZero()
{
  _configured = false;
};

void RTCZero::begin(bool resetTime)
{
  uint16_t tmp_reg = 0;
  
  PM->APBAMASK.reg |= PM_APBAMASK_RTC; // turn on digital interface clock
  config32kOSC();

  // If the RTC is in clock mode and the reset was
  // not due to POR or BOD, preserve the clock time
  // POR causes a reset anyway, BOD behaviour is?
  bool validTime = false;
  RTC_MODE2_CLOCK_Type oldTime;

  if ((!resetTime) && (PM->RCAUSE.reg & (PM_RCAUSE_SYST | PM_RCAUSE_WDT | PM_RCAUSE_EXT))) {
    if (RTC->MODE2.CTRL.reg & RTC_MODE2_CTRL_MODE_CLOCK) {
      validTime = true;
      oldTime.reg = RTC->MODE2.CLOCK.reg;
    }
  }

  // Setup clock GCLK2 with OSC32K divided by 32
  configureClock();

  RTCdisable();

  RTCreset();

  tmp_reg |= RTC_MODE2_CTRL_MODE_CLOCK; // set clock operating mode
  tmp_reg |= RTC_MODE2_CTRL_PRESCALER_DIV1024; // set prescaler to 1024 for MODE2
  tmp_reg &= ~RTC_MODE2_CTRL_MATCHCLR; // disable clear on match
  
  //According to the datasheet RTC_MODE2_CTRL_CLKREP = 0 for 24h
  tmp_reg &= ~RTC_MODE2_CTRL_CLKREP; // 24h time representation

  RTC->MODE2.READREQ.reg &= ~RTC_READREQ_RCONT; // disable continuously mode

  RTC->MODE2.CTRL.reg = tmp_reg;
  while (RTCisSyncing());

  NVIC_EnableIRQ(RTC_IRQn); // enable RTC interrupt 
  NVIC_SetPriority(RTC_IRQn, 0x00);

  RTC->MODE2.INTENSET.reg |= RTC_MODE2_INTENSET_ALARM0; // enable alarm interrupt
  RTC->MODE2.Mode2Alarm[0].MASK.bit.SEL = MATCH_OFF; // default alarm match is off (disabled)
  
  while (RTCisSyncing());

  RTCenable();
  RTCresetRemove();

  // If desired and valid, restore the time value
  if ((!resetTime) && (validTime)) {
    RTC->MODE2.CLOCK.reg = oldTime.reg;
    while (RTCisSyncing());
  }
  _configured = true;
};

void RTC_Handler(void)
{
  if (RTC_callBack != NULL) {
    RTC_callBack();
  }

  RTC->MODE2.INTFLAG.reg = RTC_MODE2_INTFLAG_ALARM0; // must clear flag at end
};

void RTCZero::enableAlarm(Alarm_Match match)
{
  if (_configured) {
    RTC->MODE2.Mode2Alarm[0].MASK.bit.SEL = match;
    while (RTCisSyncing());
  }
};

void RTCZero::disableAlarm()
{
  if (_configured) {
    RTC->MODE2.Mode2Alarm[0].MASK.bit.SEL = 0x00;
    while (RTCisSyncing());
  }
};

void RTCZero::attachInterrupt(voidFuncPtr callback)
{
  RTC_callBack = callback;
};

void RTCZero::detachInterrupt()
{
  RTC_callBack = NULL;
};

void RTCZero::standbyMode()
{
  // Entering standby mode when connected
  // via the native USB port causes issues.
  SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
  __DSB();
  __WFI();
  SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
};

/*
 * Get Functions
 */

uint8_t RTCZero::getSeconds()
{
  RTCreadRequest();
  return RTC->MODE2.CLOCK.bit.SECOND;
};

uint8_t RTCZero::getMinutes()
{
  RTCreadRequest();
  return RTC->MODE2.CLOCK.bit.MINUTE;
};

uint8_t RTCZero::getHours()
{
  RTCreadRequest();
  return RTC->MODE2.CLOCK.bit.HOUR;
};

uint8_t RTCZero::getDay()
{
  RTCreadRequest();
  return RTC->MODE2.CLOCK.bit.DAY;
};

uint8_t RTCZero::getMonth()
{
  RTCreadRequest();
  return RTC->MODE2.CLOCK.bit.MONTH;
};

uint8_t RTCZero::getYear()
{
  RTCreadRequest();
  return RTC->MODE2.CLOCK.bit.YEAR;
};

uint8_t RTCZero::getAlarmSeconds()
{
  return RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND;
};

uint8_t RTCZero::getAlarmMinutes()
{
  return RTC->MODE2.Mode2Alarm[0].ALARM.bit.MINUTE;
};

uint8_t RTCZero::getAlarmHours()
{
  return RTC->MODE2.Mode2Alarm[0].ALARM.bit.HOUR;
};

uint8_t RTCZero::getAlarmDay()
{
  return RTC->MODE2.Mode2Alarm[0].ALARM.bit.DAY;
};

uint8_t RTCZero::getAlarmMonth()
{
  return RTC->MODE2.Mode2Alarm[0].ALARM.bit.MONTH;
};

uint8_t RTCZero::getAlarmYear()
{
  return RTC->MODE2.Mode2Alarm[0].ALARM.bit.YEAR;
};

/*
 * Set Functions
 */

void RTCZero::setSeconds(uint8_t seconds)
{
  if (_configured) {
    RTC->MODE2.CLOCK.bit.SECOND = seconds;
    while (RTCisSyncing());
  }
};

void RTCZero::setMinutes(uint8_t minutes)
{
  if (_configured) {
    RTC->MODE2.CLOCK.bit.MINUTE = minutes;
    while (RTCisSyncing());
  }
};

void RTCZero::setHours(uint8_t hours)
{
  if (_configured) {
    RTC->MODE2.CLOCK.bit.HOUR = hours;
    while (RTCisSyncing());
  }
};

void RTCZero::setTime(uint8_t hours, uint8_t minutes, uint8_t seconds)
{
  if (_configured) {
    setSeconds(seconds);
    setMinutes(minutes);
    setHours(hours);
  }
};

void RTCZero::setDay(uint8_t day)
{
  if (_configured) {
    RTC->MODE2.CLOCK.bit.DAY = day;
    while (RTCisSyncing());
  }
};

void RTCZero::setMonth(uint8_t month)
{
  if (_configured) {
    RTC->MODE2.CLOCK.bit.MONTH = month;
    while (RTCisSyncing());
  }
};

void RTCZero::setYear(uint8_t year)
{
  if (_configured) {
    RTC->MODE2.CLOCK.bit.YEAR = year;
    while (RTCisSyncing());
  }
};

void RTCZero::setDate(uint8_t day, uint8_t month, uint8_t year)
{
  if (_configured) {
    setDay(day);
    setMonth(month);
    setYear(year);
  }
};

void RTCZero::setAlarmSeconds(uint8_t seconds)
{
  if (_configured) {
    RTC->MODE2.Mode2Alarm[0].ALARM.bit.SECOND = seconds;
    while (RTCisSyncing());
  }
};

void RTCZero::setAlarmMinutes(uint8_t minutes)
{
  if (_configured) {
    RTC->MODE2.Mode2Alarm[0].ALARM.bit.MINUTE = minutes;
    while (RTCisSyncing());
  }
};

void RTCZero::setAlarmHours(uint8_t hours)
{
  if (_configured) {
    RTC->MODE2.Mode2Alarm[0].ALARM.bit.HOUR = hours;
    while (RTCisSyncing());
  }
};

void RTCZero::setAlarmTime(uint8_t hours, uint8_t minutes, uint8_t seconds)
{
  if (_configured) {
    setAlarmSeconds(seconds);
    setAlarmMinutes(minutes);
    setAlarmHours(hours);
  }
};

void RTCZero::setAlarmDay(uint8_t day)
{
  if (_configured) {
    RTC->MODE2.Mode2Alarm[0].ALARM.bit.DAY = day;
    while (RTCisSyncing());
  }
};

void RTCZero::setAlarmMonth(uint8_t month)
{
  if (_configured) {
    RTC->MODE2.Mode2Alarm[0].ALARM.bit.MONTH = month;
    while (RTCisSyncing());
  }
};

void RTCZero::setAlarmYear(uint8_t year)
{
  if (_configured) {
    RTC->MODE2.Mode2Alarm[0].ALARM.bit.YEAR = year;
    while (RTCisSyncing());
  }
};

void RTCZero::setAlarmDate(uint8_t day, uint8_t month, uint8_t year)
{
  if (_configured) {
    setAlarmDay(day);
    setAlarmMonth(month);
    setAlarmYear(year);
  }
};

uint32_t RTCZero::getEpoch()
{
  RTCreadRequest();
  RTC_MODE2_CLOCK_Type clockTime;
  clockTime.reg = RTC->MODE2.CLOCK.reg;

  struct tm tm;

  tm.tm_isdst = -1;
  tm.tm_yday = 0;
  tm.tm_wday = 0;
  tm.tm_year = clockTime.bit.YEAR + EPOCH_TIME_YEAR_OFF;
  tm.tm_mon = clockTime.bit.MONTH - 1;
  tm.tm_mday = clockTime.bit.DAY;
  tm.tm_hour = clockTime.bit.HOUR;
  tm.tm_min = clockTime.bit.MINUTE;
  tm.tm_sec = clockTime.bit.SECOND;

  return mktime(&tm);
};

uint32_t RTCZero::getY2kEpoch()
{
  return (getEpoch() - EPOCH_TIME_OFF);
};

void RTCZero::setAlarmEpoch(uint32_t ts)
{
  if (_configured) {
    if (ts < EPOCH_TIME_OFF) {
      ts = EPOCH_TIME_OFF;
    }

    time_t t = ts;
    struct tm* tmp = gmtime(&t);

    setAlarmDate(tmp->tm_mday, tmp->tm_mon + 1, tmp->tm_year - EPOCH_TIME_YEAR_OFF);
    setAlarmTime(tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
  }
};

void RTCZero::setEpoch(uint32_t ts)
{
  if (_configured) {
    if (ts < EPOCH_TIME_OFF) {
      ts = EPOCH_TIME_OFF;
    }

    time_t t = ts;
    struct tm* tmp = gmtime(&t);

    RTC->MODE2.CLOCK.bit.YEAR = tmp->tm_year - EPOCH_TIME_YEAR_OFF;
    RTC->MODE2.CLOCK.bit.MONTH = tmp->tm_mon + 1;
    RTC->MODE2.CLOCK.bit.DAY = tmp->tm_mday;
    RTC->MODE2.CLOCK.bit.HOUR = tmp->tm_hour;
    RTC->MODE2.CLOCK.bit.MINUTE = tmp->tm_min;
    RTC->MODE2.CLOCK.bit.SECOND = tmp->tm_sec;

    while (RTCisSyncing());
  }
};

void RTCZero::setY2kEpoch(uint32_t ts)
{
  if (_configured) {
    setEpoch(ts + EPOCH_TIME_OFF);
  }
};

/* Attach peripheral clock to 32k oscillator */
void RTCZero::configureClock() {
  GCLK->GENDIV.reg = GCLK_GENDIV_ID(2)|GCLK_GENDIV_DIV(4);
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
  GCLK->GENCTRL.reg = (GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(2) | GCLK_GENCTRL_DIVSEL );
  while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
  GCLK->CLKCTRL.reg = (uint32_t)((GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | (RTC_GCLK_ID << GCLK_CLKCTRL_ID_Pos)));
  while (GCLK->STATUS.bit.SYNCBUSY);
};

/*
 * Private Utility Functions
 */

/* Configure the 32768Hz Oscillator */
void RTCZero::config32kOSC() 
{
  // GCLK_GENCTRL_SRC_OSCULP32K is always on!
  
  // SYSCTRL->XOSC32K.reg = SYSCTRL_XOSC32K_ONDEMAND |
  //                        SYSCTRL_XOSC32K_RUNSTDBY |
  //                        SYSCTRL_XOSC32K_EN32K |
  //                        SYSCTRL_XOSC32K_XTALEN |
  //                        SYSCTRL_XOSC32K_STARTUP(6) |
  //                        SYSCTRL_XOSC32K_ENABLE;
};

/* Synchronise the CLOCK register for reading*/
inline void RTCZero::RTCreadRequest() {
  if (_configured) {
    RTC->MODE2.READREQ.reg = RTC_READREQ_RREQ;
    while (RTCisSyncing());
  }
};

/* Wait for sync in write operations */
inline bool RTCZero::RTCisSyncing()
{
  return (RTC->MODE2.STATUS.bit.SYNCBUSY);
};

void RTCZero::RTCdisable()
{
  RTC->MODE2.CTRL.reg &= ~RTC_MODE2_CTRL_ENABLE; // disable RTC
  while (RTCisSyncing());
};

void RTCZero::RTCenable()
{
  RTC->MODE2.CTRL.reg |= RTC_MODE2_CTRL_ENABLE; // enable RTC
  while (RTCisSyncing());
};

void RTCZero::RTCreset()
{
  RTC->MODE2.CTRL.reg |= RTC_MODE2_CTRL_SWRST; // software reset
  while (RTCisSyncing());
};

void RTCZero::RTCresetRemove()
{
  RTC->MODE2.CTRL.reg &= ~RTC_MODE2_CTRL_SWRST; // software reset remove
  while (RTCisSyncing());
};

alternate solution VL53L1X

C/C++
Time of Flight sensor
#include <Arduino.h>
#include <SparkFun_VL53L1X.h>
#include <Wire.h>
#include <RH_RF69.h>
#include <RHDatagram.h>
#include <RTCZero.h>

#define VL53L1X_POWER A2
#define VBATPIN A7 // measuring battery
#define RF69_FREQ 433.0
#define RFM69_CS      8
#define RFM69_INT     3
#define RFM69_RST     4
#define CLIENT_ADDRESS 13 // RHDatagram
#define SERVER_ADDRESS 1 // RHDatagram


SFEVL53L1X distanceSensor;
RH_RF69 rf69(RFM69_CS, RFM69_INT);
RHDatagram manager(rf69, CLIENT_ADDRESS);
RTCZero rtc;
const bool resetTime = true;
const uint8_t wait = 30;
const byte seconds = 00;
const byte minutes = 00;
const byte hours = 10;    
const byte day = 1;
const byte month = 1;
const byte year = 20;

int Voltage;
int distance;
uint16_t roix;
uint16_t roiy;

int ReadBattery() {
  digitalWrite(VBATPIN, HIGH);delay(50);
  float measuredvbat = analogRead(VBATPIN);
  digitalWrite(VBATPIN, LOW);
  measuredvbat *=2; // we divided by 2, so multiply back
  measuredvbat *=3.3; // Multiply by 3.3V, our reference voltage
  measuredvbat /= 1024; // convert to voltage
  if (measuredvbat>4.25f){measuredvbat=4.25f;}
  int result = roundf(100*measuredvbat/4.25f);
  return result; // value back in percent
 };

 void Values() { 
  digitalWrite(VL53L1X_POWER, HIGH);delay(50);
  Wire.begin();
  if (distanceSensor.begin() != 0) //Begin returns 0 on a good init
  // Serial.println("Sensor failed to begin. Please check wiring. Freezing...");
  {while (1);}
  distanceSensor.setROI(11, 11, 199);
  distanceSensor.startRanging(); //Write configuration bytes to initiate measurement
  while (!distanceSensor.checkForDataReady())
  {delay(1);}
  distance = 0;
  for (byte i = 0; i < 5; i++){
  distance = distance + distanceSensor.getDistance(); //Get the result of the measurement from the sensor
  delay(1);
  }
  distance = round(distance / 50); // cm
  distanceSensor.clearInterrupt();
  distanceSensor.stopRanging();
  digitalWrite(VL53L1X_POWER, LOW);
 };


void setup(void)
{
  pinMode(VL53L1X_POWER, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); // LED off
  //Serial.begin(115200);while(!Serial) delay(10);
  digitalWrite(RFM69_RST, HIGH); delay(10);
  digitalWrite(RFM69_RST, LOW); delay(10);
  if (!manager.init()) {
    //Serial.println("init failed"); 
    while (1);}
  if (!rf69.setFrequency(RF69_FREQ)) { 
    //Serial.println("setFrequency failed"); 
    while (1);}
  rf69.setTxPower(18, true);
  uint8_t key[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
  rf69.setEncryptionKey(key);
  rtc.begin(resetTime);
  rtc.setTime(hours, minutes, seconds);
  rtc.setDate(day, month, year);

}

void alarmMatch() {
  rtc.setTime(hours, minutes, seconds);
  rtc.setDate(day, month, year);
};

void loop(void)
{
  Voltage = ReadBattery();
  Values();
  char rpacket[60]; // max packet length RFM69 60 Byte
  int n = sprintf(rpacket, "{'%d':{'B':%d,'DIST':%d}}", CLIENT_ADDRESS, Voltage, distance);
  uint8_t radiopacket[n]; // reduce to the needed packet size 'n'
  memcpy(radiopacket, (const char*)rpacket, sizeof(rpacket));
  manager.sendto(radiopacket, sizeof(radiopacket), SERVER_ADDRESS);
  delay(50);
  rf69.sleep(); 
  rtc.setAlarmMinutes(wait);
  rtc.setAlarmSeconds(0);
  rtc.enableAlarm(rtc.MATCH_MMSS);
  rtc.attachInterrupt(alarmMatch);
  rtc.standbyMode();

HAP-Python Radio Bridge

Bridge to AppleHomekit

Credits

user330352

user330352

5 projects • 1 follower
start programming on the first single chip 8086 in the '80

Comments