Go Down

Topic: Virtual function operation (Read 708 times) previous topic - next topic

dougp


This question/issue is a follow-on to this thread.

Taking Paul MurrayCBR's suggestion I've been looking into virtual functions to realize multiple timer types.  I have an example which works but it's not exactly what I want.

For background, the previous incarnation of the attached code consisted of a derived class with no added functionality, that is, essentially a clone of the base PLCtimer class.  This did work just like the base class only with a different name.

Specifically, what I thought I could do is:  Make the set/get functions, the timer increment part and the setting/resetting of the done, timing, and one-shot statuses common to all timer types and break out how and when the timer resets into a virtual function (see the commented out '----- Time to reset?' section in the code)

The attached code does work but, there should be no Reset() in the main code.  Resetting the timer should be handled automatically and invisibly within the derived class.  To wit - the given timer type runs and sets done true when preset is reached based on _Reset being false and  _Enable being true.   For all other conditions the timer is reset.

I hope this makes some kind of sense.

I don't know if these are even valid questions but:

Is it possible to do this or, do I need to make a complete, unique update function (implementation?) for each timer class?

If it is possible, is there any difference in flash memory usage or execution time between the two?

Will uninstantiated versions consume any flash memory or are they optimized away at compile time?  I lean toward the latter but, I don't know.

Code: [Select]

/*
    Attempt to convert basic timer to polymorhic/inherited form
*/

#include <Bounce2.h>

#define BUTTON_PIN 8  // to enable the timer
#define LED_PIN 13
#define RESET_PIN 9

// Instantiate a Bounce object
Bounce debouncer = Bounce();

// create a class called 'PLCtimer'

class PLCtimer {

    /*
       All types:
       > produce a positive-going one-scan pulse 'os' upon reaching
       preset value.
       > respond to a reset command by setting accumulated value to
       zero and resetting done and tt.
       > set the done bit upon reaching preset.
    */

  public:
    // constructor - permits setting of variables upon instantiation

    PLCtimer::PLCtimer(unsigned long pre)
    {
      _Preset = pre;
      negativePresetWarning(pre); // preset validation
    }; // end constructor
    /*
       User-added error handler from pert @ Arduino.cc, post #13
       thread http://forum.arduino.cc/index.php?topic=559655.new#new
    */
    void negativeError( void ) __attribute__((error("Negative PLCtimer preset! Check instantiations!")));
    void negativePresetWarning(int number) {
      if (number < 0) {
        negativeError();
      }
    }
    /*
      ===== Access functions for timer status/controls
    */
    void setEN(bool en) {
      _Enable = en;
    }
    void setres(bool res) {
      _Reset = res;
    }
    byte getEN() {
      return _Enable;
    }
    bool getres() {
      return _Reset;
    }
    bool getDn() {
      return _Done;
    }
    bool getTt() {
      return _TimerRunning;
    }
    bool getIntv() {
      return _TimerRunning;
    }
    bool getOSRise() {
      return _OSRise;
    }
    bool getOSFall() {
      return _OSFall;
    }
    /*
       Virtual timer Reset function
    */
    //        virtual void Reset() ;
    unsigned long currentMillis;

    //    Function to operate timers created under PLCtimer
    //    ----------------------------------------
    //    Update timer accumulated value & condition
    //    flags 'tt' (timer timing) and 'dn' (done) based
    //    on timer type.
    //    Function returns boolean status of done, 'dn'
    //    ===========================

    boolean update() {
      currentMillis = millis();
      if (_Enable) { // timer is enabled to run
        _TimeDelta = currentMillis - _LastMillis;
        _LastMillis = currentMillis;
        _Accumulator = _Accumulator + _TimeDelta;
        if (_Accumulator >= _Preset) { // timer done?
          _Accumulator = _Preset;
          _Done = true;
        }
      }
      else {  // timer not enabled
        _LastMillis = currentMillis;
      }
      /*
        ----- Time to reset? Reset feature moved to individual derived classes
      */
      //      if (_Reset == true or (!_Enable and !_Type == RTO)
      //          or (_Type == OSGEN and _OSRise)) {
      //        _Done = false;  // show timer as not done
      //        _Accumulator = 0; // reset accumulated value to zero
      //      }
      /*
        ----- Generate a positive going one-shot pulse on timer done f-t transition
      */
      _OSRise = (_Done and _OSRSetup);
      _OSRSetup = !_Done;
      /*
        ----- and another positive going one-shot pulse on timer done t-f transition
      */
      _OSFall = (!_Done and _OSFSetup);
      _OSFSetup = _Done;
      /*
        ----- condition the timer timing bit
      */
      if (_Enable and !_Done and !_Reset) {//experiment to simplify tt logic
        _TimerRunning = true;
      }
      else _TimerRunning = false;
      /*
        if (_Reset) _TimerRunning = false;
        if (!_Enable) _TimerRunning = false;
        if (_Enable and _Done) _TimerRunning = false;
      */
      return _Done;
    }; // end of update Timer operation

  public:
    unsigned long _Preset;
    unsigned long _Accumulator = 0;
    unsigned long _LastMillis = 0;
    byte _Done: 1;
    byte _TimerRunning: 1;
    byte _OSRise: 1;
    byte _OSFall: 1;
    byte _OSRSetup: 1;
    bool _OSFSetup: 1;
    bool _Enable: 1;
    byte _Reset: 1;
    unsigned long _TimeDelta;
}; // end of class PLCtimer

class OnDelay_tmr: public PLCtimer
// successfully instantiated sans self-contained reset capability
{
  public:

    OnDelay_tmr(unsigned long pre): PLCtimer(pre)
    {}

    void Reset() {
      if (_Reset or !_Enable) {
        _Done = false;  // show timer as not done
        _Accumulator = 0; // reset accumulated value to zero
      }
      //      return _Done;
    }
};

//---------------
// Instantiate timer object(s)
//---------------
//
OnDelay_tmr TimerTwo(1500UL);

void setup() {

  // Configure pushbuttons with an internal pull-up :
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(RESET_PIN, INPUT_PULLUP);

  // After setting up the button, setup the Bounce instance :
  debouncer.attach(BUTTON_PIN);
  debouncer.interval(5); // interval in ms

  //Setup the LED :
  pinMode(LED_PIN, OUTPUT);
  //
  Serial.begin(230400); // open serial monitor comms.
}

void loop() {
  static int count;
  TimerTwo.update(); // update timer status
  TimerTwo.Reset();  // check if timer needs to be reset
  Serial.print(TimerTwo.getDn());
  if (TimerTwo.getOSRise()) {
    count++;
    Serial.println(count);
  }
  TimerTwo.setEN( digitalRead(BUTTON_PIN));
  TimerTwo.setres( !digitalRead(RESET_PIN));
  if (TimerTwo.getDn())
    digitalWrite(LED_PIN, HIGH); // tell when timer is done
  else  digitalWrite(LED_PIN, LOW); // tell when timer is not done

} //end of loop





Thanks.
Everything we call real is made of things that cannot be regarded as real.  If quantum mechanics hasn't profoundly shocked you, you haven't understood it yet. - Niels Bohr

No private consultations undertaken!

Danois90

#1
Sep 04, 2018, 08:02 pm Last Edit: Sep 04, 2018, 08:02 pm by Danois90
You could make PLCTimer::update virtual, override it in the derived class and place a call to Reset (or the entire reset code) in there.

Code: [Select]
//PLCTimer
virtual boolean update()
{
  //Code removed for simplicity
}

//OnDelay_tmr
boolean update()
{
  PLCTimer::update(); //Run parents code
  Reset(); //Or the code from Reset()
}
Instead of mocking what's wrong, teach what's right! ;)
When you get help, remember to thank the helper and give some karma!
Please, do NOT send me any private messages!!

dougp

You're a peach!  Karma++

Although what I came up with isn't quite what you proposed (no virtual functions), it does work essentially as I envisioned it.

Aside from the effect being the same, what are the differences, at the compiled level, between a virtual and a non-virtual update() function?  Is there more overhead required to manage a virtual function?  It seems there would be.

Thanks again for helping.

Code: [Select]

/*
    Attempt to convert basic timer to polymorhic/inherited form - virtual functions, too?
*/

#include <Bounce2.h>

#define BUTTON_PIN 8  // to enable the timer
#define LED_PIN 13
#define RESET_PIN 9
#define externalLED1 7

// Instantiate a Bounce object
Bounce debouncer = Bounce();

// create a class called 'PLCtimer'

class PLCtimer {

    /*
       All types:
       > produce a positive-going one-scan pulse 'os' upon reaching
       preset value.
       > respond to a reset command by setting accumulated value to
       zero and resetting done and tt.
       > set the done bit upon reaching preset.
    */

  public:
    // constructor - permits setting of variables upon instantiation

    PLCtimer::PLCtimer(unsigned long pre)
    {
      _Preset = pre;
      negativePresetWarning(pre); // preset validation
    }; // end constructor
    /*
       User-added error handler from pert @ Arduino.cc, post #13
       thread http://forum.arduino.cc/index.php?topic=559655.new#new
    */
    void negativeError( void ) __attribute__((error("Negative PLCtimer preset! Check instantiations!")));
    void negativePresetWarning(int number) {
      if (number < 0) {
        negativeError();
      }
    }
    /*
      ===== Access functions for timer status/controls
    */
    void setEN(bool en) {
      _Enable = en;
    }
    void setres(bool res) {
      _Reset = res;
    }
    byte getEN() {
      return _Enable;
    }
    bool getres() {
      return _Reset;
    }
    bool getDn() {
      return _Done;
    }
    bool getTt() {
      return _TimerRunning;
    }
    bool getIntv() {
      return _TimerRunning;
    }
    bool getOSRise() {
      return _OSRise;
    }
    bool getOSFall() {
      return _OSFall;
    }
    /*
       Virtual timer Reset function
    */
    //        virtual void Reset() ;
    unsigned long currentMillis;

    //    Function to operate timers created under PLCtimer
    //    ----------------------------------------
    //    Update timer accumulated value & condition
    //    flags 'tt' (timer timing) and 'dn' (done) based
    //    on timer type.
    //    Function returns boolean status of done, 'dn'
    //    ===========================

    boolean update() {
      currentMillis = millis();
      if (_Enable) { // timer is enabled to run
        _TimeDelta = currentMillis - _LastMillis;
        _LastMillis = currentMillis;
        _Accumulator = _Accumulator + _TimeDelta;
        if (_Accumulator >= _Preset) { // timer done?
          _Accumulator = _Preset;
          _Done = true;
        }
      }
      else {  // timer not enabled
        _LastMillis = currentMillis;
      }
      /*
        ----- Generate a positive going one-shot pulse on timer done f-t transition
      */
      _OSRise = (_Done and _OSRSetup);
      _OSRSetup = !_Done;
      /*
        ----- and another positive going one-shot pulse on timer done t-f transition
      */
      _OSFall = (!_Done and _OSFSetup);
      _OSFSetup = _Done;
      /*
        ----- condition the timer timing status
      */
      if (_Enable and !_Done and !_Reset) {//experiment to simplify tt logic
        _TimerRunning = true;
      }
      else _TimerRunning = false;
      return _Done;
    }; // end of update Timer operation

  public:
    unsigned long _Preset;
    unsigned long _Accumulator = 0;
    unsigned long _LastMillis = 0;
    byte _Done: 1;
    byte _TimerRunning: 1;
    byte _OSRise: 1;
    byte _OSFall: 1;
    byte _OSRSetup: 1;
    bool _OSFSetup: 1;
    bool _Enable: 1;
    byte _Reset: 1;
    unsigned long _TimeDelta;
}; // end of class PLCtimer

/*  Define various types of timers derived from PLCtimer.  The timers
    differ in functionality only in the reset methods
*/

class OnDelay_tmr: public PLCtimer
// A standard timer which runs when reset is false and enable is true
// It is reset otherwise
{
  public:
    OnDelay_tmr(unsigned long pre): PLCtimer(pre)
    {}
    boolean update() {
      PLCtimer::update();
      // Test for reset conditions
      if (_Reset or !_Enable) {
        _Done = false;  // show timer as not done
        _Accumulator = 0; // reset accumulated value to zero
      }
    }
}; // End of OnDelay_tmr

class Retentive_tmr: public PLCtimer
// A timer which accumulates when enabled and reset is false.
// Accumulated value is reset only by making the reset input true
{
  public:
    Retentive_tmr(unsigned long pre): PLCtimer(pre)
    {}
    boolean update() {
      PLCtimer::update();
      // Test for reset conditions
      if (_Reset) {
        _Done = false;
        _Accumulator = 0;
      }
    }
}; // End of Retentive_tmr

class PulseGen_tmr: public PLCtimer
/* A timer which runs when enabled and not reset
  Resets itself upon reaching preset then restarts
  the timing cycle automatically as long as still enabled
*/
{
  public:
    PulseGen_tmr(unsigned long pre): PLCtimer(pre)
    {}
    boolean update() {
      PLCtimer::update();
      // Test for reset conditions
      if (_Done) {
        _Done = false;
        _Accumulator = 0;
      }
    }
}; //End of PulseGen_tmr

//---------------
// Instantiate timer object(s)
//---------------
//
OnDelay_tmr TimerTwo(1500UL);
Retentive_tmr TimerThree(3000UL);
PulseGen_tmr TimerFour(2000UL);

void setup() {

  // Configure pushbuttons with an internal pull-up :
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(RESET_PIN, INPUT_PULLUP);

  // After setting up the button, setup the Bounce instance :
  debouncer.attach(BUTTON_PIN);
  debouncer.interval(5); // interval in ms

  //Setup the LEDs :
  pinMode(LED_PIN, OUTPUT);
  pinMode(externalLED1, OUTPUT);
  //
  Serial.begin(230400); // open serial monitor comms.
}

void loop() {
  static int count;
  
  TimerFour.setEN( digitalRead(BUTTON_PIN));
  TimerFour.update();
  
  if (TimerFour.getOSRise()) {
    count++;
    Serial.println(count);
  }
  TimerThree.update();
  TimerThree.setEN(digitalRead(BUTTON_PIN));
  TimerThree.setres(!digitalRead(RESET_PIN));
  if (TimerThree.getDn())
    digitalWrite(externalLED1, HIGH);
  else digitalWrite(externalLED1, LOW);
} //end of loop





Everything we call real is made of things that cannot be regarded as real.  If quantum mechanics hasn't profoundly shocked you, you haven't understood it yet. - Niels Bohr

No private consultations undertaken!

Danois90

Virtual methods reqires a VMT (Virtual Method Table) which costs memory and some clock cycles to look up the right method to invoke. Your approach of "redeclaring" parent method will fail if a descendant is cast to, or stored as, it's parent class. Take this perfectly valid scenario:

Code: [Select]
#define NUM_TIMERS 3

PLCTimer *timers[NUM_TIMERS];
timers[0] = new OnDelay_tmr(1500UL);
timers[1] = new Retentive_tmr(3000UL);
timers[2] = new PulseGen_tmr(2000UL);
...
for (byte i = 0; i < NUM_TIMERS; i++) timers[i]->update();


The snippet above would with your code have executed "PLCTimer::update()" three times, completely ignoring the re-declaration. Had the methods been declared as virtual, all the right methods (OnDelay_tmr::update, Retentive_tmr::update and PulseGen_tmr::update) would have been invoked instead.
Instead of mocking what's wrong, teach what's right! ;)
When you get help, remember to thank the helper and give some karma!
Please, do NOT send me any private messages!!

dougp

You could make PLCTimer::update virtual, override it in the derived class and place a call to Reset (or the entire reset code) in there.
Looking at this again it's more confusing.  Since PLCtimer::update will be common to all derived from it I don't understand the advantage of making it virtual.  I would think, in my ignorance, that Reset, being unique to each derived type,  would be the one to be made virtual.

I modified the code by: eliminating all but one child class; added nnn:: to the methods (since the book I'm referring to - Problem Solving with C++, Savitch illustrates this) (this change made no difference I could discern); made PLCtimer::Reset virtual.

The result is the timer runs and turns on the LED but I cannot reset it.  I know the enable input is working because I can prevent timeout by keeping the button pressed (enable false).

Latest version:

Code: [Select]

/*
    Attempt to convert basic timer to polymorhic/inherited form - virtual functions, too?

  reference thread at http://forum.arduino.cc/index.php?topic=567010.new#new
*/

#include <Bounce2.h>

#define BUTTON_PIN 8  // to enable the timer
#define LED_PIN 13
#define RESET_PIN 9
#define externalLED1 7

// Instantiate a Bounce object
Bounce debouncer = Bounce();

// create a class called 'PLCtimer'

class PLCtimer {

    /*
       All types:
       > produce a positive-going one-scan pulse 'os' upon reaching
       preset value.
       > respond to a reset command by setting accumulated value to
       zero and resetting done and tt.
       > set the done bit upon reaching preset.
    */

  public:
    // constructor - permits setting of variables upon instantiation

    PLCtimer::PLCtimer(unsigned long pre)
    {
      _Preset = pre;
      negativePresetWarning(pre); // preset validation
    }; // end constructor
    /*
       User-added error handler from pert @ Arduino.cc, post #13
       thread http://forum.arduino.cc/index.php?topic=559655.new#new
    */
    void negativeError( void ) __attribute__((error("Negative PLCtimer preset! Check instantiations!")));
    void negativePresetWarning(int number) {
      if (number < 0) {
        negativeError();
      }
    }
    /*
      ===== Access functions for timer status/controls
    */
    void PLCtimer::setEN(bool en) {
      _Enable = en;
    }
    void PLCtimer::setres(bool res) {
      _Reset = res;
    }
    byte PLCtimer::getEN() {
      return _Enable;
    }
    bool PLCtimer::getres() {
      return _Reset;
    }
    bool PLCtimer::getDn() {
      return _Done;
    }
    bool PLCtimer::getTt() {
      return _TimerRunning;
    }
    bool PLCtimer::getIntv() {
      return _TimerRunning;
    }
    bool PLCtimer::getOSRise() {
      return _OSRise;
    }
    bool PLCtimer::getOSFall() {
      return _OSFall;
    }
    /*
       Virtual timer Reset function
    */
    virtual void PLCtimer::Reset() ;
   
    unsigned long currentMillis;

    //    Function to operate timers created under PLCtimer
    //    ----------------------------------------
    //    Update timer accumulated value & condition
    //    flags 'tt' (timer timing) and 'dn' (done) based
    //    on timer type.
    //    Function returns boolean status of done, 'dn'
    //    ===========================

    boolean PLCtimer::update() {
      currentMillis = millis();
      if (_Enable) { // timer is enabled to run
        _TimeDelta = currentMillis - _LastMillis;
        _LastMillis = currentMillis;
        _Accumulator = _Accumulator + _TimeDelta;
        if (_Accumulator >= _Preset) { // timer done?
          _Accumulator = _Preset;
          _Done = true;
        }
      }
      else {  // timer not enabled
        _LastMillis = currentMillis;
      }
      /*
        ----- Generate a positive going one-shot pulse on timer done f-t transition
      */
      _OSRise = (_Done and _OSRSetup);
      _OSRSetup = !_Done;
      /*
        ----- and another positive going one-shot pulse on timer done t-f transition
      */
      _OSFall = (!_Done and _OSFSetup);
      _OSFSetup = _Done;
      /*
        ----- condition the timer timing status
      */
      if (_Enable and !_Done and !_Reset) {//experiment to simplify tt logic
        _TimerRunning = true;
      }
      else _TimerRunning = false;
      return _Done;
    }; // end of update Timer operation

  public:
    unsigned long _Preset;
    unsigned long _Accumulator = 0;
    unsigned long _LastMillis = 0;
    byte _Done: 1;
    byte _TimerRunning: 1;
    byte _OSRise: 1;
    byte _OSFall: 1;
    byte _OSRSetup: 1;
    bool _OSFSetup: 1;
    bool _Enable: 1;
    byte _Reset: 1;
    unsigned long _TimeDelta;
}; // end of class PLCtimer

/*  Define various types of timers derived from PLCtimer.  The timers
    differ in functionality only in the reset methods
    // timers reduced to one to get inheritance worked out
*/

class OnDelay_tmr: public PLCtimer
// A standard timer which runs when reset is false and enable is true
// It is reset otherwise
{
  public:
    OnDelay_tmr::OnDelay_tmr(unsigned long pre): PLCtimer(pre)
    {}
    // Test for reset conditions
     void OnDelay_tmr::Reset() {
      if (_Reset or !_Enable) {
        _Done = false;  // show timer as not done
        _Accumulator = 0; // reset accumulated value to zero
      }
    }
}; // End of OnDelay_tmr

//---------------
// Instantiate timer object(s)
//---------------
//
OnDelay_tmr TimerThree(1500UL);

void setup() {

  // Configure pushbuttons with an internal pull-up :
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(RESET_PIN, INPUT_PULLUP);

  // After setting up the button, setup the Bounce instance :
  debouncer.attach(BUTTON_PIN);
  debouncer.interval(5); // interval in ms

  //Setup the LEDs :
  pinMode(LED_PIN, OUTPUT);
  pinMode(externalLED1, OUTPUT);
  //
  Serial.begin(230400); // open serial monitor comms.
}

void loop() {
  static int count;
  TimerThree.update();
  TimerThree.setEN(digitalRead(BUTTON_PIN));
  TimerThree.setres(!digitalRead(RESET_PIN));
  if (TimerThree.getDn())
    digitalWrite(externalLED1, HIGH);
  else digitalWrite(externalLED1, LOW);
} //end of loop





Hoping you can help.

Everything we call real is made of things that cannot be regarded as real.  If quantum mechanics hasn't profoundly shocked you, you haven't understood it yet. - Niels Bohr

No private consultations undertaken!

Danois90

#5
Sep 08, 2018, 12:42 pm Last Edit: Sep 08, 2018, 02:03 pm by Danois90
I cannot see where Reset() is invoked, so that is probably why it does not work, or am I missing something?

If you are declaring a virtual abstract method (a method where the body must be declared in any descendant), this is how to do it:

Code: [Select]
virtual void Reset() = 0;

Normally you would not add classname:: to class fields in the class specification. You only do this when you are separating the specification and the implementation in a header and a source file, like this:

Code: [Select]
//SomeClass.h
class SomeClass {
  public:
    SomeClass();
    void Hello();
};

//SomeClass.cpp
#include "SomeClass.h"

SomeClass::SomeClass() {
  //Constructor code
}

void SomeClass::Hello() {
  //Hello code
}
Instead of mocking what's wrong, teach what's right! ;)
When you get help, remember to thank the helper and give some karma!
Please, do NOT send me any private messages!!

dougp

#6
Sep 08, 2018, 09:13 pm Last Edit: Sep 08, 2018, 09:14 pm by dougp Reason: included code
I cannot see where Reset() is invoked, so that is probably why it does not work, or am I missing something?
A glimmer of light appears.  I see I was mistaking the declaration for the invocation.  ::)   Reset is now a pure virtual function called from loop().  TimerThree works, as well as the renewed TimerTwo.

However, having to explicitly test for a reset is, in my view, clunky.  It detracts from the 'object' character of the thing.

Is there a way to invoke Reset, or something which performs the actions of Reset, from within an instance and thus insulate the caller from this detail?

Thanks for your patience.

Code: [Select]

/*
    Attempt to convert basic timer to polymorhic/inherited form - virtual functions, too?

  reference thread at http://forum.arduino.cc/index.php?topic=567010.new#new
*/

#include <Bounce2.h>

#define BUTTON_PIN 8  // to enable the timer
#define LED_PIN 13
#define RESET_PIN 9
#define externalLED1 7

// Instantiate a Bounce object
Bounce debouncer = Bounce();

// create a class called 'PLCtimer'

class PLCtimer {

    /*
       All types:
       > produce a positive-going one-scan pulse 'os' upon reaching
       preset value.
       > respond to a reset command by setting accumulated value to
       zero and resetting done and tt.
       > set the done bit upon reaching preset.
    */

  public:
    // constructor - permits setting of variables upon instantiation

    PLCtimer::PLCtimer(unsigned long pre)
    {
      _Preset = pre;
      negativePresetWarning(pre); // preset validation
    }; // end constructor
    /*
       User-added error handler from pert @ Arduino.cc, post #13
       thread http://forum.arduino.cc/index.php?topic=559655.new#new
    */
    void negativeError( void ) __attribute__((error("Negative PLCtimer preset! Check instantiations!")));
    void negativePresetWarning(int number) {
      if (number < 0) {
        negativeError();
      }
    }
    /*
      ===== Access functions for timer status/controls
    */
    void setEN(bool en) {
      _Enable = en;
    }
    void setres(bool res) {
      _Reset = res;
    }
    byte getEN() {
      return _Enable;
    }
    bool getres() {
      return _Reset;
    }
    bool getDn() {
      return _Done;
    }
    bool getTt() {
      return _TimerRunning;
    }
    bool getIntv() {
      return _TimerRunning;
    }
    bool getOSRise() {
      return _OSRise;
    }
    bool getOSFall() {
      return _OSFall;
    }
    /*
       Virtual timer Reset function
    */
    //    virtual void PLCtimer::Reset() ;

    virtual void Reset() = 0;

    unsigned long currentMillis;

    //    Function to operate timers created under PLCtimer
    //    ----------------------------------------
    //    Update timer accumulated value & condition
    //    flags 'tt' (timer timing) and 'dn' (done) based
    //    on timer type.
    //    Function returns boolean status of done, 'dn'
    //    ===========================

    boolean PLCtimer::update() {
      currentMillis = millis();
      if (_Enable) { // timer is enabled to run
        _TimeDelta = currentMillis - _LastMillis;
        _LastMillis = currentMillis;
        _Accumulator = _Accumulator + _TimeDelta;
        if (_Accumulator >= _Preset) { // timer done?
          _Accumulator = _Preset;
          _Done = true;
        }
      }
      else {  // timer not enabled
        _LastMillis = currentMillis;
      }

      /*
        ----- Generate a positive going one-shot pulse on timer done f-t transition
      */
      _OSRise = (_Done and _OSRSetup);
      _OSRSetup = !_Done;
      /*
        ----- and another positive going one-shot pulse on timer done t-f transition
      */
      _OSFall = (!_Done and _OSFSetup);
      _OSFSetup = _Done;
      /*
        ----- condition the timer timing status
      */
      if (_Enable and !_Done and !_Reset) {//experiment to simplify tt logic
        _TimerRunning = true;
      }
      else _TimerRunning = false;
      return _Done;
    }; // end of update Timer operation

  public:
    unsigned long _Preset;
    unsigned long _Accumulator = 0;
    unsigned long _LastMillis = 0;
    byte _Done: 1;
    byte _TimerRunning: 1;
    byte _OSRise: 1;
    byte _OSFall: 1;
    byte _OSRSetup: 1;
    bool _OSFSetup: 1;
    bool _Enable: 1;
    byte _Reset: 1;
    unsigned long _TimeDelta;
}; // end of class PLCtimer

/*  Define various types of timers derived from PLCtimer.  The timers
    differ in functionality only in the reset methods
    // timers reduced to one to get inheritance worked out
*/

class OnDelay_tmr: public PLCtimer
// A standard timer which runs when reset is false and enable is true
// It is reset otherwise
{
  public:
    OnDelay_tmr(unsigned long pre): PLCtimer(pre)
    {}
    // Test for reset conditions
   virtual void Reset() {
      if (_Reset or !_Enable) {
        _Done = false;  // show timer as not done
        _Accumulator = 0; // reset accumulated value to zero
      }
    }
}; // End of OnDelay_tmr

//---------------
// Instantiate timer object(s)
//---------------
//
OnDelay_tmr TimerTwo(2500UL);
OnDelay_tmr TimerThree(1500UL);

void setup() {

  // Configure pushbuttons with an internal pull-up :
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(RESET_PIN, INPUT_PULLUP);

  // After setting up the button, setup the Bounce instance :
  debouncer.attach(BUTTON_PIN);
  debouncer.interval(5); // interval in ms

  //Setup the LEDs :
  pinMode(LED_PIN, OUTPUT);
  pinMode(externalLED1, OUTPUT);
  //
  Serial.begin(230400); // open serial monitor comms.
}

void loop() {
  static int count;
  TimerThree.update();
  TimerThree.Reset();
  TimerThree.setEN(digitalRead(BUTTON_PIN));
  TimerThree.setres(!digitalRead(RESET_PIN));
  if (TimerThree.getDn())
    digitalWrite(externalLED1, HIGH);
  else digitalWrite(externalLED1, LOW);
 
  TimerTwo.update();
  TimerTwo.Reset();
  TimerTwo.setEN(digitalRead(BUTTON_PIN));
  TimerTwo.setres(!digitalRead(RESET_PIN));
  if (TimerTwo.getDn())
    digitalWrite(LED_PIN, HIGH);
  else digitalWrite(LED_PIN, LOW);
} //end of loop



Everything we call real is made of things that cannot be regarded as real.  If quantum mechanics hasn't profoundly shocked you, you haven't understood it yet. - Niels Bohr

No private consultations undertaken!

Danois90

The obvious sollution would be to call "Reset()" in "PLCTimer::update()". Since "Reset()" is virtual this would cause the reset method of the respective descendants to be invoked.
Instead of mocking what's wrong, teach what's right! ;)
When you get help, remember to thank the helper and give some karma!
Please, do NOT send me any private messages!!

dougp

Got it going.  Thanks for the help!

Working version:

Code: [Select]

/*
    Basic timer converted to polymorphic/inherited form.
    Utilizes virtual function to effect timer reset.

    reference Arduino.cc thread http://forum.arduino.cc/index.php?topic=567010.new#new
*/

#include <Bounce2.h>

#define BUTTON_PIN 6   // to enable the timer
#define LED_PIN 13     // on board LED
#define RESET_PIN 8    // to reset a timer
#define externalLED1 7 //  +5--/\/\/-->|--GND

// Instantiate a Bounce object
Bounce debouncer = Bounce();

// create a class called 'PLCtimer'

class PLCtimer {

    /*
       All types:
       > produce a positive-going one-scan pulse 'os' upon reaching
       preset value.
       > respond to a reset command by setting accumulated value to
       zero and resetting done and tt.
       > set the done bit upon reaching preset.
    */

  public:
    // constructor - permits setting of variables upon instantiation

    PLCtimer::PLCtimer(unsigned long pre)
    {
      _Preset = pre;
      negativePresetWarning(pre); // preset validation
    }; // end constructor
    /*
       User-added error handler from pert @ Arduino.cc, post #13
       thread http://forum.arduino.cc/index.php?topic=559655.new#new
       Timers may not have a negative preset value.
    */
    void negativeError( void ) __attribute__((error("Negative PLCtimer preset! Check instantiations!")));
    void negativePresetWarning(int number) {
      if (number < 0) {
        negativeError();
      }
    }
    /*
      ===== Access functions for timer status/controls
    */
    void setEN(bool en) {
      _Enable = en;
    }
    void setres(bool res) {
      _Reset = res;
    }
    byte getEN() {
      return _Enable;
    }
    bool getres() {
      return _Reset;
    }
    bool getDn() {
      return _Done;
    }
    bool getTt() {
      return _TimerRunning;
    }
    bool getIntv() {
      return _TimerRunning;
    }
    bool getOSRise() {
      return _OSRise;
    }
    bool getOSFall() {
      return _OSFall;
    }
    /*
       Virtual timer Reset function
    */
    virtual void Reset(); // Reset conditions to be determined by caller

    unsigned long currentMillis;

    //    Function to operate timers created under PLCtimer
    //    ----------------------------------------
    //    Update timer accumulated value & condition
    //    flags 'tt' (timer timing) and 'dn' (done) based
    //    on timer type.
    //    Function returns boolean status of done, 'dn'
    //    ===========================

    boolean update() {
      currentMillis = millis();
      if (_Enable) { // timer is enabled to run
        _TimeDelta = currentMillis - _LastMillis;
        _LastMillis = currentMillis;
        _Accumulator = _Accumulator + _TimeDelta;
        if (_Accumulator >= _Preset) { // timer done?
          _Accumulator = _Preset;
          _Done = true;
        }
      }
      else {  // timer not enabled
        _LastMillis = currentMillis;
      }
      Reset();  // Call virtual function to reset timer based
      //           on derived class' criteria.
      /*
        ----- Generate a positive going one-shot pulse on timer done f-t transition
      */
      _OSRise = (_Done and _OSRSetup);
      _OSRSetup = !_Done;
      /*
        ----- and another positive going one-shot pulse on timer done t-f transition
      */
      _OSFall = (!_Done and _OSFSetup);
      _OSFSetup = _Done;
      /*
        ----- condition the timer timing status
      */
      if (_Enable and !_Done and !_Reset) {
        _TimerRunning = true;
      }
      else _TimerRunning = false;
      return _Done;
    }; // end of base class update Timer operation

  public:
    unsigned long _Preset;
    unsigned long _Accumulator = 0;
    unsigned long _LastMillis = 0;
    byte _Done: 1;
    byte _TimerRunning: 1;
    byte _OSRise: 1;
    byte _OSFall: 1;
    byte _OSRSetup: 1;
    bool _OSFSetup: 1;
    bool _Enable: 1;
    byte _Reset: 1;
    unsigned long _TimeDelta;
}; // end of class PLCtimer

/*  Define various types of timers derived from PLCtimer.  The timers
    differ in functionality only in the reset methods
*/
//                  On Delay Timer class definition
//--------------------------------------------------------------------
// A standard timer which runs when reset is false and enable is true
// It is reset otherwise
//--------------------------------------------------------------------
class OnDelay_tmr: public PLCtimer
{
  public:
    OnDelay_tmr(unsigned long pre): PLCtimer(pre)
    {}
    // Establish reset conditions for ON delay timer
    virtual void Reset() {
      if (_Reset or !_Enable) {
        _Done = false;  // show timer as not done
        _Accumulator = 0; // reset accumulated value to zero
      }
    };
}; // End of OnDelay_tmr

//                  Retentive timer class definition
//--------------------------------------------------------------
// A timer which accumulates when enabled and reset is false.
// Accumulated value is retained when enable is false.  This
// timer type is reset only by making the reset input true.
//--------------------------------------------------------------
class Retentive_tmr: public PLCtimer
{
  public:
    Retentive_tmr(unsigned long pre): PLCtimer(pre)
    {}
    // Establish reset conditions for retentive ON delay timer
    virtual void Reset() {
      if (_Reset) {
        _Done = false;
        _Accumulator = 0;
      }
    }
};
// End of Retentive_tmr

/*
  class PulseGen_tmr: public PLCtimer

  // A timer which runs when enabled and not reset
  //  Resets itself upon reaching preset then restarts
  //  the timing cycle automatically as long as still enabled
  //
  {
  public:
    PulseGen_tmr(unsigned long pre): PLCtimer(pre)
    {}
     // Establish reset conditions for pulse generator timer
    virtual void Reset() {
      if (_Reset or _Done) {
        _Done = false;
        _Accumulator = 0;
      }
    }
  }; //End of PulseGen_tmr
*/
//---------------
// Instantiate timer object(s)
//---------------
//
Retentive_tmr TimerThree(2500UL);
//OnDelay_tmr TimerTwo(3000UL);
//PulseGen_tmr TimerFour(2000UL);

void setup() {

  // Configure pushbuttons with an internal pull-up :
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(RESET_PIN, INPUT_PULLUP);

  // After setting up the button, setup the Bounce instance :
  debouncer.attach(BUTTON_PIN);
  debouncer.interval(5); // interval in ms

  //Setup the LEDs :
  pinMode(LED_PIN, OUTPUT);
  pinMode(externalLED1, OUTPUT);
  //
  //  Serial.begin(230400); // open serial monitor comms.
}

void loop() {
  static int count;

  TimerThree.update();
  TimerThree.setEN(digitalRead(BUTTON_PIN));
  TimerThree.setres(!digitalRead(RESET_PIN));
  if (TimerThree.getDn())
    digitalWrite(externalLED1, LOW);
  else digitalWrite(externalLED1, HIGH);
} //end of loop



Everything we call real is made of things that cannot be regarded as real.  If quantum mechanics hasn't profoundly shocked you, you haven't understood it yet. - Niels Bohr

No private consultations undertaken!

dougp

A new problem has arisen.  I built five timer types and got them working.  Tonight I tried multiple instantiations and it fails with a strange error, namely

In member function 'negativePresetWarning.constprop',

    inlined from '__base_ctor ' at C:\Users\Owner\Documents\Arduino\PLCtimer_polymorphic\PLCtimer_polymorphic.ino:38:33:

PLCtimer_polymorphic:48: error: call to 'negativeError' declared with attribute error: Negative PLCtimer preset! Check instantiations!

         negativeError();

                        ^

lto-wrapper: C:\Program Files (x86)\Arduino\hardware\tools\avr/bin/avr-gcc returned 1 exit status

c:/program files (x86)/arduino/hardware/tools/avr/bin/../lib/gcc/avr/4.9.2/../../../../avr/bin/ld.exe: error: lto-wrapper failed

collect2.exe: error: ld returned 1 exit status

Using library Bounce2-master at version 2.21 in folder: C:\Users\Owner\Documents\Arduino\libraries\Bounce2-master
exit status 1
call to 'negativeError' declared with attribute error: Negative PLCtimer preset! Check instantiations!

With the full code (five derived timer classes) I get this error whenever there's more than one instantiation of any type.  Only the watchdog type is shown to get under the forum's 9000 chr limit.  The weird part is, there is no negative preset!  Additionally, with this scaled down version I'm allowed two instantiations before the error is given.  The sketch below compiles correctly.  However, if the line //WatchDog_tmr WDT03(750UL); is uncommented the error appears.

Code: [Select]

/*
    Basic timer converted to polymorphic/inherited form.
    Utilizes virtual function to effect timer reset.

    reference Arduino.cc thread http://forum.arduino.cc/index.php?topic=567010.new#new
*/

#include <Bounce2.h>

#define BUTTON_PIN 6   // to enable the timer DIO6
#define RESET_PIN 8    // to reset a timer DIO8
#define externalLED1 7 //  +5--/\/\/-->|--DIO7

byte LED_PIN = 13;    // on board LED

// Instantiate a Bounce object
Bounce debouncer = Bounce();

// create a class called 'PLCtimer'

class PLCtimer {
    /*
       This is the base class - All types:
       > produce a positive-going one-scan pulse 'os' upon reaching
       preset value.
       > respond to a reset command by setting accumulated value to
       zero and resetting done and tt.
       > set the done bit upon reaching preset.
    */

  public:
    // constructor - permits setting of variables upon instantiation
    // Timers are instantiated with enable false by default

    PLCtimer::PLCtimer(unsigned long pre, boolean en = 0)
    {
      _Preset = pre;
      negativePresetWarning(pre); // preset validation
    }; // end constructor
    /*
       User-added error handler from pert @ Arduino.cc, post #13
       thread http://forum.arduino.cc/index.php?topic=559655.new#new
       Timers may not have a negative preset value.
    */
    void negativeError( void ) __attribute__((error("Negative PLCtimer preset! Check instantiations!")));
    void negativePresetWarning(int number) {
      if (number < 0) {
        negativeError();
      }
    }
    /*
      ===== Access functions for timer status/controls
    */
    // Allows to start/stop a timer
    void setEN(bool en) {
      _Enable = en;
    }
    // Allows to reset a timer
    void setres(bool res) {
      _Reset = res;
    }
    // Returns enable state of timer
    byte getEN() {
      return _Enable;
    }
    // Returns reset state of timer
    bool getres() {
      return _Reset;
    }
    // Returns done status of timer
    bool getDn() {
      return _Done;
    }
    // Returns timer timing state of timer
    bool getTt() {
      return _TimerRunning;
    }
    // Returns timer timing state of timer
    bool getIntv() {
      return _TimerRunning;
    }
    // Returns state of timer done rising one-shot
    bool getOSRise() {
      return _OSRise;
    }
    // Returns state of timer done falling one-shot
    bool getOSFall() {
      return _OSFall;
    }
  private:
    /*
       Virtual timer Reset function
       Reset conditions to be determined by descendants
    */
    virtual void Reset();

  public:

    //    Function to operate timers created under PLCtimer
    //    ----------------------------------------
    //    Update timer accumulated value & condition
    //    flags 'tt' (timer timing) and 'dn' (done) based
    //    on timer type.
    //    Function returns boolean status of done, 'dn'
    //    ===========================

    boolean 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;

      Reset();  // Call virtual function to reset timer based
      //           on derived class' criteria.
      /*
        ----- Generate a positive going one-shot pulse on timer done f-t transition
      */
      _OSRise = (_Done and _OSRSetup);
      _OSRSetup = !_Done;
      /*
        ----- and another positive going one-shot pulse on timer done t-f transition
      */
      _OSFall = (!_Done and _OSFSetup);
      _OSFSetup = _Done;
      /*
        ----- condition the timer timing status
      */
      if ((_Enable or _Control) and !_Done and !_Reset) {
        _TimerRunning = true;
      }
      else _TimerRunning = false;
      return _Done;
    }; // end of base class update Timer operation

  public:
    unsigned long _Accumulator = 0;
    byte _Reset: 1;
    bool _Enable: 1;
    byte _Done: 1;
    byte _OSRise: 1;
    bool _Control: 1;

  private:
    unsigned long _Preset;
    unsigned long _CurrentMillis;
    unsigned long _LastMillis = 0;
    byte _TimerRunning: 1;
    byte _OSFall: 1;
    byte _OSRSetup: 1;
    bool _OSFSetup: 1;

}; // end of class PLCtimer

/*  Define various types of timers derived from PLCtimer.  The timers
    differ in functionality in the reset methods.
*/
//                    Watchdog timer
//---------------------------------------------------------------
// Runs when enable is true. A change of state on the 'control'
// input applies a momentary reset to the timer and restarts the
// timing cycle. Continuous cycling of the control input at a rate
// faster than the time delay will cause the done status flag to
// remain low indefinitely.
//----------------------------------------------------------------

class WatchDog_tmr : public PLCtimer
{
  public:
    WatchDog_tmr(unsigned long pre): PLCtimer(pre)
    {}
    virtual void Reset() {
      /*
        Generate a positive going one-shot pulse whenever control
        input undergoes a state change.
      */
      _WD_OSRise = (_Control and _WD_OSRSetup);
      _WD_OSRSetup = !_Control;
      _WD_OSFall = (!_Control and _WD_OSFSetup);
      _WD_OSFSetup = _Control;

      if (_WD_OSFall or _WD_OSRise) { // enable did a transition
        _Done = false;
        _Accumulator = 0;
      }
    }
  private:

    bool _WD_OSRise: 1;
    bool _WD_OSFall: 1;
    bool _WD_OSFSetup: 1;
    bool _WD_OSRSetup: 1;
}; // end of class watchdog timer

//---------------
// Instantiate timer object(s) - All timers inherit from PLCtimer
//---------------
//

WatchDog_tmr WDT01(900UL);
WatchDog_tmr WDT02(1900UL);
//WatchDog_tmr WDT03(750UL);

void setup() {

  // Configure pushbuttons with an internal pull-up :
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(RESET_PIN, INPUT_PULLUP);

  // After setting up the button, setup the Bounce instance :
  debouncer.attach(BUTTON_PIN);
  debouncer.interval(5); // interval in ms

  //Setup the LEDs :
  pinMode(LED_PIN, OUTPUT);
  pinMode(externalLED1, OUTPUT);

  int counter;
  //
  //  Serial.begin(230400); // open serial monitor comms.
  WDT01.setEN(HIGH);  // enable watchdog timer to run
  WDT02.setEN(HIGH);  // enable watchdog timer to run
}

void loop() {
  static int count;
 
  //============= Watchdog timer test

  //   Enable set high in setup()

  WDT01.update();
  WDT01._Control = (digitalRead(BUTTON_PIN)); // activity on control input resets timer
  if (WDT01.getDn()) {
    digitalWrite(externalLED1, LOW);
  }
  else digitalWrite(externalLED1, HIGH);

/*
  //============= Watchdog timer no. 2 test

  //   Enable set high in setup()

  WDT02.update();
  WDT02._Control = (digitalRead(BUTTON_PIN)); // activity on control input resets timer
  if (WDT02.getDn()) {
    digitalWrite(externalLED1, LOW);
  }
  else digitalWrite(externalLED1, HIGH);
*/

} //end of loop



Any clues as to why this is happening?
Everything we call real is made of things that cannot be regarded as real.  If quantum mechanics hasn't profoundly shocked you, you haven't understood it yet. - Niels Bohr

No private consultations undertaken!

RayLivingston

Your constructor gets argument pre of type unsigned long.  It then passes that to negativePresetWarning, which expects an argument of type int.  What's up with that?


Also, your constructor is making a call to negativePresetWarning before it was been defined, so the compiler is looking for a version of negativePresetWarning which takes an unsigned long argument, which does not exist.


Regards,
Ray L.

dougp

Your constructor gets argument pre of type unsigned long.  It then passes that to negativePresetWarning, which expects an argument of type int.  What's up with that?
Can't say how that got in there except to say that it's an oversight that got lost in the mix twenty seven versions ago.  Anyhow, changing negativePresetWarning's offending int to unsigned long fixes all!  Thank you!  I tested it up to ten instantiations with no compiler complaints.

Also, your constructor is making a call to negativePresetWarning before it was been defined, so the compiler is looking for a version of negativePresetWarning which takes an unsigned long argument, which does not exist.
I don't know how that works but it does.  That part was worked out in this thread.

What's curious to me is, why would it work for one instantiation and fail when the second is added?

karma++
Everything we call real is made of things that cannot be regarded as real.  If quantum mechanics hasn't profoundly shocked you, you haven't understood it yet. - Niels Bohr

No private consultations undertaken!

Go Up