Renamed 'iterating through a series of objects' - original title: OOP, this pointer questions

As I mentioned back in Post #18 of this thread:

I did and It'd be hard for me to know if anything was wrong, this stuff's getting way over my head. I've learned quite a bit on this thread already, which isn't yet fully digested. Different constructor flavors will have to wait for another day.

I will note that the iterating mechanism is simpler than I thought it would be.

Sorry about that, some of the concepts and terminology from different posts is getting muddled when it hits the wetware!

Comtrya!

I removed keyword static from

bool enableOff = !_Enable;  // reverse sense of enable for TOF // original line

in the redefined update() and all is well!
-=-=-

Original content of this post:

After getting the mass update feature working, with much help, I wanted to add another timer type, an off-delay which operates opposite of an on-delay like so:

on-delay timer - this works

enable      _________
       ____|         |___

                 ____
done  __________|    |___
           ^ Td ^ 

-=-=-

off-delay timer – this does not work

enable  ____          ___  
            |________|
            
done    _________     ___
                 |___|
            ^ Td ^

It’s easy to achieve this by adding some accessor functions unique to the offDelay type and leaving update() alone but I wanted to keep a common method for enabling and testing doneness of the timers, ie. someTimer.setEnable(). After some research I settled on redefining the update function for just the OFF delay timer class. I’ve gotten this to compile and upload and other timers still work as expected. However, the OFF delay refuses to cooperate.

I first proved the basic logic, in isolation, by making a function which only does the off delay. The problem arises when incorporating into the library.
I know I’m calling the redefined update function because I inserted a print statement and it dutifully informs me every time it’s called. The problem is getting   _Enable and  _Done  to respond in any way. I’ve tried including them in the redefined function and also declaring them static but, no dice.

The redefinition examples I've seen online and in my two C++ books basically only include some cout  statement to show which version has been called. I have not seen any examples showing the redefined function getting input to act as a guide.

I have to assume it's possible. What am I missing?

.ino


// demo program for forum thread:
// https://forum.arduino.cc/t/renamed-iterating-through-a-series-of-objects-original-title-oop-this-pointer-questions/1244434
//
#include "Multi_timer.h"

// demonstrate three types of timers

OnDelayTimer onDelay_timer(1000);

OffDelayTimer offDelay_timer(1000);

Flasher_tmr Flasher(2000, 300);  // 300ms on, 1700ms off

const byte buttonA = 3;
const byte buttonB = 4;
const byte buttonC = A3;
const byte ledA_run = 6;
const byte ledA_done = 8;
const byte ledB = 7;
const byte ledFlash = 9;
const byte ledB_run = 10;


void setup() {
  Serial.begin(115200);
  pinMode(buttonA, INPUT_PULLUP);
  pinMode(buttonB, INPUT_PULLUP);
  pinMode(buttonC, INPUT_PULLUP);

  // All LEDs are common cathode

  pinMode(ledA_run, OUTPUT);
  pinMode(ledA_done, OUTPUT);
  pinMode(ledB, OUTPUT);
  pinMode(ledFlash, OUTPUT);
  pinMode(ledB_run, OUTPUT);
  Serial.println("started");

  offDelay_timer.setEnable(true);
}

void loop() {
  static byte count;
  Multi_timer::Update_All_Timers();

  // Serial.println(offDelay_timer.getCount());
  // Serial.println(offDelay_timer.isDone());

  // demonstrate OnDelayTimer
  onDelay_timer.setEnable((digitalRead(buttonA) ? false : true));
  digitalWrite(ledA_done, onDelay_timer.isDone() ? HIGH : LOW);
  digitalWrite(ledA_run, (onDelay_timer.isRunning() ? HIGH : LOW));

  // demonstrate OffDelayTimer
  offDelay_timer.setEnable((digitalRead(buttonB) ? false : true));
  digitalWrite(ledB, (offDelay_timer.isDone() ? HIGH : LOW));
  digitalWrite(ledB_run, (offDelay_timer.isRunning() ? HIGH : LOW));
   if (offDelay_timer.isEnabled()) Serial.println(count++);

  // demonstrate flasher timer
  Flasher.setEnable((digitalRead(buttonC) ? false : true));
  digitalWrite(ledFlash, (Flasher.isFlashing() ? HIGH : LOW));
}  // end of loop()

.cpp

// filename: Multi_Timer.cpp  6/18/19
// this version has minimal set/get methods for demonstration

#include "Multi_Timer.h"
#include "Arduino.h"
//
/*
      Multi_timer is the base timer class.
      > Sets done status true upon reaching preset.
      > Produces a positive-going one-scan pulse 'os' upon reaching
      preset value (done status = true) and another one-scan pulse
      when done goes false.
      > responds to a reset command by setting accumulated value to
      zero and resetting _Done and _TimerRunning.
      > This version has preset checking removed.
*/
//----------------------------------------------------
//

Multi_timer *Multi_timer::first = nullptr;  // Initialize pointer variable 'first'

// One-argument constructor for non-flasher types
// One-argument constructor delegates to the two-argument constructor

Multi_timer::Multi_timer(unsigned long pre)
  : Multi_timer(pre, 0UL) {  // trailing zero is default onTime initialization
}

//------------------------------------------------------
// two-argument constructor for flasher type
// one-argument constructors call this as a delegate

Multi_timer::Multi_timer(unsigned long pre, unsigned long onTime)

{
  _Preset = pre;
  _OnTime = onTime;
  _Control = false;

  //
  // Set up pointers to list of objects to enable one call to
  // an 'updateall' function rather than each timer making a
  // separate call to 'update'
  //
  // Multi_timer *next = nullptr;
  if (first == nullptr) {
    first = this;
  } else {
    // Start at the first pointer and walk the list until we
    // find one that doesn't have a next
    Multi_timer *ptr = first;
    while (ptr->next != nullptr) {
      ptr = ptr->next;
    }
    ptr->next = this;
  }
}
/*
            Getter / Setter functions
*/

void Multi_timer::setEnable(bool en) {
  _Enable = en;
}

bool Multi_timer::isEnabled() {
  return _Enable;
}

bool Multi_timer::isReset() {
  return _Reset;
}

bool Multi_timer::isDone() {
  return _Done;
}


bool Multi_timer::isRunning() {
  return _TimerRunning;
}

void Multi_timer::setCtrl(bool ctrl) {
  _Control = ctrl;
}

unsigned long Multi_timer::getCount() {
  return _Accumulator;
}
//========================================================

//  The 'update' function is the heart of the thing.
//  ----------------------------------------------------
//  Updates timer accumulated value & conditions flags '_Done',
// '_Done_Rising_OS', '_Done_Falling_OS' and '_TimerRunning'.
//
//  Returns boolean status of _Done.  update() must be called
//  periodically in loop to update the flags and accumulated
//  value.
//  ====================================================


bool Multi_timer::update() {
  _CurrentMillis = millis();  // Get system clock ticks
  if (_Enable or _Control) {  // timer is enabled to run
    _Accumulator = _Accumulator + _CurrentMillis - _LastMillis;
    if (_Accumulator >= _Preset) {  // timer done?
      _Accumulator = _Preset;       // Don't let accumulator run away
      _Done = true;
    }
  }
  _LastMillis = _CurrentMillis;

  if (reset()) {  // Call virtual reset function.  Reset timer if
    //              returns true, based on derived class' criteria.
    _Done = false;
    _Accumulator = 0;
    _Control = false;  // ensures reset of latched type
  }

  // ----- Condition the timer running flag.

  if ((_Enable or _Control) and !_Done and !_Reset) {
    _TimerRunning = true;
  } else _TimerRunning = false;

  return _Done;  // exit to caller
}  // end update function

//
/* +++++++++++++++++++++++++++++++++++++++++++++++++
   Update_All_Timers does exactly what it says, all 
   timers are updated by one call from user.
   +++++++++++++++++++++++++++++++++++++++++++++++++
*/
void Multi_timer::Update_All_Timers() {
  for (Multi_timer *ptr = first; ptr != nullptr; ptr = ptr->next) {
    ptr->update();
  }
}

/*                  On Delay Timer class definition
 --------------------------------------------------------------------
 A timer which runs when reset is false and enable is true.
 It is reset otherwise. _Done goes true when preset is reached, 
 false otherwise.
 --------------------------------------------------------------------
*/
OnDelayTimer::OnDelayTimer(unsigned long pre)
  : Multi_timer(pre){};

OnDelayTimer::~OnDelayTimer() {}  // give a destructor

// Establish reset conditions for ON delay timer
bool OnDelayTimer::reset() {
  return (_Reset or !_Enable);
}  // End of OnDelay timer
/*
//========================================================
//
//                  Off Delay Timer class definition
//--------------------------------------------------------------------
// A timer which runs when reset is false and _Enable is false
// _Done goes false preset milliseconds after _Enable goes false.
// It is reset otherwise.
//--------------------------------------------------------------------
*/
OffDelayTimer::OffDelayTimer(unsigned long pre)
  : Multi_timer(pre){};

OffDelayTimer::~OffDelayTimer() {}  // give a destructor

// update function/method is redefined here

bool OffDelayTimer::update() {
  //Serial.println("called redefined func"); // for update proof of life
  // static unsigned long _Accumulator = 0;
  // static bool _Reset = false;
  // static bool _Enable = false;
  // static bool _Done = false;
  // static unsigned long _Preset;
  // static unsigned long _CurrentMillis;
  // static unsigned long _LastMillis = 0;
  // static bool _TimerRunning ;


  static bool enableOff = !_Enable;  // reverse sense of enable for TOF
  _CurrentMillis = millis();         // Get system clock ticks

  if (enableOff) {  // timer is enabled to run

    _Accumulator = _Accumulator + _CurrentMillis - _LastMillis;
    if (_Accumulator >= _Preset) {  // timer done?
      _Accumulator = _Preset;       // Don't let accumulator run away
      _Done = false;
    }
  }
  _LastMillis = _CurrentMillis;

  if (_Reset or !enableOff) {  // Find out if reset needed based on derived class' criteria.
    _Done = true;
    _Accumulator = 0;
  }
  /*
    ----- condition the timer timing status
  */
  if ((enableOff) and _Done and !_Reset) {
    _TimerRunning = true;
  } else _TimerRunning = false;

  return !_Done;
}

bool OffDelayTimer::reset() {
  return (_Reset or _Enable);
}  // End of OffDelay timer


/*=================================================================

                 Flasher Timer class definition
-----------------------------------------------------------------
This timer runs and resets itself automatically when enabled.
It is basically an enhanced OnDelay timer. The second constructor
argument specifies an ON time for a special output (_FlashOut)
unique to this type. If _Enable goes false the timer is reset
immediately.

 Set onTime to some fraction of pre
*/
Flasher_tmr::Flasher_tmr(unsigned long pre, unsigned long onTime)
  : Multi_timer(pre, onTime) {}

Flasher_tmr::~Flasher_tmr() {}  // give a destructor

bool Flasher_tmr::reset() {
  return (!_Enable or _Done);
}

bool Flasher_tmr::isFlashing() {

  // 4/2/24 : Moved _FlashOut code from update() to getFlash()
  // so that _FlashOut only applies to Flasher_tmr objects.
  //
  // Code below will turn on a common cathode LED for _OnTime
  // milliseconds when timing cycle starts.

  if (_Accumulator <= _OnTime) {
    _FlashOut = true;
  } else _FlashOut = false;

  return (_FlashOut and _Enable);
}

// 11/17/18 : Added method to runtime adjust _OnTime
// 12/18/18 : Added forced reset

void Flasher_tmr::setOnTime(unsigned long newOnTime) {
  _OnTime = newOnTime;
  _Accumulator = _Preset;  // Force a reset when new onTime loaded
}
// End of Flasher timer

.h

// filename: Multi_Timer.h  6/18/19
//
// https://forum.arduino.cc/t/renamed-iterating-through-a-series-of-objects-original-title-oop-this-pointer-questions/1244434

// Some basic timers with a (it is hoped) simple interface.

#ifndef MULTI_TIMER_H
#define MULTI_TIMER_H

#include "Arduino.h"

class Multi_timer {

private:
public:
  Multi_timer(unsigned long);                 // constructor declaration
  Multi_timer(unsigned long, unsigned long);  // flasher constructor
  void setEnable(bool);
  //void setTmrOffEnable(bool);
  //bool isTmrOffDone();
  void setReset(bool);
  bool isEnabled();
  bool isReset();
  void setCtrl(bool);
  bool isDone();
  bool isRunning();
  bool isIntv();

  unsigned long pre;
  unsigned long getCount();

protected:
  virtual bool reset() = 0;

public:
  //    Function to operate timers created under Multi_timer
  virtual boolean update();  // Updates the timer which calls it.
  static void Update_All_Timers();

private:
  // Variables for setting up list of objects (timers)
  //
  static Multi_timer *first;  // set location of first base class
  Multi_timer *next;          // = nullptr;


  //public:
protected:
  unsigned long _Preset;
  bool _Reset : 1;
  bool _Enable : 1;
  bool _Done : 1;
  bool _Control : 1;

protected:
  unsigned long _Accumulator;
  unsigned long _CurrentMillis;
  unsigned long _LastMillis;
  unsigned long _OnTime;
  bool _TimerRunning : 1;
  bool _Done_Falling_OS : 1;
  bool _Done_Rising_OS : 1;
  bool _Done_Rising_Setup : 1;
  bool _Done_Falling_Setup : 1;
  bool _FlashOut;


};  //end of base class Multi_timer declarations

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   Define various types of timers which inherit/derive from Multi_timer.
   The timers differ in functionality mainly by their reset methods.
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
//                  On Delay Timer class
//--------------------------------------------------------------------
// A timer which runs when reset is false and enable is true
// If reset is true or enable is false it is reset.
//--------------------------------------------------------------------

class OnDelayTimer : public Multi_timer {
public:
  OnDelayTimer(unsigned long);  // constructor
  ~OnDelayTimer();              // destructor

  virtual bool reset();
};  // End of class OnDelayTimer

/*===================================================================

     Off delay timer - (implemented as an alias of OnDelayTimer)

 https://www.learncpp.com/cpp-tutorial/typedefs-and-type-aliases/
 
Off delay timer functions like an on delay timer but enable and done
operate inversely to an on delay timer. Timer is reset and done is
true when enable is true.  Timing cycle begins when enable goes false
and after the delay time done goes false. 
//------------------------------------------------------------------
*/

class OffDelayTimer : public Multi_timer {

public:
  OffDelayTimer(unsigned long);
  ~OffDelayTimer();

  bool update();

  virtual bool reset();
};  // End of class OffDelayTimer

/*
                 Flasher Timer class definition
-----------------------------------------------------------------
This timer runs and resets itself automatically when enabled.
It is basically an enhanced OnDelay timer. The second constructor
argument specifies an ON time for a special output (_FlashOut)
unique to this type. If _Enable goes false the timer is reset
immediately.

onTime must be some fraction of pre
--------------------------------------------------------------------*/

class Flasher_tmr : public Multi_timer {
public:
  Flasher_tmr(unsigned long, unsigned long);
  ~Flasher_tmr();

  virtual bool reset();

  // 4/2/24 : Moved _FlashOut decision from update() to getFlash()
  // so that _FlashOut code only applies to Flasher_tmr objects.
  //

  bool isFlashing();

  // 11/17/18 : Added method to runtime adjust _OnTime
  // 12/18/18 : Added forced reset


  void setOnTime(unsigned long);
};
// end of class Flasher_tmr
//---------------------------------------------

#endif

A destructor question.

I’ve been trying to integrate destructors into this code and test them and I’m seeing weird things.

The library works fine without destructors invoked. But, say an OffDelayTimer is instantiated in setup(). So far, so good. The instance prints a brief message as it goes out of scope, ie. setup() ends. OK as far as it goes but now the other timers declared outside of setup() don’t work at all.

I included a 'heartbeat' message in loop() and although loop() still runs the message is garbled - it just prints little square boxes.

Is this a case of @codingBadly’s caution biting?

1 Like

Since I just duplicated the code you posted at #38 I'd say, no.

I've been looking at @gfvalvo's code to do that but haven't put it in place yet. This was really in the nature of an experiment to see what might actually happen with destructors called on objects in setup() with objects also global.

As I understand it

object1
|         next pointer
V         points to object2
object2
|         next pointer
V        points to object3
object3

For some reason object2 is destroyed so -

object1
|         next pointer
V         points to object2 but,
object2's memory is now unused/garbage contents
|
V        object2 valid pointer to object3 no longer exists
object3 - is now inaccessible because its pointer from object2 is gone?

Would separate calls to update() eliminate this mess seeing as there aren't any pointers needed (the original way)?

It would, but the whole point of this 100+ post thread was to implement a common function that would update all instances in one shot. Why take a step backwards?

As it stands, you have a problem not only with non-static local instances but also dynamically-created ones.

Like I said way back in Post #23:

So, why not just implement the destructor properly?

I wasn't planning to, just trying secure my understanding.

That is my next step.

Thanks, I'll check that out.

I've been pecking at this destructor issue.  I think I now have a properly working example.  I did a lot of reading and YT watching and still don't have a perfect understanding but, like I said, it works.

Nods to @gfvalvo, @Delta_G, @Coding_Badly

Looking over the library code for the umpteenth time I noticed that next in the constructor loop appeared to be an unitialized pointer - at least I couldn't find any place it was explicitly initialized. I've seen that unitialized pointers are a bad thing but I thought "maybe it doesn't apply to pointers to objects". Anyhow, I added it to the constructor loop

if (first == nullptr) {
    first = this;
  } else {
    next = nullptr;  // lets timers survive power cycle intact
    //
    // Start at the first pointer and walk the list until we
    // find one that doesn't have a next

    Multi_timer *ptr = first;
    while (ptr->next != nullptr) {
      ptr = ptr->next;
    }
    ptr->next = this;
    //Serial.print(this->update());
  }

and it didn't seem to hurt anything.  It works but, Is this the best place to initialize next?

I made minor tweaks here and there experimenting with instantiating/destroying timers in setup() and it sometimes worked with or without next initialization.  :confounded:  Through all this the NANO remained powered.

What I eventually found is that if next is not initialized the sketch reliably crashes on restart after a power cycle, presumably on exit of setup().  If next is initialized all is well - sun comes out, grass sparkles, birds sing, etc.

I assumed a reset from the onboard switch or via programming would be the same as a power cycle but that doesn't seem to be the case.  Any ideas why this is?

p.s. I've got this on order from the library hoping for a greater understanding:

Have you posted the complete code you're talking about? Seeing as how we're 111 posts into this thread, it would probably be useful to post it all again.

Here goes:

.ino


// demo program for forum thread:
// https://forum.arduino.cc/t/renamed-iterating-through-a-series-of-objects-original-title-oop-this-pointer-questions/1244434
//
#include "Multi_timer.h"

// demonstrate three types of timers - on an Arduino NANO

OnDelayTimer onDelay_timer(1000);

OffDelayTimer offDelay_timer(1000);

FlasherTimer Flasher(1500, 1200);  // 1200ms on, 300ms off

const byte buttonA = 3;
const byte buttonB = 4;
const byte buttonC = A3;
const byte ledA_run = 6;
const byte ledA_done = 8;
const byte ledB = 7;
const byte ledFlash = 9;
const byte ledB_run = 10;


void setup() {
  Serial.begin(115200);

   OffDelayTimer offDestruct(24);
   OnDelayTimer onDestruct(17);
  /// PulseGenTimer pulseDestruct(23);

  pinMode(buttonA, INPUT_PULLUP);
  pinMode(buttonB, INPUT_PULLUP);
  pinMode(buttonC, INPUT_PULLUP);

  // All LEDs are common cathode

  pinMode(ledA_run, OUTPUT);
  pinMode(ledA_done, OUTPUT);
  pinMode(ledB, OUTPUT);
  pinMode(ledFlash, OUTPUT);
  pinMode(ledB_run, OUTPUT);
  Serial.println("started");
  
}

void loop() {

  Multi_timer::updateAllTimers();

  // demonstrate OnDelayTimer
  onDelay_timer.setEnable(digitalRead(buttonA) ? false : true);
  digitalWrite(ledA_done, onDelay_timer.isDone() ? HIGH : LOW);
  digitalWrite(ledA_run, (onDelay_timer.isRunning() ? HIGH : LOW));

  // demonstrate OffDelayTimer
  offDelay_timer.setEnable(digitalRead(buttonB) ? false : true);
  digitalWrite(ledB, (offDelay_timer.isDone() ? HIGH : LOW));
  digitalWrite(ledB_run, (offDelay_timer.isRunning() ? HIGH : LOW));

  // demonstrate flasher timer
  Flasher.setEnable(digitalRead(buttonC) ? false : true);
  digitalWrite(ledFlash, (Flasher.isFlashing() ? HIGH : LOW));

  //Serial.println("heartbeat"); // heartbeat still prints even though timers don't work


}  // end of loop()

 
.cpp

// filename: Multi_Timer.cpp  6/18/19
//
// https://forum.arduino.cc/t/renamed-iterating-through-a-series-of-objects-original-title-oop-this-pointer-questions/1244434


#include "Multi_Timer.h"
#include "Arduino.h"
//
/*
      Multi_timer is the base timer class.
      > Sets done status true upon reaching preset (false for off timer).
      > Produces a positive-going one-scan pulse 'os' upon reaching
      preset value (done status = true) and another one-scan pulse
      when done goes false.
      > responds to a reset command by setting accumulated value to
      zero and resetting _Done and _TimerRunning.
    */
//----------------------------------------------------
//

Multi_timer *Multi_timer::first = nullptr;  // Initialize static pointer

// One-argument constructor for non-FlasherTimer types - delegates
// to the two-argument constructor, removing need for object
// pointer setup loop.

Multi_timer::Multi_timer(unsigned long pre)
  : Multi_timer(pre, 0UL) {  // trailing zero value is default onTime initialization
}

/*==============================================

    Two-argument constructor for FlasherTimer type.
---------------------------------------------------
Acts as delegate for one-argument constructor
*/
Multi_timer::Multi_timer(unsigned long pre, unsigned long onTime) {
  _Preset = pre;
  _OnTime = onTime;
  _Control = false;

  /*
  Set up pointers to list of objects to enable one call
  to Update_All_Timers() rather than each timer making 
  its own call to 'update'.

  first and next are pointers to Multi_timer objects
  */
  if (first == nullptr) {
    first = this;
  } else {
    next = nullptr;  // lets timers survive power cycle intact
    //
    // Start at the first pointer and walk the list until we
    // find one that doesn't have a next

    Multi_timer *ptr = first;
    while (ptr->next != nullptr) {
      ptr = ptr->next;
    }
    ptr->next = this;
  }
}

/*==========================

   Destructor - Comments from forum post #38
   https://forum.arduino.cc/t/renamed-iterating-through-a-series-of-objects-original-title-oop-this-pointer-questions/1244434/38?
 ------------------------*/
Multi_timer::~Multi_timer() {

  // gfvalvo destructor model from post #54
  Multi_timer **ptrPtr{ &first };
  while (*ptrPtr != nullptr) {
    if (*ptrPtr == this) {
      *ptrPtr = next;
      break;
    }
    ptrPtr = &((*ptrPtr)->next);
  }


  /* original working code
  // If this is the first instance then we need to make the next one first
  // If there's no next instance then first becomes nullptr and the next
  // new instance will be first.
  if (first == this) {
    first = this->next;
  } else {
    // Start at the first instance and walk the list until we find the one
    // whose next pointer points to the one being destroyed.
    Multi_timer *ptr = first;
    while (ptr->next != this) {
      ptr = ptr->next;
    }
    // Give that instance this one's next.  If there is no next
    // (ie this is last in the list) then ptr->next becomes nullptr
    // and marks the new end of the list.    
    ptr->next = this->next;
  }*/
}
/*======================================

           Accessor methods
--------------------------------------*/

void Multi_timer::setEnable(bool en) {
  _Enable = en;
}

bool Multi_timer::isEnabled() const {
  return _Enable;
}

bool Multi_timer::isReset() const {
  return _Reset;
}

bool Multi_timer::isDone() const {
  return _Done;
}

bool Multi_timer::isRunning() const {
  return _TimerRunning;
}

void Multi_timer::setCtrl(bool ctrl) {
  _Control = ctrl;
}

unsigned long Multi_timer::getCount() const {
  return _Accumulator;
}

bool Multi_timer::getDoneRose() const {
  return _Done_OSR;
}

bool Multi_timer::getDoneFell() const {
  return _Done_OSF;
}

//========================================================

//  The 'update' function is the heart of the thing.
//  ----------------------------------------------------
//  Updates timer accumulated value & conditions flags '_Done',
// '_Done_Rising_OS', '_Done_Falling_OS' and '_TimerRunning'.
//
//  Returns boolean status of _Done.  update() must be called
//  periodically in loop to update the flags and accumulated
//  value.
//  ====================================================

bool Multi_timer::update() {  // this is a virtual function
  _CurrentMillis = millis();  // Get system clock ticks
  if (_Enable or _Control) {  // timer is enabled to run
    _Accumulator = _Accumulator + _CurrentMillis - _LastMillis;
    if (_Accumulator >= _Preset) {  // timer done?
      _Accumulator = _Preset;       // Don't let accumulator run away
      _Done = true;
    }
  }
  _LastMillis = _CurrentMillis;

  if (reset()) {  // Call virtual reset function.  Reset timer if
    //              returns true, based on derived class' criteria.
    _Done = false;
    _Accumulator = 0;
    _Control = false;  // ensures reset of latched type
  }
  /*-----
    Generate a one-scan momentary flag on _Done
    false-to-true transition
  */
  _Done_OSR = (_Done and _Done_Rising_Setup);  // timer done OS
  _Done_Rising_Setup = !_Done;

  /*----
    and another one-scan momentary flag on _Done
    true-to-false transition
  */
  _Done_OSF = (!_Done and _Done_Falling_Setup);  // timer not done OS
  _Done_Falling_Setup = _Done;

  /*
     Condition the timer running flag.
  */
  if ((_Enable or _Control) and !_Done and !_Reset) {
    _TimerRunning = true;
  } else _TimerRunning = false;

  return _Done;  // exit to caller
}  // end update function

/* ===================================================

              Update All Timers
   ---------------------------------------------------
   Loop through all timer objects and refresh them by
   successively calling update().
   ---------------------------------------------------
*/
void Multi_timer::updateAllTimers() {
  for (Multi_timer *ptr = first; ptr != nullptr; ptr = ptr->next) {
    ptr->update();
  }
}  // end of updateAllTimers

/* ================================================

            On Delay Timer class definition
 --------------------------------------------------------------------
 A timer which runs when reset is false and enable is true.
 It is reset otherwise. _Done goes true when preset is reached, 
 false otherwise.

 OnDelayTimer timer waveforms

enable      _________
       ____|         |___

                 ____
done  __________|    |___
           ^ Td ^ 

 --------------------------------------------------------------------
*/
OnDelayTimer::OnDelayTimer(unsigned long pre)
  : Multi_timer(pre){};

OnDelayTimer::~OnDelayTimer() {
  Serial.println("one on delay timer destroyed");
}  // give a destructor

// Establish reset conditions for ON delay timer
bool OnDelayTimer::reset() {
  return (_Reset or !_Enable);
}  // End of OnDelay timer

/*=======================================================

               Off Delay Timer class definition
--------------------------------------------------------------------
A timer which runs when reset is false and _Enable is false
_Done goes false preset milliseconds after _Enable goes false.
It is reset otherwise. This timer object redefines the base
class update function.

OffDelayTimer waveforms

enable  ____          ___  
            |________|
            
done    _________     ___
                 |___|
            ^ Td ^ 

-------------------------------------------------------------------*/

OffDelayTimer::OffDelayTimer(unsigned long pre)
  : Multi_timer(pre){};

OffDelayTimer::~OffDelayTimer() {
  Serial.println("one off timer destroyed");
}  // give a destructor

// update function/method is redefined here

bool OffDelayTimer::update() {
  bool enableOff = !_Enable;  // reverse sense of enable for TOF // original line
  _CurrentMillis = millis();  // Get system clock ticks
                              // Serial.println(enableOff);
  if (enableOff) {            // timer is enabled to run

    _Accumulator = _Accumulator + _CurrentMillis - _LastMillis;
    if (_Accumulator >= _Preset) {  // timer done?
      _Accumulator = _Preset;       // Don't let accumulator run away
      _Done = false;
    }
  }
  _LastMillis = _CurrentMillis;

  if (_Reset or !enableOff) {  // Find out if reset needed based on derived class' criteria.
    _Done = true;
    _Accumulator = 0;
  }
  /*
    ----- condition the timer running status
  */
  if ((enableOff) and _Done and !_Reset) {
    _TimerRunning = true;
  } else _TimerRunning = false;

  return !_Done;
}

bool OffDelayTimer::reset() {
  return (_Reset or _Enable);
}  // End of OffDelay timer

/*=================================================================

                 FlasherTimer Timer class definition
-----------------------------------------------------------------
This timer runs and resets itself automatically when enabled.
It is basically an enhanced OnDelay timer. The second constructor
argument specifies an ON time for a special output (_FlashOut)
unique to this type. If _Enable goes false the timer is reset
immediately.

 Set onTime to some fraction of pre
*/
FlasherTimer::FlasherTimer(unsigned long pre, unsigned long onTime)
  : Multi_timer(pre, onTime) {}

FlasherTimer::~FlasherTimer() {}  // give a destructor

bool FlasherTimer::reset() {
  return (!_Enable or _Done);
}

bool FlasherTimer::isFlashing() {

  // 4/2/24 : Moved _FlashOut code from update() to isFlashing()
  // so that _FlashOut only applies to FlasherTimer objects.
  //
  // Code below will turn on a common cathode LED for _OnTime
  // milliseconds when timing cycle starts.

  if (_Accumulator <= _OnTime) {
    _FlashOut = true;
  } else _FlashOut = false;

  return (_FlashOut and _Enable);
}

// 11/17/18 : Added method to runtime adjust _OnTime
// 12/18/18 : Added forced reset

void FlasherTimer::setOnTime(unsigned long newOnTime) {
  _OnTime = newOnTime;
  _Accumulator = _Preset;  // Force a reset when new onTime loaded
}
// End of FlasherTimer timer

 
.h

// filename: Multi_Timer.h  6/18/19
//
// https://forum.arduino.cc/t/renamed-iterating-through-a-series-of-objects-original-title-oop-this-pointer-questions/1244434

// Some basic timers with a (it is hoped) simple interface.

// This version, for forum discussion has several of the derived classes removed
// to remove clutter. Only onDelayTimer, offDelayTimer and FlasherTimer are retained.

#ifndef MULTI_TIMER_H
#define MULTI_TIMER_H

#include "Arduino.h"

class Multi_timer {

private:
  // Variables for setting up list of objects (timers) for use by
  // Update_All_Timers().
  //
  static Multi_timer *first;  // set location of first base class
  Multi_timer *next;          // = nullptr;

public:
  Multi_timer(unsigned long);                 // constructor declaration
  Multi_timer(unsigned long, unsigned long);  // flasher constructor
  virtual ~Multi_timer();                             // destructor declaration

  // Enable/disable a timer
  void setEnable(bool);

  // Reset or unreset a timer
  void setReset(bool);

  // Find out if a timer is enabled
  bool isEnabled() const;

  // Find out if a timer is reset
  bool isReset() const;

  // Start signal for certain timers
  void setCtrl(bool);

  // Find out if a timer has reached preset value
  bool isDone() const;

  // Find out if a timer is progressing toward preset
  bool isRunning() const;

  // Returns state of timer done rising one-shot
  bool getDoneRose() const;

  // Returns state of timer done falling one-shot
  bool getDoneFell() const;

  // Find out the current accumulator of a timer
  unsigned long getCount() const;

  unsigned long pre;


protected:
  virtual bool reset() = 0;

public:
  /* ========================================================

   The 'update' function is the heart of the thing.
   ----------------------------------------------------
   Updates timer accumulated value & conditions flags '_Done',
  '_Done_Rising_OS', '_Done_Falling_OS' and '_TimerRunning'.
  
   Returns boolean status of _Done.  update() must be called
   periodically in loop to update the flags and accumulated
   value for each timer.
   ====================================================*/

  virtual boolean update();  // Updates the timer which calls it.

  /* =============================================================
              Update All Timers
   ---------------------------------------------------------------
   Loop through all timer objects and update them by successively
   calling update(). This relieves the user of adding a call to 
   xxx.update() for every timer instance.
   ----------------------------------------------------------------*/

  static void updateAllTimers();

protected:

  bool _Reset : 1;
  bool _Enable : 1;
  bool _Done : 1;
  bool _Control : 1;
  bool _TimerRunning : 1;
  bool _Done_OSF : 1;
  bool _Done_OSR : 1;
  bool _Done_Rising_Setup : 1;
  bool _Done_Falling_Setup : 1;
  bool _FlashOut;

  unsigned long _Accumulator;
  unsigned long _CurrentMillis;
  unsigned long _LastMillis;
  unsigned long _OnTime;
  unsigned long _Preset;



};  //end of base class Multi_timer declarations

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   Define various types of timers which inherit/derive from Multi_timer.
   The timers differ in functionality mainly by their reset methods.
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
//                  On Delay Timer class
//--------------------------------------------------------------------
// A timer which runs when reset is false and enable is true
// If reset is true or enable is false it is reset.
//--------------------------------------------------------------------

class OnDelayTimer : public Multi_timer { // virtual is optional for compilation
public:
  OnDelayTimer(unsigned long);  // constructor
  ~OnDelayTimer();              // destructor

  virtual bool reset();
};  // End of class OnDelayTimer

/*===================================================================

     Off delay timer - this timer redefines update()
 
Off delay timer functions like an on delay timer but enable and done
operate inversely to an on delay timer. Timer is reset and done is
true when enable is true.  Timing cycle begins when enable goes false
and after the delay time done goes false. 
//------------------------------------------------------------------
*/

class OffDelayTimer : public Multi_timer {

public:
  OffDelayTimer(unsigned long);  //constructor
  ~OffDelayTimer();

  virtual bool update();

  virtual bool reset();
};  // End of class OffDelayTimer

/*
                 Flasher Timer class definition
-----------------------------------------------------------------
This timer runs and resets itself automatically when enabled.
It is basically an enhanced pulse generator timer. The second
constructor argument specifies an ON time for a special output
(_FlashOut) unique to this type. If _Enable goes false the
timer is reset immediately.

Flasher timer can be used as a pulse generator timer but the 
flashout and settable on time features will incur a penalty
in memory and clock cycle usage.

onTime must be some fraction of pre
--------------------------------------------------------------------*/

class FlasherTimer : public Multi_timer {
public:
  FlasherTimer(unsigned long, unsigned long);
  ~FlasherTimer();

  virtual bool reset();

  // 4/2/24 : Moved _FlashOut decision from update() to getFlash()
  // so that _FlashOut code only applies to FlasherTimer objects.
  //

  bool isFlashing();

  // 11/17/18 : Added method to runtime adjust _OnTime
  // 12/18/18 : Added forced reset when _Ontime changed

  void setOnTime(unsigned long);
};
// end of class FlasherTimer
//---------------------------------------------

#endif

1 Like

That always applies. Anyone who tells you otherwise is a fool. Initializing next is the right choice.

As early as possible is the best choice. Where you did it seems correct to me.

That is absolutely not true. SRAM cells after a power cycle behave strangely. Some will consistently go to the same value. Others will not. There is no "clear memory" circuitry on AVR processors so, I assume, quantum physics comes into play.

RESET changes nothing in SRAM. Whatever was in a cell before is what is in that cell afterwards.

Libc clears some memory (the BSS section) and initializes some (DATA section). The rest (the heap) is mostly untouched. If an object instance that was created on the heap has a constructor that does not set all fields, those not-set fields will be storing whatever happens to be in the memory cell. That would explain strange RESET and POWERUP behaviour.

And, to make all of that really crazy, AVR SRAM retains values when powered by an LED. You read that correctly. The electricity generated by an LED when it's exposed to light is enough to preserve the memory contents of an AVR processor.

All of which is why it's super important to initialize everything.

Ah. The new timer is placed at the end of the linked list. You need to always set next to nullptr. That assignment should be completely outside the if.

1 Like

You jest. But, weirdly, that beautifully describes what they're like inside of Windows kernel drivers. That is a painful never forgotten lesson!

Pointers (like all other variables) can only be initialized in one place ... the point at which they are created. If they are given a value after this point, it is an assignment, not an initialization.

Thanks for the clarification.
 

That is crazy! I have to wonder if that has ever caused some hair pulling when a test bed project that works is put into its completely dark enclosure and acts up.
 

So, thusly?

next = nullptr;  // lets timers survive power cycle intact
  if (first == nullptr) {
    first = this;
  } else {

As a bonus, putting the assignment outside the if saves six bytes flash.

Noted. Thank you.

Thanks to @Coding_Badly, @Delta_G, @gfvalvo for the replies. After a week with no response I thought the post had fallen off the back of the wagon.

1 Like

Naw. Just busy at work.

Exactly.

Excellent!

Yes. Yes it is. Unfortunately, I cannot find the blog post. If I ever do I'll post a link. It was a good read. The author did a great job troubleshooting. I recall their experience was somewhat similar to yours ... When the gadget failed it stayed failed through a POWERUP. The question became "How the heck can this thing's state be preserved through a POWERUP?" It was a status LED connected to the processor. That kept the memory intact. Power off then short circuit was one solution.