Proposal for Tutorial: Use of array, struct and class to handle several actuators in one sketch

Beginners often copy the same code multiple times into their sketch to read or control sensors or actuators that appear multiple times in their application. This usually leads to unnecessarily long and difficult to understand code. It also leads to a significant effort in troubleshooting, maintenance and expansion.

The following tutorial uses the example of controlling one or more LEDs to show how arrays, structures and classes can be used to write short, clear and extensible code. The examples are designed to build on each other.

1 Like

This tutorial intends to be (or at least become) a structured introduction how to use arrays, structures and classes to handle multiple actuators or sensors in one sketch.

The links in the following content list refer to the corresponding Wokwi projects.

Content

  1. Without Struct I
    Without Struct I - Wokwi Arduino and ESP32 Simulator
    Basic solution to switch a LED on and off in given time periods
Sketch ''Without Struct I"

uint8_t pinNo = 13;
uint8_t state = LOW;
unsigned long onTime  = 1000; // 1000 msec = 1 sec
unsigned long offTime =  500; //  500 msec = 0.5 sec
unsigned long lastTimeSwitched = 0;


void switchLED() {
  unsigned long waitTime;
  if (state)
  {
    waitTime = onTime;
  }
  else
  {
    waitTime = offTime;
  }
  if (millis() - lastTimeSwitched >= waitTime) {
    lastTimeSwitched = millis();
    state = !state;
    digitalWrite(pinNo, state);
  }
}


void setup() {
  Serial.begin(115200);
  Serial.println("Start ");
  pinMode(pinNo, OUTPUT);
}

void loop() {
  switchLED();
}
Detailed Explanations "Without Struct I"
/*
  =============================================================
  Background:

  Introductory examples often make use of the delay function as it
  is the most simple approach to wait for a certain time. This
  is okay for basic demonstration purposes. However the delay()
  function blocks the microcontroller for the complete time interval.

  This means the controller cannot perform any other function
  in loop() until the delay interval is over. This hinders
  e.g. to react to status changes of buttons, read data from
  Serial input, measure distances or perform other time critical
  functions.

  Objective of this Sketch:

  This first sketch shows a usual solution to switch a LED on and off
  for given intervals without using the delay() function in loop().

  It is possible now to add further functionality as loop() is
  not blocked by a delay().

  Hardware:

  This sketch uses pin 13 of an ESP32 as used in the corresponding
  Wokwi project. However it is neither limited nor oprimized for use
  with an ESP32. The sketch can be changed easily to work with other
  controllers such as Arduino UNO, MEGA or the like.

  For Arduino Uno pin 13 (D13) is connected to the board internal
  LED. So the sketch can be used without any change to control this
  LED. An external LED (with additional resistor to limit the current)
  can also be connected to this pin. Or any other pin that allows
  digitial output can be used. In that case pinNo has to changed to
  the appropiate value.

  =============================================================

  The Sketch in Detail:
*/

uint8_t pinNo = 13;

/*
  defines the number of the output pin where the LED is connectet to.
  As this number will never be less than zero (we do not expect a negative
  number for the pin assignment in the Arduino world) it is declared as uint8_t.
  It could also be "byte" as this is also an 8 bit unsigned number but uint8_t
  clearly states "u" for unsigned, "int" for integer and 8 for eight bit.
  We are just introducing the habit of using "uintxx_t" as this becomes
  more important when it comes to "integer" types where it may make a big
  difference whether one uses e.g. "long" or "unsigned long".
*/

uint8_t state = LOW;

/*
  declares a variable, again of type uint8_t, which will hold the state of
  the pin pinNo. "state" will contain either the value LOW or HIGH which
  control the voltage level at pinNo (LOW -> 0V, HIGH -> 3.3V or 5V depending
  on the board used).

  (The names LOW and HIGH are usually defined in the library Arduino.h which is - in most cases -
  included by the Arduino IDE or other libraries without the need to explicitely
  call it in a sketch. A lot of #defines and basic functions will be missing
  if the IDE fails to include this library.)
*/

unsigned long onTime  = 1000; // 1000 msec = 1 sec

/*
  declares a variable of data type "unsigned long" with a range from 0 to 4,294,967,295.
  Although we use only a small part of this range, it is wise to use this data type:
  The millis() function which counts the milliseconds since the last (re)start of the
  controller returns an "unsigned long" value and we want compare time differences.
  By using  the same type we avoid possible conversion/calculation problems between 
  data of different types. The value of onTime specifies the interval in msecs we 
  want the LED to be switched on.
*/

unsigned long offTime =  500; //  500 msec = 0.5 sec

/*
  The value of offTime specifies the interval in msecs we want the LED to be switched off.
  The reason for the data type is exactly the same as with onTime.
*/

unsigned long lastTimeSwitched = 0;

/*
  This variable will be used to store the value of millis() at the time we switch
  the LED on and off. It is initialized with zero so that the first activity will
  be performed after the sketch has been (re)started.
*/

void switchLED() {

  /*
    This function is declared and defined before loop() and will be used in loop()
    to check whether and when the LED status has to change and if the LED will be
    switched on or off.
  */

  unsigned long waitTime;

  /*
    is a local variable only used inside switchLED() to store the actual time to wait
    depending on the state (LOW or HIGH).
  */

  if (state)

    /*
      if the state is HIGH the variable waitTime shall have the same value as onTime
    */

  {
    waitTime = onTime;
  }

  else

    /*
      else the variable waitTime shall have the same value as offTime
    */
  {
    waitTime = offTime;
  }


  if (millis() - lastTimeSwitched >= waitTime)
    /*
    	Here we subtract the time when the LED was switched before from
    	the actual time (millis()) and check whether this is equal or greater than
    	the waitTime as set in the lines above.

      After restart or start the sketch will also not switch the LED before the
    	programmed first(!) waitTime is reached! As we defined state = LOW, it will
    	take in minimum "offTime". If you have setup() procedures that delay before
    	loop() is started or "offTime" is very short, millis() may already have
      reached or exceeded "offTime"	of course.


    	If this is the case the sketch enters the next software block otherwise
    	this block is ignored:
    */

  {
    lastTimeSwitched = millis();

    /*
    	As the time interval is over we copy the actual time to lastTimeSwitched so
    	that the next time interval starts "now"...
    */

    state = !state;

    /*
    	We use the "logical not" operator (the exclamation mark !) to invert the value
    	of state as !LOW equals HIGH and !HIGH equals LOW.
    */

    digitalWrite(pinNo, state);

    /*
    	Here we switch the pin pinNo to either HIGH or LOW depending on the value of state.
    */
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("Start ");

  /*
    In setup() we start Serial so that we get the message "Start" in the Serial Terminal 
    when the sketch has started, just to please our nerves ...
  */

  pinMode(pinNo, OUTPUT);
  /*
    With pinMode() we configure the hardware pin pinNo as a digital output.
  */

}

void loop() {
  switchLED();
  /*
    Here our function switchLED() is called more than 100,000 times per second!
    And everything loops in loop() ...
  */
}


  1. Without Struct II
    Without Struct II - Wokwi Arduino and ESP32 Simulator
    Brute Force method to control three LEDs by copying variables and functions three times
    as often done by beginners
Sketch "Without Struct II"

uint8_t greenPinNo = 13;
uint8_t greenState = LOW;
unsigned long greenOnTime  = 1000; // 1000 msec = 1 sec
unsigned long greenOffTime =  500; //  500 msec = 0.5 sec
unsigned long greenLastTimeSwitched = 0;

uint8_t yellowPinNo = 12;
uint8_t yellowState = LOW;
unsigned long yellowOnTime  =  500; //
unsigned long yellowOffTime =  500; //
unsigned long yellowLastTimeSwitched = 0;

uint8_t redPinNo = 14;
uint8_t redState = LOW;
unsigned long redOnTime  =  500; //
unsigned long redOffTime = 1000; //
unsigned long redLastTimeSwitched = 0;

void switchLEDGreen() {
  unsigned long waitTime;
  if (greenState)
  {
    waitTime = greenOnTime;
  }
  else
  {
    waitTime = greenOffTime;
  }
  if (millis() - greenLastTimeSwitched >= waitTime) {
    greenLastTimeSwitched = millis();
    greenState = !greenState;
    digitalWrite(greenPinNo, greenState);
  }
}

void switchLEDYellow() {
  unsigned long waitTime;
  if (yellowState)
  {
    waitTime = yellowOnTime;
  }
  else
  {
    waitTime = yellowOffTime;
  }
  if (millis() - yellowLastTimeSwitched >= waitTime) {
    yellowLastTimeSwitched = millis();
    yellowState = !yellowState;
    digitalWrite(yellowPinNo, yellowState);
  }
}

void switchLEDRed() {
  unsigned long waitTime;
  if (redState)
  {
    waitTime = redOnTime;
  }
  else
  {
    waitTime = redOffTime;
  }
  if (millis() - redLastTimeSwitched >= waitTime) {
    redLastTimeSwitched = millis();
    redState = !redState;
    digitalWrite(redPinNo, redState);
  }
}



void setup() {
  Serial.begin(115200);
  Serial.println("Start ");
  pinMode(greenPinNo, OUTPUT);
  pinMode(yellowPinNo, OUTPUT);
  pinMode(redPinNo, OUTPUT);
}

void loop() {
  switchLEDGreen();
  switchLEDYellow();
  switchLEDRed();
}

  1. Without Struct III
    Without Struct III - Wokwi Arduino and ESP32 Simulator
    Solution to control three LEDs using one dimensional arrays for the variables
    Much easier to maintain and add further LEDs.
Sketch "Without Struct III"
const int noOfLeds = 3;
uint8_t pinNo[noOfLeds] = {13, 12, 14};
uint8_t state[noOfLeds]  = {LOW, LOW, LOW };
unsigned long onTime[noOfLeds]  = {1000, 500,  500};
unsigned long offTime[noOfLeds] = { 500, 500, 1000};
unsigned long lastTimeSwitched[noOfLeds] = { 0, 0, 0};



void switchLED(int no) {
  unsigned long waitTime;
  if (state[no])
  {
    waitTime = onTime[no];
  }
  else
  {
    waitTime = offTime[no];
  }
  if (millis() - lastTimeSwitched[no] >= waitTime) {
    lastTimeSwitched[no] = millis();
    state[no] = !state[no];
    digitalWrite(pinNo[no], state[no]);
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("Start ");
  for (int i = 0; i < noOfLeds; i++) {
    pinMode(pinNo[i], OUTPUT);
  }
}

void loop() {
  for (int i = 0; i < noOfLeds; i++) {
    switchLED(i);
  }
}

  1. With Struct I
    With Struct I - Wokwi Arduino and ESP32 Simulator
    Solution to control three LEDs with a struct for the LED specific variables and a function to switch
    the LEDs that uses the address operator & to handle the struct and not a copy of its data. There is a separate struct defined per LED.
Sketch "With Struct I"
struct ledType {
  uint8_t pinNo;
  uint8_t state;
  unsigned long  onTime;
  unsigned long offTime;
  unsigned long lastTimeSwitched;
};

ledType greenLED  = {13, LOW, 1000,  500, 0};
ledType yellowLED = {12, LOW,  500,  500, 0};
ledType redLED    = {14, LOW,  500, 1000, 0};



void switchLED(ledType &led) {    // & tells the compiler to use the address of the variable and NOT a copy!!
  unsigned long waitTime;
  if (led.state)
  {
    waitTime = led.onTime;
  }
  else
  {
    waitTime = led.offTime;
  }
  if (millis() - led.lastTimeSwitched >= waitTime) {
    led.lastTimeSwitched = millis();
    led.state = !led.state;
    digitalWrite(led.pinNo, led.state);
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("Start ");
  pinMode(greenLED.pinNo, OUTPUT);
  pinMode(yellowLED.pinNo, OUTPUT);
  pinMode(redLED.pinNo, OUTPUT);
}

void loop() {
  switchLED(greenLED);
  switchLED(yellowLED);
  switchLED(redLED);
}
  1. With Struct II
    With Struct II - Wokwi Arduino and ESP32 Simulator
    The same solution as in 4. but this sketch defines an array of structs. This way it combines structs with the advantage of arrays as in 3.
Sketch "With Struct II"
struct ledType {
  uint8_t pinNo;
  uint8_t state;
  unsigned long  onTime;
  unsigned long offTime;
  unsigned long lastTimeSwitched;
};

const int noOfLeds = 3;
ledType LEDs[noOfLeds] =
{
  {13, LOW, 1000,  500, 0},
  {12, LOW,  500,  500, 0},
  {14, LOW,  500, 1000, 0}
};


void switchLED(ledType &led) {    // & tells the compiler to use the address of the variable and NOT a copy!!
  unsigned long waitTime;
  if (led.state)
  {
    waitTime = led.onTime;
  }
  else
  {
    waitTime = led.offTime;
  }
  if (millis() - led.lastTimeSwitched >= waitTime) {
    led.lastTimeSwitched = millis();
    led.state = !led.state;
    digitalWrite(led.pinNo, led.state);
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("Start ");
  for (int i = 0; i < noOfLeds; i++) {
    pinMode(LEDs[i].pinNo, OUTPUT);
  }
}

void loop() {
  for (int i = 0; i < noOfLeds; i++) {
    switchLED(LEDs[i]);
  }

}


  1. With Struct III
    With Struct III - Wokwi Arduino and ESP32 Simulator
    This sketch is based on solution 4. but the struct includes now functions to initialize data and
    pins as well as the function to switch the LED on/off. The variables inside the struct are declared
    as "private" so that they can only be accessed and changed via the public functions.
Sketch "With Struct III"
struct ledType {
  public:
    void init(uint8_t pin, unsigned long on, unsigned long off);
    void switchLED();
  private:
    uint8_t pinNo;
    uint8_t state = LOW;
    unsigned long onTime;
    unsigned long offTime;
    unsigned long lastTimeSwitched = 0;
};

void ledType::init(uint8_t pin, unsigned long on, unsigned long off) {
  ledType::pinNo   = pin;
  ledType::onTime  = on;
  ledType::offTime = off;
  pinMode(pin, OUTPUT);
}

void ledType::switchLED() {
  unsigned long waitTime;
  if (ledType::state)
  {
    waitTime = ledType::onTime;
  }
  else
  {
    waitTime = ledType::offTime;
  }
  if (millis() - ledType::lastTimeSwitched >= waitTime)
  {
    ledType::lastTimeSwitched = millis();
    ledType::state = !ledType::state;
    digitalWrite(ledType::pinNo, ledType::state);
  }
}

ledType greenLED;
ledType yellowLED;
ledType redLED;

void setup() {
  Serial.begin(115200);
  Serial.println("Start ");
  greenLED.init( 13, 1000,  500);
  yellowLED.init(12,  500,  500);
  redLED.init(   14,  500, 1000);
}

void loop() {
  greenLED.switchLED();
  yellowLED.switchLED();
  redLED.switchLED();
}

  1. With Class I
    With Class I - Wokwi Arduino and ESP32 Simulator
    This sketch is identical to solution 6. except that the definition of "struct" has now been named
    "class". It shows that both are almost the same in C++. Just that every entry in a struct is public
    if not declared as private while in a class every entry is private if not explicitly declared as
    public.
Sketch "With Class I"
class ledType {
  public:
    void init(byte pin, unsigned long on, unsigned long off);
    void switchLED();
  private:
    byte pinNo;
    byte state = LOW;
    unsigned long onTime;
    unsigned long offTime;
    unsigned long lastTimeSwitched = 0;
};

void ledType::init(byte pin, unsigned long on, unsigned long off) {
  ledType::pinNo   = pin;
  ledType::onTime  = on;
  ledType::offTime = off;
  pinMode(pin, OUTPUT);
}

void ledType::switchLED() {
  unsigned long waitTime;
  if (ledType::state)
  {
    waitTime = ledType::onTime;
  }
  else
  {
    waitTime = ledType::offTime;
  }
  if (millis() - ledType::lastTimeSwitched >= waitTime) {
    ledType::lastTimeSwitched = millis();
    ledType::state = !ledType::state;
    digitalWrite(ledType::pinNo, ledType::state);
  }
}

ledType greenLED;
ledType yellowLED;
ledType redLED;

void setup() {
  Serial.begin(115200);
  Serial.println("Start ");
  greenLED.init( 13, 1000,  500);
  yellowLED.init(12,  500,  500);
  redLED.init(   14,  500, 1000);
}

void loop() {
  greenLED.switchLED();
  yellowLED.switchLED();
  redLED.switchLED();
}


  1. With Class II
    With Class II - Wokwi Arduino and ESP32 Simulator
    This sketch shows how to declare and use the constructor of a class to set variables and to perform required initialisation processes (like pinMode()).
Sketch "With Class II"
class ledType {
  public:
    ledType(uint8_t pin, unsigned long on, unsigned long off);
    void switchLED();
    void begin();
  private:
    uint8_t pinNo;
    uint8_t state = LOW;
    unsigned long  onTime;
    unsigned long offTime;
    unsigned long lastTimeSwitched = 0;
};

ledType::ledType(uint8_t pin, unsigned long on, unsigned long off) {
  pinNo   = pin;
  onTime  = on;
  offTime = off;
}

void ledType::begin() {
  pinMode(pinNo, OUTPUT);
}

void ledType::switchLED() {
  unsigned long waitTime;
  if (state == HIGH) {
    waitTime = onTime;
  } else {
    waitTime = offTime;
  }
  if (millis() - lastTimeSwitched >= waitTime) {
    lastTimeSwitched = millis();
    state = !state;
    digitalWrite(pinNo, state);
  }
}

ledType greenLED( 13, 1000,  500);
ledType yellowLED(12,  500,  500);
ledType redLED(   14,  500, 1000);

void setup() {
  Serial.begin(115200);
  Serial.println("Start ");
  greenLED.begin();
  yellowLED.begin();
  redLED.begin();
}

void loop() {
  greenLED.switchLED();
  yellowLED.switchLED();
  redLED.switchLED();
}

  1. With Class III
    With Class III - Wokwi Arduino and ESP32 Simulator
    This sketch is identical to 8. but the class declaration has been moved to a separate file that is
    included in the main file by the #include instruction.
Sketch "With Class III"

Main sketch:

#include "LedType.h"

ledType greenLED( 13, 1000,  500);
ledType yellowLED(12,  500,  500);
ledType redLED(   14,  500, 1000);

void setup() {
  Serial.begin(115200);
  Serial.println("Start ");
  greenLED.begin();
  yellowLED.begin();
  redLED.begin();
}

void loop() {
  greenLED.switchLED();
  yellowLED.switchLED();
  redLED.switchLED();
}

The separate file "LedType.h" to be stored in the same directory as the main file:

#ifndef LEDTYPE_H 
#define LEDTTYPE_H
#include <Arduino.h> 

class ledType {
  public:
    ledType(uint8_t pin, unsigned long on, unsigned long off);
    void switchLED();
    void begin();
  private:
    uint8_t pinNo;
    uint8_t state = LOW;
    unsigned long  onTime;
    unsigned long offTime;
    unsigned long lastTimeSwitched = 0;
};

#endif

The separate file "LedType.cpp" to be stored in the same directory as the main file and the header file:

#include "LedType.h"

ledType::ledType(uint8_t pin, unsigned long on, unsigned long off) {
  pinNo   = pin;
  onTime  = on;
  offTime = off;
}

void ledType::begin() {
  pinMode(pinNo, OUTPUT);
}

void ledType::switchLED() {
  unsigned long waitTime;
  if (state == HIGH) {
    waitTime = onTime;
  } else {
    waitTime = offTime;
  }
  if (millis() - lastTimeSwitched >= waitTime) {
    lastTimeSwitched = millis();
    state = !state;
    digitalWrite(pinNo, state);
  }
}
Explanations to "With Class III"

Header file with explanations

/*
   This an example of a Header File as e.g. used for libraries.
   It contains 

   - a header guard (the #ifndef part ...) which ensures that this file is only
     used once when included 
   - the inclusion of the file Arduino.h where "defines" are performed that
     are used in the following code, like e.g. "uint8_t"
   - the interface to the class "ledType"

   If this header file is included in a sketch the .cpp file with the same name 
   (in this case LedType.cpp) will be loaded by the compiler automatically.
*/

#ifndef LEDTYPE_H     // If LEDTYPE_H is already defined, the rest of the sketch
                      // until the final "#endif" is ignored
#define LEDTTYPE_H    // Otherwise LEDTYPE_H will be defined here and is now 
                      // available for a possible but unwanted additional inclusion  

// This type of "header guard" is widely spread in Arduino libraries. 
// A newer possibility is just to write 
//         #pragma once
// at the beginning of the file which has the same effect

#include <Arduino.h>  // This include is required just in case that this lib has not
                      // been included before, it contains the declaration of uint8_t
                      // used in the following code, here in .h and in LedType.cpp



class ledType {    // This line defines the name of the class ledType
  public:          // The following elements are available for use in sketch.ino
                   
    ledType(uint8_t pin, unsigned long on, unsigned long off); // The constructor function that
                                                               // sets the LED pin and the on- and off
                                                               // intervals
    void switchLED();  // The function to be called in loop() to switch the LED
    void begin();      // This function has to be called once in setup() to
                       // prepare the pinMode for the LED pin.
  private:         // The following elements cannot be changed or read in sketch.ino
                   // They are for internal use within the class functions only
                   // This concept "encapsulates" data and functions in a class
                   // which shall not be controlled or used from outside.
                   // This way the developer can avoid interference by third party
                   // to the coded functionality.  
    uint8_t pinNo;                  // The pin where the LED is connected to
    uint8_t state = LOW;            // The actual state of the LED LOW = off, HIGH = on
    unsigned long  onTime;          // The time the LED shall remain switched on
    unsigned long offTime;          // The time the LED shall remain switched off
    unsigned long lastTimeSwitched = 0; // The last time the LED changed its state
};

#endif   // The closing "#endif" for the "#ifndef" of the "header guard" at the top 

The .cpp-File with explanations:

/*
   This an example of a cpp File as e.g. used for libraries.
   It contains

   - no header guard as this is taken care of in the header file "LedType.h".
   - the inclusion of the file LedType.h the interface to the class "ledType"
     is defined
   - the definitions of the functions which are a part of the class ledType
     which are named in the header file but are not defined yet
*/

#include "LedType.h"  // This line includes the definition of the class members/elements 

// The following line defines how to call the constructor when using the class
// Example:
// ledType blueLED(12, 500, 1000);
//  - creates an object of type ledType named blueLED
//  - sets the internal variables
//       pinNo to 12
//       onTime to 500 [msec]
//       offTime to 1000 [msec]
ledType::ledType(uint8_t pin, unsigned long on, unsigned long off) {
  pinNo   = pin;
  onTime  = on;
  offTime = off;
}

// As one cannot be sure that the required hardware access is already available when the
// constructor is called, it is recommended to use a separate function e.g. .begin()
// to perform hardware specific functions
// The function begin() has to be called once in setup() to set the pinMode correctly
void ledType::begin() {
  pinMode(pinNo, OUTPUT);
}

// This is the main functionality of the class. It requires a non-blocking loop()
// for correct performance as it is based on the evaluation of the actual time in
// millis(). If other functions in loop() block longer than the programmed intervals
// it cannot switch in due time.
void ledType::switchLED() {
  unsigned long waitTime;    // A temporary storage for the actual waitTime (interval to wait)
  if (state == HIGH) {       // If the LED is on (the pin is set HIGH)
    waitTime = onTime;       // waitTime shall be equal to onTime
  } else {                   // if not (which means LED is off, the pin set to LOW)
    waitTime = offTime;      // waitTime shall become equal to offTime
  }
  if (millis() - lastTimeSwitched >= waitTime)   // The time between now (millis()) and the last change
                                                 // (lastTimeSwitched) is calculated and compared
                                                 // to waitTime
                                                 // if waitTime is neither reached nor exceed
                                                 // the following lines will be ignored
  {                              // But if the time is over
    lastTimeSwitched = millis(); // lastTimeSwitched stores the actual time             
    state = !state;              // The state is inverted (LOW -> HIGH, HIGH -> LOW)
    digitalWrite(pinNo, state);  // and the LED pin is set to the new state
 }
}

wow.
Good work @ec2021 .

Your are not targeting the first day beginner. So I think you can make really nice code.
Some short comments to make the examples more consistent.

sketch 1:
byte pin_no = 13;
i would use const uint8_t
because: it is const. digitalWrite also uses uint8_t
name it camelCase (or omit _no)

on_time
use camelcase, not underscores

void SwitchLED(){
functions should start with lower camelCase

if (state)
Arduino proposes to use state == true

format the Sketch with CTRL-T

Sketch 3:
const int noOfLeds = 3;
not needed to be ever negative.
use either a unsigned type or even size_t

Sketch 6:
explain why you need ledType::
or put them within the structure.

Sketch 8:
don't call hardware in the constructor (pinMode), put it in a separate init/begin function (as you did later anyway).

ledType::ledType(byte pin, unsigned long on, unsigned long off){
  ledType::pin_no   = pin;
  ledType::on_time  = on;
  ledType::off_time = off;
  pinMode(pin,OUTPUT);
}

some "Guidelines" I found from Arduino:

Arduino Style Guide:

Arduino Style Guide for Creating Libraries:
(as I marked that if(state) thing)

1 Like

Thanks for the quick reply! I wrote down these examples in the last three to four hours and I am an old Pascal user ... :wink: [Just to provide some vague excuses.]

I will collect your and future findings in a separate post which I will update from time to time and implement them in certain steps just to keep the effort reasonable.

Also it is clear to me that there is a lack of comments in the sketches but I fancied it better to publish in an early stage to get feedback from the community whether it is worth to go on or not.

Due to personal reasons I will quite likely not be able to maintain the thread in the next days but hopefully again in the next week.

Further Ideas

  1. make some members const (the pin?) and introduce the initializer list.

  2. objects in arrays: introduce auto range base for loop.

no problem if that takes longer - take your time :wink:

1 Like

List of findings

To be changed/amended:

  • Remove underscores, replace by lower camelCase
    pin_no --> pinNo
    on_time --> onTime
    off_time --> offTime
    etc.
    [Done in all examples]

  • Use lower camelCase for functions
    void SwitchLED(){} --> void switchLED(){}
    [Done in all examples]

  • Use uint8_t instead of byte for pins
    byte pin_no --> uint8_t pinNo
    etc.
    [Done in all examples]

  • Use unsigned type where negative values not required
    [Done in all examples]

  • Add explanation for external function definition in sketch 6ff

  • Format sketches CTRL-T (or SHIFT-ALT F in Wokwi)
    [Done in all examples]

  • ledType::pin_no can be simply written as pin_no (as long as there are no name clashes).
    [Done in example 8 and 9]

  • Prefer {} for blocks, even when they contain one line.
    [Done in all examples]

  • Sketch 8:
    don't call hardware in the constructor (pinMode), put it in a separate init/begin function (as you did later anyway). [Limit the initialisation in the constructor because not all the Arduino environment has been set up at the time the constructor is invoked]
    [Done in example 8 and 9]

  • Prefer if (state == HIGH) or if (state != LOW) instead of if (state)
    [Done in example 8 and 9]

To be discussed:

  • Use a single underscore suffix for private member variables and functions (no recommendation as far as I know though).

To be realized for "more experienced users"

New:

Last update

Added a detailed explanation for "Without Struct I"
My intention is to write similar but much less detailed explanations for the following examples but it will take some time ... :wink:

Changed example "With Class III" using header guard and two separate files (.h and .cpp), added explanations

2023-04-19 17:05 CET

I am fully aware that there are plenty of sophisticated supplements and additions which will be of interest not only for beginners but also for the more experienced (not the very experts of course).

It would be highly appreciate if you and/or other capable members would contribute example sketches that build on the existing basis! That would widen the scope (and reduce my effort :wink: ).

However, my primary intention is to provide examples that can be understood and used as templates by beginners and users who are hobbyists and require some support without getting to deeply into C++.

This is no longer the case, also see the C++ core guidelines.

This is not recommended, as it will introduce problems with copying these objects. Having const member functions is fine of course.

Some other suggestions (in addition to those of @noiasca):

  • Prefer uniform initialisation.
  • Prefer initialisation over assignment. The constructor may even be redundant.
  • ledType::pin_no can be simply written as pin_no (as long as there are no name clashes).
  • Prefer {} for blocks, even when they contain one line.
  • Instead of declaring the size of an array, you could consider deducing it (e.g., see this post). This will make maintenance easier.
  • Something I find useful: use a single underscore suffix for private member variables and functions (no recommendation as far as I know though).

oh nice, everday something new to learn ^^

what I don't understand:

why were verbose if statements brought into context of using block format?

This information got lost in the Arduino Guideline regarding handling of "ES.87 Don’t add redundant == or != to conditions"

Doing so avoids verbosity and eliminates some opportunities for mistakes. Helps make style consistent and conventional.

imho "don't add redundant == " and "use block format" are two separate guidelines and both should be a topic in the guideline.

No offence but it is really difficult to find out what is considered good and bad style ...

I see your point with byte versus uint8_t

but here

https://docs.arduino.cc/learn/contributions/arduino-writing-style-guide

they suggest to use integer ...

1 Like

Agreed, that is why I proposed to change the example.

Thanks for your suggestions and comments @jfjlaros,

as stated in my post #7 here I would really appreciate if you would provide example sketch(es) based on one of the existing examples (not ment to threaten you :wink: )...

As my primary intention is to give some guidelines to beginners I am not sure how deep we/I should dive into C++ ...

The (state) example should have been modified (because the former information got outdated).
The block format added (because that wasn't in before).

Two different topics.

I integrated your suggestions/comments in post #6 and introduced a new category

To be realized for "more experienced users"

Please check and let me know if not ok for you!

For clarity of the code respect the types and API, don’t write

if (state) …

And prefer

if (state != LOW) …

or

if (state == HIGH) …

As the doc does not say LOW is 0.

1 Like

@ec2021

Well done on making a good start with this. Personally I feel that a tutorial should explain why you are doing something in a particular way as well as how to do it. This involves writing an explanation of each technique as it is introduced and why it is different/better than what precedes it

A point that arises in this topic, but not brought out specifically, is that you must be careful when writing a tutorial that you know exactly what the subject is and stick to it, otherwise you end up having to explain several concepts at once, which is at best confusing

A couple of illustrations taken from this topic

The reason for the use of const would be easy to explain but the use of uint8_t would require a diversion from the main purpose of the tutorial

Again, explaining range based loops would require a diversion from the main purpose of the tutorial

If you are not careful a tutorial on a single subject can end up being a full C++ course

One way round this is to list the prerequisites needed to follow the tutorial and preferably provide links to tutorials on subjects that need in depth explanation, but you have to start somewhere

A particular problem that is difficult to overcome is that, by definition, anyone writing a tutorial knows the subject and it is difficult for them to put themselves in the shoes of a beginner. I find when debugging code, mine or anyone else's, that it is helpful to imagine that you are explaining what it does and and how to a novice programmer who is asking questions such as why, how, when etc Try imagining explaining your examples to such a person and you will see what I mean

Good luck going forwards with writing tutorials

I am not sure what you are referring to, all I know is that people thought that one of the messages in that example was that adding a redundant == true was recommended. This was fixed by changing the example.


Prefer uniform initialisation.

With uniform initialisation:

byte pin_no {13};

Combined with the other suggestions:

uint8_t const pinNo {13};

Prefer initialisation over assignment.

Using initialisation:

ledType::ledType(byte pin, unsigned long on, unsigned long off)
    : pin_no {pin}, on_time {on}, off_time {off} {
  pinMode(pin, OUTPUT);
}

Combined with other suggestions:

ledType::ledType(
    byte const pinNo, unsigned long const onTime, unsigned long const offTime)
    : pinNo_ {pinNo}, onTime_ {onTime}, offTime_ {offTime} {
  pinMode(pinNo_, OUTPUT);
}

The constructor may even be redundant. (note)

If you choose to remove the call to pinMode, the constructor can be removed (a simple list initialisation can be used instead (see you struct examples)).

ledType::pin_no can be simply written as pin_no.

This should work too:

ledType::ledType(byte pin, unsigned long on, unsigned long off) {
  pin_no = pin;
  // ...
}

Prefer {} for blocks, even when they contain one line.

With {} syntax:

  if (state) {
    waitTime = on_time;
  }
  else  {
    waitTime = off_time;
  }

Instead of declaring the size of an array, you could consider deducing it.

Deducing the size of the array:

byte pin_no[] = {13, 12, 14};
const int noOfLeds = sizeof(pin_no) / sizeof(pin_no[0]);

Combined with other suggestions:

byte const pinNo[] {13, 12, 14};
int const noOfLeds {sizeof(pinNo) / sizeof(pinNo[0])};

Or you could use the arraySize template as suggested in the post linked in post #8.

Use a single underscore suffix for private member variables and functions.

Again, this is not a recommendation as far as I know, but I find it convenient.

class ledType {
  // ...
  private:
    uint8_t pinNo_;
    // ...
};

I am merely giving suggestions, just pick whatever you think is useful.

@jfjlaros See my previous post

Your suggestions, as good practice as they might be, have strayed into the territory of writing a C++ tutorial

A tutorial cannot just consist of a series of sketches no matter how well they are written

1 Like

@jfjlaros, @noiasca , @J-M-L and last but not least dear @UKHeliBob ,

Thanks for helping me develop a tutorial that (at least ) I find useful...

I second @UKHeliBob's post which definitely points out where the difficulty lies:

Find the right level for beginners...

As I've said in various places, if you ask n developers, there are at least n+1 solutions. And in general, each of these solutions has its advantages ...

I myself am not a trained C++ specialist, I am more familiar with Pascal and therefore try to use my limited experience to show a way that can also help the hobbyist to acquire sufficient basic knowledge.

Personally, of course, I benefit from all your inputs!

I tried already to update the examples 8 and 9 with respect to your comments ...

P.S.: As it is 23:10 CET now I quit ... for the next days ...

Thanks!

I fancy that a Wiki would be a nice addition to the forum because it allows all members to contribute to each topic. However I'm afraid that this will require too much man-power to maintain the content ... Is it so?