Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
|
Any years before, I buried a water tank, catch rain water from the roof to water my garden. The buried tank have a 200 mm diameter maintenance tube, that rise from the ground. In the tank I have a immersion pump. I want to know, how much water is left in the tank for watering at any time.
My first try, using a ultrasonic sensor failed, because of the special tank design. The sensor position on the top of the maintance tube leads into limited field of view down in the tube - too many reflexions, I was not able to get a stable and exact positioning of the ultrasonic sensor. Then, I used water pressure. The unit itself (mcu, sensors, etc) needs to be water resistent (IP67) and should run with LiPos over long periods.
solution one - using the difference in pressure measuringOne sensor measure the pressure above the surface (air pressure), the second sensor measure the pressure under water - rough estimates, one meter below the water surface, the sensor will measure air pressure plus around 100 hPa additional water pressure. The immersion pump have a kind of float switch. This float switch stop the pump, when the water level falls below a certain mark to prevent immersion pump overheating. This mark should be the "zero point" , means, no water left.
Based on my other project, I want to get the data displayed in the Apple Homekit. The mcu is battery powered and should work with 18650 cells other months. In the past I decided to use 433 MHz, because of signal coverage - I take the Adafruit Feather M0 RFM69 for these kind of installations. This board based on SAMD21 cpu. We can use the RTC timer to send the board into deep sleep mode between the measuring actions.
( If have tried the Adafruit 32u4 board too. Unfortunately, for the ATmega32u4, I didn't found a working RTC driver. So I use the TPL5111 breakout, a external timer board, the timing cycle can be set by a resistor up to 2 hours to switch on/off the board. )
For the SAMD21G18A-based board I use a modified RTCZero lib to let the unit go into deep sleep. Unfortunately, the original Arduino RTCZero library let the mcu freeze sporadically on this board. My solution is a modified library, use the less precise but more stable internal oscillator OSCULP32K. (see RTCZero modified)
For sensing the under water pressure data, I take the LPS33HW water resistance sensors. To save battery capacity, i use digital Pins to power the sensors before reading starts and switch off, if reading finished.
Soldering
For the "Water Sensor" solder the "address bridge" on LPS33HW back side, to set the I2C address on 0x5C. On the "Air Sensor" let the address at default 0x5D.
- SCK - this is the I2C clock pin, connect to your microcontrollers I2C clock line SCL.
- SDI - this is the I2C data pin, connect to your microcontrollers I2C data line SDA.
- VIN Air Sensor - connect to your microcontroller A1
- VIN Water Sensor - connect to your microcontroller A2
- GND to GND
Using the whole unit outside, put all together into a IP66 case.
The sensor itself is water resistant, but for the water sensor, you need to cover the rest of the board electronics into epoxy.
The prog (see Sensor unit) calculate the difference between the measured data (air and water pressure) and send the data along the Semtec RFM69 chip RFM69 bridge. For easier handling I use a json-like format.
solution two - Time of flight sensorAgainst the ultrasonic sensor, the Time of flight sensor has a smaller beam, that results in a smaller field of view. I use the VL53L1X sensor, because this sensor has a range up to 4 meter. Additionally, the beam can be configured. The light emitter is a 16 x 16 field. The focus can be set.
The only disadvantage, i see is the power consumption while setting up - it goes up to 40 mA, seen in the data sheet.
The Bridge can based on a cheap Raspberry Pi Zero W, a RFM69 breakout hooked up. Over the time, i have any different 433 MHz sensors. All these sensor are independent and sending data. To prevent missing, overlapping communication/data i use the ability of python threading. For that, the raspberry needs to have a multi core processor. The Pi Zero is a single core unit, so i use a Raspberry Pi 3. The breakout cabling ist the same.
Use the following Pins:
- Vin to Raspberry Pi 3.3V
- GND to Raspberry Pi Ground
- RFM G0 to Raspberry Pi GPIO #24
- RFM RST to Raspberry Pi GPIO #25
- RFM CLK to Raspberry Pi SCLK = GPIO #11
- RFM MISO to Raspberry Pi MISO = GPIO #9
- RFM MOSI to Raspberry Pi MOSI = GPIO #10
- RFM CS to Raspberry Pi CS1 = GPIO #7
For better signal strength attach a external antenna.
The bridge software based on a HAP python implementation. How it works, you can see that on GitHub for my other projects.
checking incoming data with:
sudo grep "Transceiver" </var/log/syslog -a
seen problems:
Because of price or the fact, these are evaluation sensors, the LPS35HW gives different values. I saw a different of 3 hPa between these two sensors on the same height level.
/*!
* @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
/*!
* @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);
}
#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();
#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
/*
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());
};
#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();
Comments