You are correct that in that case it is a trivial exercise and doesn't help much. You're blinking a single led. OOP doens't really help much when you only have one of the object.
Now rewrite that same sketch to blink all the pins at different rates. I think in that case you can see how a class can greatly reduce code duplication.
Here's an example with three pins:
First the normal way, with arrays so it's still short:
uint8_t pin[3] = {2,3,4};
uint32_t onPeriod[3] = {200, 500, 800};
uint32_t offPeriod[3] = {800, 500, 200};
uint32_t lastChange[3];
bool state[3] = {false, false, false};
void setup() {
for (int i = 0;i<3;i++){
pinMode(pin[i], OUTPUT);
}
}
void loop() {
for (int i = 0;i<3;i++){
uint32_t currentTime = millis();
if(currentTime - lastChange[i] >= (state[i] ? onPeriod[i] : offPeriod[i])){
state[i] = !state[i];
lastChange[i] = currentTime;
digitalWrite(pin[i], (state[i]?HIGH:LOW));
}
}
}
The code isn't long. But all those arrays are cumbersome. The pin numbers and the on and off times are all in separate arrays. If I want to add more then I have to go through and add a piece to each array. And if I was doing something more involved than blinking a light, I might have a lot more than three or four variables to keep track of for each instance.
Now when I make it a class:
class Blinker {
private:
uint8_t _pin;
uint32_t _onPeriod;
uint32_t _offPeriod;
uint32_t _lastChange;
bool _state;
public:
Blinker(uint8_t pin, uint32_t onPeriod, uint32_t offPeriod)
: _pin(pin), _onPeriod(onPeriod), _offPeriod(offPeriod), _state(false) {}
void begin();
void run();
};
void Blinker::begin() {
pinMode(_pin, OUTPUT);
}
void Blinker::run() {
uint32_t currentTime = millis();
if (currentTime - _lastChange >= (_state ? _onPeriod : _offPeriod)) {
_state = !_state;
_lastChange = currentTime;
digitalWrite(_pin, (_state ? HIGH : LOW));
}
}
Blinker blinkers[] {
Blinker(2, 200, 800),
Blinker(3, 500, 500),
Blinker(4, 800, 200),
};
void setup() {
for (int i = 0; i < 3; i++) {
blinkers[i].begin();
}
}
void loop() {
for (int i = 0; i < 3; i++) {
blinkers[i].run();
}
}
Number of lines of code actually went up considerably. That's due mostly to the trivial nature of what this code does. In something more involved the code might be a lot simpler.
However, I have gained some notable advantages now. Each of my Blinker objects is defined in one place with all the variables together in the blinkers array. I can easily see everything for one instance together at once. And if I want to add another instance, I only have to add one entry to this one array.
My loop code gets much simpler, and this is key too. Because if this is something that I do a lot, then I can put this class into a library file and use it repeatedly in many sketches. That means I wrote this once and from now on blinking a led is three lines of code, instantiate, begin() and run(). For something more involved than an led, you can certainly see where this would be a great advantage.
The array example could be made into a library as well. But it wouldn't be as easy or as neat or as simple to work with. I'll leave that as an exercise for you if you want. I think that will convince you of the utility of a class.