Weird program behaviour when using interrupts

I'm making a solarlogger with an arduino.
The arduino is hooked up to the S0 of a kWh counter that generates a pulse for every Wh that is generated. The results are send over rf to a base station for logging in a mysql database

These pulses are caught by an interrupt on pin 2.

Every night the variable "DailyProd" should be reset after 5 hours of inactivity.

But for some reason it resets from time to time during the day... I've put the reset for the variable at -10 to be sure it's not the arduino that's rebooting.

I suppose it has something to do with the interrupt, I thought volatile would take care of the problems that might be linked to iterrupt problems.

the code can also be downloaded...

#include <RH_ASK.h>
#include <SPI.h> // Not actually used but needed to compile

struct RFSensorData
{
  int sensorID;
  float sensorValue;
};

//Pin settings
//============
const byte confirmLedPin = 13;
//RF module
const byte rfPin = 6;
//Interrupt Variables
const byte interruptPinS0 = 2; //S0 interrupt

//Confirm led variables
const byte confirmLedInterval = 100;                    //Blink speed Confirm LED
volatile byte confirmLedBlinks = 0;                    //Program variable DO NOT MODIFY
byte confirmLedState = LOW;                            //Program variable DO NOT MODIFY
unsigned long confirmLedPreviousMillis = 0;            //Program variable DO NOT MODIFY

// RF TRANSMITTER (data)
RH_ASK driver = RH_ASK(1000,11,rfPin);

//Interrupt Variables
const byte minimumInteruptDifference = 200;          //Minimum ms between interrupts prevents accidental double counts 200 ms is good up to 18.000W
volatile unsigned long previousInterruptMillis = 0;  //Program variable DO NOT MODIFY
volatile float DailyProd = 0;                //Program variable DO NOT MODIFY

//Power variables
const long millisinHour = 3600000;
volatile float currentPower = 0;

// RF data broadcastdelay
unsigned long lastMillisDataBroadcast = 0;     //Program variable DO NOT MODIFY
const unsigned long delayDataBroadcast = 9333;//How often should the RF codes be rebroadcasted in ms

//Const variables
const unsigned long currentPowerMilisReset = 600000;
const unsigned long DailyProdMilisReset = 18000000;

void setup()
{
  pinMode(confirmLedPin,OUTPUT);
  pinMode(3,OUTPUT);
  digitalWrite(3, HIGH);
  //INIT RF data module
  driver.init();

  //Attach interupt to interupt pin 0 = pin 2 on the arduino
  attachInterrupt(0, S0interrupt, FALLING);
  confirmLedBlinks = 5;
}

void loop()
{
  // put your main code here, to run repeatedly:
  //Broadcast data over RF if needed
  unsigned long currentMillis = millis();
  SendWirelessData();

  //Reset currentPower if the timeout is reached
  if((currentMillis + (unsigned long)1000) - previousInterruptMillis > currentPowerMilisReset)
  {
    currentPower = 0;
  }
  
  //Reset DailyProd if timeout is reached
  if((currentMillis + (unsigned long)1000) - previousInterruptMillis > DailyProdMilisReset)
  {
    DailyProd = -10; // <====== This thing is triggered from time to time during the day
  }
  confirmBlinks();
}

void S0interrupt()
{
  unsigned long currentInteruptMillis = millis();
  //Wait 5 ms and recheck pin to be sure it's low after 5 ms
  delay(5);
  if (currentInteruptMillis - previousInterruptMillis > minimumInteruptDifference && digitalRead(interruptPinS0) == LOW)
  {
    DailyProd++;
    currentPower = (float)millisinHour / (float)(currentInteruptMillis - previousInterruptMillis);
    previousInterruptMillis = currentInteruptMillis;
    confirmLedBlinks++;
  }
}

void confirmBlinks()
{
  unsigned long currentBlinkMillis = millis();
  if(currentBlinkMillis - confirmLedPreviousMillis > confirmLedInterval && confirmLedBlinks > 0)
  {
    // save the last time you blinked the LED
    confirmLedPreviousMillis = currentBlinkMillis;

    // if the LED is off turn it on and vice-versa:
    // if it's turned off reduce the number of blinks to go
    if (confirmLedState == LOW)
    {
      confirmLedState = HIGH;
    }
    else
    {
      confirmLedState = LOW;
      confirmLedBlinks--;
    }

    // set the LED with the ledState of the variable:
    digitalWrite(confirmLedPin, confirmLedState);
  }
}

void SendWirelessData()
{
  if(millis() - lastMillisDataBroadcast > delayDataBroadcast)
  {

    sendSensorValue(6,currentPower);
    sendSensorValue(7,DailyProd);
    lastMillisDataBroadcast = millis();
    confirmLedBlinks++;
  }
}
void sendSensorValue(int sensorID, float sensorValue)
{
  RFSensorData RFdata;
  RFdata.sensorID = sensorID;
  RFdata.sensorValue = sensorValue;
  byte buflen = sizeof(RFdata);
  byte buf[buflen];
  memcpy(&buf, &RFdata, buflen);
  driver.send(buf, buflen);
  driver.waitPacketSent();
}

SolarLogger_V2_01.ino (3.9 KB)

void S0interrupt()
{
unsigned long currentInteruptMillis = millis();
//Wait 5 ms and recheck pin to be sure it's low after 5 ms
delay(5);

Weird use of "delay()" in interrupt context.

Don't use delay() in an ISR.

I would eliminate all the code from the ISR apart from DailyProd++; and, if necessary, saving the value of millis() if time matters.

Do the rest of the calculations outside the ISR.

There is probably no need to reset DailyProd if you use the same technique with it that is used with millis() to measure elapsed time.

If you are timing things over a long period (days, weeks) it might be a good idea to use a Real Time Clock (RTC) module.

...R

void sendSensorValue(int sensorID, float sensorValue)
{
  RFSensorData RFdata;
  RFdata.sensorID = sensorID;
  RFdata.sensorValue = sensorValue;
  byte buflen = sizeof(RFdata);
  byte buf[buflen];
  memcpy(&buf, &RFdata, buflen);
  driver.send(buf, buflen);
  driver.waitPacketSent();
}

or

void sendSensorValue(int sensorID, float sensorValue)
{
  RFSensorData RFdata = {sensorID, sensorValue};
  driver.send((byte*)&RFdata, sizeof(RFdata));
  driver.waitPacketSent();
}

I added the delay because i'm unsure if ripple in the power supply voltage may trigger a falling edge.

I'll remove the delay and see how it goes.

Thank you for the answers, i'll keep you guys posted.

If the code that you posted is all that the Arduino is doing then do you actually need to use an interrupt at all ? Could you not simplify things by polling the input pin ?

How long do the kWh pulses last ? Unless you are consuming vast amounts of power then I assume that they don't occur very often.

UKHeliBob:
If the code that you posted is all that the Arduino is doing then do you actually need to use an interrupt at all ? Could you not simplify things by polling the input pin ?

How long do the kWh pulses last ? Unless you are consuming vast amounts of power then I assume that they don't occur very often.

the pulse length is 50ms.

Using an interupt is the right way to go in my opinion. This allows the project to grow without getting into trouble.
At the moment the biggest time consumer is sending the data over RF. The complete package is about 13 bytes or 104 bits. The transfer rate is 1000 bps. Hence each package will take about 1/10 of a second wich is already more then one pulse...

How long are these pulses? If they are very short I can see that an interrupt might be
needed to guarantee catching one, but if not its definitely not needed - there's no
urgency in a signal generated every kWh.

The whole approach makes me feel uncomfortable though, since you have no way
of knowing if the pulse was genuine or an artifact (noise on the cable).

I suggest if the pulses have a known width to actually measure the pulse width received
and if its out-of-spec ignore the pulse. If your code has nothing else to do it could use
pulseIn() for this, but that's unlikely. You could have the interrupt routine triggered
on CHANGE and make it record the value of micros() on the rising and falling edges
into two variables. The body of the program can detect changes to those values and
then verify the pulse width and so forth.

Good suggestion MarkT measuring pulse width is better then the actual system.

The pulses are every Wh not every kWh. The kWh counter is rated upto 32A@230V = 7360W = 489 ms pulse interval.

deleenheir:
I'll remove the delay and see how it goes.

Delay or Serial calls in an ISR will hang the entire system, since they wait for other
interrupts which won't happen because interrupts are disabled.

const unsigned long delayDataBroadcast = 9333;//How often should the RF codes be rebroadcasted in ms

This looks like a very magic number....

Do you actually need to broadcast the data every 9333 milliseconds or could you do it only when currentPower and/or DailyProd have changed ?

UKHeliBob:

const unsigned long delayDataBroadcast = 9333;//How often should the RF codes be rebroadcasted in ms

This looks like a very magic number....

Do you actually need to broadcast the data every 9333 milliseconds or could you do it only when currentPower and/or DailyProd have changed ?

Yes, it is a very magical number :wink:

Broadcasting when currentPower and/or DailyProd have changed would be very often since the kWh counter pulses every Wh and is rated up to 7360W = 489 ms pulse interval. broadcasting every 489ms would be very bad...

At the moment there is no protocol to prevent package collision. Also there is no ACK from the base station. But there are multiple arduino's broadcasting their data. So each arduino has a slightly different delayDataBroadcast to prevent their broadcasts from synchronizing. I know it's a bad sollution and i'll be upgrading from 433mhz transmitters and receivers to a nrf24l01 based network with proper ACK's

Fire and forget isn't that bad a technique so long as you resend each piece of data in several
successive packets, then the odd dropped packet loses no data (but may delay its arrival a
bit). With a micropower setup just keeping the microcontroller awake to wait for an ACK
might consume several times more power than sending the packet in the first place.

As promised I would keep you guys posted.

The system works 99%. I'm getting a 1% error margin if I compare the data from the kWh meter and the Inverter. Sometimes a pulse is measured in the middle of the night. This might be due to the Mc Giver style circui with Dupont wires, twist wire connections, plastic box, no hardware debounce, etc.

I'll be building a decent circuit on a pcb soon.

For those who are interested the working code can be downloaded here:
http://deleenheir.proxy8080.com:8080/public/?dir=SAAC (SolarLogger Sender V2.01.zip)

The data that is being sent by the Solar logger can be consulted here:
http://deleenheir.proxy8080.com:8080/data/ (just change the drop down to one of the Photovoltaic System sensors.