@Experienced programmers / Arduino users:
I am drawing up a list of tips and hints for a "sticky" or fixed web page. This is the current draft, below. Please comment, as you see fit, on:
- Errors I have made
- Suggested improvements
- Suggested re-ordering of tips/traps
- Any other ideas
Please, please, no four pages of discussion about whether or not goto is harmful.
Note: Although it is usually bad form to edit a post after people have commented on it, to keep things in one place I will incorporate suggestions directly below. However to keep the comments meaningful the original text will be kept with a strikeout through it, as far as possible.
Trap: Using 'string' instead of "string".
eg.
Wrong! ...
char foo = 'start';
Correct:
char foo [] = "start";
Note that unlike some languages (eg. Lua, PHP) strings cannot be placed in single quotes. A letter in a single quote is used as a way of specifying (usually) a single character, eg.
char forwardCommand = 'F';
Trap: Reading too much from serial / ethernet.
eg.
Wrong! ...
if (Serial.available ())
{
char a = Serial.read ();
char b = Serial.read ();
}
Correct: See Gammon Forum : Electronics : Microprocessors : How to process incoming serial data without blocking
Trap: Not using arrays.
Tip: Use arrays to hold multiple similar things.
eg.
int ledPin1 = 3;
int ledPin2 = 4;
int ledPin3 = 5;
int ledPin4 = 6;
Instead of:
int ledPin [4] = { 3, 4, 5, 6 };
Or preferably:
const byte ledPin [4] = { 3, 4, 5, 6 };
Trap: Trying to check a pin number rather than a pin.
eg.
Wrong! ...
const byte switchPin = 4;
...
if (switchPin == HIGH)
{
// switch is pressed
}
Correct:
const byte switchPin = 4;
...
if (digitalRead (switchPin) == HIGH)
{
// switch is pressed
}
Trap: Using "=" (assign) when you mean "==" (compare).
eg.
Wrong! ...
if (temperature = 10)
Correct:
if (temperature == 10)
Trap: Doing a serial print, or delay, inside an interrupt service routine.
eg.
Wrong! ...
ISR (TIMER0_OVF_vect)
{
Serial.print ("Timer overflowed!");
delay (100);
}
Neither will work properly because interrupts are turned off inside interrupt routines.
Trap: Using too much RAM.
eg.
int myReadings [2000];
The above line will use 4 kB of RAM. The Uno and similar boards only have 2 kB of RAM.
Tip: Place string literals for printing inside the F() macro.
eg.
Instead of:
Serial.print ("Program starting.");
Use:
Serial.print (F("Program starting."));
This saves the string being copied from PROGMEM (program memory) into RAM (random access memory). You usually have a lot more program memory than RAM.
Trap: Trying to sprintf a float.
eg.
float foo = 42.56;
char buf [20];
sprintf (buf, "%f", foo);
On the Arduino, sprintf does not support floats.
Trap: Trying to sscanf a float.
eg.
float foo;
char buf [] = "123.45";
sscanf (buf, "%f", &foo);
On the Arduino, sscanf does not support floats.
Trap: Trying to add one to a variable in a way that is not defined.
eg.
Wrong! ...
i = i++;
The result of the above statement is not defined. That is, it is not necessarily "i + 1".
http://c-faq.com/expr/seqpoints.html
Instead use:
i++;
Or:
i = i + 1;
A note about "undefined behavior":
When an instance of undefined behavior occurs, so far as the language specification is concerned anything could happen, maybe nothing at all.
Trap: Calling a function without using parentheses.
eg.
Wrong! ...
void takeReading ()
{
// do something
}
void loop ()
{
takeReading; // <-- no parentheses
}
The above code compiles but will not do anything useful.
Correct:
void takeReading ()
{
// do something
}
void loop ()
{
takeReading ();
}
The parentheses are required even if the function takes no arguments.
Trap: Doing multiple things after an "if" without using braces:
eg.
Wrong! ...
if (temperature > 40)
digitalWrite (furnaceControl, LOW);
digitalWrite (warningLight, HIGH);
The indentation suggests that you want to do both things.
Correct:
if (temperature > 40)
{
digitalWrite (furnaceControl, LOW);
digitalWrite (warningLight, HIGH);
} // end if temperature > 40
Note the comments to make it clear what the closing brace refers to.
Trap: Overwrite the end of an array.
eg.
Wrong! ...
int foo [5];
foo [5] = 42;
The above code will corrupt memory. If you have 5 elements in an array, they are numbered: 0, 1, 2, 3, 4.
Trap: Overflowing an integer.
eg.
Wrong! ...
unsigned long secondsInDay = 60 * 60 * 24;
Small literal constants (like 60) are treated by the compiler as an int type, and have a maximum value of 32767. Multiplying such numbers together has a maximum value of 32767 still.
Correct:
unsigned long secondsInDay = 60UL * 60 * 24;
The "UL" suffix promotes the number to an unsigned long.
Tip: Put things might vary, and other configuration numbers, at the start of a sketch as constants.
Tip: Put things that you might want to change (such as pin numbers, baud rates, etc.) at the start of a sketch as constants.
Instead of:
void setup ()
{
Serial.begin (115200);
pinMode (5, OUTPUT);
digitalWrite (5, LOW);
pinMode (6, OUTPUT);
digitalWrite (6, HIGH);
Use (for ease of changing later):
const unsigned long BAUD_RATE = 115200;
const byte LED_PIN = 5;
const byte MOTOR_PIN = 6;
void setup ()
{
Serial.begin (BAUD_RATE);
pinMode (LED_PIN, OUTPUT);
digitalWrite (LED_PIN, LOW);
pinMode (MOTOR_PIN, OUTPUT);
digitalWrite (MOTOR_PIN, HIGH);
Tip: Use a consistent and widely-accepted naming style for variables and constants.
In C it is traditional to put constants in all UPPER-CASE and variables in mixed upper/lower case.
The Arduino libraries tend to use camelCase (like "pinMode" and "digitalWrite") where you run words together (without underscores) starting with lower case, and using an upper case letter for each subsequent word.
However constants usually use all upper-case and thus have an underscore to separate individual words (like "NUM_DIGITAL_PINS").
Tip: Go easy with leading underscores in variable names.
In C++ these variable names are reserved:
Reserved in any scope, including for use as implementation macros:
- identifiers beginning with an underscore and an uppercase letter
- identifiers containing adjacent underscores (or "double underscore")
Reserved in the global namespaces:
- identifiers beginning with an underscore
Therefore a global variable like this should not be used:
int _foo;
Also in any context a variable like this should not be used:
int _MotorPin; // <--- underscore followed by upper-case letter
int My__Variable; // <--- double underscores
I suggest not using leading underscores. If you want to differentiate class variables from non-class variables, use a trailing underscore.
Tip: Do not use goto
It is widely-accepted that the use of the goto statement in C/C++ is not only unnecessary, but harmful. There are occasional exceptions, but if you are reading a beginner's tutorial (like this) you definitely won't fall into the category of needing to use goto.
In almost every case where goto seems useful, it can be avoided by restructuring your code. For loops use "for", "while" or "do". To break out of a loop use "break". To leave a function use "return".