sleep & interrupts



  • I have a sketch which is counting in an interrupt (interrupt 0) the number of trips of a rain gauge. In the sketch loop I have a sleep(sleepTime) statement.
    I was expecting that whenever the interrupt is triggered, the sleep function returns and the loop continues. This, however seems not to be the case. However the number of interrupts are correctly counted.
    Can somebody shed some light on that? I am somehow confused.


  • Hardware Contributor

    So what happens? 🙂 It never returns to the loop or...
    I had (still have) some issues here: http://forum.mysensors.org/topic/2436/node-with-interrupt-sleep-and-batteries
    Is it the same?

    Also some code and/or logs would be great!



  • No it returns the sleep. But this does not seem to be related to the interrupt triggered. But the interrupts are fired since the counts are correct.



  • So here is the sketch. It is rather larger, since it should drive my weather station.
    It is not yet tested. The relevant parts are the loop, the interrupt handler and the rainGaugeTripped(). Note: I have a RTC and thus I know the seconds elapsed even if the Arduino wakes up after a sleep.

    I have added to print statements after the sleep.
    My expectation would be that the two print statements are executed each time the interrupt was triggered. So an increase in the counter by one each time?

    However, the excerpt from the log shows differently. The first statement demonstrates that no triggers happened. Than the next shows 8 triggers, followed by 14 triggers.

    ignature in message: 013D1E22311216785F6A80C3FE988584C8A4CCB0C05DE1D2
    send: 27-27-0-0 s=255,c=3,t=0,pt=1,l=1,sg=1,st=ok:1
    rain gauge counter: 0
    remainingSleep: 60000
    rain gauge counter: 8
    remainingSleep: 60000
    rain gauge counter: 14
    remainingSleep: 45000
    send: 27-27-0-0 s=14,c=3,t=16,pt=0,l=0,sg=0,st=ok:
    
    /**
     * Weather station using the MySensor library
     *
     * Documentation: http://www.mysensors.org
     * Support Forum: http://forum.mysensors.org
     *
     *
     *******************************
     *
     * REVISION HISTORY
     * Version 1.0 - Thomas Krebs
     * 
     * DESCRIPTION
     * Pressure sensor example using BMP085 module  
     * http://www.mysensors.org/build/pressure
     *
     */
    
    #define MY_RADIO_NRF24
    #define MY_SIGNING_ATSHA204
    #define MY_SIGNING_ATSHA204_PIN A3
    #define MY_SIGNING_REQUEST_SIGNATURES
    #define MY_DEBUG
    #define MY_NODE_ID            27
    
    // Overwrite Radio Pins for Ceech Board
    #define MY_RF24_CE_PIN		  7 
    #define MY_RF24_CS_PIN		  8
    
    #include <SPI.h> 
    #include <MySensor.h>  
    #include <Wire.h>
    #include <Adafruit_BMP085.h>
    #include <Sodaq_SHT2x.h>
    //#include <HTU21D.h>
    
    #define VERSION           "1.0"
    #define SKETCH_NAME       "W-Station"
    
    #ifdef __DEBUG
    #define DEBUG_PRINT(x) Serial.print(x)
    #define DEBUG_PRINTLN(x) Serial.println(x)
    #else
    #define DEBUG_PRINT(x) 
    #define DEBUG_PRINTLN(x) 
    #endif
    
    // Overwrite Radio Pins for Ceech Board
    #define MY_RF24_CE_PIN		  7 
    #define MY_RF24_CS_PIN		  8
    
    #define LTC4067_CHRG_PIN	    A1		//analog input A1 on ATmega 328 is /CHRG signal from LTC4067
    #define BATTERY_VOLTAGE_PIN	    A0		//analog input A0 on ATmega328 is battery voltage ( /2)
    #define SOLAR_VOLTAGE_PIN	    A2		//analog input A2 is solar cell voltage (/ 2)
    #define SOLAR_CURRENT_PIN	    A6		//analog input A6 is input current ( I=V/Rclprog x 1000 )
    #define BATTERY_CHARGE_CURRENT_PIN  A7          //analog input A7 is battery charge current ( I=V/Rprog x 1000 )
    #define LTC4067_SUSPEND_PIN	    9		//digital output D9 - drive it high to put LTC4067 in SUSPEND mode
    
    #define RAIN_GAUGE_PIN              2           // Interrupt 0
    #define AENOMETER_PIN               3           // Interrupt 1
    #define WIND_DIRECTON_PIN           A3           
    
    
    const float VccMin        = 1.0*3.5;  // Minimum expected Vcc level, in Volts. Example for 1 rechargeable lithium-ion.
    const float VccMax        = 1.0*4.2;  // Maximum expected Vcc level, in Volts. 
    
    // Sensor IDs
    #define BARO_CHILD       0
    #define TEMP_CHILD       1
    #define HUM_CHILD        2
    #define BATT_CHILD       10
    #define SOLAR_CHILD      11
    #define WIND_DIR_CHILD   12
    #define WIND_SPEED_CHILD 13
    #define RAIN_GAUGE_CHILD 14
    
    //--------------------------------------------
    // Control transmit intervals
    #define SLEEP_TIME 60000  	//  do not change weather forecast needs a sample every 60 sec 
    
    // FORCE_TRANSMIT_INTERVAL, this number of times of wakeup, the sensor is forced to report all values to the controller
    #define FORCE_TRANSMIT_INTERVAL 30 
    
    // Number or re-send attempts
    #define RETRIES 3
    //--------------------------------------------
    // Variables for RTC, RTC is used to measure up the intervals for the Pressure Sensor
    #define DS3231_I2C_ADDRESS 0x68
    #define MY_BASE_TIME 2451547     // according to formula below Julian day for 1.1.2000 00.00.000 ; seems to be offset by 3 days but this does not matter here
    
    unsigned long lastMillis = 0;
    
    //--------------------------------------------
    
    const float ALTITUDE = 640; // <-- adapt this value to your own location's altitude.
    
    // 
    // If thresholds are exceeded transmission is being forced
    //
    #define HUMI_TRANSMIT_THRESHOLD     0.5
    #define TEMP_TRANSMIT_THRESHOLD     0.5
    #define VOLTAGE_TRANSMIT_THRESHOLD  0.2
    #define CURRENT_TRANSMIT_THRESHOLD  0.001
    #define PRESS_TRANSMIT_THRESHOLD    0.2 
    
    
    const char *weather[] = { "stable", "sunny", "cloudy", "unstable", "thunderstorm", "unknown" };
    enum FORECAST
    {
    	STABLE = 0,			// "Stable Weather Pattern"
    	SUNNY = 1,			// "Slowly rising Good Weather", "Clear/Sunny "
    	CLOUDY = 2,			// "Slowly falling L-Pressure ", "Cloudy/Rain "
    	UNSTABLE = 3,		        // "Quickly rising H-Press",     "Not Stable"
    	THUNDERSTORM = 4,	        // "Quickly falling L-Press",    "Thunderstorm"
    	UNKNOWN = 5			// "Unknown (More Time needed)
    };
    
    Adafruit_BMP085 bmp = Adafruit_BMP085();      // Digital Pressure Sensor 
    SHT2xClass SHT21;		              // Hum/Temp (SHT12)
    //HTU21D sht21;
    
    int measureCount = 0;
    
    float lastTemperature = -1;	
    float lastHumidity    = -1;
    float lastPressure    = -1;
    int   lastForecast    = -1;
    float lastBattVoltage = -1;
    float lastBattCurrent = -1;
    float lastSolarVoltage= -1;
    float lastSolarCurrent= -1;
    int   lastBattPct       = -1;
    int   lastWindDir     = -1;
    int   lastWindSpeed   = -1;
    int   lastRainGauge   = -1;
    float VccReference = 3.3 ;				// voltage reference for measurement, definitive init in setup
    
    // ----------------------------------------------------
    // for weather forecasting aglorithm
    #define LAST_SAMPLES_COUNT 5
    float lastPressureSamples[LAST_SAMPLES_COUNT];
    
    // this CONVERSION_FACTOR is used to convert from Pa to kPa in forecast algorithm
    // get kPa/h be dividing hPa by 10 
    #define CONVERSION_FACTOR (1.0/10.0)
    
    int minuteCount = 0;
    bool firstRound = true;
    // average value is used in forecast algorithm.
    float pressureAvg;
    // average after 2 hours is used as reference value for the next iteration.
    float pressureAvg2;
    
    float dP_dt;
    
    //-----------------------------------------------------
    
    unsigned int  windSpeedSamples = 0;     // number of sampling periods, required for avg calc
    unsigned int  windSpeedCounter = 0;     // number of events per active sample period (1s)
    unsigned long lastSample = 0;           // track when the last sample was taken
    
    //-----------------------------------------------------
    // counters for interrupt handlers; in order to avoid race conditions
    // one is active and one can be used to read out values. Access is
    // through pointer which is switched in an re-assignement assumed to be atomic
    volatile int rainGaugeCounter[2];    
    volatile float windSpeedAvg1m[2];
    volatile int windSpeedMax1m[2];
    volatile int *activeRainGaugeCounter = &rainGaugeCounter[0];
    volatile float *activeWindSpeedAvg1m = &windSpeedAvg1m[0];
    volatile int *activeWindSpeedMax1m = &windSpeedMax1m[0];
    
    
    MyMessage temperatureMsg(TEMP_CHILD, V_TEMP);
    MyMessage pressureMsg(BARO_CHILD, V_PRESSURE);
    MyMessage forecastMsg(BARO_CHILD, V_FORECAST);
    MyMessage humidityMsg(HUM_CHILD, V_HUM);			// SHT hum (% rh)
    MyMessage batteryVoltageMsg(BATT_CHILD, V_VOLTAGE);		// Battery voltage (V)
    MyMessage batteryCurrentMsg(BATT_CHILD, V_CURRENT);		// Battery current (A)
    MyMessage solarVoltageMsg(SOLAR_CHILD, V_VOLTAGE);		// Solar voltage (V)
    MyMessage solarCurrentMsg(SOLAR_CHILD, V_CURRENT);		// Solar current (A)
    MyMessage windSpeedMsg(WIND_SPEED_CHILD, V_WIND);
    MyMessage windGustMsg(WIND_SPEED_CHILD, V_GUST);
    MyMessage windDirMsg(WIND_DIR_CHILD, V_DIRECTION);		// Wind Direction
    MyMessage rainGaugeMsg(RAIN_GAUGE_CHILD, V_RAIN);
    
    //----------------------------------------------------------------
    // Interrupt Handlers
    //----------------------------------------------------------------
    void rainGaugeTripped()
    { 
      (*activeRainGaugeCounter)++;
    }
    
    void aenometerTripped()
    {/*
        static unsigned long lastAenometerInterrupt = 0;
        // we sample over 1 sec interval and take a 2 min average over the 1 sec samples
         if (interruptTime - lastAenometerInterrupt > 5) {
           if (_millis() - lastSample > 1) {
             lastSample = _millis();
             // update average
             *activeWindSpeedAvg1m  = *activeWindSpeedAvg1m * (windSpeedSamples-1.0)/windSpeedSamples*1.0 + windSpeedCounter/windSpeedSamples;
             windSpeedSamples++;
             if (windSpeedCounter > *activeWindSpeedMax1m) {
               *activeWindSpeedMax1m = windSpeedCounter;
             }
             windSpeedCounter = 0;  
           } 
           else {
             windSpeedCounter++;
           }
         }
         lastAenometerInterrupt = interruptTime;*/
    }
    
    
    void setup() 
    {	
        // use VCC (3.3V) reference
        analogReference(DEFAULT);						// default external reference = 3.3v for Ceech board
        VccReference = 3.323 ;						// measured Vcc input (on board LDO)
        pinMode(LTC4067_SUSPEND_PIN, OUTPUT);					// suspend of Lion charger set
        digitalWrite(LTC4067_SUSPEND_PIN,LOW);       			        //  active (non suspend) at start
        
        Wire.begin();								// init I2C
      
        // Send the sketch version information to the gateway and Controller
        sendSketchInfo(SKETCH_NAME, VERSION);
      
        if (!bmp.begin()) 
        {
          Serial.println("Could not find a valid BMP085 sensor, check wiring!");
          while (1) {}
        }
    
       // pinMode(AENOMETER_PIN, OUTPUT);
       // attachInterrupt (AENOMETER_PIN-2, aenometerTripped, CHANGE);
       // pinMode(AENOMETER_PIN, INPUT);
       // digitalWrite(AENOMETER_PIN,HIGH);  // Turn on the internal Pull Up Resis
    
        // Register sensors to gw (they will be created as child devices)
        // without the waits in between I get send fails
        present(BARO_CHILD, S_BARO);
        present(TEMP_CHILD, S_TEMP);
        present(HUM_CHILD, S_HUM);	// SHT humidity
        wait(500);
        present(BATT_CHILD, S_POWER);	// Battery parameters  
        wait(200);
        present(SOLAR_CHILD, S_POWER);	// Solar parameters
        wait(200);
        present(WIND_DIR_CHILD, S_WIND);
        wait(200);
        present(WIND_SPEED_CHILD, S_WIND);
        wait(200);
        present(RAIN_GAUGE_CHILD, S_RAIN);
    
        pinMode(RAIN_GAUGE_PIN, INPUT_PULLUP);
        attachInterrupt (RAIN_GAUGE_PIN-2,rainGaugeTripped, FALLING);     
    
        lastMillis = _millis(); 
    }
    
    void loop() 
    {
        unsigned long remainingSleep = SLEEP_TIME;
        measureCount++; 
        bool forceTransmit = false;
    
        if (_millis() - lastMillis < SLEEP_TIME) {
            // wake up was triggered by an interrupt
            remainingSleep = SLEEP_TIME - (_millis()-lastMillis);  
        }
        else {
           if (measureCount > FORCE_TRANSMIT_INTERVAL) {
              forceTransmit = true;
              measureCount = 0;
           }
           sendTempHum(forceTransmit); 
           sendBaro(forceTransmit);
           sendVoltage(forceTransmit);
           //sendWindSpeed();
           //sendWindDirection(forceTransmit);
           sendRainGauge(forceTransmit);
           lastMillis = _millis();
        }
        sleep(remainingSleep);
        Serial.print("rain gauge counter: "); Serial.println(*activeRainGaugeCounter);
        Serial.print("remainingSleep: "); Serial.println(remainingSleep);
    }
    
    
    //----------------------------------------------------------------
    // Temperature/Humidity
    //----------------------------------------------------------------
    void sendTempHum(bool force)
    {
      bool tx = force;
      
      // SHT2x sensor
      // Temperature and Humidity
      float humidity = SHT21.GetHumidity();
      //float humidity = sht21.readHumidity();
    
      // send to MySensor network / only if change is above threshold
      if (abs(lastHumidity - humidity) > HUMI_TRANSMIT_THRESHOLD) tx = true;
      
      if (tx) {
        lastHumidity = humidity;
        resend(humidityMsg.set(humidity, 2));  
     //   humMeasureCount = 0;
      }
      
      tx = force;
      
      float temperature = SHT21.GetTemperature();
      //float temperature = sht21.readTemperature();
      if (abs(lastTemperature - temperature) > TEMP_TRANSMIT_THRESHOLD) tx = true;
      
      if (tx) {
        lastTemperature = temperature;
        resend(temperatureMsg.set(temperature, 2)); 
    //    tempMeasureCount = 0;
      }
    	
      DEBUG_PRINT(F("SHT_ temp: "));
      DEBUG_PRINTLN(temperature);
      DEBUG_PRINT(F(" SHT_ hum: "));
      DEBUG_PRINTLN(humidity);
    }
    
    
    //----------------------------------------------------------------
    // Rain Gauge
    //----------------------------------------------------------------
    void sendRainGauge(bool force)
    {
      // switch pointer so that the current value can be read without race conditions
      byte ndx;
      if (*activeRainGaugeCounter > 0) {
        (activeRainGaugeCounter == &rainGaugeCounter[0]) ? ndx = 0 : ndx = 1;
        activeRainGaugeCounter = &rainGaugeCounter[(ndx + 1) % 2];
        if (!resend(rainGaugeMsg.set(rainGaugeCounter[ndx]))) {
          *activeRainGaugeCounter += rainGaugeCounter[ndx]; // we might loose an update due to a non atomic operation
                                                            // however this is better than loosing the whole count
        }    
      }
    }
    
    //----------------------------------------------------------------
    // WindSpeed
    //----------------------------------------------------------------
    void sendWindSpeed()
    {
      // switch pointer so that the current value can be read without race conditions
      byte ndx;
      (activeWindSpeedAvg1m == &windSpeedAvg1m[0]) ? ndx = 0 : ndx = 1;
      activeWindSpeedAvg1m = &windSpeedAvg1m[(ndx + 1) % 2];
      activeWindSpeedMax1m = &windSpeedMax1m[(ndx + 1) % 2];
      
      resend(windSpeedMsg.set(windSpeedAvg1m[ndx], 2));
      resend(windGustMsg.set(windSpeedMax1m[ndx]));
    }
    
    //----------------------------------------------------------------
    // Wind Direction
    //----------------------------------------------------------------
    //Takes an average of readings on a given pin
    //Returns the average
    int averageAnalogRead(int pinToRead)
    {
        const byte numberOfReadings = 8;
        unsigned int runningValue = 0;
        for(int x = 0 ; x < numberOfReadings ; x++)
          runningValue += analogRead(pinToRead);
        runningValue /= numberOfReadings;
        return(runningValue);
    }
    
    int getWindDirection()
    // read the wind direction sensor, return heading in degrees
    {
        unsigned int adc;
    
        adc = averageAnalogRead(WIND_DIRECTON_PIN); // get the current reading from the sensor
        // The following table is ADC readings for the wind direction sensor output, sorted from low to high.
        // Each threshold is the midpoint between adjacent headings. The output is degrees for that ADC reading.
        // Note that these are not in compass degree order! See Weather Meters datasheet for more information.
        if (adc < 380) return (113);
        if (adc < 393) return (68);
        if (adc < 414) return (90);
        if (adc < 456) return (158);
        if (adc < 508) return (135);
        if (adc < 551) return (203);
        if (adc < 615) return (180);
        if (adc < 680) return (23);
        if (adc < 746) return (45);
        if (adc < 801) return (248);
        if (adc < 833) return (225);  
        if (adc < 878) return (338);
        if (adc < 913) return (0);
        if (adc < 940) return (293);
        if (adc < 967) return (315);
        if (adc < 990) return (270);
        return (-1); // error, disconnected?
    }
    
    void sendWindDirection(bool force)
    {
        bool tx = force;
        int windDir = getWindDirection();
        if (lastWindDir != windDir) tx = true;
    
        if (tx) {
          resend(windDirMsg.set(windDir));
          lastWindDir = windDir;
        }
    }
    
    
    //----------------------------------------------------------------
    // Barometer, Forecast
    //----------------------------------------------------------------
    void sendBaro(bool force) {
        bool tx = force;
    
        float pressure = bmp.readSealevelPressure(ALTITUDE) / 100.0;
        int forecast = sample(pressure);
    
        DEBUG_PRINT(F("Pressure = "));
        DEBUG_PRINT(pressure);
        DEBUG_PRINTLN(F(" hPa"));
        DEBUG_PRINT(F("Forecast = "));
        DEBUG_PRINTLN(weather[forecast]);
    
        if ( (abs(pressure-lastPressure) > PRESS_TRANSMIT_THRESHOLD) || (forecast != lastForecast)) tx = true;
    
        if (tx) {
          resend(pressureMsg.set(pressure, 0));
          lastPressure = pressure;
          resend(forecastMsg.set(weather[forecast]));
          lastForecast = forecast;
        }
    }
    
    
    float getLastPressureSamplesAverage()
    {
    	float lastPressureSamplesAverage = 0;
    	for (int i = 0; i < LAST_SAMPLES_COUNT; i++)
    	{
    	    lastPressureSamplesAverage += lastPressureSamples[i];
    	}
    	lastPressureSamplesAverage /= LAST_SAMPLES_COUNT;
    
    	return lastPressureSamplesAverage;
    }
    
    
    
    // Algorithm found here
    // http://www.freescale.com/files/sensors/doc/app_note/AN3914.pdf
    // Pressure in hPa -->  forecast done by calculating kPa/h
    int sample(float pressure)
    {
    	// Calculate the average of the last n minutes.
    	int index = minuteCount % LAST_SAMPLES_COUNT;
    	lastPressureSamples[index] = pressure;
    
    	minuteCount++;
    	if (minuteCount > 185)
    	{
    		minuteCount = 6;
    	}
    
    	if (minuteCount == 5)
    	{
    		pressureAvg = getLastPressureSamplesAverage();
    	}
    	else if (minuteCount == 35)
    	{
    		float lastPressureAvg = getLastPressureSamplesAverage();
    		float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
    		if (firstRound) // first time initial 3 hour
    		{
    			dP_dt = change * 2; // note this is for t = 0.5hour
    		}
    		else
    		{
    			dP_dt = change / 1.5; // divide by 1.5 as this is the difference in time from 0 value.
    		}
    	}
    	else if (minuteCount == 65)
    	{
    		float lastPressureAvg = getLastPressureSamplesAverage();
    		float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
    		if (firstRound) //first time initial 3 hour
    		{
    			dP_dt = change; //note this is for t = 1 hour
    		}
    		else
    		{
    			dP_dt = change / 2; //divide by 2 as this is the difference in time from 0 value
    		}
    	}
    	else if (minuteCount == 95)
    	{
    		float lastPressureAvg = getLastPressureSamplesAverage();
    		float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
    		if (firstRound) // first time initial 3 hour
    		{
    			dP_dt = change / 1.5; // note this is for t = 1.5 hour
    		}
    		else
    		{
    			dP_dt = change / 2.5; // divide by 2.5 as this is the difference in time from 0 value
    		}
    	}
    	else if (minuteCount == 125)
    	{
    		float lastPressureAvg = getLastPressureSamplesAverage();
    		pressureAvg2 = lastPressureAvg; // store for later use.
    		float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
    		if (firstRound) // first time initial 3 hour
    		{
    			dP_dt = change / 2; // note this is for t = 2 hour
    		}
    		else
    		{
    			dP_dt = change / 3; // divide by 3 as this is the difference in time from 0 value
    		}
    	}
    	else if (minuteCount == 155)
    	{
    		float lastPressureAvg = getLastPressureSamplesAverage();
    		float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
    		if (firstRound) // first time initial 3 hour
    		{
    			dP_dt = change / 2.5; // note this is for t = 2.5 hour
    		}
    		else
    		{
    			dP_dt = change / 3.5; // divide by 3.5 as this is the difference in time from 0 value
    		}
    	}
    	else if (minuteCount == 185)
    	{
    		float lastPressureAvg = getLastPressureSamplesAverage();
    		float change = (lastPressureAvg - pressureAvg) * CONVERSION_FACTOR;
    		if (firstRound) // first time initial 3 hour
    		{
    			dP_dt = change / 3; // note this is for t = 3 hour
    		}
    		else
    		{
    			dP_dt = change / 4; // divide by 4 as this is the difference in time from 0 value
    		}
    		pressureAvg = pressureAvg2; // Equating the pressure at 0 to the pressure at 2 hour after 3 hours have past.
    		firstRound = false; // flag to let you know that this is on the past 3 hour mark. Initialized to 0 outside main loop.
    	}
    
    	int forecast = UNKNOWN;
    	if (minuteCount < 35 && firstRound) //if time is less than 35 min on the first 3 hour interval.
    	{
    		forecast = UNKNOWN;
    	}
    	else if (dP_dt < (-0.25))
    	{
    		forecast = THUNDERSTORM;
    	}
    	else if (dP_dt > 0.25)
    	{
    		forecast = UNSTABLE;
    	}
    	else if ((dP_dt > (-0.25)) && (dP_dt < (-0.05)))
    	{
    		forecast = CLOUDY;
    	}
    	else if ((dP_dt > 0.05) && (dP_dt < 0.25))
    	{
    		forecast = SUNNY;
    	}
    	else if ((dP_dt >(-0.05)) && (dP_dt < 0.05))
    	{
    		forecast = STABLE;
    	}
    	else
    	{
    		forecast = UNKNOWN;
    	}
    
    	// uncomment when debugging
    	DEBUG_PRINT(F("Forecast at minute "));
    	DEBUG_PRINT(minuteCount);
    	DEBUG_PRINT(F(" dP/dt = "));
    	DEBUG_PRINT(dP_dt);
    	DEBUG_PRINT(F("kPa/h --> "));
    	DEBUG_PRINTLN(weather[forecast]);
    
    	return forecast;
    }
    
    
    /********************************************
     *
     * Send message, resend on error
     *
     * Parameters
     * - msg : message to send
     * - repeats: number of repetitions
     *
     *******************************************/
    bool resend(MyMessage &msg)
    {
      int repeat = 1;
      int repeatdelay = 0;
      boolean sendOK = false;
    
      while ((sendOK == false) and (repeat < RETRIES)) {
        if (send(msg)) {
          sendOK = true;
        } else {
          sendOK = false;
          DEBUG_PRINTLN(F("Send ERROR "));
          repeatdelay += random(20,150);
        } 
        repeat++; 
        delay(repeatdelay);
      }
      return sendOK;
    }
    
    //----------------------------------------------------------------
    // Functions for RTC
    //----------------------------------------------------------------
    // 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) );
    }
    
    /*
    void saveTime()
    {
      byte dayOfWeek, dayOfMonth, month, year;  // will be ignored
      // retrieve data from DS3231
      readDS3231time(&baseTime.second, &baseTime.minute, &baseTime.hour, &dayOfWeek, &dayOfMonth, &month,
      &year);  
    }
    */
    unsigned long _millis()
    {
      byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;  // will be ignored
      // retrieve data from DS3231
      readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month,
      &year); 
      return (julianDay(dayOfMonth, month, year) + second*1000+minute*60*1000+hour*24*60*1000 ) - MY_BASE_TIME;
    }
    /*
    unsigned long elapsedTime() {
      byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;  // will be ignored
      // retrieve data from DS3231
      readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month,
      &year); 
      long retVal=((abs(second-baseTime.second)) + (abs(minute-baseTime.minute)*60) + (abs(hour-baseTime.hour)*60*60));
      baseTime.second = second;
      baseTime.minute=minute;
      baseTime.hour=hour;
      long t=julianDay(1,1,00);
      return retVal;
    }*/
    
    unsigned long julianDay(byte dayOfMonth, byte month, byte year ) 
    {
      // Algorithm from http://www.hermetic.ch/cal_stud/jdn.htm
      return (1461*(2000+year+4800+(month-14)/12))/4+(367*(month-2-12*((month-14)/12)))/12-(3*((2000+year+4900+(month-14)/12)/100))/4+dayOfMonth-32075;
    }
    
    void setDS3231time(byte second, byte minute, byte hour, byte dayOfWeek, byte
    dayOfMonth, byte month, byte year)
    {
      // sets time and date data to DS3231
      Wire.beginTransmission(DS3231_I2C_ADDRESS);
      Wire.write(0); // set next input to start at the seconds register
      Wire.write(decToBcd(second)); // set seconds
      Wire.write(decToBcd(minute)); // set minutes
      Wire.write(decToBcd(hour)); // set hours
      Wire.write(decToBcd(dayOfWeek)); // set day of week (1=Sunday, 7=Saturday)
      Wire.write(decToBcd(dayOfMonth)); // set date (1 to 31)
      Wire.write(decToBcd(month)); // set month
      Wire.write(decToBcd(year)); // set year (0 to 99)
      Wire.endTransmission();
    }
    
    
    void readDS3231time(byte *second,
    byte *minute,
    byte *hour,
    byte *dayOfWeek,
    byte *dayOfMonth,
    byte *month,
    byte *year)
    {
      Wire.beginTransmission(DS3231_I2C_ADDRESS);
      Wire.write(0); // set DS3231 register pointer to 00h
      Wire.endTransmission();
      Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
      // request seven bytes of data from DS3231 starting from register 00h
      *second = bcdToDec(Wire.read() & 0x7f);
      *minute = bcdToDec(Wire.read());
      *hour = bcdToDec(Wire.read() & 0x3f);
      *dayOfWeek = bcdToDec(Wire.read());
      *dayOfMonth = bcdToDec(Wire.read());
      *month = bcdToDec(Wire.read());
      *year = bcdToDec(Wire.read());
    }
    
    
    
    //----------------------------------------------------------------
    // Battery Functions
    //----------------------------------------------------------------
    void sendVoltage(bool force)
    // battery and charging values
    {
      bool tx = force;
      
      // get Battery Voltage & charge current
      float batteryVoltage = ((float)analogRead(BATTERY_VOLTAGE_PIN)* VccReference/1024) * 2;	// actual voltage is double
      DEBUG_PRINT(F("Batt: "));
      DEBUG_PRINT(batteryVoltage);
      DEBUG_PRINT(F("V ; "));
        
      float batteryChargeCurrent = ((float)analogRead(BATTERY_CHARGE_CURRENT_PIN) * VccReference/1024)/ 2.5 ; // current(A) = V/Rprog(kohm)
      DEBUG_PRINT(batteryChargeCurrent);
      DEBUG_PRINTLN(F("A "));
       
      // get Solar Voltage & charge current
      float solarVoltage = ((float)analogRead(SOLAR_VOLTAGE_PIN)/1024 * VccReference) * 2 ;		// actual voltage is double
      DEBUG_PRINT(F("Solar: "));
      DEBUG_PRINT(solarVoltage);
      DEBUG_PRINT(F("V ; "));
      // get Solar Current
      float solarCurrent = ((float)analogRead(SOLAR_CURRENT_PIN)/1024 * VccReference)/ 2.5;		// current(A) = V/Rclprog(kohm)
      DEBUG_PRINT(solarCurrent);
      DEBUG_PRINT(F(" A; charge: "));
      DEBUG_PRINTLN(digitalRead(LTC4067_CHRG_PIN)?F("No"):F("Yes"));
    	
      // send battery percentage for node
      int battPct = 1 ;
      if (batteryVoltage > VccMin){
        battPct = 100.0*(batteryVoltage - VccMin)/(VccMax - VccMin);
      }
      DEBUG_PRINT(F("BattPct: "));
      DEBUG_PRINT(battPct);
      DEBUG_PRINTLN(F("% "));
    
      if ( (abs(batteryVoltage - lastBattVoltage) > VOLTAGE_TRANSMIT_THRESHOLD) ||
           (abs(batteryChargeCurrent - lastBattCurrent) > CURRENT_TRANSMIT_THRESHOLD) ||
           (abs(solarVoltage - lastSolarVoltage ) > VOLTAGE_TRANSMIT_THRESHOLD) ||
           (abs(solarCurrent - lastSolarCurrent) > CURRENT_TRANSMIT_THRESHOLD) ||
           (abs(batteryVoltage - lastBattVoltage) > VOLTAGE_TRANSMIT_THRESHOLD)) tx = true;
    
      if (tx) {
        resend(batteryVoltageMsg.set(batteryVoltage, 3));  		// Send (V)
        resend(batteryCurrentMsg.set(batteryChargeCurrent, 6));    	// Send (Amps)
        resend(solarVoltageMsg.set(solarVoltage, 3)); 		// Send (V)
        resend(solarCurrentMsg.set(solarCurrent, 6));  		// Send (Amps)
        sendBatteryLevel(battPct);
        
        lastBattCurrent = batteryChargeCurrent;
        lastBattVoltage = batteryVoltage;
        lastSolarVoltage = solarVoltage;
        lastSolarCurrent = solarCurrent;
        lastBattVoltage = batteryVoltage;
      }
    }
    
    
    /* Ceech board specifics for reference:
    It provides power for the circuit and charges the backup single-cell lithium battery while greatly extends battery life. You can monitor the voltages and currents. It has suspend mode, which reduces current consumption to around 40μA. The power source is a small, 5V solar cell. Connections:
    
    analog input A1 on ATmega 328 is /CHRG signal from LTC4067 (indicates fully charged)
    analog input A0 on ATmega328 is battery voltage
    analog input A2 is solar cell voltage
    analog input A6 is input current ( I=V/Rclprog x 1000 )
    analog input A7 is battery charge current ( I=V/Rprog x 1000 )
    digital output D9 - drive it high to put LTC4067 in SUSPEND mode
    All the voltages on analog inputs can be read with an analogRead() command in the Arduino IDE sketch. Those on inputs A0 an A2 represent direct values of the measured voltages divided by 2. The voltages on analog inputs A6 and A7 can be translated to currents. For example:
    
    Let us say that the voltage on A7 is 0.12V. And the trimmer pot on PROG pin is set to 2.5kOhm. This means that the current into the battery equals to 0.12V/2500ohm x 1000, which is 48mA.
    
    voltmeters on both battery and solar cell connections
    They are connected to analog inputs A0 and A2 on the ATmega328p. The voltage dividers resistors are equal, so the measured voltage is double the shown voltage.
    
    NRF24l01+ socket
    with CE and CSN pins connected to digital pins 7 and 8 ( you use RF24 radio(7, 8); in Arduino code). There is a 4.7uF capacitor connected across Vin and GND of the port
    */```

  • Hardware Contributor

    Thats a big sketch 🙂 Nice work.
    Atleast in the previous versions you cant use millis() and sleep together - please correct me someone if im wrong.
    But i think that millis() is not calculated/accumulated during sleep so when you try to calculate that it might be wrong.



  • @sundberg84 said:

    Thats a big sketch 🙂 Nice work.
    Atleast in the previous versions you cant use millis() and sleep together - please correct me someone if im wrong.
    But i think that millis() is not calculated/accumulated during sleep so when you try to calculate that it might be wrong.

    Yes, it is quite a big sketch just stretching memory to its limits.
    I am aware that I cannot use millis() with sleep. For that reason, I am using a RTC. The function I am using to is _millis() which is contained in my sketch accessing the RTC. Although I have only second resolution it would be sufficient.

    The question I am having is: If the interrupt handler is called 14 times, shouldn't I see the sleep returning 14 times and thus 14 times the series of print statements, each time printing a counter increased by one???


  • Plugin Developer

    @tomkxy

    Hi!

    Read the thread that @sundberg84 linked to, and especially my post here:
    http://forum.mysensors.org/topic/2436/node-with-interrupt-sleep-and-batteries/14

    The interrupt is probably triggering during the ISR, which creates the unexpected counts.



  • @martinhjelmare said:

    @tomkxy

    Hi!

    Read the thread that @sundberg84 linked to, and especially my post here:
    http://forum.mysensors.org/topic/2436/node-with-interrupt-sleep-and-batteries/14

    The interrupt is probably triggering during the ISR, which creates the unexpected counts.

    @martinhjelmare I think I do not explain myself properly.

    What I observe:

    • The counts the ISR produces are correct
    • From my observation sleep(60000) does not return on each ISR execution

    Basically, I have not a real problem with that. I just do not understand, it seems to be in contradiction with all the threads you guys have pointed me to.


  • Mod

    I think the MySensors sleep code gets awakened, but that code goes back to sleep because you've asked it to sleep based on time only. I think you need to use sleep(int interrupt, int mode, unsigned long ms=0); to achieve the behavior you expect.


  • Plugin Developer

    I suggest following both advice from @mfalkvidd and me, i.e. don't use the attachInterrupt function, but check the return value of the mysensors sleep function, and call your ISRs if the interrupt(s) was the cause of the wake-up. See the mysensors API for the sleep function with interrupts.

    If you don't have a good reason to use the attachInterrupt function, it's easier and safer to rely on the mysensors sleep and interrupt implementation.



  • @mfalkvidd @martinhjelmare : Thanks a lot for your answers.

    How would you implement a rain gauge or anemometer measuring wind speed. Both devices are being triggered by "external" events, thus cannot be polled. I think there is no way around to count those events in interrupts.
    If I enable interrupts only during sleep, I guess I will loose events or am I wrong on that?


  • Plugin Developer

    I would first go the simpler route and try to both read the pins in the loop and have the interrupts during sleep and test that. If loosing events in the test, I would go for a more complex solution, i.e. not using mysensors sleep but coding that from "scratch" and using attachInterrupt.

    Edit: On second thought, I think the second option would be to try and combine mysensors sleep function with interrupt and an attachedInterrupt with ISR. If that doesn't work, I would write a custom sleep function.

    If using attachInterrupt, I would detachInterrupt first thing in the ISR, then when the pin state is back to normal attach the interrupt again.


Log in to reply
 

Suggested Topics

25
Online

11.2k
Users

11.1k
Topics

112.5k
Posts