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.
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
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.
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'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.
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. 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 nextis 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.
// 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
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.
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.
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.
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.