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();
}
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.
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
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.
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.
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.