Multiple IDE files error

I'm working on a big(is) project and decided to move a load of definitions, constants and functions to a separate file in the same sketch folder expecting the IDE to just link them all together when compiled but am having a problem with a function with an optional parameter.

This single file sketch compiles without problem...

#include <avr/pgmspace.h>

byte cableNum = 0;                          // Cable number, used to index into PROGMEM cable arrays
#define cableMax 12                         // MAX value for cableNum

// Cable Name Strings
prog_uchar cableText00[] PROGMEM = {"UNKNOWN00 \0"};
prog_uchar cableText01[] PROGMEM = {"BD SD01 \0"};
prog_uchar cableText02[] PROGMEM = {"BD SD03 \0"};
prog_uchar cableText03[] PROGMEM = {"UNKNOWN03 \0"};
prog_uchar cableText04[] PROGMEM = {"UNKNOWN04 \0"};
prog_uchar cableText05[] PROGMEM = {"UNKNOWN05 \0"};
prog_uchar cableText06[] PROGMEM = {"UNKNOWN06 \0"};
prog_uchar cableText07[] PROGMEM = {"UNKNOWN07 \0"};
prog_uchar cableText08[] PROGMEM = {"UNKNOWN08 \0"};
prog_uchar cableText09[] PROGMEM = {"UNKNOWN09 \0"};
prog_uchar cableText10[] PROGMEM = {"UNKNOWN10 \0"};
prog_uchar cableText11[] PROGMEM = {"UNKNOWN11 \0"};
//Message String Pointer Table (Needed to access above Message Strings in PROGMEM)
prog_uchar *string_table[cableMax] PROGMEM ={
    cableText00,
    cableText01,
    cableText02,
    cableText03,
    cableText04,
    cableText05,
    cableText06,
    cableText07,
    cableText08,
    cableText09,
    cableText10,
    cableText11
};

void printFS(byte messageNumber, bool CRLF = false) {
    if (messageNumber >= cableMax){
        return;
    }
    prog_uchar * messageString = (prog_uchar*)pgm_read_word(&(string_table[messageNumber]));
    
    int counter = 0;
    byte myChar = 0;
    
    do {
        // read back a char 
        myChar =  pgm_read_byte_near(messageString + counter++); 
        if (myChar != 0){
            Serial.print((char)myChar);
        }
    } 
    while (myChar != 0);
    
    if (CRLF)
    Serial.println();
}

void setup(){
    Serial.begin(115200);
    for (byte x = 0; x < cableMax; x++){
        Serial.print(x);
        Serial.print(F(" = "));
        printFS(x, true);
    }
}

void loop(){
}

But when split into 2 files...

byte cableNum = 0;                          // Cable number, used to index into PROGMEM cable arrays
#define cableMax 12                         // MAX value for cableNum

void setup(){
    Serial.begin(115200);
    for (byte x = 0; x < cableMax; x++){
        Serial.print(x);
        Serial.print(F(" = "));
        printFS(x, true);
    }
}

void loop(){
}

and

#include <avr/pgmspace.h>

// Cable Name Strings
prog_uchar cableText00[] PROGMEM = {"UNKNOWN00 \0"};
prog_uchar cableText01[] PROGMEM = {"BD SD01 \0"};
prog_uchar cableText02[] PROGMEM = {"BD SD03 \0"};
prog_uchar cableText03[] PROGMEM = {"UNKNOWN03 \0"};
prog_uchar cableText04[] PROGMEM = {"UNKNOWN04 \0"};
prog_uchar cableText05[] PROGMEM = {"UNKNOWN05 \0"};
prog_uchar cableText06[] PROGMEM = {"UNKNOWN06 \0"};
prog_uchar cableText07[] PROGMEM = {"UNKNOWN07 \0"};
prog_uchar cableText08[] PROGMEM = {"UNKNOWN08 \0"};
prog_uchar cableText09[] PROGMEM = {"UNKNOWN09 \0"};
prog_uchar cableText10[] PROGMEM = {"UNKNOWN10 \0"};
prog_uchar cableText11[] PROGMEM = {"UNKNOWN11 \0"};
//Message String Pointer Table (Needed to access above Message Strings in PROGMEM)
prog_uchar *string_table[cableMax] PROGMEM ={
    cableText00,
    cableText01,
    cableText02,
    cableText03,
    cableText04,
    cableText05,
    cableText06,
    cableText07,
    cableText08,
    cableText09,
    cableText10,
    cableText11
};

void printFS(byte messageNumber, bool CRLF = false) {
    if (messageNumber >= cableMax){
        return;
    }
    prog_uchar * messageString = (prog_uchar*)pgm_read_word(&(string_table[messageNumber]));
    
    int counter = 0;
    byte myChar = 0;
    
    do {
        // read back a char 
        myChar =  pgm_read_byte_near(messageString + counter++); 
        if (myChar != 0){
            Serial.print((char)myChar);
        }
    } 
    while (myChar != 0);
    
    if (CRLF)
    Serial.println();
}

I get

sketch_dec15a.ino: In function 'void setup()':
sketch_dec15a:9: error: 'printFS' was not declared in this scope

error message.
If I change

void printFS(byte messageNumber, bool CRLF = false) {

to

void printFS(byte messageNumber, bool CRLF) {

then it compiles okay.
Is this an error or have I missed some subtle point.

void printFS(byte messageNumber, bool CRLF = false) {

The primitive IDE parse routine doesn't recognise that as a valid function prototype as it doesn't like the " = false" bit. As a result it doesn't add the prototype to the top of your program.

You can manually fix it by adding:

void printFS(byte messageNumber, bool CRLF);

to the top of your program.

majenko:

void printFS(byte messageNumber, bool CRLF = false) {

The primitive IDE parse routine doesn't recognise that as a valid function prototype as it doesn't like the " = false" bit. As a result it doesn't add the prototype to the top of your program.

You can manually fix it by adding:

void printFS(byte messageNumber, bool CRLF);

to the top of your program.

Do you mean just add the single line? I had tried moving the entire procedure to the main file but then get errors with string_table not declared.
I have since turned the extra file into an header file instead and it compiles again without errors and appears to run okay.

You can't just randomly or arbitrarily divide source code files into separate components. Not in Arduino C++ and not in any other kind of C or C++ either.

There has to be some kind of hierarchy or framework connecting them logically together, whether through header files or class definitions or prototypes or extern declarations or compiler directives or some combination of those.

michinyon:
You can't just randomly or arbitrarily divide source code files into separate components. Not in Arduino C++ and not in any other kind of C or C++ either.

There has to be some kind of hierarchy or framework connecting them logically together, whether through header files or class definitions or prototypes or extern declarations or compiler directives or some combination of those.

There was nothing random about the way I divided the source code.
I thought the Arduino IDE just basically concatenated all .ino files in the same folder but it is obviously not quite that simple. I have since resorted to putting the code in a header file and the IDE seems happy with that. I was wondering if the problem is a bug in the IDE where it cannot resolve optional arguments across .ino files or I had missed something subtle.

By just adding the single line I quoted you are telling the compiler "This is a function I will be defining later in the program". The IDE usually adds these lines automatically, but doesn't do a very good job of it.

majenko:
By just adding the single line I quoted you are telling the compiler "This is a function I will be defining later in the program". The IDE usually adds these lines automatically, but doesn't do a very good job of it.

Okay thanks Mark. I won't change it all back from the header file now but will try to remember for the next big project.

michinyon:
You can't just randomly or arbitrarily divide source code files into separate components. Not in Arduino C++ and not in any other kind of C or C++ either.

There has to be some kind of hierarchy or framework connecting them logically together, whether through header files or class definitions or prototypes or extern declarations or compiler directives or some combination of those.

I wonder if this is entirely correct - even if it may be the recommended way.

My experience suggests that the IDE loads additional .ino files in alphabetical order after the principal file and later files can rely on stuff in earlier files. This is certainly logical and hierarchical but doesn't require the other stuff you mention.

I am not a C expert (nor do I want to be) and it seems to me that the compiler treats the code in #include files as if their content had been typed at the point of the #include.

I haven't carried out any tests to see where within the principal script it "includes" the additional .ino files.

...R

Robin2:

michinyon:
You can't just randomly or arbitrarily divide source code files into separate components. Not in Arduino C++ and not in any other kind of C or C++ either.

There has to be some kind of hierarchy or framework connecting them logically together, whether through header files or class definitions or prototypes or extern declarations or compiler directives or some combination of those.

I wonder if this is entirely correct - even if it may be the recommended way.

My experience suggests that the IDE loads additional .ino files in alphabetical order after the principal file and later files can rely on stuff in earlier files. This is certainly logical and hierarchical but doesn't require the other stuff you mention.

Under normal circumstances that is quite right, but not when the IDE fails to do its job properly.

The problem is that the regex pattern that matches the function prototypes to find what should be entered at the top of the main file does not cope with anything that's not really basic.

That includes specifying default values for parameters.

Consequently the function prototype for the printFS function doesn't get added to the top of the main file, and so it doesn't compile as the function is being called before it is defined.

In C and C++ you must declare your functions before using them - either by having the whole function BEFORE it's first called in the file, or by having a function prototype before the function is first called.

majenko:
The problem is that the regex pattern that matches the function prototypes to find what should be entered at the top of the main file does not cope with anything that's not really basic.

That includes specifying default values for parameters.

Interesting, and helpful.

Does this mean that you can specify a default value for the parameters in a function provided you have previously declared a function prototype?

And, if so, does that mean you don't need to provide a value for the parameter that has the default value so that if I could do the following? ...

void myFunc(int firstVal, int secondVal);

// later ...

void myFunc(int firstVal, int secondVal = 12) {
  // etc, etc
}

// later still ...

myFunc(2376); // which assumes that secondVal will automatically get the value 12

I hope that makes sense.

...R

Apart from me making a small mistake, yes.

You have to specify the default value in the prototype, not the function:

void myFunc(int firstVal, int secondVal = 12);

void myFunc(int firstVal, int secondVal) {
  // etc, etc
}

Note that you can only specify defaults from right-to-left with no breaks, so the following is valid:

void myFunc(int v1, int v2, int v3 = 8, int v4 = 19, int v5 = 1873);

But this isn't valid:

void myFunc(int v1=8, int v2, int v3, int v4 = 19, int v5);

And neither is this:

void myFunc(int v1, int v2, int v3 = 49, int v4 int v5);

Thanks. That's pretty much the same as JRuby / Ruby - I hadn't realized it was possible in C.

...R

Robin2:
Thanks. That's pretty much the same as JRuby / Ruby - I hadn't realized it was possible in C.

...R

It's not. It's possible in C++ though.

OK. Thanks for the correction.

...R