Simpler way to address elements in an array of structs

I have created a struct like this

struct turnoutStruct {
  byte servoPin;
  unsigned long straightMicros;
  unsigned long turnedMicros;
  Servo tServo;
};

and initialized it like this (for testing)

turnoutStruct turnout[2] = {
  {24,1300,1000},
  {26,1301,1001}
};

and then I wrote this little function to print the values just to make sure it is being intialized properly

void setupTurnouts() {
  for (byte n = 0; n < 2; n++) {
     Serial.print(turnout[n].servoPin);
     Serial.print(' ');
     Serial.print(turnout[n].straightMicros);
     Serial.print(' ');
     Serial.print(turnout[n].turnedMicros);
     Serial.println();
     turnout[n].tServo.attach(turnout[n].servoPin);
     turnout[n].tServo.writeMicroseconds(turnout[n].straightMicros);
     Serial.println("done");
  }
}

All this works fine.

However look at these two lines

turnout[n].tServo.attach(turnout[n].servoPin);
turnout[n].tServo.writeMicroseconds(turnout[n].straightMicros);

There is a huge amount of repetition of turnout[n]

Is there some simpler way I can express that code - maybe something like this

turnX = turnout[n];
turnX.tServo.attach(turnX.servoPin);
turnX.tServo.writeMicroseconds(turnX.straightMicros);

or even something simpler and more obvious?

I have done some Googling but it was ineffective, perhaps because I don’t know the appropriate search term, or maybe there is no simpler way.

…R

Macro definitions?

#define  turnX  turnout[n]

How about a pointer or reference:

void setupTurnouts()
{
  for (byte n = 0; n < 2; n++)
  {
    turnoutStruct *tsp;
    turnoutStruct &tsr=turnout[n];
    tsp=&turnout[n];
    Serial.print(turnout[n].servoPin);
    Serial.print(' ');
    Serial.print(turnout[n].straightMicros);
    Serial.print(' ');
    Serial.print(turnout[n].turnedMicros);
    Serial.println();
    turnout[n].tServo.attach(turnout[n].servoPin);
    turnout[n].tServo.writeMicroseconds(turnout[n].straightMicros);
    tsp->tServo.attach(turnout[n].servoPin);
    tsp->tServo.writeMicroseconds(turnout[n].straightMicros);
    tsr.tServo.attach(turnout[n].servoPin);
    tsr.tServo.writeMicroseconds(turnout[n].straightMicros);
    Serial.println("done");
  }
}

use reference. it will be optimized away

TurnoutStruct& item = turnout[n];
Servo& servo = item.tServo;

I would name the struct type with uppercase first letter.

I’d go full-OOP:

struct turnoutStruct {
  byte servoPin;
  unsigned long straightMicros;
  unsigned long turnedMicros;
  Servo tServo;

  void begin() {
    tServo.attach(servoPin);
    tServo.writeMicroseconds(straightMicros);
  }
};

turnoutStruct turnout[] = {
  {24, 1300, 1000},
  {26, 1301, 1001}
};

const uint8_t numTurnout = sizeof(turnout) / sizeof(turnout[0]);

void setupTurnouts() {
  for (byte n = 0; n < numTurnout; n++) {
    
    Serial.print(turnout[n].servoPin);
    Serial.print(' ');
    Serial.print(turnout[n].straightMicros);
    Serial.print(' ');
    Serial.print(turnout[n].turnedMicros);
    Serial.println();
    turnout[n].begin();
    Serial.println("done");
  }
}


void setup() {
  Serial.begin(115200);
  delay(1000);
  setupTurnouts();
}

void loop() {}

Or:

struct turnoutStruct {
  byte servoPin;
  unsigned long straightMicros;
  unsigned long turnedMicros;
  Servo *tServo;

  void begin() {
    if (!tServo) {
      tServo = new Servo;
      if (tServo) {
        tServo->attach(servoPin);
        tServo->writeMicroseconds(straightMicros);
      }
    }
  }
};

turnoutStruct turnout[] = {
  {24, 1300, 1000, nullptr},
  {26, 1301, 1001, nullptr}
};

const uint8_t numTurnout = sizeof(turnout) / sizeof(turnout[0]);

void setupTurnouts() {
  for (byte n = 0; n < numTurnout; n++) {

    Serial.print(turnout[n].servoPin);
    Serial.print(' ');
    Serial.print(turnout[n].straightMicros);
    Serial.print(' ');
    Serial.print(turnout[n].turnedMicros);
    Serial.println();
    turnout[n].begin();
    Serial.println("done");
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  setupTurnouts();
}

void loop() {}

Why not give the turnoutStruct struct (or class) methods to initialize and print itself? Then, it would be really simple.

void setupTurnouts()
{
  for (byte n = 0; n < 2; n++)
  {
     turnout[n].init();
     turnout[n].print();
  }
}

As you don't need the index, you could use range-based for loop + auto specifier:

void setupTurnouts() {
  for (auto& t : turnout) {
     Serial.print(t.servoPin);
     Serial.print(' ');
     Serial.print(t.straightMicros);
     Serial.print(' ');
     Serial.print(t.turnedMicros);
     Serial.println();
     t.tServo.attach(t.servoPin);
     t.tServo.writeMicroseconds(t.straightMicros);
     Serial.println("done");
  }
}

Thanks.

I had overlooked the idea of creating a class with methods. I will consider that. As there will be 10 or 12 turnouts that may be a good idea.

...R

I have arrived at the following using a Class which seems to work very neatly. Thanks again tor the hint.

// python-build-start
// action, upload
// board, arduino:avr:mega:cpu=atmega2560
// port, /dev/ttyACM0
// ide, 1.8.6
// python-build-end

#include <Servo.h>


class TurnoutClass {
    private:
        byte servoPin;
        unsigned long straightMicros;
        unsigned long turnedMicros;
        char setting;
        Servo tServo;
    public:
        TurnoutClass(byte p, unsigned long s, unsigned long t) {
            servoPin = p;
            straightMicros = s;
            turnedMicros = t;
            tServo.attach(servoPin);
            straight();
        }

        void straight() {
            tServo.writeMicroseconds(straightMicros);
            setting = 'S';
        }
        void turned() {
            tServo.writeMicroseconds(turnedMicros);
            setting = 'T';
        }
        bool isServoAttached() {
            return tServo.attached();
        }
        void showVals() {
            Serial.print("Pin "); Serial.print(servoPin);
            Serial.print("  Str "); Serial.print(straightMicros);
            Serial.print("  Trn "); Serial.print(turnedMicros);
            Serial.print("  Set "); Serial.print(setting);
        }
};

TurnoutClass turnout[2] = {
    {23,1300,1000},
    {26,1301,1001}
};

void setup() {
    Serial.begin(500000);
    Serial.println("Starting ClassTest.ino");
    Serial.println("Starting values");
    for (byte n = 0; n <2; n++) {
        turnout[n].showVals();
        Serial.print("  Attached "); Serial.print(turnout[n].isServoAttached());
        Serial.println();
    }
    Serial.println("Updated Setting");
    turnout[0].turned();
    turnout[0].showVals();
    Serial.println();
}



void loop() {

}

…R

Robin2:
I had overlooked the idea of creating a class with methods.

The idea was staring you in the face. A struct is a class. The only difference is the default access modifier.

Which is how tServo came to be reasonably initialized. The compiler synthesized a default constructor for your turnoutStruct class.

        TurnoutClass(byte p, unsigned long s, unsigned long t) {
...
            tServo.attach(servoPin);
            straight();

Do not touch the hardware until setup is called. Those calls to tServo.attach and straight need to be moved to a begin method which will (usually) be called from setup.

I suggest replacing showVals with printTo...

While having a galss of wnie I realized that the attach could not happen at compile time. This version works

// python-build-start
// action, upload
// board, arduino:avr:mega:cpu=atmega2560
// port, /dev/ttyACM0
// ide, 1.8.6
// python-build-end

#include <Servo.h>


class TurnoutClass {
    private:
        byte servoPin;
        unsigned long straightMicros;
        unsigned long turnedMicros;
        char setting;
        Servo tServo;
    public:
        TurnoutClass(byte p, unsigned long s, unsigned long t) {
            servoPin = p;
            straightMicros = s;
            turnedMicros = t;
        }

        void straight() {
            tServo.writeMicroseconds(straightMicros);
            setting = 'S';
        }
        void turned() {
            tServo.writeMicroseconds(turnedMicros);
            setting = 'T';
        }
        bool isServoAttached() {
            return tServo.attached();
        }
        void attach() {
            tServo.attach(servoPin);
            straight();
        }
        char direction() {
            return setting;
        }

        void showVals() {
            Serial.print("Pin "); Serial.print(servoPin);
            Serial.print("  Str "); Serial.print(straightMicros);
            Serial.print("  Trn "); Serial.print(turnedMicros);
            Serial.print("  Set "); Serial.print(setting);
        }
};

TurnoutClass turnout[2] = {
    {A0,1400,1000},
    {26,1301,1001}
};

void setup() {
    Serial.begin(500000);
    Serial.println("Starting ClassTest.ino");
    Serial.println("Starting values");
    for (byte n = 0; n <2; n++) {
        turnout[n].attach();
        turnout[n].showVals();
        Serial.print("  Attached "); Serial.print(turnout[n].isServoAttached());
        Serial.println();
    }
    Serial.println("Updated Setting");
    turnout[0].turned();
    turnout[0].showVals();
    Serial.println();
}



void loop() {

    if (turnout[0].direction() == 'S') {
        turnout[0].turned();
    }
    else {
        turnout[0].straight();
    }
    turnout[0].showVals();
    Serial.println();
    delay(2000);
}

…R

I have looked at the link but I don't understand what you are suggesting or why.

...R

Instead of this..

turnout[0].showVals();
Serial.println();

This...

Serial.println(turnout[0]);

Which is more intuitive and allows printing to other destinations like an LCD.

I think I see what you are getting at. The showVals() is only for debugging so not worth changing.

...R