Would RPi solve my memory overrun issue?

I built a Differduino board to monitor several temperatures and decide on when to on/off a fan (via relay). The "Differduino" is a Atmega328p micro with several analog temp sensors and a relay (to control a 120v AC circuit based on the temps sensed). The specs on the board and device are no longer available to me, so cannot include them here.

The issue is that I believe my code is causing a memory overrun. I've taken the advice of many on this and other forums to reduce the memory used, but still have the issue.

My questions are:

  1. Would another platform (e.g., RPi) be more appropriate, since its OS would (presumably) provide memory management/protection? (But, as I understand it, RPi interfaces to analog I/O is more complex)
  2. If the answer to 1) is "no", then how can I prevent memory overruns (aside from building a device that has more RAM)?

PS- I did not include the code here, since I'm not sure if it would help someone answer the questions, but I can certainly include it if need be.

Forum members can help solve memory problems with the existing ATmega328 MCU, if you post the code using code tags.

Otherwise, you could move without much trouble to an Arduino Mega, which is compatible with the ATmega328 but has much more memory.

... and don't use an RPi if you need analog inputs.

To get any help at all with a software problem, people need to look at the software. Don't keep is a secret. The quickest way for a program to run out of memory is to call "loop()" from loop(). No one will know by guessing.

Also please post all of it, because most of the time the problem is not where you think it is.

One big mistakes would be to use Strings ( note this is different to strings with a lower case s ) and can easly cause you to run out of memory.

1 Like

Thank You for that!

Ok. The code is below. Again, the suspected issue is a memory overrun.


/*   ----------------------------------------------------------
 Differduino Attic Fan Control ver 6.0
 Works with the PCB Differduino ver 0.91
 
 Uses Dallas 1-wire DS18B20 temperature sensors.
 Decides when to turn on attic fan based on outside vs attic temperature.
 Turns on attic fan via digital output hooked to a relay circuit.
 
 Posts data to the IoT platform via a wiz812mj ethernet module
 using the standard Arduino ethernet library and the official ThingSpeak library.
 
 All temperatures are in fahrenheit
 -------------------------------------------------------------
 */
#define ThingHTTPapi "(redacted)"    // twilio acct SID: (redacted)
#define WadesCell  "+1(redacted)"
//===========================================================================================================================
//===========================================================================================================================
const bool use_ThingSpeak = true;    // true=>output data to ThingSpeak,  false=>don't output data to ThingSpeak
//DEBUG const bool dbg  = true;      // general debug messages
//DEBUGmem const bool dbgmem  = true;    // memory usage debug messages
//===========================================================================================================================
//===========================================================================================================================


/*  BOF preprocessor bug prevent
 * insert me on top of your arduino-code
 * per http://a-control.de/arduino-fehler/?lang=en#english
 */
#define nop() __asm volatile ("nop")
#if 1
nop();
#endif
/*    EOF preprocessor bug prevent   */

//#include <MemoryFree.h>
//#include <pgmStrToRam.h>
#include <DallasTemperature.h>
#include <SPI.h>
#include <OneWire.h>
#include <Ethernet.h>
#include "C:\Users\wade\Documents\Arduino\libraries\HttpClient\HttpClient.h"
#include <ThingSpeak.h>
//#include <LiquidCrystal.h>

//define pins used by lcd display. 
//LiquidCrystal lcd (3, 4, 5, 6, 7, 8 );

const int ONE_WIRE_BUS= 9;  //define input pin for 1-wire bus. The Differduino PCB uses digital pin 9

// constants for logging data using ThingSpeak
#define  APIKEYdatawrite "(redacted)"  //Data write API key
#define APIKEYdataread "(redacted)"  // Data read API key
unsigned long dataChannelID = 535098; //
#define APIKEYctrlwrite "(redacted)" // Control write API key
#define APIKEYctrlread "(redacted)"  // Control read API key
unsigned long ctrlChannelID = 535544;
#define thingSpeakAddress "api.thingspeak.com"
//ThingSpeak Channel Fields
//dataChannelID:  field 1=attic temp; field 2=outside temp; field 3=Fan state; field 4=sensorsOK; field 5=SMSbalance
//ctrlChannelID:  field 1=maxFanTimeOn; field 2=fanRestTime; field 3=vacation mode; field 4=maxSummerDiff; field 5= minSummerDiff; field 6=SMSlevel
//    SMS level 0=no SMS notifications; 1=bad sensors reads only; 2=1 plus fan state changes

//GET https://api.thingspeak.com/update?api_key=(redacted)&field1=0
//Get a Channel Feed
//GET https://api.thingspeak.com/channels/535544/feeds.json?api_key=(redacted)&results=2
//Get a Channel Field
//GET https://api.thingspeak.com/channels/535544/fields/1.json?api_key=(redacted)&results=2
//Get Channel Status Updates
//GET https://api.thingspeak.com/channels/535544/status.json?api_key=(redacted)
//......................................
// String types must be sent to ThingSpeak, therefore a conversion is required. 
//    The light value straight from the A0 pin and the previously calculated temperature are cast to strings.
//        String light = String(analogRead(A0),DEC);
//        String temp = String(tempVal,DEC);
//......................................

OneWire oneWire(ONE_WIRE_BUS);   // Setup a oneWire instance to communicate with any OneWire devices

DallasTemperature sensors(&oneWire);   // Pass our oneWire reference to Dallas Temperature. 

//########################################################################################
//##########################   VARIABLES   ###############################################
int ret;  // return code for gets and puts
bool winter;
bool summer;
bool maxExceeded;   // max attic-outside before fan is triggered
bool minAchieved;   // when attic reaches outside+minAchieved, fan will turn off

//****************************************************************************************
//**************************   CONTROL VARIABLES   ***************************************
//****************************************************************************************
//These variables are where the adjustments are made that effect when system will cycle.
//These are the defaults that are used when the differential controller first powers up 
//If a ThingSpeak 'control' feed is used, these variables can be adjusted through the ThingSpeak interface,
//but system defaults to the values below upon restart/reset of the controller.
int maxfanMinutesOn = 30;      // 30 minutes = maximum time fan can be on before shutting off
int fanRestMinutes =  10;         // 10 minutes = minimum time the fan is off after being on
int vacationMode =  0;   // control variable indicating whether the user has enabled vacation mode or not (0=off; 1=on)
int maxSummerDiff = 15;   // attic/outside temp diff at which fan gets turned on in the summertime, also a CONTROL var
int minSummerDiff = 10;  // attic/outside temp diff at which fan gets turned off in the summertime, also a CONTROL var
int SMSlevel = 0;     //  level of SMS messaging (see comments above)
int ShutDown = 0;     // 1-> stop recording data and sending SMSs;  0-> record data and send SMSs acc. to SMSlevel
//****************************************************************************************

const int setWinterMax = 33;   // upper bound on outside temp that defines wintertime
const int setSummerMin = 75;   // lowerbound on outside temp that defines summertime
int numberOfDevices;   // Number of temperature devices (sensors) found
unsigned long ThingSpeakPutDelay = 120000UL;   // 'puts' to ThingSpeak every 120 secs  - must never be < ~20 secs due to free TS acct constraints
unsigned long ThingSpeakPutLast = 0UL;   // time (in millis) of last ThingSpeakPut
unsigned long readingDelay = 60000UL;  // 60 sec interval between sensor reading attempts
unsigned long readingLast = 0UL;   // time of most recent reading of sensors
unsigned long ThingSpeakGetDelay = 60000UL;   // 'get' new control values from ThingSpeak every 60 seconds
unsigned long ThingSpeakGetLast = 0UL;   //time of most recent 'get' of control values from ThingSpeak in millisecs
static bool putOK;  // did the put to the IoT platform occur without error
static bool getOK;  // did the get from the IoT platform occur without error
unsigned int fanMinutesOn = 0;   // millisecs that the fan has been on
unsigned int fanMinutesOff = 60;   // minutes that the fan has been off -- initialize large 
unsigned long fanStartTime = 0UL;   // time (in ms) that the fan has been on since last being turned on
unsigned long fanStopTime = fanRestMinutes * 1000 * 60;  // time (in ms) that the fan has been off since last being turned on
static bool fanOn;
static bool fanStateChanged;
static bool sensorsOK;
int invalidCount=0;
float SMSbalance = 14.4700;  //Twilio acct balance
//SLOPE float slope;        // most recent rate of temp. decline in attic when fan is on

//variables to hold temperature values 
float attic;   // attic temp
float outside;    // outside temp
float differential;   //delta between attic and outside
// prevAttic =attic temp (Tmp) readings and timestamps (Tim) prior to current reading
// initialize these arrays
//SLOPE  float prevAtticTmp [6]={999.0,999.0,999.0,999.0,999.0,999.0};
//SLOPE  float prevAtticTim [6]; // should not need to initialize global vars to zero         ={0,0,0,0,0,0};    
//SLOPE  int nAtticTemps; //=0;        // number of most recent attic temps & tiemstamps contained in prevAtticTmp and prevAtticTim


/* logic rationale
Winter: if attic temp > 32 & outside temp <32, then turn fan on UNTIL 
	a) maxfanMinutesOn is reached, or 
	b) attic temp <= 32, or
	c) fan's ability to reduce attic temp has reached point of diminishing returns (i.e., 
	   attic temp rate of reduction ("slope") during most recent period (maxfanMinutesOn/6) 
	   will not achieve attic temp goal of 32 if it were applied over two maxfanMinutesOn durations)
Summer: if (attic temp)- (outside temp) > maxSummerDiff, then turn fan on UNTIL	
	a) maxfanMinutesOn is reached, or 
	b) (attic temp)- (outside temp) < minSummerDiff, or
	c) fan's ability to reduce attic temp has reached point of diminishing returns (i.e., 
	   attic temp rate of reduction ("slope") during most recent period (maxfanMinutesOn/6) 
	   will not achieve attic temp goal of (outside temp + minSummerDiff) if it were applied over two maxfanMinutesOn durations)
*/


//SET YOUR THERMOMETER ADDRESSES!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//The 1-wire thermometers are identified by their unique hexidecimal identifier
//use the example sketch 'tester' provided with the DallasTemperature library 
//to get each device address.
//These must be specific to my sensor IDs.
byte atticSensor[8] = {
  0x28, 0x12, 0xDD, 0x57, 0x05, 0x0, 0x0, 0xF2};
byte outsideSensor[8] = {
  0x28, 0x80, 0xD1, 0x4E, 0x05, 0x0, 0x0, 0xA7};     //   2880D14E050000A7


////physical mac address of ethernet, may be printed on shield. The wiz812mj does not come with an assigned mac address! 
//Give it any mac (hexidecimal) you wish, AS LONG AS device is connected to your lan, NOT directly to the internet.
//if you wish to connect directly to the internet, with a public IP address, you must find a valid
//assigned mac address, for example, you could use a mac address from some other broken ethernet devise.
byte    mac[] =     { 
  0x00, 0x24, 0xd6, 0x7f, 0xa2, 0x54 };   // from Wade's Vixar laptop -- Charlie, you can use this MAC address too

//assign IP address manually for now. Pick ip not in your routers dhcp pool, and not otherwise in use. 
//Comment out causes dhcp but is not properly implemented into this code yet.
//ie, waits for dhcp indefinetly if not plugged in, thus controller never starts.
//TODO implement DHCP. Controller should start even if can't get address, and periodically check again for dhcp address.
//
//WVC   Wade's wifi router DHCP range is 192.168.1.100 to 192.168.1.199, and the following ip is reserved in the router for use with the Arduino's MAC address
byte    ip[] =      { 
  192, 168, 0, 199 };


//----------------------------------------------------------------------------------------------


//Setup Ethernet client
EthernetClient client; //Creates a client which can connect to a specified internet IP address and port (defined in the client.connect() function)


//define pin for fan relay
const int fan = A0;

// temp vars for integer GETs and float GETs
int intGET;
float floatGET;

//##################################################################################
//##############################   SETUP     #######################################
//##################################################################################
//##################################################################################
//this part of the code runs once to get everything setup and started.
void setup ()
{
  //start serial port and print information for debugging
  Serial.begin(9600L);//DEBUG    
  delay (4000);    //give user time to start serial monitor after compile & upload//DEBUG    

//  printState(F("setup"));
//DEBUG  Serial.println (F("Waduino Ver 5.1"));
//DEBUG  Serial.println (F("Attic Fan Diff Cntrlr"));
//DEBUG  Serial.println();

// block of statements used only for testing 
//pretends that summertime=outside temp >50, and diff threshholds are 5 & 2 def F
//TEST    maxfanMinutesOn = 2;    // statement for testing only
//TEST    fanRestMinutes =  1;    // statement for testing only
//TEST    maxSummerDiff = 5;    // statement for testing only
//TEST    minSummerDiff = 4;    // statement for testing only
//TEST    setSummerMin = 50;    // statement for testing only

  // start the ethernet connection
//DEBUG    if(dbg) Serial.println (F("starting E/N"));
  Ethernet.begin(mac, ip);   //Initializes the ethernet library and network settings

  // Start up the 1-wire library
//DEBUG  Serial.println (F("starting 1-wire sensors"));
  sensors.begin();
	
	//  start up TS
	ThingSpeak.begin(client);

  // locate devices on the one wire bus
//DEBUG    if(dbg) {
//DEBUG      Serial.println(F(""));
//DEBUG      Serial.print(F("Locating 1-wire devices..."));
//DEBUG      Serial.println (F(""));
//DEBUG      Serial.print(F("Found "));
//DEBUG      Serial.print(sensors.getDeviceCount(), DEC);
//DEBUG      Serial.println(F(" devices."));
//DEBUG    }

  //// report parasite power requirements
  //Serial.print(F("Parasite power is: ")); 
  //if (sensors.isParasitePowerMode()) Serial.println(F("ON"));
  //else Serial.println(F("OFF"));
  //Serial.println (F(""));

  //Set the sensor resolution
  //Resolution can be set from 9 to 12 bit. The higher the resolution, the slower the read time
  //12 bit resolution is still plenty fast for most applications. We're talking about milliseconds differences
  sensors.setResolution(atticSensor, 12);
  sensors.setResolution(outsideSensor, 12);

  //set pins to output
  pinMode (fan, OUTPUT);

  //so that the initial state is known, turn off fan and set fanOn to false
  digitalWrite (fan, LOW);
  fanOn =false;
  fanStateChanged = false;   //  set default setting
	readingLast = 0 - readingDelay;  // to force early read
	ThingSpeakGetLast= 0-ThingSpeakGetDelay;  // to force early GET

//DEBUGmem    if (dbgmem) {
//DEBUGmem   Serial.println(F("*Free RAM = ")); //F function does the same and is now a built in library, in IDE > 1.0.0
//DEBUGmem   Serial.println(freeMemory(), DEC);  // print how much RAM is available.
//DEBUGmem   }

//#######################################################################################
//###########################          get SMS acct balance     #########################
//#######################################################################################
    floatGET = ThingSpeak.readFloatField(dataChannelID,5U,APIKEYdataread);  //  SMSbalance
    if (ThingSpeak.getLastReadStatus() != 200) 
      {Serial.print(F("Failed to get SMSbalance: "));
       Serial.println(ThingSpeak.getLastReadStatus());
      }
    else {
      Serial.print(F("Got SMSbalance="));
      Serial.println(floatGET);
      if (floatGET > 15) 
        {Serial.print(F("Invalid SMSbalance="));  
         Serial.println(floatGET);
        }
      else if (floatGET>0)
        {SMSbalance = floatGET;}
      else {  //  assume value was never written to the data field
        ThingSpeak.setField(5,SMSbalance);
      }
    }
//#######################################################################################


//Serial.println(F("starting loop  "));
}
//##################################################################################
//#############################    LOOP      #######################################
//##################################################################################
void loop ()
{
/*
 * 
/*Serial.print(F("starting loop  "));
Serial.print(millis());
Serial.print(F("  "));
Serial.print(ThingSpeakGetLast);
Serial.print(F("  "));
Serial.println(ThingSpeakGetDelay);
*/
/*Serial.print(ThingSpeakGetLast);
Serial.print(F("---"));
Serial.print(ThingSpeakGetDelay);
Serial.print(F("---"));
Serial.println(ShutDown);
*/
//######################################################################################
//############################   GET CONTROL VARIABLES FROM ThingSpeak  ################
//######################################################################################
//  int   readIntField (unsigned long channelNumber, unsigned int field, const char *readAPIKey)
//    intGET = ThingSpeak.readIntField(ctrlChannelID,1U,APIKEYctrlread);   //maxfanMinutesOn
//ctrlChannelID:  field 1=maxFanTimeOn; field 2=fanRestTime; 
//                field 3=vacationMode; 
//                field 4=maxSummerDiff; field 5= minSummerDiff;
//                field 6=SMSlevel

getOK=true;   // initialize -- a GET failure on any of the control variables will set this to false=>don't reset GET timer
if (millis() - ThingSpeakGetLast >= ThingSpeakGetDelay) 
  {  
    intGET = ThingSpeak.readIntField(ctrlChannelID,1U,APIKEYctrlread);  //  maxfanMinutesOn
    if (ThingSpeak.getLastReadStatus() != 200) 
      {getOK=false;}
    else {
      Serial.print(F("Got maxfanMinutesOn="));
      Serial.println(intGET);
      if (intGET <5 || intGET > 60) 
        {Serial.println(F("Invalid maxfanMinutesOn"));  }
      else 
        {maxfanMinutesOn = intGET;}
    }

    intGET = ThingSpeak.readIntField(ctrlChannelID,2U,APIKEYctrlread);  //  fanRestMinutes
    if (ThingSpeak.getLastReadStatus() != 200) 
      {getOK=false;}
    else {
      Serial.print(F("Got fanRestMinutes="));
      Serial.println(intGET);
      if (intGET < 5 || intGET > 60) 
        {Serial.println(F("Invalid fanRestMinutes "));}
      else 
        {fanRestMinutes = intGET;}
    }

    intGET = ThingSpeak.readIntField(ctrlChannelID,3U,APIKEYctrlread);  //  vacationMode
    if (ThingSpeak.getLastReadStatus() != 200) 
      {getOK=false;}
    else {
      Serial.print(F("Got vacationMode="));
      Serial.println(intGET);
      if (intGET != 0 && intGET != 1) 
        {Serial.println(F("Invalid vacationMode " ));}
      else 
        {vacationMode = intGET;}
    }

    intGET = ThingSpeak.readIntField(ctrlChannelID,4U,APIKEYctrlread);  //  maxSummerDiff
    if (ThingSpeak.getLastReadStatus() != 200) 
      {getOK=false;}
    else 
    {
      Serial.print(F("Got maxSummerDiff="));
      Serial.println(intGET);
      if (intGET < 10) 
        {
          Serial.println(F("Invalid maxSummerDiff - setting to 10 " ));
          maxSummerDiff=10;
        }
      else 
        {maxSummerDiff = intGET;}
    }

    intGET = ThingSpeak.readIntField(ctrlChannelID,5U,APIKEYctrlread);  //  minSummerDiff
    if (ThingSpeak.getLastReadStatus() != 200) 
      {getOK=false;}
    else {
      Serial.print(F("Got minSummerDiff="));
      Serial.println(intGET);
      if (maxSummerDiff-intGET<5 || intGET<5) 
        {
          minSummerDiff=maxSummerDiff-5;
          Serial.print(F("Invalid minSummerDiff - setting to maxSummerDiff-5 = " ));
          Serial.println(minSummerDiff);
        }
      else 
        {minSummerDiff = intGET;}
    }

    intGET = ThingSpeak.readIntField(ctrlChannelID,6U,APIKEYctrlread);  //  SMSlevel
    if (ThingSpeak.getLastReadStatus() != 200) 
      {getOK=false;}
    else {
      Serial.print(F("Got SMSlevel="));
      Serial.println(intGET);
      if (intGET<0 || intGET>2) 
        {
          SMSlevel=1;
          Serial.println(F("Invalid SMSlevel - setting to 1 = " ));
        }
      else 
        {SMSlevel = intGET;}
    }

    intGET = ThingSpeak.readIntField(ctrlChannelID,7U,APIKEYctrlread);  //  ShutDown
    if (ThingSpeak.getLastReadStatus() != 200) 
      {getOK=false;}
    else {
      Serial.print(F("Got ShutDown="));
      Serial.println(intGET);
      if (intGET<0 || intGET>1) 
        {
          ShutDown=1;
          Serial.println(F("Invalid ShutDown - setting to 1 = " ));
        }
      else 
        {ShutDown = intGET;}
    }
    
    if (getOK == true) // indicates the 'gets' occured without a hitch
    {
			ThingSpeakGetLast = millis(); 
		}
	  
  }   // if (millis() - ThingSpeakGetLast >= ThingSpeakGetDelay)
//#############################################################################################
//############################   end of GET CONTROL VARIABLES FROM ThingSpeak  ################
//#############################################################################################
if (maxSummerDiff-minSummerDiff < 5) {maxSummerDiff=minSummerDiff+5;}
/*
HTTP status codes. Negative values indicate an error generated by the library. Possible response codes:
 200: OK / Success
 400: Bad Request (e.g., malformed request syntax, size too large, invalid request message framing, or deceptive request routing)
 404: Incorrect API key (or invalid ThingSpeak server address)
-101: Value is out of range or string is too long (> 255 characters)
-201: Invalid field number specified
-210: setField() was not called before writeFields()
-301: Failed to connect to ThingSpeak
-302: Unexpected failure during write to ThingSpeak
-303: Unable to parse response
-304: Timeout waiting for server to respond
-401: Point was not inserted (most probable cause is the rate limit of once every 15 seconds)
*/
		
//##################################################################################
//###########################      READ SENSORS     ################################
    if ((millis() - readingLast >= readingDelay) && ShutDown == 0) // millis() returns an unsigned long integer
    {
      Serial.print(F("Reading sensors at millis = "));  //hang
      Serial.println(millis());   //hang
      //get the values from the DS8B20's 
      sensors.requestTemperatures();

      //assign value to variables
      attic = (sensorValue(atticSensor));
      outside = (sensorValue(outsideSensor));

      //here is where we decide if the reading from the DS18B20 is valid. I use simple comparisons here.
      //If thermometer is hooked up wrong, or wires cross etc. , they can return a temp of exactly
      //185.0 or 32.0 degrees F. This would indicate a bad sensor reading.
      //TODO improve. The method below may be superfluous
//DEBUG       if (dbg) Serial.print (F("Validating sensor readings..."));

      if ( attic == 185.0 || attic == 32.0 ) {
        sensorsOK = false;
      }   
      else if (outside < -30 || outside > 120 || outside == 185.0 || outside == 32.0) {
        sensorsOK = false;
      } 
      else {
        sensorsOK = true;
      }  

//printState(F("1"));
      //if sensor readings are invalid, print to serial and/or send SMS
      if (!sensorsOK) {
        invalidCount++; //increment the invalid counter
				if (invalidCount > 20)   // must get 20 bad reads in a row before the system is shut down.
				{
					if (SMSlevel>0) {sendSMS(WadesCell,URLEncode(F("TOO MANY BAD READS -- SHUTTING DOWN")));}
					Serial.println (F("TOO MANY BAD READS -- SHUTTING DOWN"));  //hang
					turnFanOff();
					ShutDown = 1;
				}
      }
      else { // sensor readings are valid, so make fan decisions

//##################################################################################
//###########################   GATHER FAN DECISION INFO  ##########################
//##################################################################################
//DEBUG         if (dbg) Serial.println (F("Valid"));
        readingLast = millis();
				if (invalidCount>20) {
					if (SMSlevel>0) {sendSMS(WadesCell, URLEncode(F("Finally got a good sensor read -- STARTING UP")));}
					ShutDown = 0;
				}
        invalidCount = 0; //we got a good read, so invalid count is reset to zero. 
        differential= attic-outside;
				if (differential < 0) {differential = 0;}   //  differential should never be negative
//SLOPE		addAtticTemp();   //  adds most recent attic temp & temstamp to arrays 'prevAtticTmp' & 'prevAtticTim'
		// 'prevAtticTmp and prevAtticTim contain 6 most recent attic temp readings & timestamps, 
		//  with prevAtticXXX[5] being the oldest and prevAtticXXX[0] being the most recent 
		
		// get slope of most recent attic temps
//SLOPE  		calcSlope();

        //Decide if it is summertime, wintertime, or neither
        if(outside < setWinterMax) {
          winter = true;
          summer = false;
        }
        else if(outside > setSummerMin) {
          summer = true;
          winter = false;
          minAchieved = differential < float(minSummerDiff);
          maxExceeded = differential > float(maxSummerDiff);
        }
        else {
          summer=false;
          winter=false;
        }

        if (((vacationMode == 1 && summer) || (!summer && !winter)) && fanOn)  // should continue to operate in winter even when no one is home
        {
//DEBUG           if (dbg) Serial.print(F("ON VACATION!"));
					turnFanOff();
        }
        else  // determine what to do with fan
        {
          // How long has fan been in its current state?
          if (fanOn) {
            fanMinutesOn = (millis() - fanStartTime)/1000/60; // unsigned result should roll over
          }
          else {
            fanMinutesOff = (millis() - fanStopTime)/1000/60; // unsigned result should roll over
          }
//printState(F("B4 ctl logic"));
//DEBUG           if (dbg) printState();




//####################################################################################################
//################   CONTROL LOGIC that decides when to turn on and off the fan   ####################
//####################################################################################################
//DEBUG           if (dbg) {
             Serial.println (F("Entering CTRL LOGIC: "));  //hang
//DEBUG             Serial.print (F(" milli = "));
//DEBUG             Serial.print (millis());
//DEBUG             Serial.println (F("   #######################################"));
//DEBUG           }
          if (summer)   {    
            if(!fanOn) { 
              if(maxExceeded) {
                if(fanMinutesOff > fanRestMinutes) {             //fan is off, max threshold exceeded, & fan has rested long enough
//DEBUG                   if (dbg) Serial.print (F("Turn fan on" ));
                  turnFanOn();                             //  so turn it off
                }
                else {                                     // fan is off, max threshold exceeded, but fan not rested enough
//DEBUG                   if (dbg) Serial.print (F("Leave fan off" ));
                }                                          // so leave it off
              }
              else {                                       // fan is off, and max threshold not reached
              }                                            // so leave it off
            }  // (end of "if(!fanOn)"
            else {   // fan is on
              if(fanMinutesOn > maxfanMinutesOn) {               //fan has been on too long
//DEBUG                 if (dbg) Serial.print(F("FAN EXCEEDED MAX TIME ON!!"));
                turnFanOff(); //  so turn it off
              }
              else if (minAchieved) {                      //fan on, but not too long yet, but min threshold achieved
//DEBUG                 if (dbg) Serial.print(F("MIN. DIFF. ACHIEVED"));
                turnFanOff();        //  so turn it off
              }
              else {                                       //fan on, but not too long yet, and min threshold not yet achieved
														   //...so determine if slope warrants leaving it on
              }                                            //  so leave it on
            }  //  ############################################################################################(end of "if fan is on")
          }  // (end of "if summer")
          else if (winter)   {
            if(!fanOn) { 
              if(attic > 32 && outside < 32) {
                if(fanMinutesOff > fanRestMinutes) {             //fan is off, ice dam conditions exist, & fan has rested long enough
//DEBUG                   if (dbg) Serial.print (F("Turn fan on" ));
                  turnFanOn();                             //  so turn it off
                }
                else {                                     // fan is off, ice dam conditions exist, but fan not rested enough
//DEBUG                   if (dbg) Serial.print (F("Leave fan off" ));
                }                                          // so leave it off
              }
              else {                                       // fan is off, and ice dam conditions do not exist
              }                                            // so leave it off
            }  // (end of "if(!fanOn)"
            else {   // fan is on
              if(fanMinutesOn > maxfanMinutesOn) {               //fan has been on too long
//DEBUG                 if (dbg) Serial.print(F("FAN EXCEEDED MAX TIME ON!!"));
                turnFanOff(); //  so turn it off
              }
              else if (attic <= 32 && outside >= 31) {                      //fan on, but not too long yet, but ice dam conditions do not exist
//DEBUG                 if (dbg) Serial.print(F("ICE DAM CONDITIONS NO LONGER EXIST"));
                turnFanOff();        //  so turn it off
              }
              else if (attic < 30 && fanMinutesOn > 10) {                                       //fan on, but not too long yet, and ice dam conditions still exist
                               //...so determine if slope warrants leaving it on
                turnFanOff();
              }                                            //  so leave it on
            }  //  ############################################################################################(end of "if fan is on")
          }  // (end of "if winter")
          else {  //neither summer or winter
            if (fanOn) {
//DEBUG               if (dbg) Serial.print(F("Neither summer or winter-"));
              turnFanOff();
            }
          }
        }    // "if (((vacationMode == 1 && summer) || (!summer && !winter)) && fanOn)"
//DEBUG             if (dbg) {
        Serial.println (F("Leaving CNTL LOGIC"));  //hang
//DEBUG               Serial.println (F("   #######################################"));
//DEBUG             }

      }   // sensor readings are ok
    }   // if (millis() - readingLast >= readingDelay), i.e. its time to read sensors
//####################################################################################
//############################   put data to ThingSpeak   ############################
//####################################################################################
    if (((millis() - ThingSpeakPutLast >= ThingSpeakPutDelay) || (fanStateChanged && (millis()-ThingSpeakPutLast > 15000))) && ShutDown == 0)
    { 
			Serial.print(F("PUT attempt to dataChannelID="));
			Serial.print(dataChannelID);
			Serial.print(F(";   at millis = "));
			Serial.println(millis());
			// Serial.println(attic);
			// Serial.println(outside);

 //post to ThingSpeak feed
      //if (sensorsOK)
      //{
        //the datastreams number corresponds to the order of the datastreams array.
        if (use_ThingSpeak)    {
					ThingSpeak.setField(1,attic);
					ThingSpeak.setField(2,outside);
					ThingSpeak.setField(3,fanOn);
					ThingSpeak.setField(4,sensorsOK);
          ThingSpeak.setField(5,SMSbalance);
/*
					ThingSpeak.setField(5,int(fanMinutesOn));
					ThingSpeak.setField(6,int(fanMinutesOff));
*/
					
					ret = ThingSpeak.writeFields(dataChannelID,APIKEYdatawrite);


//DEBUG             Serial.println(F("Uploading to ThingSpeak"));
					if (ret != 200)
					{
            Serial.print(F(" -- ThingSpeak PUT failed, ret= "));
            Serial.println(ret);

						putOK = false;
					}
					else 

					{
						Serial.println(F(" -- ThingSpeak PUT succeeded "));
						putOK = true;
					}
//DEBUG           if (dbg) {
//DEBUG             Serial.print(F("ThingSpeak put ret= "));
//DEBUG             Serial.print(ret);
//DEBUG             if (ret =200) 
//DEBUG             {
//DEBUG               Serial.print(F(" which means OK"));
//DEBUG             }
//DEBUG             else
//DEBUG             {
//DEBUG               Serial.print(F(" WHICH MEANS ERROR OCCURED!"));
//DEBUG             }
//DEBUG             Serial.println(F(""));
//DEBUG             Serial.println(F(""));
//DEBUG           }
        }
      //}

      ThingSpeakPutLast = millis();    
    }

//DEBUGmem   if (dbgmem) {
//DEBUGmem     Serial.println(F("Free RAM = ")); //F function does the same and is now a built in library, in IDE > 1.0.0
//DEBUGmem     Serial.println(freeMemory(), DEC);  // print how much RAM is available.
//DEBUGmem   }


}    //And thats the end of the loop.
//##################################################################################
//##################################################################################
//##################################################################################
//##################################################################################



//Here are a couple simple functions used in the code to make it a little cleaner.

//##################################################################################
//sensorValue function---------------------------------------------------------
//reads temp from sensors and returns as floating point value in F.
//To use celsius instead, the last line should be changed to "return tempC".
float sensorValue (byte deviceAddress[])
{
  float tempC = sensors.getTempC (deviceAddress);
  float tempF = (DallasTemperature::toFahrenheit(tempC));
  return tempF;
}



//##################################################################################
//   function to maintain array of most recent attic temp readings
//SLOPE  void addAtticTemp()  
//SLOPE  {
//SLOPE    for (int i = 5; i>0;i--) {   // 0th is the most recent, 5th is the oldest
//SLOPE  	prevAtticTmp[i]=prevAtticTmp[i-1];
//SLOPE  	prevAtticTim[i]=prevAtticTim[i-1];
//SLOPE    }
//SLOPE    if (nAtticTemps <6)
//SLOPE  	{
//SLOPE  		nAtticTemps=nAtticTemps + 1;
//SLOPE  	}
//SLOPE  }

//##################################################################################
//   function to calc slope of most recent attic temps
//SLOPE  void calcSlope() 
//SLOPE  {
//SLOPE  	
//SLOPE  }



//##################################################################################
// function to turn fan off-----------------------------------------------------
void turnFanOff () 
{
// Serial.println (F("TURNING FAN OFF"));
  fanStartTime=0UL;
  fanStopTime=millis();
  fanMinutesOn=0;
  digitalWrite(fan,LOW);
  if (fanOn) 
	{
/*
     Serial.print(F("TURNING FAN OFF at "));
    Serial.println(fanStopTime); 
*/
		fanStateChanged = true;
//		printState(F("turnFanOff"));
	}
  fanOn=false;
	if (SMSlevel==2) {sendSMS(WadesCell, URLEncode(F("Fan Turned Off")));}
//DEBUG   if (dbg)  printState();
}

//##################################################################################
// function to turn fan on-------------------------------------------------------
void turnFanOn () 
{
// Serial.println (F("TURNING FAN ON"));
  fanStartTime=millis();
  fanStopTime=0UL;
  fanMinutesOff=0;
  digitalWrite(fan,HIGH);
  if (!fanOn) 
	{
/*     Serial.print(F("TURNING FAN ON at "));
    Serial.println(fanStartTime); */
		fanStateChanged = true;
//		printState(F("turnFanOn"));
	}
  fanOn=true;
	if (SMSlevel==2) {sendSMS(WadesCell, URLEncode(F("Fan Turned On")));}
//DEBUG   if(dbg)  printState();
}

    
//##################################################################################
void sendSMS(String number,String message)
  //this function will send the sms
  //the first argument is the number to send to, formatted like this +12345678901
  //the second argument is the body of the text message, which must be within URLEncode()
  //sendSMS(WadesCell, URLEncode(F("Hello World!")));
{
  // Make a TCP connection to remote host
  Serial.println(F("Sending SMS"));
  if (client.connect(thingSpeakAddress, 80))
  {

    //should look like this...
    //api.thingspeak.com/apps/thinghttp/send_request?api_key={api key}&number={send to number}&message={text body}

    client.print(F("GET /apps/thinghttp/send_request?api_key="));
    client.print(ThingHTTPapi);
    client.print(F("&number="));
    client.print(number);
    client.print(F("&message="));
    client.print(message);
    client.println(F(" HTTP/1.1"));
    client.print(F("Host: "));
    client.println(thingSpeakAddress);
    client.println(F("Connection: close"));
    client.println();
    SMSbalance=SMSbalance-0.0075;
  }
  else
  {
    Serial.print(F("Connection failed to "));
    Serial.println(thingSpeakAddress);
  } 

  // Check for a response from the server, and route it
  // out the serial port.
  while (client.connected())
  {
    if ( client.available() )
    {
      char c = client.read();
      Serial.print(c);
    }      
  }
  Serial.println();
  Serial.println(F("*******   end of SMS response  ********"));
  client.stop();

}

//##################################################################################
String URLEncode(String str)
{
    String encodedString="";
    char c;
    char code0;
    char code1;
    char code2;
    for (int i =0; i < str.length(); i++){
      c=str.charAt(i);
      if (c == ' '){
        encodedString+= '+';
      } else if (isalnum(c)){
        encodedString+=c;
      } else{
        code1=(c & 0xf)+'0';
        if ((c & 0xf) >9){
            code1=(c & 0xf) - 10 + 'A';
        }
        c=(c>>4)&0xf;
        code0=c+'0';
        if (c > 9){
            code0=c - 10 + 'A';
        }
        code2='\0';
        encodedString+='%';
        encodedString+=code0;
        encodedString+=code1;
        //encodedString+=code2;
      }
      yield();
    }
    return encodedString;
    
}

/* 
//##################################################################################
 void printState (String location)  {  //--------------------------------------------------------
   //prints values to serial port and lcd
   Serial.println();
   Serial.print(location);
   Serial.print(F(" STATE============ millis = "));
   Serial.println(millis());
   Serial.print (F("Vacation? "));
   Serial.print (vacationMode);
   Serial.print (F(" ; sensorsOK "));
   Serial.print (sensorsOK);
   Serial.print (F(" ; Summ? "));
   Serial.print (summer);
   Serial.print (F(" ; Wint? "));
   Serial.println (winter);
   Serial.print (F("maxSummerDiff: "));
   Serial.print (maxSummerDiff);
   Serial.print (F(" ; minSummerDiff: "));
   Serial.println (minSummerDiff);
   Serial.print (F("attic "));
   Serial.print (attic);
   Serial.print (F("; outside "));
   Serial.print (outside);
   Serial.print (F("; differential "));
   Serial.println (differential);
   Serial.print (F("Max Diff exceeded? "));
   Serial.print (maxExceeded);
   Serial.print (F("; Min Diff achieved? "));
   Serial.println (minAchieved);
   Serial.print (F("fan on? "));
   Serial.print (fanOn);
   Serial.print (F("; fanStartTime "));
   Serial.print (fanStartTime);
   Serial.print (F("; fanMinutesOn "));
   Serial.print (fanMinutesOn);
   Serial.print (F("; fanStopTime "));
   Serial.print (fanStopTime);
   Serial.print (F("; fanMinutesOff "));
   Serial.print (fanMinutesOff);
   Serial.print (F(" ; maxfanMinutesOn "));
   Serial.print (maxfanMinutesOn);
   Serial.print (F(" ; fanRestMinutes "));
   Serial.println (fanRestMinutes);
   Serial.println (F("=================="));
   //WVC       printLCD();
 }
*/

Also, I am looking for a free iot service that will accept requests from my arduino over the web, and at least allow me to monitor the status, and preferably allow me to modify parameters and download them to the arduino. I believe Twilio and ThingSpeak no longer offer hobbyists free accounts.

Your code is inserted incorrectly, because the whole code was treated as comment. Please return to the post and fix the issue.
Also, the code contains a lot of commented/unused parts, that make it less understandable. It is recommended to remove this parts before inserting the code again.

post your serial monitor output (as text not a screen image)
how do you know it is memory overrun? what does it do? what should it do?
add extra Serial.println() statements to print the values of key variables and display program flow
could it be the relay switching causing noise on the power supply lines and resetting the microcontroller?

First place to check is right behind the keyboard.

1 Like

Unfortunately the ThingSpeak library makes extensive use of String. The library also makes no use of the F() macro when printing text literals. The github page shows an issue about excessive memory usage from 2019, but this was closed apparently without any fixes implemented.

The sketch compiles for an UNO with 1007 bytes of ram free, but that may not be sufficient.

It might not, but there is nothing to stop you using this in your code.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.