Call functions numerically with pointers

Hi. I'm using something similar to the following:

switch (Command)
{
  case 1:
    Command1();
    break;

  case 2:
    Command2();
    break;

  case 3:
    Command3();
    break;

  // etc...
}

Is there a less long-winded way to achieve this? For example, by using a PROGMEM lookup table where each element contained a pointer to a function?
I notice 'Command1();' can be replaced with '(*Command1)();' without issue as long as the procedure is declared in the code before it is called. However I'm not sure how to implement a method that exploits this.

Have a look at pointers to functions - an array of them will help you significantly reduce that code.

Why not have 1 command and pass in the argument?

Command (selectedval);

I guess you'd still need to use case to see what got passed...

a linked list would do it...

To clarify, arrays and linked lists use RAM, which I'd hope could be avoided in this case.

A PROGMEM array would be ideal, but I can't find anything on how to get function pointers in and out of them.

but I can't find anything on how to get function pointers in and out of them.

Perhaps because you are thinking that there is something magic about a pointer to a function. There is not. How may functions are you think of? I can't see that moving a few pointers out of SRAM is going to accomplish all that much.

PROGMEM void (*Farr[])() = {func1, func2, func3};

void setup(){
  Serial.begin(115200);
  for(int i = 0; i < 3; i++) Farr[i]();
}//setup()

void loop(){}//loop()

void func1(){
  Serial.println(F("function1"));
}//func1()

void func2(){
  Serial.println(F("function2"));
}//func2()

void func3(){
  Serial.println(F("function3"));
}//func3()

Thank you, nilton61! Karma gratefully bestowed!

I've encountered a weird problem with the method above. I'm baffled. Can anyone figure out why the code below fails when you uncomment either or both of the marked lines?

PROGMEM void (*Farr[])() = {FuncA, FuncB, FuncC};

byte Global;

void setup(){
  Serial.begin(9600);
  
  Global = 1;
  byte Local = 1;
  //Local = Global; // THIS SCREWS EVERYTHING UP
  
  // Global and Local display as expected:
  Serial.print("Global = ");
  Serial.println(Global);
  Serial.print("Local = ");
  Serial.println(Local);
  
  // These work fine:
  Farr[0](); // Calls FuncA
  Farr[1](); // Calls FuncB
  Farr[2](); // Calls FuncC

  // This works fine:
  Farr[Local](); // Calls FuncB
  
  //Farr[Global](); // THIS ALSO SCREWS EVERYTHING UP
}

void loop(){}

void FuncA(){
  Serial.println("Function A");
}

void FuncB(){
  Serial.println("Function B");
}

void FuncC(){
  Serial.println("Function C");
}

I wonder if there's some behind-the-scenes optimisation happening and the compiler doesn't realise what the 'FarrGlobal' line needs to function properly?

Perhaps you need to say more than "This screws everything up".

The code does something. You expect it to do something. Presumably those two are not the same thing. But, what either one is is a mystery.

Sorry, I assumed people would try it for themselves. To clarify, the code demonstrates the principle of calling functions numerically, provided in nilton61's post above. However, both the commented lines cause the program to go into an infinite loop, spewing characters from the serial port, despite containing nothing obvious to cause such a dramatic change the operation of the code. It seems they cause the function call to jump to the wrong location, disrupting the flow of the program, but I can't see why.

You are storing the function pointers in PROGMEM and then accessing them as though they were stored in SRAM. That is not going to work well. Since the 3 pointers use a total of 6 bytes, I really can't understand why they can't live in SRAM.

I had no problem compiling and running your program with the suspicious calls uncommented. Are you sure that you have changed the serial speed in the monitor? My example was 115200 you use 9600(why?)

PaulS:
You are storing the function pointers in PROGMEM and then accessing them as though they were stored in SRAM. That is not going to work well. Since the 3 pointers use a total of 6 bytes, I really can't understand why they can't live in SRAM.

Apart from that, the example shouldn't have worked.

The pointers are in PROGMEM and it was called from non-PROGMEM. However the compiler came to the rescue and optimized the loop away:

 11a:	0e 94 12 01 	call	0x224	; 0x224 <_ZN14HardwareSerial5beginEm>
 11e:	0e 94 80 00 	call	0x100	; 0x100 <_Z5func1v>
 122:	0e 94 79 00 	call	0xf2	; 0xf2 <_Z5func2v>
 126:	0e 94 72 00 	call	0xe4	; 0xe4 <_Z5func3v>

Notice that the loop doesn't exist in the code?

So to make it really do what we want we have to prevent the optimization:

void (*Farr[])() PROGMEM  = {func1, func2, func3};

volatile int foo = 3;

void setup(){
  Serial.begin(115200);
  for(int i = 0; i < foo; i++) Farr[i]();
}//setup()

Now, the code fails to print anything, which is what you want. :wink:

I agree with PaulS that you hardly need to use PROGMEM here, but in case you were envisaging a much larger array, this works:

typedef void (* myFunction) ();

PROGMEM myFunction Farr []  = {func1, func2, func3};

volatile int foo = 3;

void setup(){
  Serial.begin(115200);
  for(int i = 0; i < foo; i++) 
    {
    myFunction f = (myFunction) pgm_read_word (&Farr [i]);
    f ();
    }
}//setup()

void loop(){}//loop()

void func1(){
  Serial.println(F("function1"));
}//func1()

void func2(){
  Serial.println(F("function2"));
}//func2()

void func3(){
  Serial.println(F("function3"));
}//func3()

A little strange. When trying nicks example i get this:

sketch_dec13a.ino:3: warning: only initialized variables can be placed into program memory area

When running this code:

void (*Farr[])() PROGMEM  = {FuncA, FuncB, FuncC};

volatile int foo = 3;

void setup(){
  union {
  void (*p)();
  int i;
  }cv;
  Serial.begin(115200);
  for(int i = 0; i < foo; i++){
    cv.p = Farr[i];
    Serial.println(cv.i);
  }
}//setup()

void loop(){}

void FuncA(){
  Serial.println("Function A");
}

void FuncB(){
  Serial.println("Function B");
}

void FuncC(){
  Serial.println("Function C");
}

i get this output:

0
514
514

This leads me to believe that despite that the addresses of the functions should be known at compile time the array is not initialized. Why??

nilton61, I used 9600 because I've known the serial monitor to crash when it receives large volumes of data. Your last example isn't using pgm_read_word, perhaps this is the problem.

Nick, accessing the compiled code is useful, how do you do that? Seems the optimiser is overstepping its responsibilities if it makes code work that would otherwise fail! Your code compiled fine for me. Packaging it into a macro and implementing it in my example gives the following, which now also works!

typedef void (* myFunction) ();
PROGMEM myFunction FunctionArray[] = {FuncA, FuncB, FuncC};
#define CallF(Num) ((myFunction) pgm_read_word (&FunctionArray [Num])) ()

byte Global;

void setup(){
  Serial.begin(115200);
  
  Global = 1;
  byte Local = 1;
  // Local = Global; // THIS NO LONGER SCREWS EVERYTHING UP
  
  // Global and Local display as expected:
  Serial.print("Global = ");
  Serial.println(Global);
  Serial.print("Local = ");
  Serial.println(Local);
  
  // These work fine:
  CallF(0);
  CallF(1);
  CallF(2);
  
  // These now work too!
  CallF(Local); // Calls FuncB
  CallF(Global); // Calls FuncB
  
  // For good measure so does this:
  for(byte a = 0; a <= 2; a++)
  {
    CallF(a);
  }
}

void loop(){}

void FuncA(){
  Serial.println("Function A");
}

void FuncB(){
  Serial.println("Function B");
}

void FuncC(){
  Serial.println("Function C");
}

Thanks for the advice!

As an aside, yes I'm using PROGMEM because I want the potential to handle much larger arrays of functions, and because storing constants in RAM just seems wrong to me!

paulrd:
Nick, accessing the compiled code is useful, how do you do that?

So everything allocated with the PROGMEM macro must be retrieved with pgm_read_xxxxx()?

I read this:

GCC has a special keyword, attribute that is used to attach different attributes to things such as function declarations, variables, and types. This keyword is followed by an attribute specification in double parentheses. In AVR GCC, there is a special attribute called progmem. This attribute is use on data declarations, and tells the compiler to place the data in the Program Memory (Flash).

AVR-Libc provides a simple macro PROGMEM that is defined as the attribute syntax of GCC with the progmem attribute. This macro was created as a convenience to the end user, as we will see below. The PROGMEM macro is defined in the <avr/pgmspace.h> system header file.

here:http://www.atmel.no/webdoc/AVRLibcReferenceManual/pgmspace_1pgmspace_introduction.html

A few pages down it says this:

Now that your data resides in the Program Space, your code to access (read) the data will no longer work. The code that gets generated will retrieve the data that is located at the address of the mydata array, plus offsets indexed by the i and j variables. However, the final address that is calculated where to the retrieve the data points to the Data Space! Not the Program Space where the data is actually located. It is likely that you will be retrieving some garbage. The problem is that AVR GCC does not intrinsically know that the data resides in the Program Space.

http://www.atmel.no/webdoc/AVRLibcReferenceManual/pgmspace_1pgmspace_data.html

Any reason why the compiler isnt aware of its own attribute keyword?

I believe C++ can distinguish types but not attributes.