Should I avoid parameters in Arduino code?

I programmed part of my current project using parameters to functions for all the good Software Engineering reasons. For example, one function declaration was:

void send(uint64_t slaveAddress,
          unsigned long* timeData,
          char* handshakeData,
          char* handshakeNumber,
          int* ackData,
          bool* newData,
          bool* dataSwapped);

Trouble was, my beautiful program would not run! :roll_eyes: I finally got it working by removing almost all the parameters. This function needed a slaveAddress which could be assigned so it is now declared thus:

void send(uint64_t slaveAddress);

Everything else is done using Global Parameters - and it works.

Question is, is this a general rule in programming for Arduino? Should programmers avoid using function parameters wherever possible?

void send(uint64_t slaveAddress,
          unsigned long* timeData,
          char* handshakeData,
          char* handshakeNumber,
          int* ackData,
          bool* newData,
          bool* dataSwapped);

void send(uint64_t slaveAddress,
          unsigned long* timeData,
          char* handshakeData,
          char* handshakeNumber,
          int* ackData,
          bool* newData,
          bool* dataSwapped){
  //does nothing          
}
/*
  Blink
  Turns on an LED on for one second, then off for one second, repeatedly.
 
  This example code is in the public domain.
 */
 
// Pin 13 has an LED connected on most Arduino boards.
// Pin 11 has the LED on Teensy 2.0
// Pin 6  has the LED on Teensy++ 2.0
// Pin 13 has the LED on Teensy 3.0
// give it a name:
int led = 13;

// the setup routine runs once when you press reset:
void setup() {                
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);     
}

// the loop routine runs over and over again forever:
void loop() {
  send(0, 0, 0, 0, 0, 0, 0); //test the send function
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);               // wait for a second
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);               // wait for a second
}

So this example works. There must be something else wrong in your non-working example. Running out of memory is very easy if you're not careful or you're used to programming big computers. "640K should be enough for anyone!"

Memory exhaustion can be "fixed" by just removing the one thing on top, which might have been this function.

No, using parameters doesn't cause functions not to run. Bad code causes functions not to run. You must have had a mistake somewhere.

Making variables global does reduce the need to pass parameters to functions, but you do pay a price. Any variable defined with global scope means that every line of code in that file has access to the variable. If the variable of interest has global scope but an incorrect value, where do you start to look for the error? If you define variables within a function block, at least you have a smaller section of code to examine to find the bug.

When you call a function, the parameters are passed on the stack; in your case that is 22 bytes if I counted correctly. Add at least 2 bytes (return address, not sure if anything else is pushed on the stack) so 24 bytes. So that is 24 bytes extra and those 24 bytes don't show in the RAM usage that is reported when you compile the code.

Assuming that all parameters are populated with global variables when you call the function and you use the global variables directly in your function, you save those 22 bytes. In your case it's 8 less (size of uint64_t) so you save 14 bytes.

Next you maybe declare a buffer in your function to place all your data in before actually sending it (a call to possibly some library function) so that is another 22 bytes.

Without seeing the complete code, it's difficult to say how much all this affects your running application but running low on memory will probably the root cause of your problem.

I suggest you remove uint64_t and replace it with an 8-byte array. The code for handling 64-bit arithmetic on an 8-bit processor is notoriously bulky.

You removed all the other parameters but kept the uint64_t one. I would have done it the other way around. A slave address surely does not go from 0 to 18446744073709551616? I mean, how many slaves are you expecting exactly? Eighteen quintillion, four hundred forty-six quadrillion, seven hundred forty-four trillion, seventy-three billion, seven hundred and nine million, five hundred fifty-one thousand, six hundred and sixteen?

1 billion slaves per second for 584 years.

Maybe he's enslaved all the grains of sand on all the beaches and he's looking to take over a few more planets?

Didn't you say in your other post that you found the problem was you had mixed up the order of the parameters to that function?

vagulus:
There have been many hours debugging to find the order of parameters switched. :cry:

As in here?

So there is nothing wrong with parameters, right? You just made a simple mistake. Well please post that there is no problem with "avoiding parameters".

It occurred to me that UNOs don't have sufficient runtime stack to cope with that sort of thing so I removed all the parameters from the code and it runs like a charm!

Because you had them in the wrong order, right?

Unos do in fact have enough stack to handle parameters. You are thrashing here.

Wow! I have never seen so many responses to a single posting! I shall try to cover all the main points.

Reply #1 MorganS

The only change to the 'non-working example' was the removal of all the extra parameters to the function.

Reply #4 sterretje

I am working with UNO R3s which is why I also suspect 'running low on memory'.

Reply #5 Nick Gammon

uint64_t is required by RF24::openWritingPipe().

nRF24L01s work with six pipes so I am working with max six slaves.

Reply #8 pert

Your quote refers to my undergrad years. A lot of Gin and Tonic has flowed under innumerable bridges since then. ;}

Reply #9 Nick Gammon

The point I was making about the parameter order was that I had meticulously checked that possibility.

My reason for bringing this up:

MorganS mentioned the old catch-cry "640 K of memory should be enough ! I started fooling around with PCs when the 8086 ruled the roost. That beast could address 1 MB of memory, although I am sure I never saw a machine that had that much, so it comes as a bit of a shock to me to realize that I am working with 32 KB!

I am hoping other Arduino Beginners will find this thread and not have the same headaches I had. :slight_smile:

OK, I see now. I didn't understand that comment.

uint64_t is required by RF24::openWritingPipe().

May I suggest, therefore, that you're using the wrong library?

I don't know anything about that library but I had a quick look at the documentation and saw this:

From http://tmrh20.github.io/RF24/classRF24.html#a50c8e68ee840e1860a31dbdc83afbd77

void RF24::openWritingPipe ( uint64_t address )

Open a pipe for writing

Note
For compatibility with old code only, see new function

I believe the new function it's referring to is (http://tmrh20.github.io/RF24/classRF24.html#af2e409e62d49a23e372a70b904ae30e1):

void RF24::openWritingPipe ( const uint8_t * address )

New: Open a pipe for writing via byte array. Old addressing format retained for compatibility.

I think that that depends on the rest of the code.

vagulus:
The only change to the 'non-working example' was the removal of all the extra parameters to the function.

That can't be the only change. Either you hardcoded the parameters or you changed to using globals or something else was changed.

Put all the "parameters" in a struct and pass a pointer to that struct.

Thanks again for the debate.

#13 pert
I certainly missed the ability to use uint8_t. I 'll try that.

#15 MorganS

It is my practice to use the same identifiers in parameters and function code as at variable/constant instantiation. That way I have don't get quite so confused :o :slight_smile: .

Simply removing the parameters from the pre-declaration and the function instantiation made the identifiers in the function code refer to the variables at instantiation.

There was no need for any other changes.

#16 gfvalvo

Using a struct sounds great. I'll try that too.

Looks like there is a lot of playing around to be done.
I guess it'll keep me off the street ;D

post #13 pert

My compiler (GCC) is happy with uint8_t but I am working with the nRF24L01 RF communications chip and it requires a 5-byte pipe address such as 0xF0F0F0F0AA. That's why I am stuck with u8int64_t.

I can't remember whether I mentioned the nRF24L01.

Thanks for bringing it up.

and it requires a 5-byte pipe address such as 0xF0F0F0F0AA. That's why I am stuck with u8int64_t.

But the forty bit number doesn't have to be stored in a uint64_t (which comes with baggage), it could be stored in a byte array.
That's down to the library, as has already been pointed out.