Rain Data Calculation

Hi everyone

I am currently trying to make a weather station and am wanting to messure the amount of rain over set time period I want to be able to have a number the represents the number of times the rain tip tray has tipped in a 1 hour 1 day 1 week and 1 month time period. for example on the hourly rain it needs to be a reading of rain for the past 60mins writing over the first minute when it reaches 60min so it is always the last hour of rain. I also need this to work for the other time periods the same.

this is the code I have so far it works only for 1 hour but it does not loop back to the start until 2 mins after it should

byte rainHour[60];
byte rainDay[24];
byte rainWeek[7];

byte rain;
byte totalRain;
byte arrayLocation = 0;

const long interval = 10000;
const long interval2 = 60000;


unsigned long previousMillis = 0;
unsigned long last_interrupt_time = 0;
unsigned long previousMillis2 = 0;

unsigned long currentMillis = millis();
const byte interruptPin = 2;

void setup() {
  pinMode(13, OUTPUT);
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), rainTrigger, FALLING);
  Serial.begin(9600);
}

void loop() {

  currentMillis = millis();




  if (currentMillis - previousMillis2 >= interval2) {

    previousMillis2 = currentMillis;
    if (rain > 0) {

      rainHour[arrayLocation] = rain;
      rain = 0;
    }
    else {
      rainHour[arrayLocation] = 0;
    }
    if (arrayLocation == 60) {
      arrayLocation = 0;
    }
    else {
      arrayLocation++;
    }
  }


  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    int i;
    totalRain = 0;
    for (i = 0; i < 60; i++) {
      totalRain = totalRain + rainHour[i];
      Serial.print(rainHour[i]);
    }
    Serial.print("         ");
    Serial.println(totalRain);
  }
}


void rainTrigger() {
  digitalWrite(13, HIGH);
  delay(100);
  digitalWrite(13, LOW);
  currentMillis = millis();

  if (currentMillis - last_interrupt_time > 200)
  {
    rain = rain + 1;

    last_interrupt_time = currentMillis;
  }
}
  attachInterrupt(digitalPinToInterrupt(interruptPin), rainTrigger, FALLING);
...
void rainTrigger() {
  digitalWrite(13, HIGH);
  delay(100);
  digitalWrite(13, LOW);

Don't ever use delay() inside an interrupt. Yes it works, but that pretty much destroys any other interrupts, including the one that keeps track of millis() for you.

The rain trigger interrupt should turn the LED on, then increment the counter and set the timer. Then the main loop can look at the timer to see if it must turn the LED off

void loop() {
  if(millis() - last_interrupt_time > 100) digitalWrite(13,LOW);  //turn off the LED that flashes for each rain trigger
  if (currentMillis - previousMillis2 >= interval2) {
    previousMillis2 = currentMillis;

You realize that sometimes you can skip a millisecond? The delay above is one cause but it can even occur in a correctly-written program. So previousMillis2 ends up 'late' by 1 millisecond, or maybe more. Instead of updating it to the current (late) time, just add interval2 to it. That way the next event won't slip even later.

    if (rain > 0) {

      rainHour[arrayLocation] = rain;
      rain = 0;
    }
    else {
      rainHour[arrayLocation] = 0;
    }
    if (arrayLocation == 60) {
      arrayLocation = 0;
    }
    else {
      arrayLocation++;
    }

You are doing this in the wrong order. When your arrayLocation becomes 60, you dereference that index and then set it back to 0. Change it to this:

++arrayLocation;
if( arrayLocation==60 )
  arrayLocation=0;

I also recommend replaced the magic number 60 with a named constant.

Also, any variables that your interrupts modify must be declared volatile so that access to them is not improperly “optimized” by the compiler, and they must be accessed and modified atomically, with interrupts disabled. One of the AVRLibc headers contains a good macro for this: include the file <util/atomic.h> and do it like this:

byte localRain;
ATOMIC_BLOCK(ATOMIC_FORCEON)
{
  localRain = rain;
}

The ATOMIC_BLOCK macro disables interrupts when you enter its block, and the ATOMIC_FORCEON parameter to it enables interrupts when you exit the block. This prevents the interrupt from cutting in while you are accessing the variable and modifying it before it is completely read. When you reset it, you should also do so within a critical section like this.

It may also be necessary to fundamentally rethink your project design. The way you have this implemented, you have all of your data stored in SRAM. If your Arduino loses power for even the briefest of moments, you lose everything.

If you will be using this in a long-term installation, you will probably want to be able to retain information after a power loss. Especially since this is a rain monitor; I shouldn’t need to remind you want can happen to the electrical mains in a rain storm. This will necessitate the use of non-volatile memory. The Arduinos have inbuilt EEPROM, but not very much, most likely nowhere near enough for a long term monitoring project.

Mouser has through hole serial EEPROMs up to 128kB capacity and SMD serial EEPROMs up to 256kB capacity. Whether these will be enough will depend on how you choose to store the data. You can always use more than one chip if you need more memory than that.

Or you can use an SD card shield. I’ve never used one before so I wouldn’t be able to help you with that.

For a long-term monitoring project like this, I would consider a battery backed real-time clock module to be essential. This will allow you to write the time of events to the EEPROM along with counts. I’m not sure how your rain sensor works, but I infer from the code that it will toggle the interrupt pin after a set amount of rain has fallen (I assume you know how much), and that you expect it to be triggered at least a few times per minute.

In order to preserve your EEPROM’s life and to extend the amount of information you can store in it, you won’t want to write to it at a fixed time interval, nor do you want to have a single entry every time the pin toggles. I would implement it by, after the interrupt pin is toggled, wait 5 minutes (this can be done with a millis() timer like you’re doing now) and then write the current time from the RTC and rain count to the EEPROM. Reset the rain count and timer and wait for the next rain trigger. If you do it right, your data will persist even if the controller loses power or resets. Since the events will also have full timestamps from the RTC, you’ll be able to analyze the data any which way you want, not just be the limited “each hour, each day, each week, each month” bins that you currently have.

For a long-term installation like this I would also put in some kind of diagnostics. No matter how good your coding is there is always a chance, however minute, that some bug or freak edge condition could crash the controller and send it into a wonky infinite loop and it stops performing normally. I would enable the watchdog timer to give it a chance to recover from a freak condition like that.

You can also use external hardware to detect power loss, and either switch to battery power or write your data out to the EEPROM before the power’s totally gone. I would probably do this with the internal analog comparator.

Given the chance of resets or power loss, I would use the inboard EEPROM to hold a log of when power was lost, when power was restored (power-on reset), and when there was a watchdog reset. You’ll also need some debugging method to dump this information into a form that you can understand, like Serial Monitor or a file on an SD card.

This is just what I can think of sitting here while I was watching a movie. There’s probably several things I haven’t considered yet.