Array-of-struct and Servo instance array initialization question for C++

Hi
I'm trying to make an array of Servo instances scale depending on the size of an array of structs. The larger code, and this MRE, both compile successfully, and the larger code works just fine, if the value numChan is a constant at compile time. But that leads me to editing a constant, and editing the array, if I change the number of lines, which is suboptimal.
I'd like the value of numChan to be pre-determined by the number of lines present in the array-of-structs, but the compiler gives me an error if the size of the Servo array when compiling isn't a constant. Amusingly, it effectively is, because the compiler will have just finished resolving Turnouts through it's initialization, so array size is known. Funny, hah!

Is this happening because sizeof() has some limitation in this context? Compiler gurus?

As can be seen in the code, it would be even tidier if the Servo reference could be incorporated into the Struct (see commented out member srvo), but that may be a 'bridge too far'.

There is probably a way, but I'm not getting it on this dreary Christmas Eve morning. Any suggestions will be welcome. FYI, the code in setup() can safely be ignored, although some form of allocation for the servo instance within the loop in setup() might be the solution. The use of Turnouts needs to be there in setup() or the compiler happily throws away everything as unused, preventing observation of memory usage changes when the array of structs has a row added or removed. Ultimately, I'd like this array of structs to be drawn from EEPROM, hence my interest in it's size. Code and Error message:

#include <Servo.h>
struct Turnout {
  const uint8_t servo_Pin;
  const uint8_t button_Pin;
  const uint8_t LED_Pin;
  uint8_t lastButtonState;
  uint8_t turnoutState;
  uint8_t servo_Home;
  uint8_t servo_Away;
  uint32_t lastTimeButtonStateChanged;
  //Servo srvo;
};

struct Turnout Turnouts[] = {
  {8, A5, 9, 1, 0, 45, 135, 0},
  {10, A6, 11, 1, 0, 45, 135, 0},
  {12, A7, 14, 1, 0, 45, 135, 0}
};

//now calculate our loop count for for loops
uint8_t numChan = sizeof(Turnouts) / sizeof(Turnouts[0]);           //defines how many servos we're driving

//And lastly, create Servo instances; ideally, this would also be folded into the Turnout struct as shown above, but I can live with it separate.  This is the line that causes the compile error
Servo Servos[numChan] = {};

void setup() {
  for (int chan = 0; chan < numChan; chan++) {
    pinMode(Turnouts[chan].LED_Pin, OUTPUT);
    Servos[chan].attach(Turnouts[chan].servo_Pin);//attach servo
  }
}

void loop() {
}

Arduino: 1.8.15 (Windows 10), Board: "Arduino Nano, ATmega328P"

structtest:25:21: error: array bound is not an integer constant before ']' token

Servo Servos[numChan] = {};

                 ^

C:\Users\CAMSYSCA4\Documents\Arduino\structtest\structtest.ino: In function 'void setup()':

structtest:30:5: error: 'Servos' was not declared in this scope

 Servos[chan].attach(Turnouts[chan].servo_Pin);//attach servo

 ^~~~~~

C:\Users\CAMSYSCA4\Documents\Arduino\structtest\structtest.ino:30:5: note: suggested alternative: 'Servo'

 Servos[chan].attach(Turnouts[chan].servo_Pin);//attach servo

 ^~~~~~

 Servo

Multiple libraries were found for "Servo.h"

Used: C:\Users\CAMSYSCA4\Documents\Arduino\libraries\Servo

Not used: C:\Program Files (x86)\Arduino\libraries\Servo

Using library Servo at version 1.2.1 in folder: C:\Users\CAMSYSCA4\Documents\Arduino\libraries\Servo

exit status 1

array bound is not an integer constant before ']' token

that seems to me the right Idea.
Why didn't you continue to follow that way?

change to

Servo Servos[numChan];

@paulpaulson
Nope. Same error.

compiles but not tested:

/*
  https://forum.arduino.cc/t/array-of-struct-and-servo-instance-array-initialization-question-for-c/1203322/5
*/

#include <Servo.h>
struct Turnout {
  const uint8_t servo_Pin;
  const uint8_t button_Pin;
  const uint8_t LED_Pin;
  uint8_t lastButtonState;
  uint8_t turnoutState;
  uint8_t servo_Home;
  uint8_t servo_Away;
  uint32_t lastTimeButtonStateChanged;
  Servo servo;
};

struct Turnout turnouts[] = {
  {8, A5, 9, 1, 0, 45, 135, 0},
  {10, A6, 11, 1, 0, 45, 135, 0},
  {12, A7, 14, 1, 0, 45, 135, 0}
};

//now calculate our loop count for for loops
uint8_t numChan = sizeof(turnouts) / sizeof(turnouts[0]);           //defines how many servos we're driving

//And lastly, create Servo instances; ideally, this would also be folded into the Turnout struct as shown above, but I can live with it separate.  This is the line that causes the compile error
//Servo Servos[numChan] = {};

void setup() {
  for (int chan = 0; chan < numChan; chan++) {
    pinMode(turnouts[chan].LED_Pin, OUTPUT);
    turnouts[chan].servo.attach(turnouts[chan].servo_Pin);//attach servo
  }
}

void loop() {
}

p.s.:

Classnames start with Capital (start with large letter)
a object/one instance and variables start with small letter - didn't correct all ... but do it for your own.

by the way ... start using base range for loop

2 Likes

Because, what then do you put in ?? for the initialization?

struct Turnout Turnouts[] = {
  {8, A5, 9, 1, 0, 45, 135, 0, ??},
  {10, A6, 11, 1, 0, 45, 135, 0, ??},
  {12, A7, 14, 1, 0, 45, 135, 0, ??}
};

The following compiles, so I'll go test it in the larger code; will be big changes, so I hope it works:

#include <Servo.h>

struct Turnout {
  const uint8_t servo_Pin;
  const uint8_t button_Pin;
  const uint8_t LED_Pin;
  uint8_t lastButtonState;
  uint8_t turnoutState;
  uint8_t servo_Home;
  uint8_t servo_Away;
  uint32_t lastTimeButtonStateChanged;
  Servo srvo;
};

struct Turnout Turnouts[] = {
  {8, A5, 9, 1, 0, 45, 135, 0},
  {10, A6, 11, 1, 0, 45, 135, 0},
  {12, A7, 14, 1, 0, 45, 135, 0}
};

//now calculate our loop count for for loops
uint8_t numChan = sizeof(Turnouts) / sizeof(Turnouts[0]);           //defines how many servos we're driving

//And lastly, create Servo instances; ideally, this would also be folded into a Turnout struct as shown above, but I can live with it separate.
//Servo Servos[numChan];

void setup() {
  for (int chan = 0; chan < numChan; chan++) {
    pinMode(Turnouts[chan].LED_Pin, OUTPUT);
    Turnouts[chan].srvo.attach(Turnouts[chan].servo_Pin);//attach servo
  }
}

void loop() {
}

This seems to be the relevant part of the error message.

Adding a const seems to fix the problem (well, it compiles, I did no further testing).

uint8_t const numChan = sizeof(Turnouts) / sizeof(Turnouts[0]);

.

don't hope.
Test it!

Well, that's what I said I was going to do, but thanks! We'll see.

I read like you want to test it in a larger code - in my opinion you should test it with that small code.

1 Like

was just looking at something very similar on the Arduini website

// model RR turnout controller

# include <Servo.h>

struct Turnout {
    const uint8_t ServoPin;
    const uint8_t ButtonPin;
    const uint8_t LedPin;

    uint8_t     servoPos [2];
    const char *desc;

    uint8_t     butState;
    uint8_t     swState;
    uint32_t    lastTimeButtonStateChanged;
    Servo       servo;
};

struct Turnout turnouts[] = {
//    svr  but  led    svr pos
    {   8,  A5,   9, { 45,  135 },  "sw1" }, 
    {  10,  A6,  11, { 45,  135 },  "sw2" }, 
    {  12,  A7,  14, { 45,  135 },  "sw3" }
};

uint8_t numChan = sizeof(turnouts) / sizeof(turnouts[0]); 

enum { LedOff = HIGH, LedOn = LOW };

char s [90];

// -----------------------------------------------------------------------------
void sw (
    Turnout *p )
{
     sprintf (s, "sw: %2d %s\n",   p->swState, p->desc);
     digitalWrite (p->LedPin,     p->swState ? LedOn : LedOff);
     p->servo.write (p->servoPos [p->swState]);
}

// -----------------------------------------------------------------------------
void loop()
{
    Turnout *p = &turnouts [0];
    for (int chan = 0; chan < numChan; chan++, p++) {
        byte but = digitalRead (p->ButtonPin);
        if (p->butState != but)  {
            p->butState = but;

            if (LOW == but)  {
                p->swState = ! p->swState;
                sw (p);
            }
            delay (50);         // debounce
        }
    }
}

// -----------------------------------------------------------------------------
void setup()
{
    Serial.begin (9600);

    Turnout *p = &turnouts [0];
    for (int chan = 0; chan < numChan; chan++, p++) {
        pinMode    (p->ButtonPin, INPUT_PULLUP);
        p->butState = digitalRead (p->ButtonPin);

        pinMode      (p->LedPin, OUTPUT);
        p->servo.attach (p->ServoPin);

        sw (p);
    }
}

If you don't want the style police on your six.

a7

Too late!

It does compile and work, at least in Wokwi.
However, of course, we still have the initialization issue I asked about in #6, which generates a compiler warning per line of the array initialization:
warning: missing initializer for member 'Turnout::servo' [-Wmissing-field-initializers]

are type names variables ?

this goes away by making numChan const

bear in mind that not all struct elements needs to be initialized. the ones that need to be can be put first and the variables put at the end so that they are initialized by default to zero.

you could use a initializer list and just handover the values you need.
This gets rid off the warning:

/*
  https://forum.arduino.cc/t/array-of-struct-and-servo-instance-array-initialization-question-for-c/1203322/5
*/

#include <Servo.h>
struct Turnout {
  const uint8_t servo_Pin;
  const uint8_t button_Pin;
  const uint8_t LED_Pin;
  uint8_t lastButtonState;
  uint8_t turnoutState;
  uint8_t servo_Home;
  uint8_t servo_Away;
  uint32_t lastTimeButtonStateChanged;
  Servo servo;
  
  Turnout (uint8_t servo_Pin, uint8_t button_Pin, uint8_t LED_Pin, uint8_t lastButtonState, uint8_t turnoutState, uint8_t servo_Home, uint8_t servo_Away, uint32_t lastTimeButtonStateChanged) :
    servo_Pin(servo_Pin),
    button_Pin(button_Pin),
    LED_Pin(LED_Pin),
    lastButtonState(lastButtonState),
    turnoutState(turnoutState),
    servo_Home(servo_Home),
    servo_Away(servo_Away),
    lastTimeButtonStateChanged(lastTimeButtonStateChanged) {}
};

struct Turnout turnouts[] = {
  {8, A5, 9, 1, 0, 45, 135, 0},
  {10, A6, 11, 1, 0, 45, 135, 0},
  {12, A7, 14, 1, 0, 45, 135, 0}
};

//now calculate our loop count for for loops
uint8_t numChan = sizeof(turnouts) / sizeof(turnouts[0]);           //defines how many servos we're driving

//And lastly, create Servo instances; ideally, this would also be folded into the Turnout struct as shown above, but I can live with it separate.  This is the line that causes the compile error
//Servo Servos[numChan] = {};

void setup() {
  for (int chan = 0; chan < numChan; chan++) {
    pinMode(turnouts[chan].LED_Pin, OUTPUT);
    turnouts[chan].servo.attach(turnouts[chan].servo_Pin);//attach servo
  }
}

void loop() {
}

May be you want to initialize some 0 values now (turnoutState and lastTimeButtonStateChanged) so you don't need to set them to 0 externally.

Sorry, I was in a bad mood. Never mind why.

I should have just said

While any identifier can be used as the name of a class, by convention class names start with a capital letter.

Class names are identifiers, as far as I can tell anything that flies as an identifier will be syntactically valid.

It is part of keeping C and the languages that are its progeny simple. Very few exceptions to broad rules of syntax.

a7

1 Like

Tidied up a little more:

/*
  https://forum.arduino.cc/t/array-of-struct-and-servo-instance-array-initialization-question-for-c/1203322/5
*/

#include <Servo.h>
struct Turnout {
  const uint8_t servo_Pin;
  const uint8_t button_Pin;
  const uint8_t LED_Pin;
  uint8_t lastButtonState;
  uint8_t turnoutState;
  uint8_t servo_Home;
  uint8_t servo_Away;
  uint32_t lastTimeButtonStateChanged;
  Servo servo;

  Turnout (uint8_t servo_Pin, uint8_t button_Pin, uint8_t LED_Pin, uint8_t lastButtonState, uint8_t turnoutState, uint8_t servo_Home, uint8_t servo_Away, uint32_t lastTimeButtonStateChanged) :
    servo_Pin(servo_Pin),
    button_Pin(button_Pin),
    LED_Pin(LED_Pin),
    lastButtonState(lastButtonState),
    turnoutState(turnoutState),
    servo_Home(servo_Home),
    servo_Away(servo_Away),
    lastTimeButtonStateChanged(lastTimeButtonStateChanged) {}

  void begin() {
    servo.attach(servo_Pin);
  }
};

struct Turnout turnouts[] = {
  {8, A5, 9, 1, 0, 45, 135, 0},
  {10, A6, 11, 1, 0, 45, 135, 0},
  {12, A7, 14, 1, 0, 45, 135, 0}
};

void setup() {
  for (auto &turnout : turnouts) {
    turnout.begin();
  }
}

void loop() {
}

this isn't being invoked because turnouts [] is statically initialized rather than using constructors