OOP need some help with conversion from noob 0.0 to noob 0.1b level

DKWatson:
There you go. You really ought to get in the mode of understanding rather than playing pin-the-tail.

MorganS:
(snip)
Why does Count have a capital C anyway? The usual pattern you will see in Arduino code is to always make the first letter lowercase and then intermediate words use camelCase. (See, the humps in a camel, gettit?)

I'm no programmer clearly :slight_smile:
I know some math and logic, but modern programming techniques is all new to me

darrob:
I'm having some regrets in making my initial suggestion in the first place.
While it does make life very easy, it does appear to lead to bad programming habits

It is better practice to add a method to return the value of Count, rather than directly accessing it. It does lead to a bit more code, but actually not that much.

I've reworked the latest version you posted to include such a method.

class PulseOutput

{
  byte outPin;
  unsigned long OnTime;
  unsigned long OffTime;
  byte outState;
  unsigned long previousMillis;
  unsigned long Count;  // ******* Note that count has been moved from the public section
 
  public:
   
    PulseOutput(int pin, long on, long off, unsigned long mCount)
    {
      outPin = pin;
      pinMode(outPin, OUTPUT);
      OnTime = on;
      OffTime = off;
      outState = LOW;
      previousMillis = 0;
    }
   
    void Update()
    {
      unsigned long currentMillis = millis();
      if ((outState == HIGH) && (currentMillis - previousMillis >= OnTime))
      {
        outState = LOW;
        previousMillis = currentMillis;
        digitalWrite(outPin, outState);
      }
      else if ((outState == LOW) && (currentMillis - previousMillis >= OffTime))
      {
        outState = HIGH;  // turn it on
        previousMillis = currentMillis;
        digitalWrite(outPin, outState);
        Count ++;
      }
    }
   
    // ***** A new method to return the value of count
    unsigned long getCount()
    {
      return Count;
    }
};




The way it gets used is very similar to accessing the variable directly.



...
void loop()
{
  Serial.print(" A ");
  Serial.print(led1.getCount());
  Serial.print("  || B ");
  Serial.print(led2.getCount());
  Serial.print("  || C ");
  Serial.print(led3.getCount());
  Serial.print("  || D ");
  Serial.println(led4.getCount());

if (digitalRead(2) == HIGH)
  {
    led1.Update();
    led2.Update();
    led3.Update();
  }
  led4.Update();
}

If you need to update the value of Count later on, another method can be added. For example

[code[
class PulseOutput
{
  ...
  public:
  ...
  void setCount(unsigned long newCountVal)
  {
    Count = newCountVal;
  }
};

changing the value of count in your loop() would then be

[code]
void loop()
{
  ...
  led1.setCount(SOME_NEW_VALUE);
  ...
}




The value of such an approach, I have since discovered, is this
So, I've shown you that is is easy to access Count directly. **But** getting into the habit of doing so via methods *now* is better.

massive help, thanks a ton.
I'll pull this apart and try to make sense of it

darrob:
I'm having some regrets in making my initial suggestion in the first place.
While it does make life very easy, it does appear to lead to bad programming habits

It is better practice to add a method to return the value of Count, rather than directly accessing it. It does lead to a bit more code, but actually not that much.
.
.
.
The value of such an approach, I have since discovered, is this
So, I've shown you that is is easy to access Count directly. But getting into the habit of doing so via methods now is better.

You've discovered the OOP pillars of Abstraction and Encapsulation per my comment in Reply #3.

Glad we're now all singing from the same page. Next come Inheritance and Polymorphism (which could blow your mind :o).

So, it's working :slight_smile: but i think there is a problem with my method of counting the change from low to high ???

if i set all four outputs to be on (for different lengths) once every two seconds the counts change over time

From this pulse cycle, I get this

PulseOutput led1(9, 1000, 1000, 1);
PulseOutput led2(10, 500, 1500, 1);
PulseOutput led3(11, 1900, 100, 1);
PulseOutput led4(13, 1900, 100, 1);


 A 768  || B 781  || C 770  || D 770
 A 768  || B 781  || C 770  || D 770
 A 768  || B 781  || C 770  || D 770
 A 768  || B 781  || C 770  || D 770
 A 768  || B 781  || C 770  || D 770
 A 768  || B 781  || C 770  || D 770
 A 768  || B 781  || C 770  || D 770
 A 768  || B 781  || C 770  || D 770
 A 768  || B 781  || C 770  || D 770
 A 768  || B 781  || C 770  || D 770
 A 768  || B 781  || C 770  || D 770
 A 768  || B 781  || C 770  || D 770
 A 768  || B 781  || C 770  || D 770
 A 768  || B 781  || C 770  || D 770
 A 768  || B 781  || C 771  || D 771
 A 768  || B 781  || C 771  || D 771
 A 768  || B 781  || C 771  || D 771

I could just run the output back in and 'count' that, but that seems like the hard way.
Do i need to track previous state and current state to count the change ? or is my 'count ++' in the wrong place in the code ??

Another question on the 'not public' code, is this

 unsigned long getcount()
    {
      return count;
    }

kinda like saying

getcount = count;

? or is the interaction different ?

Let's see the full code, please.

Oh sorry, not much has changed. I removed the hold on pin high/low just to eliminate that as a potential source.

i thought his line
else if ((outState == LOW) && (currentMillis - previousMillis >= OffTime))
would be enough to stop the count being increased ??

the full ino

class PulseOutput
{
    byte outPin;
    unsigned long OnTime;
    unsigned long OffTime;
    byte outState;
    unsigned long previousMillis;
    unsigned long count;

  public:
    PulseOutput(int pin, long on, long off, unsigned long mcount)
    {
      outPin = pin;
      pinMode(outPin, OUTPUT);
      OnTime = on;
      OffTime = off;
      outState = LOW;
      previousMillis = 0;
    }

    void Update()
    {
      unsigned long currentMillis = millis();
      if ((outState == HIGH) && (currentMillis - previousMillis >= OnTime))
      {
        outState = LOW;
        previousMillis = currentMillis;
        digitalWrite(outPin, outState);
      }
      else if ((outState == LOW) && (currentMillis - previousMillis >= OffTime))
      {
        outState = HIGH;  // turn it on
        previousMillis = currentMillis;
        digitalWrite(outPin, outState);
        count ++;
      }
    }
    unsigned long getcount()
    {
      return count;
    }
};

PulseOutput led1(9, 1000, 1000, 1);
PulseOutput led2(10, 500, 1500, 1);
PulseOutput led3(11, 1900, 100, 1);
PulseOutput led4(13, 1900, 100, 1);

void setup()
{
  Serial.begin(9600);
}

void loop()
{
//  if (digitalRead(2) == LOW)
//  {
    Serial.print(" A ");
    Serial.print(led1.getcount());
    Serial.print("  || B ");
    Serial.print(led2.getcount());
    Serial.print("  || C ");
    Serial.print(led3.getcount());
    Serial.print("  || D ");
    Serial.println(led4.getcount());
//  }

//  if (digitalRead(2) == HIGH)
//  {
    led1.Update();
    led2.Update();
    led3.Update();
    led4.Update();
//  }
}

thanks guys/gals
you must hate noobs with no clue :slight_smile:

So, what is it you expect to see and how is that different from what is happening?

Each of the four outputs should be equal
each output is set to be a total of 2 seconds of ON and OFF time
only the cycle changes, so the number of counts should remain the same across all four

i have set the third and fourth out put to be the same, and the count is the same, yet the first and second are different.

edit - I'd expect all counts to be equal, it appears I'm somehow counting "on time" cycles not the actual count of transitions ...

the current count on the serial monitor is

 A 2949  || B 2924  || C 2909  || D 2909
 A 2949  || B 2924  || C 2909  || D 2909
 A 2949  || B 2924  || C 2909  || D 2909
 A 2949  || B 2924  || C 2909  || D 2909
 A 2949  || B 2924  || C 2910  || D 2910
 A 2949  || B 2924  || C 2910  || D 2910
 A 2949  || B 2924  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2949  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2910  || D 2910
 A 2950  || B 2925  || C 2911  || D 2911
 A 2950  || B 2925  || C 2911  || D 2911
 A 2950  || B 2925  || C 2911  || D 2911
 A 2950  || B 2926  || C 2911  || D 2911
 A 2950  || B 2926  || C 2911  || D 2911
 A 2950  || B 2926  || C 2911  || D 2911
 A 2950  || B 2926  || C 2911  || D 2911
 A 2950  || B 2926  || C 2911  || D 2911
 A 2950  || B 2926  || C 2911  || D 2911
 A 2950  || B 2926  || C 2911  || D 2911
 A 2950  || B 2926  || C 2911  || D 2911
 A 2950  || B 2926  || C 2911  || D 2911

I see a couple things I'd look at. First, try initializing the class's 'count' member variable to zero in the constructor. Is that what the mcount parameter is for? You're currently not using it for anything.

Second, you're printing to the Serial port the 4 getcount() results on every pass through loop. I'd change that to print all 4 only when one (or more) of them changes.

Thanks for the tips gfvalvo

I've been busy trying code that i do know (kind of) how to write :slight_smile:
So i've got this counter working almost properly, currently it counts both the Low to High and High to Low transitions. but i can live with that for now ...

//DECLARE
byte chan1OutState = LOW;
byte prevChan1OutState = LOW;
int chan1Out =  9;
unsigned long currentMillis;
unsigned long prevMillisChan1 = 0;
long OnTimeChan1 = 100;
long OffTimeChan1 = 4900;
long CounterChan1 = 0;

//SETUP
void setup()
{
Serial.begin (9600);
pinMode(chan1Out, OUTPUT);
}

//LOOP
void loop()
{
currentMillis = millis();
if ((chan1OutState == HIGH) && (currentMillis - prevMillisChan1 >= OnTimeChan1))
{
chan1OutState = LOW;
prevMillisChan1 = currentMillis;
digitalWrite(chan1Out, chan1OutState);
}
if ((chan1OutState == LOW) && (currentMillis - prevMillisChan1 >= OffTimeChan1))
{
chan1OutState = HIGH;
prevMillisChan1 = currentMillis;
digitalWrite(chan1Out, chan1OutState);
}
if (chan1OutState != prevChan1OutState)
{
CounterChan1 ++;
prevChan1OutState = chan1OutState;
}
Serial.println(CounterChan1);
}

So a complete ground up rewrite (but not in OOP) and i have a working accurate four channel pulse output and counter :smiley:
I can select;
High or Low start state
On and Off duty cycle times
Pause the increment and subsequent count

Currently the digital in as a high or low toggles between the code counting or to pause and display the current count value

All in all, I'm happy with what I've learnt with this.
A bit about the counter side of things, I'm unable to explain whats going on there, I just know that moving the counter away from the toggle pulse high/low logic has worked (?seems to so far)

Future - I'd like to add a 16x2 LCD with buttons and have a menu (shudder) to select the On/Off times for each channel, as well as display the current counts

The glorious code is below :slight_smile: - I should document it a bit, but this thread serves as fairly good documentation :slight_smile:
I'd still like to see this done in OOP, but it seems it's beyond my skill-set for now.
Happily the current version uses ~ the same memory and variables space as the last OOP version. So I feel that is justification for me to continue down this path, but bearing in mind it's not the best/smartest/most efficient way to go about it :slight_smile:

edit - If one of you fine persons would like to do a 'OOP' of this it would be good for me to see to learn a bit more ... but I cant help but think this old dog can only learn a couple of new tricks a year :smiley:

[code

//https://forum.arduino.cc/index.php?topic=578629.15
unsigned long currentMillis;

byte chan1OutState = LOW;
byte chan2OutState = LOW;
byte chan3OutState = LOW;
byte chan4OutState = LOW;

byte prevChan1OutState = LOW;
byte prevChan2OutState = LOW;
byte prevChan3OutState = LOW;
byte prevChan4OutState = LOW;

int chan1Out =  9;
int chan2Out =  10;
int chan3Out =  11;
int chan4Out =  13;

unsigned long prevMillisChan1 = 0;
unsigned long prevMillisChan2 = 0;
unsigned long prevMillisChan3 = 0;
unsigned long prevMillisChan4 = 0;

long CounterChan1 = 0;
long CounterChan2 = 0;
long CounterChan3 = 0;
long CounterChan4 = 0;

//Channel Cycle Times/////////
long OnTimeChan1 = 1;
long OffTimeChan1 = 9;

long OnTimeChan2 = 9; 
long OffTimeChan2 = 1;

long OnTimeChan3 = 63;
long OffTimeChan3 = 63;

long OnTimeChan4 = 630;
long OffTimeChan4 = 630;
//////////////////////////////


void setup()
{
  Serial.begin (9600);
  pinMode(chan1Out, OUTPUT);
  pinMode(chan2Out, OUTPUT);
  pinMode(chan3Out, OUTPUT);
  pinMode(chan4Out, OUTPUT);
}


void loop()
{
  if (digitalRead(2) != LOW)
  {
  currentMillis = millis();
  
  //chan1
  if ((chan1OutState == HIGH) && (currentMillis - prevMillisChan1 >= OnTimeChan1))
  {
    chan1OutState = LOW;
    prevMillisChan1 = currentMillis;
    digitalWrite(chan1Out, chan1OutState);
  }
  if ((chan1OutState == LOW) && (currentMillis - prevMillisChan1 >= OffTimeChan1))
  {
    chan1OutState = HIGH;
    prevMillisChan1 = currentMillis;
    digitalWrite(chan1Out, chan1OutState);
  }

  //chan2
  if ((chan2OutState == HIGH) && (currentMillis - prevMillisChan2 >= OnTimeChan2))
  {
    chan2OutState = LOW;
    prevMillisChan2 = currentMillis;
    digitalWrite(chan2Out, chan2OutState);
  }
  if ((chan2OutState == LOW) && (currentMillis - prevMillisChan2 >= OffTimeChan2))
  {
    chan2OutState = HIGH;
    prevMillisChan2 = currentMillis;
    digitalWrite(chan2Out, chan2OutState);
  }

  //chan3
  if ((chan3OutState == HIGH) && (currentMillis - prevMillisChan3 >= OnTimeChan3))
  {
    chan3OutState = LOW;
    prevMillisChan3 = currentMillis;
    digitalWrite(chan3Out, chan3OutState);
  }

  if ((chan3OutState == LOW) && (currentMillis - prevMillisChan3 >= OffTimeChan3))
  {
    chan3OutState = HIGH;
    prevMillisChan3 = currentMillis;
    digitalWrite(chan3Out, chan3OutState);
  }
  //chan4
  if ((chan4OutState == HIGH) && (currentMillis - prevMillisChan4 >= OnTimeChan4))
  {
    chan4OutState = LOW;
    prevMillisChan4 = currentMillis;
    digitalWrite(chan4Out, chan4OutState);
  }
  if ((chan4OutState == LOW) && (currentMillis - prevMillisChan4 >= OffTimeChan4))
  {
    chan4OutState = HIGH;
    prevMillisChan4 = currentMillis;
    digitalWrite(chan4Out, chan4OutState);
  }

  //Counter
  if (chan1OutState != prevChan1OutState && chan1OutState != LOW)
  {
    CounterChan1 ++;
  }
  if (chan2OutState != prevChan2OutState && chan2OutState != LOW)
  {
    CounterChan2 ++;
  }
  if (chan3OutState != prevChan3OutState && chan3OutState != LOW)
  {
    CounterChan3 ++;
  }
  if (chan4OutState != prevChan4OutState && chan4OutState != LOW)
  {
    CounterChan4 ++;
  }

  prevChan1OutState = chan1OutState;
  prevChan2OutState = chan2OutState;
  prevChan3OutState = chan3OutState;
  prevChan4OutState = chan4OutState;
  }

  if (digitalRead(2) != HIGH)
  {
  //Serial Out
  Serial.print(" ");
  Serial.print(CounterChan1);
  Serial.print(", ");
  Serial.print(CounterChan2);
  Serial.print(", ");
  Serial.print(CounterChan3);
  Serial.print(", ");
  Serial.println(CounterChan4);
  }
}

[/code]]

Great. You got it working.

Now read up on arrays and halve the size of the code (at least)
Look to post #7 for some inspiration

Hey thanks UKHeliBob, I did remember your array while thinking about this and though "next time" :slight_smile:
But yes, it's another step to take.
I can see a lot of merit in the OOP way of doing things, I just need to signup for some C++ classes I think

Cheers for all the suggestions so far, happy to hear more :slight_smile:

A suggestion for a very minor change
Make these

int chan1Out =  9;
int chan2Out =  10;
int chan3Out =  11;
int chan4Out =  13;

const byte

A byte is large enough and the values will not change within the program

Done !
Thank You :slight_smile:

When I started this non-oop rewrite, i tried with the counter inside the 'OOP function' and it exhibited the same error 'creep' over time/cycles ...
I wonder why ? At least I have some confidence that I should be able to have a crack at converting this to OPP (maybe one day soonish)

Of course, once you have the OOP version working you can create an array of objects !

So, I think the major functional difference between your 2 codes is that the newest version has a single concept of "now" for all of your counters. "Now" is set once right at the top of the loop:

currentMillis = millis();

and, this same value is used for all of your time comparisons.

In contrast, your version with classes / objects supplied a different version of "now" in the Update() method of each object:

unsigned long currentMillis = millis();

The Update methods run at different times, so the value returned by the millis() function may change between calling 'led2.Update()' and 'led3.Update', for example. That could cause a slight difference in counter values over time.

I'd have to think about this a little more to rationalize the exact behavior you saw, but I think that's it. How you solve this in the class / object version depends on your application and whether or not it's actually a problem that needs solving.

You could have a static class member function that sets a static member variable to "now" (aka millis) just before calling the ledx.Update() methods. All the Update() functions would then use this common value. This would only be good if you updated them all in quick succession (like your second code is doing anyway).

Yes, so maybe the counter is correct (as far as the number of pulses sent) and the actual 'error' is that the cycle time is being.
And, a bit of an update the 'long' code eventually exhibits the same 'count' creep, but it takes longer...

This means the actual On/Off times are not actually 'true' and the code and state changes are costing me time.
Is this what was being discussed in Robin2's thread "Demonstration code for several things at the same time"
http://forum.arduino.cc/index.php?topic=223286.0

Post 22 has this GEM of a comment

The problem is that the timing will be subject to cumulative slip when there is any latency between the timed condition becoming true, and the code to detect that executing. As soon as that latency exceeds 1ms, the time of the next scheduled event will be wrong, and so on for all subsequent events. The timing will have slipped by a small amount. It will keep doing this every time there is any latency. There may be some situations where that doesn't matter, and it's even possible to design a sketch that relies on that behaviour, but as a general approach for making something happen at regular intervals this is a problem.

Fortunately the solution is easy: schedule the next event based on when the current event was due, not when it was detected.

so my code is 'slipping' or I think this is like jitter, but it keeps pushing the start of the next transition ...

Seems I might need more variables to track the actually milli count in relation to the next transition

edit - i havent finished reading that thread, so the answer might be in there ?? hope so

Could be. You could certainly improve on your timing method and tighten up the code some. Like this for Chan1:

    //chan1
    if ((chan1OutState == HIGH) && (currentMillis - prevMillisChan1 >= OnTimeChan1))
    {
      chan1OutState = LOW;
      digitalWrite(chan1Out, chan1OutState);
      prevMillisChan1 += OffTimeChan1;
    } else {
      if ((chan1OutState == LOW) && (currentMillis - prevMillisChan1 >= OffTimeChan1))
      {
        chan1OutState = HIGH;
        CounterChan1 ++;
        digitalWrite(chan1Out, chan1OutState);
        prevMillisChan1 += OnTimeChan1;
      }
    }

Then you wouldn't need any of this code:

    //Counter
    if (chan1OutState != prevChan1OutState && chan1OutState != LOW)
    {
      CounterChan1 ++;
    }
    if (chan2OutState != prevChan2OutState && chan2OutState != LOW)
    {
      CounterChan2 ++;
    }
    if (chan3OutState != prevChan3OutState && chan3OutState != LOW)
    {
      CounterChan3 ++;
    }
    if (chan4OutState != prevChan4OutState && chan4OutState != LOW)
    {
      CounterChan4 ++;
    }

    prevChan1OutState = chan1OutState;
    prevChan2OutState = chan2OutState;
    prevChan3OutState = chan3OutState;
    prevChan4OutState = chan4OutState;

You could then get rid of your prevChan1OutState, prevChan2OutState, etc variables. Also, change CounterChan1, OnTimeChan1, OffTimeChan1, etc to 'unsigned long'. They are NEVER going to be negative!

sorry for delay reply, work has been nuts this week
thanks gfvalvo, i'll look over your tips this weekend.
that code was more a test of my logic, and i wanted it nice and simple to read and understand in my mind, I can learn a few tricks from the way you have shorteded it up. thanks

i also hope to get a chance to introduce a better timing method.
I'll move to micros rather than millis, and maybe have another tim two variables for "start" and "finish" timing ....

as long as i get a bit of time that is ........