More than one way to code to do the same exact thing with 4 LEDs.

Hello everyone.

I find it interesting that defining variables and constants can be a learning curve with Arduino.

Here are 3 examples to pulse 4 LEDs sequentially.
(More than one way to skin a cat, yes?)

Is #define the same as const int in the Arduino world?

Example 1:

#define LED1 2 // R
#define LED2 3 // Y
#define LED3 4 // G
#define LED4 5 // W


void setup() {
   pinMode(LED1, OUTPUT);
   pinMode(LED2, OUTPUT);
   pinMode(LED3, OUTPUT);
   pinMode(LED4, OUTPUT);
 
}

void loop() {
  digitalWrite(LED1, HIGH);    // turn on LED1
  delay(0.5);                 // wait for 2ms
  digitalWrite(LED1, LOW);    // turn off LED1
  delay(100);                  // wait for 2ms
  digitalWrite(LED2, HIGH);    // turn on LED2
  delay(0.5);                  // wait for 2ms
  digitalWrite(LED2, LOW);    // turn off LED1
  delay(100);                  // wait for 2ms  
  digitalWrite(LED3, HIGH);    // turn on LED3
  delay(0.5);                  // wait for 2ms
  digitalWrite(LED3, LOW);    // turn off LED1
  delay(100);                  // wait for 2ms
  digitalWrite(LED4, HIGH);    // turn on LED4
  delay(0.5);                // wait for 2ms
  digitalWrite(LED4, LOW);    // turn off LED1
  delay(100);                  // wait for 2ms
}

Example 2:

const int LED1 = 2; // R
const int LED2 = 3; // Y
const int LED3 = 4; // G
const int LED4 = 5; // W


void setup() {
   pinMode(LED1, OUTPUT);
   pinMode(LED2, OUTPUT);
   pinMode(LED3, OUTPUT);
   pinMode(LED4, OUTPUT);
 
}

void loop() {
  digitalWrite(LED1, HIGH);    // turn on LED1
  delay(100);                 // wait for 2ms
  digitalWrite(LED1, LOW);    // turn off LED1
  delay(100);                  // wait for 2ms
  digitalWrite(LED2, HIGH);    // turn on LED2
  delay(100);                  // wait for 2ms
  digitalWrite(LED2, LOW);    // turn off LED1
  delay(100);                  // wait for 2ms  
  digitalWrite(LED3, HIGH);    // turn on LED3
  delay(100);                  // wait for 2ms
  digitalWrite(LED3, LOW);    // turn off LED1
  delay(100);                  // wait for 2ms
  digitalWrite(LED4, HIGH);    // turn on LED4
  delay(100);                // wait for 2ms
  digitalWrite(LED4, LOW);    // turn off LED1
  delay(100);                  // wait for 2ms
}

Example 3:

int LED1 = 2; // R
int LED2 = 3; // Y
int LED3 = 4; // G
int LED4 = 5; // W


void setup() {
   pinMode(LED1, OUTPUT);
   pinMode(LED2, OUTPUT);
   pinMode(LED3, OUTPUT);
   pinMode(LED4, OUTPUT);
 
}

void loop() {
  digitalWrite(LED1, HIGH);    // turn on LED1
  delay(100);                 // wait for 2ms
  digitalWrite(LED1, LOW);    // turn off LED1
  delay(100);                  // wait for 2ms
  digitalWrite(LED2, HIGH);    // turn on LED2
  delay(100);                  // wait for 2ms
  digitalWrite(LED2, LOW);    // turn off LED1
  delay(100);                  // wait for 2ms  
  digitalWrite(LED3, HIGH);    // turn on LED3
  delay(100);                  // wait for 2ms
  digitalWrite(LED3, LOW);    // turn off LED1
  delay(100);                  // wait for 2ms
  digitalWrite(LED4, HIGH);    // turn on LED4
  delay(100);                // wait for 2ms
  digitalWrite(LED4, LOW);    // turn off LED1
  delay(100);                  // wait for 2ms
}

Since the exact same thing occurs with 3 different ways of written Arduino code, it raises questions IMHO.
(I wonder what kind of "special" backdoors may be in the Atmega microprocessors!)

const byte for pin assignments is even better. Why use 2 bytes of memory to hold a value from 0 to 255, when one will do?

When not use const or define, the variables are stored in SRAM instead of in PROGMEM.
Only have 2K SRAM in an Uno, lot of unchanging variables in SRAM could use it up and leave less room for real variables.

Arduino_n00b:
Is #define the same as const int in the Arduino world?

First of all, this is not about the "Arduino world". This is just general C++.

Second, no it's not the same.

#define and all other preprocessor directives (which start with #) are handled by the C++ preprocessor. The preprocessor just does a text search replacement. So every place in your code where the word LED1 occurs gets replaced with 2. This can sometimes lead to bugs that are very difficult to troubleshoot because the code being compiled is different from what you see in your sketch. For this reason, I recommend only using preprocessor directives when there is no better option.

const int (or any other variable) is handled by the compiler, which is a lot smarter than the preprocessor. The compiler is pretty good at analyzing your code and spotting possible problems, which it will communicate to you as warnings in the console window if you have turned warnings on in File > Preferences > Compiler Warnings. The data type of the variable defines properties of that variable, which the compiler can use to do its job better.

Arduino_n00b:
Since the exact same thing occurs with 3 different ways of written Arduino code, it raises questions IMHO.
(I wonder what kind of "special" backdoors may be in the Atmega microprocessors!)

This isn't about "backdoors". This is just the basics of C++. No matter which language and platform you are writing code for, there will always be multiple ways to do things. This is where best practices come in. If you learn some principles of general best practices, they will guide you in choosing which of the multiple ways of doing things to pick. For example:

  • Only use the preprocessor when you absolutely need to.
  • Pick the most appropriate data type for your variables.
  • Always use the const keyword when you don't intent a variable's value to change.

When you take those into consideration, you find that there is no longer a bewildering number of different but seemingly equivalent ways of doing things.

There are situations where there are still multiple ways of doing something while following best practices. At that point, it comes down to your personal preference. However, remember that using consistent coding style is also a best practice. If you're a collaborator on an existing code base, follow whatever coding style was previously established in the code. If you're writing your own code, pick one style and stick with it throughout the project.

Thank you everyone for your input. :slight_smile:

I was not aware of the const byte function.
(Thank you, sir.)

If the const byte function is more efficient, then why don't we see it applied in the example codes more often?

I am very aware of the 'BYTE' keyword is no longer supported error.
(But not clear as to why.)

I am also not clear as to why the void loop(void) vs void loop() are somehow treated the same.

I am also aware that The Arduino language is C++, but it is very different from most C++ varieties.
(Therefore I have and will continue to regard it as a separate species.)

#define simply EQUATES a word with a value. For example, if you do this:

#define LED 13 <-- notice no semicolon after the 13

This means wherever in your code you use the word "LED", the value "13" will be inserted instead. The #define itself uses NO memory, it's simply a substitute.


int LED = 13;

This reserves a signed integer (two bytes in Arduino memory) and assigns it a numeric value of "13". it consumes 2 bytes of memory. Note that the value of "LED" can be changed. In your program you can do this:

LED = -44;

And it will work and from then on LED will contain the integer value "-44".


const int LED = 13;

As above, two bytes of Arduino memory are used and assigned the numeric value "13". HOWEVER, the "const" keyword means that this variable cannot be changed. The compiler will give you an error if you try this:

LED = -44;

Depending on compiler optimizations and how you use the variable, "LED" may not be assigned any storage itself, but instead "plugged into" where it's needed. As I said, this depends on the compiler, the optimization level used and the way you use the variable. NEVER count on a compiler doing something because it may fool you and create an almost impossible to find bug. Always write your code to do what you mean it to do, never what you think it may do.

Hope this helps.

Arduino_n00b:
Thank you everyone for your input. :slight_smile:

I was not aware of the const byte function.
(Thank you, sir.)

If the const byte function is more efficient, then why don't we see it applied in the example codes more often?

I am very aware of the 'BYTE' keyword is no longer supported error.
(But not clear as to why.)

I am also not clear as to why the void loop(void) vs void loop() are somehow treated the same.

I am also aware that The Arduino language is C++, but it is very different from most C++ varieties.
(Therefore I have and will continue to regard it as a separate species.)

The (thankfully now) unsupported "byte" is the same thing as "uint8_t". In fact, in Arduino it was simply a typedef.

void loop (void) simply means that loop returns nothing (the first void) and expects nothing (the second void).

If you run "loop()" you are saying "loop returns nothing and I'm giving it nothing (which is the same thing I said in the last sentence).

That's why void loop (void) and void loop () are the same thing.

Now if you had, say, this: int func (int value), you are saying that the function named "func" returns an integer value and expects a value passed to it by an int variable. Since it expects one value given to it, you CANNOT use it this way:

return = func ();

See? You are not giving it anything for "value". Note that what it returns is being properly "captured" in the variable named "return". Also note that you are NOT required to use the return value. That is, THIS is legal:

func (value); --or-- func (123);

Actually, there is a way to assign a default value for the input "value", but that's beyond the discussion at the moment and would just confuse.....

Arduino_n00b:
I am also not clear as to why the void loop(void) vs void loop() are somehow treated the same.

The latter is just shorthand for the former.

Arduino_n00b:
I am also aware that The Arduino language is C++, but it is very different from most C++ varieties.
(Therefore I have and will continue to regard it as a separate species.)

In what ways is it very different?

It's only "different" in that the resources available to the program in an embedded context are very different from a desktop computer with an OS (embedded systems don't have an OS, a file system, or the concept of stdin/stdout, exiting a program, etc, very limited storage and memory compared to a PC, and all these on-chip peripherals that you can access directly).

Things that are very common when programming a PC (dynamic memory allocation for example) have all sorts of complications and caveats on an embedded system due to the constrained resources and lack of an OS, and you have to be much more cognizant of execution speed and memory usage. Accordingly, common programming practice is very different.

One difference that is specific to Arduino is the use of setup() and loop() instead of main(); this is just an illusion though - there's actually a main() function defined in the core library that calls setup, and then calls loop in an infinite (while(1)) loop.

The (thankfully now) unsupported "byte" is the same thing as "uint8_t". In fact, in Arduino it was simply a typedef.

Unsupported in which sense ?

UKHeliBob:
Unsupported in which sense ?

Someone said that the "byte" keyword is now unsupported and I just took it for granted that the statement was true.

Doesn't matter to me anyway since I always explicitly use "uint8_t" or whatever so that I know what I'm getting.

Is "char" signed or unsigned? What if the default is overridden with "-funsigned-char"?

if I want a signed char, I explicitly use "int8_t".

Note that most Arduino code I write is xxx.c and built with a Makefile, so I can't depend on how the Arduino IDE is set up.

DrAzzy:
It's only "different" in that the resources available to the program in an embedded context are very different from a desktop computer with an OS (embedded systems don't have an OS, a file system, or the concept of stdin/stdout, exiting a program, etc, very limited storage and memory compared to a PC, and all these on-chip peripherals that you can access directly).

Surely you know that std in/out/err exist and are usable in AVR-GCC?

They are just by default not connected to anything. They can be used by simply setting them up using fdev_open.

Or, just use this simple library: GitHub - krupski/Stdinout: Standard input/output/error support for Arduino

krupski:
Someone said that the "byte" keyword is now unsupported and I just took it for granted that the statement was true.

That was about the BYTE macro, which used to be one of the options for the format parameter of the Print class's print function back in the pre-Arduino IDE 1.0 days:

Now the only supported format values are BIN, OCT, DEC, and HEX. If you use BYTE in your sketch, you get a message in the console that is hardcoded into the Arduino IDE:

sketch_dec06a:3:17: error: The 'BYTE' keyword is no longer supported.
As of Arduino 1.0, the 'BYTE' keyword is no longer supported.
Please use Serial.write() instead.


 Serial.print(42,BYTE);

                 ^~~~

This was intended to help people transition through the API change

The byte typedef will always be supported. I'm not a fan of the boolean typedef, since I don't think it provides enough of an advantage over bool to be worth the non-standardness, but I think byte was a reasonable decision, since it is more beginner friendly than uint8_t. I also think the original idea was to make Arduino code as similar as possible to the Java-based Processing language, though I don't think that ended up being as helpful as they expected in the end.

Arduino_n00b:
I am also aware that The Arduino language is C++, but it is very different from most C++ varieties.
(Therefore I have and will continue to regard it as a separate species.)

Arduino's official stance is that the .ino files of Arduino sketches are written in Arduino Language, so it's reasonable for you to consider it a "separate species" from C++. However, it's very important for you to understand that the Arduino Language is C++ with only two fairly minor differences caused by the sketch preprocessing operation of the build:

  • Automatic function prototype generation for any function that doesn't already have a manually added prototype
  • #include <Arduino.h> is automatically added to the primary .ino file if that file doesn't already contain the #include for Arduino.h

The .ino files of the sketch are concatenated, starting with the file that matches the sketch folder name, followed by the rest in alphabetical order and renamed to have a .cpp extension. After that, the file is compiled as C++ using a standard C++ compiler.

The reason this is important is because anything you can do in C++ you can do in Arduino Language, but the Arduino Language documentation doesn't make any mention of a huge number of extremely useful features of C++. So if you don't take advantage of the tremendous amount of C++ documentation available, you be forced to learn all but the most basic parts of the Arduino Language by trial and error.

pert:
That was about the BYTE macro, which used to be one of the options for the format parameter of the Print class's print function back in the pre-Arduino IDE 1.0 days:
Arduino/hardware/arduino/cores/arduino/Print.h at 0023 · arduino/Arduino · GitHub
Now the only supported format values are BIN, OCT, DEC, and HEX. If you use BYTE in your sketch, you get a message in the console that is hardcoded into the Arduino IDE:

sketch_dec06a:3:17: error: The 'BYTE' keyword is no longer supported.

As of Arduino 1.0, the 'BYTE' keyword is no longer supported.
Please use Serial.write() instead.

Serial.print(42,BYTE);

^~~~



This was intended to help people transition through the API change

The byte typedef will always be supported. I'm not a fan of the boolean typedef, since I don't think it provides enough of an advantage over bool to be worth the non-standardness, but I think byte was a reasonable decision, since it is more beginner friendly than uint8_t. I also think the original idea was to make Arduino code as similar as possible to the Java-based Processing language, though I don't think that ended up being as helpful as they expected in the end.

Ah I see. Never ran into that mostly because I use printf() instead of Serial.print (and text editor + Makefile instead of the IDE) to build sketches. Thanks for the info.

Thank you everyone for the great tips, folks.

I think it's great when we can share expertise.