Bcjams
Published © GPL3+

Tipping Bucket Rain Gauge

This is a tipping bucket rain gauge designed in two separate modules; an outdoor sensing unit, and an indoor display with SD card data.

IntermediateFull instructions provided17,874
Tipping Bucket Rain Gauge

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
Tiny RTC I2C DS1307
×1
SD Card Module - LC Studios
×1
7-12 VDC Power Supply (or Wall Wart)
×1
Standard LCD - 16x2 White on Blue
Adafruit Standard LCD - 16x2 White on Blue
×1
Shift Register- Serial to Parallel
Texas Instruments Shift Register- Serial to Parallel
×1
Resistor 1k ohm
Resistor 1k ohm
×2
Resistor 330 ohm
Resistor 330 ohm
×2
Capacitor 100 nF
Capacitor 100 nF
×1
Single Turn Potentiometer- 10k ohms
Single Turn Potentiometer- 10k ohms
×1
PCB Momentary Contact Pushbutton Switch
×1
Reed Switch, SPST-NO
Reed Switch, SPST-NO
×1
Magnet (7mm OD x 7MM long)
×1
5x10CM single side prototype board
×1

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

Base

STL for 3D Printing Outdoor unit base. I printed with PETG at 30% infill

Top Cover

STL file for 3D printing outdoor unit cover. I printed with PETG at 30% infill

Funnel

STL File for 3D Printing outdoor unit funnel. I printed using PETG at 30% infill.

Filter

STL File for 3D Printing outdoor unit filter. I printed using PETG at 20% infill. This part must be printed with NO horizontal bottom or top layers, creating a 'porous' filter.

Sub Base

STL file for 3D Printing the sub base that the bucket attaches to through pivot rod. I printed using PETG with 30% infill.

Bucket

STL File for 3D Printing the bucket. I printed using PETG with 30% infill.

Control Box Enclosure

STL File for printing the indoor unit enclosure. I printed with PETG at 30% infill.

Front Cover for Indoor Unit

STL file for 3D Printing the front cover for indoor unit. I printed with PETG at 30% infill.

LCD spacers

STL file to print spacers to keep LCD unit raised above the shift register IC. I printed using PETG at 30% infill.

Outdoor Unit CAD model

Fusion 360 model of outdoor unit

Outdoor Unit Step File CAD Model

.STP file of outdoor unit

Indoor Unit Step file CAD model

STEP Cad model of indoor Unit

Outdoor Unit Fusion 360 CAD Model

Fusion 36 CAD model of Outdoor unit

Switch Button

STL file for 3D Printing the Switch push button

Filter with Tab

Schematics

Wiring Diagram

Project Wiring Schematic

Code

Arduino Code to set time on RTC

Arduino
Load this code first to set time on RTC. Edit the code with actual date and time before uploading to Arduino.
#include "Wire.h"
#define DS1307_I2C_ADDRESS 0x68 // the I2C address of Tiny RTC
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
return ( (val/10*16) + (val%10) );
}
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
return ( (val/16*10) + (val%16) );
}
// Function to set the currnt time, change the second&minute&hour to the right time
void setDateDs1307()
{
second =00;
minute = 11;
hour = 12;
dayOfWeek = 5;
dayOfMonth =24;
month =7;
year= 20;
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(decToBcd(0));
Wire.write(decToBcd(second)); // 0 to bit 7 starts the clock
Wire.write(decToBcd(minute));
Wire.write(decToBcd(hour)); // If you want 12 hour am/pm you need to set
// bit 6 (also need to change readDateDs1307)
Wire.write(decToBcd(dayOfWeek));
Wire.write(decToBcd(dayOfMonth));
Wire.write(decToBcd(month));
Wire.write(decToBcd(year));
Wire.endTransmission();
}
// Function to gets the date and time from the ds1307 and prints result
void getDateDs1307()
{
// Reset the register pointer
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(decToBcd(0));
Wire.endTransmission();
Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
second = bcdToDec(Wire.read() & 0x7f);
minute = bcdToDec(Wire.read());
hour = bcdToDec(Wire.read() & 0x3f); // Need to change this if 12 hour am/pm
dayOfWeek = bcdToDec(Wire.read());
dayOfMonth = bcdToDec(Wire.read());
month = bcdToDec(Wire.read());
year = bcdToDec(Wire.read());
Serial.print(hour, DEC);
Serial.print(":");
Serial.print(minute, DEC);
Serial.print(":");
Serial.print(second, DEC);
Serial.print(" ");
Serial.print(month, DEC);
Serial.print("/");
Serial.print(dayOfMonth, DEC);
Serial.print("/");
Serial.print(year,DEC);
Serial.print(" ");
Serial.println();
//Serial.print("Day of week:");
}
void setup() {
Wire.begin();
Serial.begin(19200);
setDateDs1307(); //Set current time;
}
void loop()
{
delay(2000);
getDateDs1307();//get the time data from tiny RTC
}

Main Program Arduino code For Rain Gauge

Arduino
Main Program for Rain Gauge. Make sure you install all associated libraries before compiling your program. Reference website [ https://roboindia.com/tutorials/arduino-3-pin-serial-lcd/ ] for instructions on updating the LiquidCrystal library.
/*
Tipping Bucket Rain Gauge
Written by Bob Torrence
*/
#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include "RTClib.h"
#include <LiquidCrystal_SR.h> // includes the LiquidCrystal Library (special version) ref https://roboindia.com/tutorials/arduino-3-pin-serial-lcd/
// Defining LCD and Pins for interfacing.
LiquidCrystal_SR lcd(6, 5, 9); // Pin 6 - Data Enable/ SER, Pin 5 - Clock/SCL, Pin 9 -SCK
RTC_DS3231 rtc;
int backlight = 7; // the pin the LED is connected to pin 7 (D7)
// constants won't change. They're used here to set pin numbers:
const byte interruptPin_bucket = 3;
const byte interruptPin_menu = 2;

// Variables will change:
volatile int Bucket_Tip_Occurence;
volatile int Menu_Select;
float Bucket_tip_hour_total = 0;
float Bucket_tip_current_hour_total = 0;
float Bucket_tip_previous_hour_total = 0;
float Bucket_tip_current_day_total = 0;
float Bucket_tip_previous_day_total = 0;
int current_minute;
int loop_minute;
int current_hour;
int loop_hour;
int current_day;
int loop_day;
int tip_counter;
float conversion_factor = .00854;  //inches of rain per tip - calculated by measuring bucket volume and area of collector (16.605 sq.in.)
volatile unsigned long backlightOfftime;
volatile unsigned long backlightOnDuration=30000; // duration (miiliseconds) that backlight remains on aftter menu select button pushed

String print_time(DateTime timestamp) {
  char message[120];

  int Year = timestamp.year();
  int Month = timestamp.month();
  int Day = timestamp.day();
  int Hour = timestamp.hour();
  int Minute = timestamp.minute();
  int Second= timestamp.second();

  sprintf(message, "%02d:%02d:%02d %02d/%02d", Hour,Minute,Second,Month,Day);
  
  return message;
}


File myFile;

void setup() {
  lcd.begin(16,2); // Initializes the interface to the LCD screen, and specifies the dimensions (width and height) of the display 
  lcd.home();  // Setting Cursor at Home i.e. 0,0
  rtc.begin(); //initiate use of Real Time Clock variables
  pinMode(10, OUTPUT);
  pinMode(backlight, OUTPUT); // Declare the LED as an output
  digitalWrite(backlight,HIGH);  //turn on lcd backlight
  backlightOfftime = millis() + backlightOnDuration;  //set inital time delay for the LCD backlight to be On
  
   
  if (!SD.begin(4)) {
    lcd.print("insert SD Card");
    return;
                     }
 

  // Set up our digital pin as an interrupt for bucket
  pinMode(interruptPin_bucket, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin_bucket), count, FALLING);

    // Set up our digital pin as an interrupt for bucket
  pinMode(interruptPin_menu, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin_menu), menu, RISING);


  
  myFile = SD.open("test.txt", FILE_WRITE);
     if (myFile) {
        myFile.println("Rain Gauge Ready - unit powered up");
  myFile.close();      
                  }
   DateTime now = rtc.now() ;
   current_minute = now.minute();
   loop_minute = now.minute();
   current_hour = now.hour();
   loop_hour = now.hour();
   lcd.setCursor (0,0);
   lcd.print(print_time(now)); // Prints "Arduino" on the LCD
   lcd.setCursor(1,1);
   lcd.print("Rain Gauge");
   
}

void loop(){
   DateTime now = rtc.now() ;
   current_minute = now.minute();
   loop_minute = now.minute();
   current_hour = now.hour();
   loop_hour = now.hour();
   current_day = now.day();
   loop_day = now.day();
          
          //Begin Loop to determine tally current day totals 
              while (loop_day - current_day == 0){  
                    //Begin Loop to determine tally current hour totals 
                    while (loop_hour - current_hour == 0){  
                            if (millis() > backlightOfftime) {
                              digitalWrite(backlight,LOW);
                            }
                     if  (Bucket_Tip_Occurence == 1) {
                     Bucket_Tip_Occurence = 0;
                     tip_counter = tip_counter + 1;
                     DateTime now = rtc.now() ;
                        myFile = SD.open("test.txt", FILE_WRITE); 
                        myFile.print("Event ");
                        myFile.print(now.year(), DEC);
                        myFile.print('/');
                        myFile.print(now.month(), DEC);
                        myFile.print('/');
                        myFile.print(now.day(), DEC);
                        myFile.print(" ");
                        myFile.print(now.hour(), DEC);
                        myFile.print(':');
                        myFile.print(now.minute(), DEC);
                        myFile.print(':');
                        myFile.print(now.second(), DEC);
                        myFile.print("  ");
                        myFile.print(conversion_factor,5);
                        myFile.println();
                        myFile.close();
                        delay(200); }
                     
                          else { //Check for current hour status
                                DateTime now = rtc.now() ;
                                loop_hour= now.hour();} 
                                
                           switch (Menu_Select) {
                              case 1:                                 
                                  lcd.clear();  
                                  lcd.setCursor(0,0);
                                  lcd.print("Current Hour");
                                  lcd.setCursor(0,1);
                                  lcd.print(tip_counter * conversion_factor);
                                  delay(500);
                                  break;
                             case 2:                               
                                  lcd.clear();  
                                  lcd.setCursor(0,0);
                                  lcd.print("Previous Hour");
                                  lcd.setCursor(0,1);
                                  lcd.print(Bucket_tip_previous_hour_total);
                                  delay(500);
                                  break;
                            case 3:                       
                                  lcd.clear();  
                                  lcd.setCursor(0,0);
                                  lcd.print("Current Day");
                                  lcd.setCursor(0,1);
                                  lcd.print(Bucket_tip_current_day_total + tip_counter * conversion_factor);
                                  delay(500);
                                  break;
                            case 4:                        
                                  lcd.clear();  
                                  lcd.setCursor(0,0);
                                  lcd.print("Previous Day");
                                  lcd.setCursor(0,1);
                                  lcd.print(Bucket_tip_previous_day_total);
        //                          lcd.print(print_time(now));
                                  delay(500); 
                                  break;   
                           }
                    }

                DateTime now = rtc.now() ;
                        // Reset counters for next one hour loop
                current_hour = now.hour();
                loop_day = now.day();
                loop_hour= now.hour();
                        //Totalize current hour total
                Bucket_tip_previous_hour_total = tip_counter * conversion_factor;
                Bucket_tip_current_day_total = Bucket_tip_previous_hour_total + Bucket_tip_current_day_total;
                tip_counter = 0;

   /*  Optional file write with only hourly total
                myFile = SD.open("test.txt", FILE_WRITE); 
                myFile.print("Hourly Summary ");
                myFile.print(Bucket_tip_previous_hour_total);
                myFile.print(now.year(), DEC);
                myFile.print('/');
                myFile.print(now.month(), DEC);
                myFile.print('/');
                myFile.print(now.day(), DEC);
                myFile.print(" ");
                myFile.print(now.hour(), DEC);
                myFile.print(':');
                myFile.print(now.minute(), DEC);
                myFile.print(':');
                myFile.print(now.second(), DEC);
                myFile.print(" ");
                myFile.print(Bucket_tip_previous_hour_total);
                myFile.println();
                myFile.close();
                delay(200);        
*/
                 }            
        
    //Totalize current hour total
    Bucket_tip_previous_day_total =  Bucket_tip_current_day_total;
    Bucket_tip_current_day_total = 0;
    Bucket_tip_current_hour_total = 0;
    tip_counter = 0;

/* Optional file write with previous day total only
    myFile = SD.open("test.txt", FILE_WRITE); 
    myFile.print("Day Total  ");
    myFile.print(Bucket_tip_previous_day_total);
    myFile.print(print_time(now));
    myFile.close();        
*/                            
}


              

//initaiate interrupt fromm bucket reed switch
void count() {
 static unsigned long last_interrupt_time_bucket = 0;
 unsigned long interrupt_time_bucket = millis();
 // If interrupts come faster than 300ms, assume it's a bounce and ignore
 if (interrupt_time_bucket - last_interrupt_time_bucket > 300)
 {
Bucket_Tip_Occurence = 1;
 }
 last_interrupt_time_bucket = interrupt_time_bucket;
}

//initaiate interrupt fromm menu toggle switch
void menu() {
 static unsigned long last_interrupt_time_menu = 0;
 unsigned long interrupt_time_menu = millis();
 // If interrupts come faster than 300ms, assume it's a bounce and ignore
 if (interrupt_time_menu - last_interrupt_time_menu > 300)
 {
    if (digitalRead(backlight)== LOW) {
      digitalWrite(backlight,HIGH); //turns on backlight if was previously off
    }
    else 
        Menu_Select = Menu_Select + 1;
        if(Menu_Select > 4){
           Menu_Select = 1;
           }
backlightOfftime = millis() + backlightOnDuration;
Menu_Select = Menu_Select;
 last_interrupt_time_menu = interrupt_time_menu;
 }}

Credits

Bcjams

Bcjams

2 projects • 42 followers

Comments