[Home] [Downloads] [Search] [Help/forum]


Register forum user name Search FAQ

Gammon Forum

[Folder]  Entire forum
-> [Folder]  Electronics
. -> [Folder]  Microprocessors
. . -> [Subject]  Arduino programming traps, tips and style guide

Arduino programming traps, tips and style guide

Postings by administrators only.

[Refresh] Refresh page


Posted by Nick Gammon   Australia  (22,973 posts)  [Biography] bio   Forum Administrator
Date Sat 28 Sep 2013 11:16 PM (UTC)

Amended on Thu 18 Jun 2015 07:57 PM (UTC) by Nick Gammon

Message
Traps, tips, style guide and further reading for the Arduino C++ development environment.

This page can be quickly reached from the link: http://www.gammon.com.au/tips


Individual traps or tips can be reached by appending #trapx or #tipx to the URL. For example:




Before posting on the Arduino forum:

Read this before posting a programming question

How to use the forum

Trap #1: Reading too much from serial / ethernet.


eg.

Wrong! ...


if (Serial.available ())
  {
  char a = Serial.read ();
  char b = Serial.read ();
  }


Correct: See http://www.gammon.com.au/serial

If Serial.available () returns non-zero, you know have at least one byte. Trying to read two bytes is wrong, unless you specifically test for at least two.

Trap #2: Trying to check a pin number rather than a pin state.


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 #3: Confusing "=" (assign) with "==" (compare)


eg.

Wrong! ...


if (temperature = 10)


Correct:


if (temperature == 10)   // compare


Similarly, this is wrong:


counter == 5;


It should be:


counter = 5;   // assign


Trap #4: 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.

http://www.gammon.com.au/interrupts


Trap #5: 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.

A classic symptom of running out of RAM is the program resetting or not even starting at all. For example if you have:


// ... a lot of data declarations here ...

void setup ()
  {
  Serial.begin (115200);
  Serial.println ("Starting");
  }  // end of setup


If you don't see "Starting" printed (or if it prints repeatedly) then you probably are using too much RAM. Some libraries may use RAM for buffers (eg. SD card library, LCD libraries, etc.).

The Wire library (for I2C), for example, uses 5 lots of 32-byte buffers, so that is 160 bytes used straight away, if you use Wire.

Also see Tip #2 below.


Trap #6: Trying to sprintf a float or variable-width field.


eg.


float foo = 42.56;
char buf [20];
sprintf (buf, "%f", foo);


On the Arduino, sprintf does not support floats.

Suggest dtostrf as an alternative. For example:


  float f = 42.56;
  char buf [20];
  dtostrf (f, 10, 4, buf);  // number, width, decimal places, buffer


Also, the variable width or precision field (an asterisk * symbol) is not supported.

eg.


char buf [20];
sprintf (buf, "%*s", 10, "hello");


If you want variable-width fields you will need to find other ways of coding them.

Trap #7: Trying to sscanf a float.


eg.


float foo;
char buf [] = "123.45";
sscanf (buf, "%f", &foo);


On the Arduino, sscanf does not support floats.

Suggest scanning into a %s field (a string) and then using atof on the result.


Trap #8: 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://en.wikipedia.org/wiki/Sequence_point
http://c-faq.com/expr/seqpoints.html

Instead use:


i++;


Or:


i = i + 1;


A note about "undefined behavior":

http://en.wikipedia.org/wiki/Undefined_behavior

Quote:

When an instance of undefined behavior occurs, so far as the language specification is concerned anything could happen, maybe nothing at all.



Trap #9: 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.

Also see trap #31.


Trap #10: 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 #11: Overshooting 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.

Also, reading past the end of an array will return garbage.

Trap #12: 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.

http://www.gammon.com.au/forum/?id=12146


Trap #13: Don't put a semicolon at the end of every line


Semicolons end statements. However "if", "for" and "while" are compound statements, so they should not have a semicolon after them like this:

Wrong! ...


  if (temperature > 40);    // <----- this semicolon is incorrect!
    {
    digitalWrite (furnaceControl, LOW);
    digitalWrite (warningLight, HIGH);
    }

  for (int i = 0; i < 10; i++);   // <----- this semicolon is incorrect!
    digitalWrite (i, HIGH);


The semicolons above (as indicated) terminate the "if" and "for" so they don't do anything useful. The lines following them are just unconditional blocks of code which are always executed, once.

Correct:


  if (temperature > 40)
    {
    digitalWrite (furnaceControl, LOW);
    digitalWrite (warningLight, HIGH);
    }

  for (int i = 0; i < 10; i++)
    digitalWrite (i, HIGH);


Similarly this is wrong:


#define LED_PIN 10;    // <--- no semicolon!
#define LED_PIN = 10;  // <--- no semicolon, no "=" symbol


Correct:


#define LED_PIN 10


Or, better still:


const byte LED_PIN = 10;


Using a const rather than a #define has various advantages, including the one of consistency. Now the semicolon and "=" symbol are required. This is consistent with the way you write variables. However there is no performance or space penalty for using constants like this (compared to using #define).


Trap #14: Don't over-prettify your comments


Wrong! ...


// I want my comment to look symmetrical \\
int butTheCompilerNeverSeesThisDeclaration;


The comment looks cute, but the trailing backslash makes the following line part of the comment (because of line folding).

Correct:


// Here is my comment with no trailing backslash
int someVariable;



Trap #15: Trying to initialize multiple variables


For example:


int x, y, z = 12;


Only z is initialized there.

Good:


int x = 1, y = 2, z = 12;


Better:


int x = 1;
int y = 2;
int z = 12;



Trap #16: Not initializing local function variables


Wrong! ...


void calculateStuff ()
  {
  int a;

  a = a + 1;


Since "a" was not initialized, adding one to it is undefined.

http://en.wikipedia.org/wiki/Undefined_behavior


Trap #17: Trying to work on a "set"


Wrong! ...


digitalWrite ((8, 9, 10), HIGH);


Whilst the above will compile, it only sets a single pin high (pin 10).

http://en.wikipedia.org/wiki/Comma_operator

Correct:


digitalWrite (8, HIGH);
digitalWrite (9, HIGH);
digitalWrite (10, HIGH);


Or:


for (byte i = 8; i <= 10; i++)
  digitalWrite (i, HIGH);



Trap #18: Returning a pointer to a local variable


Wrong! ...

char * getString ()
{
  char s [] = "abc";
  return s;
}


The string "s" is allocated on the stack and goes out of scope when the function returns, and is no longer valid. There are various work-arounds, one would be:

Better:

char * getString ()
{
  static char s [] = "abc";
  return s;
}


That isn't perfect (you might call getString twice and try to use both returned values) however at least it won't actually crash.

Other work-arounds are:


  • Make the variable you are planning to change global.
  • Pass the variable by reference and change that.


Example of passing by reference:


void getString (char (& s) [10])
  {
  strcpy (s, "abc");
  }  // end of getString

// later ...

  char buf [10];
  getString (buf);  // <--- pass array by reference


Trap #19: Leaving a semicolon off after a "return"


Wrong!


  if (a < 10)
    return     // <----- no semicolon!
  a = a + 1;


The code above compiles as if it were written:


  if (a < 10)
    return a = a + 1;


Also note that code after an unconditional "return" is never executed. For example:


  a = a + 1;
  return a;
  b = 5;     // <--- never executed!



Trap #20: Doing too much in a constructor


There is a subtle problem with initializing things in a class constructor, particularly if the constructor might be called at the global level (that is, not inside a function).

Example:


myClass::myClass ()  // constructor
  {
  Wire.begin (32);  // initialize I2C library
  whenStarted = millis ();
  Serial.begin (115200);
  Serial.println ("Ready to go.");
  }


The problem is that libraries like Wire, Serial, and the timers may not have been initialized at this time. In other words, your constructor may have been called before their constructor.

You are best off doing minimal things in the class constructor, and making a "begin" function (like Serial.begin, Wire.begin, etc.) and calling that in "setup". Acceptable things to do in a constructor are to store simple types (eg. integers like pin numbers).

http://www.parashift.com/c++-faq/static-init-order.html



Trap #21: Be careful with recursion


These micro-controllers don't have much memory. Be cautious when using recursive functions (functions that call themselves).

In particular this will fail spectacularly:

void loop ()
  {
  // do stuff
 
  loop ();    //  <--- recursion!
  }


A more subtle version:


void loop ()
  {
  foo ();
  }

void foo ()
  {
  if (something)
    loop ();
  }


The above will almost certainly crash your program. You cannot call "loop" like that to get back to the main loop.

Trap #22: 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 #23: Using the String class


The String class (with an upper-case "S") is an inbuilt class (in the Arduino IDE) for managing strings. Whilst it doesn't have any known bugs there are problems with using it:


  • It is comparatively slow.
  • For versions of the IDE up and and including 1.0.3 there is a bug in the dynamic memory allocation library which affects Strings.
  • Frequent use of String may fragment memory (especially if you are reading a byte at a time and concatenating them).


See here for some details: http://www.gammon.com.au/concat

I recommend reworking your code to manage without String. Use C-style strings instead (strcpy, strcat, strcmp, etc.), as described here for example.

Alternatively, if you prefer a "string class" try installing the Standard Template Library (http://www.gammon.com.au/forum/?id=11119) and using the STL string class instead. This uses less program memory and is faster.

Trap #24: Problems with Interrupt Services Routines (ISRs)



  • Do not turn interrupts on or off inside an ISR (as a general rule)
  • Variables shared between an ISR and main code should be declared volatile
  • Access to shared variables (volatile variables) in your main code should be protected by noInterrupts() / interrupts() function calls.


If you are dealing with interrupts I strongly suggest you read:

http://www.gammon.com.au/interrupts

Trap #25: Problems with multiply defined ISRs


Sometimes you may get an error message along these lines:


core.a(wiring.c.o): In function `__vector_16':
.../arduino/wiring.c:49: multiple definition of `__vector_16'


Example code to reproduce:


ISR (TIMER0_OVF_vect)
  {
  // Handle Timer 0 overflow
  }

void setup () 
  {
  // whatever
  }  // end of setup

void loop ()
  {
  // whatever  
  }  // end of loop


This is particularly likely if you use multiple libraries, especially if one is the SoftwareSerial library. The explanation is that different libraries may try to share the same "resource" such as a pin-change interrupt, or a timer. The work-around is not always simple, try posting a query on the Arduino forum. If two libraries both need Timer 2, for example, then they both can't have it.

Trap #26: Message: 'Error compiling'


If you get the message "Error compiling" (but no other error) when compiling (even the tutorial or example programs that come with the IDE), and you are running Windows, try getting the properties of the Arduino.exe program and setting "Compatibility mode" to "Windows XP".




Trap #27: Cannot upload code to Arduino Mega if it has !!! in it


Some bootloaders shipped with the Mega stop uploading code if they find "!!!" inside the code. This is because they chose "!!!" to enter some sort of "console" mode. Example code:


Serial.println ("Error in your input!!!");


The solution is to either not use "!!!" in your code, or to install a more recent bootloader.


Trap #28: Running new hardware with an old sketch


This isn't really a programming problem, but still it can cause damage to your Arduino. If you have finished a project and are planning to reconfigure your hardware interface (eg. switches, LEDs, sensors) then there can be a problem if you run an old sketch with the newer configuration. For example, your old sketch might turn on a motor when it shouldn't.

A simple solution is to upload an "empty" sketch before reconfiguring the hardware, for example:


void setup () { }
void loop  () { }


Since the default for an empty sketch is that all pins are configured as inputs, this would be the least likely to do damage, or suffer damage, by the way the hardware is connected.


Trap #29: The X-Y Problem


In brief, this is asking a question about how to do Y when you really want to do X.


  • You want to do X.
  • You don't know how to do X, but are guessing that maybe doing Y will help.
  • You ask on the forum how to do Y, without mentioning X (your real goal)
  • People spend a lot of time helping you with Y, not being sure why you want to do it in the first place.
  • Eventually (sometimes days later) everyone realizes your goal is to do X.
  • Finally you get assistance with the real problem.


Solution: State what you really want to do (eg. flash 3 LEDs at different rates) rather than how you think it might be achieved (eg. manipulate internal processor registers).

http://xyproblem.info/


Trap #30: Being tricked by the IDE preprocessor


The Arduino IDE (development environment) does some preprocessing of your sketch (source code) before submitting it to the C++ compiler. This can sometimes lead to obscure errors, for example:


// generic minimum
template <typename T> T minimum (T a, T b) 
  {
  if (a < b)
    return a;
  return b;
  }  // end of minimum

void setup () 
  { 
  int foo = minimum (6, 7);
  }
void loop () { }


The above code gives the error:


sketch_sep09b:3: error: ‘T’ does not name a type


But T does name a type!

The trouble is that the IDE "helpfully" generated a function prototype for you, so the code submitted to the compiler looks like this:


#line 1 "sketch_sep09b.ino"
// generic minimum
#include "Arduino.h"
T minimum (T a, T b);
void setup ();
void loop ();
#line 2
template <typename T> T minimum (T a, T b)
  {
  if (a < b)
    return a;
  return b;
  }  // end of minimum

void setup ()
  {
  int foo = minimum (6, 7);
  }
void loop () { }


The line in bold is the problem line, because at that point, T does not name a type.

There are two solutions:

Solution 1: Make your own prototype


// generic minimum
template <typename T> T minimum (T a, T b); // prototype
template <typename T> T minimum (T a, T b) 
  {
  if (a < b)
    return a;
  return b;
  }  // end of minimum

void setup () 
  { 
  int foo = minimum (6, 7);
  }
void loop () { }


Now it compiles OK, as we have submitted our own, correct, prototype.

Solution 2: Move the template declaration into a .h file

That is, put this into a .h file (make a new "tab" in the IDE):


template <typename T> T minimum (T a, T b) 
  {
  if (a < b)
    return a;
  return b;
  }  // end of minimum


Then include that .h file in your main sketch, and it will compile OK.

For more details see: http://www.gammon.com.au/forum/?id=12625

Trap #31: Putting "void" in front of a function call


The code below will not call the function "foo":


void foo ()
  {
  Serial.println ("Hello, world.");
  }
  
void loop() 
  {
  void foo ();  //  <---- This will not call foo
  }


Putting "void" in front of a function call like that turns it into a "function prototype". This will not call the function.

Instead use:


void loop() 
  {
  foo ();  
  }


Also see trap #9.

Trap #32: Not using "case" in a switch statement


The code below will not do anything useful:


switch (action)
  {
  wateringOn:
    digitalWrite (wateringPin, ON);
    break;
    
  wateringOff:
    digitalWrite (wateringPin, OFF);
    break;
  }  // end of switch


The above looks like a "switch" statement but the author has forgotten to use "case". It should read:


switch (action)
  {
  case wateringOn:
    digitalWrite (wateringPin, ON);
    break;
    
  case wateringOff:
    digitalWrite (wateringPin, OFF);
    break;
  }  // end of switch






Tip #1: Use arrays to hold multiple similar things.


eg.


int ledPin1 = 3;
int ledPin2 = 4;
int ledPin3 = 5;
int ledPin4 = 6;


If you start numbering variables with the suffix 1, 2, 3 etc. that generally means you should be using an array instead. Like this:


int ledPin [4] = { 3, 4, 5, 6 };


Or preferably:


const byte ledPin [4] = { 3, 4, 5, 6 };


(We use const because pin numbers don't usually change, and byte because they are only in the range 0 to 255).


Tip #2: 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.

For other types of strings (eg. arrays) see: http://gammon.com.au/progmem

Tip #3: Put things that you might want to change 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);
...


Note - by change I mean in the sense that you, the author of the code, might want to change the baud rate one day, or a pin number, or a delay period. Of course, the code itself cannot change constants (otherwise they would need to be variables).


Tip #4: 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 rare 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 start a loop over again use continue. To leave a function use return.

http://en.wikipedia.org/wiki/Goto#Criticism_and_decline


Tip #5: Don't try to out-think the compiler


The compiler aggressively optimizes your code. In general you don't need to try to improve on its performance by attempting your own optimizations, especially if that makes the code obscure and hard to maintain. In particular you almost never need to use assembler code to get speed improvements, because the compiler generates good assembler code from your C source.

However you can help the compiler by choosing good data types. For example, for small numbers use char or byte, for medium size numbers int or unsigned int, and for larger numbers long or unsigned long. The limits for such types on the Arduino (8-bit) platform are:


char:                 -128  to         +127
byte:                    0  to         +255
int:                -32768  to       +32767
unsigned int:            0  to       +65535
long:          -2147483648  to  +2147483647
unsigned long:           0  to  +4294967295


Note that for storing times (eg. from millis() or micros() function calls) you should use unsigned long.

Also the String class tends to be slow, and also a bit of a memory hog. Try to avoid using it.


Tip #6: Let the compiler do the work for you


A. Instead of working out how many seconds there are in a day on a calculator and then typing it in, for example:


const unsigned long secondsInDay = 86400;  // 60 * 60 * 24


Let the compiler do it:


const unsigned long secondsInDay = 60UL * 60 * 24;


B. Let the compiler work out how big things are:

Instead of:


char buffer [20];

// later ...

if (i < 20)
  {
  buffer [i] = c;
  }


Use this:


char buffer [20];

// later ...

if (i < sizeof (buffer))
  {
  buffer [i] = c;
  }


Now, if you ever change 20 to 30, the code still works properly, without having to remember to change it in multiple places.

For arrays of things larger than one byte you can use this macro to find how large the array is:


#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))


For example:


int myArray [20];

int numberOfItems = ARRAY_SIZE (myArray);   // <--- will be 20 in this case


C. Let the compiler work out how many things you put into an array of (say) pin numbers:

Instead of:


const byte pinNumbers [5] = { 5, 6, 7, 8, 9 };


Use:


const byte pinNumbers [] = { 5, 6, 7, 8, 9 };


Later you can use ARRAY_SIZE to find how many elements there actually are in the array.

Warning: Using "sizeof" on an array passed to a function will not work. You will always get 2 (the size of the pointer), not the size of the array. To pass a variable size array to a function you need to pass the array, and its size.

Tip #7: Use "else" rather than testing for the same thing twice


Bad:


if (a == 5)
  {
  // do something
  }
if (a != 5)
  {
  // do something else
  }


Good:


if (a == 5)
  {
  // do something
  }
else
  {
  // do something else
  }



Tip #8: Use "switch/case" rather than lengthy "if" tests


Bad:


if (command == 'A')
  {
  // do something
  }
else if (command == 'B')
  {
  // do something
  }
else if (command == 'C')
  {
  // do something
  }
else
  {
  // unexpected command
  }


Good:


switch (command)
  {
  case 'A':
        // do something
        break;

  case 'B':
        // do something
        break;

  case 'C':
        // do something
        break;

  default:
        // unexpected command
        break;
  }  // end of switch


Tip #9: Debugging tips


If you are not getting results that you expect, instead of trying to guess what the computer is doing, have the computer tell you what it is doing. The easiest way to do this (usually) is by liberal use of Serial.print() statements.

If this is not feasible due to timing constraints or other issues, save values in variables for printing later.

Or save values in EEPROM until you can read them later. Or blink or beep messages in binary or Morse code (for example, three blinks means you are in function X). It doesn't matter. You just have to narrow down where the good data is turning into bad data. Getting the computer to tell you intermediate values is important.

If your serial port is in use by your code, you can use another Arduino and send messages via I2C or SPI connections. How to do this:

http://www.gammon.com.au/forum/?id=11329

A low-impact debugging technique is to use direct port access to toggle a pin. This only takes 2 clock cycles on a 16 MHz processor (125 nS). For the Atmega328 (Uno, Duemilanove, Nano, Mini, Pro Mini, Fio, etc.) the appropriate code (for each pin) is:


// toggle digital pins (need to be output)
PIND = bit (0); // toggle D0
PIND = bit (1); // toggle D1
PIND = bit (2); // toggle D2
PIND = bit (3); // toggle D3
PIND = bit (4); // toggle D4
PIND = bit (5); // toggle D5
PIND = bit (6); // toggle D6
PIND = bit (7); // toggle D7
PINB = bit (0); // toggle D8
PINB = bit (1); // toggle D9
PINB = bit (2); // toggle D10
PINB = bit (3); // toggle D11
PINB = bit (4); // toggle D12
PINB = bit (5); // toggle D13

// toggle analog pins (need to be output)
PINC = bit (0); // toggle A0
PINC = bit (1); // toggle A1
PINC = bit (2); // toggle A2
PINC = bit (3); // toggle A3
PINC = bit (4); // toggle A4
PINC = bit (5); // toggle A5


Example code:


void setup ()
  {
  pinMode (13, OUTPUT);
  }  // end of setup

void loop ()
  {
  PINB = bit (5); // toggle D13
  delay (500);  
  }  // end of loop


So if you have a spare pin, an LED and a resistor in series (say, 220Ω) then you can use that LED to detect that certain parts of your code are reached, and how often.

Tip #10: Give functions good names and keep them short


Quote:

Q. What is the point of giving functions good names?


It's not just for our benefit. You will benefit greatly, too. It is not hard to come up with good names. The function does something. The name should be obvious, based on what the function does. The problem arises when the function name needs to be doThisAndThatAndSomethingElseTooOhAndDontForgetThatWeAlsoDoTheOtherThing() to describe what the function does.

After a while, picking good names becomes second nature, and you'll look at early code with names like PerformTask() and wonder what the hell you were thinking.

Quote:

Q. Is there a reason why the main loop() function should be kept short, or is just for clarity's sake?


Isn't that a sufficient reason? I teach that functions should be able to be printed on one page. More than that, and by the time you read to the end, you've forgotten what happened at the top. Or, maybe that's old age.

But, loop() is a function, like any other function. It should do one thing, and do that one thing very well. Primarily, that one thing is the whole application, but the real work is done in functions. Functions should be specialized, like contractors. The loop function should be like the general contractor, in charge of hiring the subcontractors, getting them to do their part on time, etc.

The general contractor shouldn't be poring the foundation, doing the wiring, running the plumbing, putting the roof on, etc. The various subcontractors should be doing the work, while the general contractor does next to nothing.

Quote:

Q. After all, If I'm calling up other functions in the loop that run the same code as I have in there now, that's the only difference I can see. Am I right?


Yes, but it's easier to test a function that does one thing, and does that one thing well. Like the contractor analogy, if a function does roofing, it doesn't need to do plumbing. You can verify that the roofer does roofing well, and then just call the roofer when you need a roof. You don't need to be concerned with how well the roofer does plumbing.

With all the code in loop() it's like the general contractor trying to do all the work, and manage the process at the same time.

Thanks to PaulS on the Arduino forum for this tip!

Tip #11: How to use individual bits from the datasheet


Datasheets often give individual bits "names" to help locate them inside a register byte.

For example:



Say that you want to set WGM01 and COM0A0. The register is named TCCR0A.


  TCCR0A = bit (WGM01) | bit (COM0A0); // CTC mode, toggle OC0A on match


There! You "or" in the bits you want, inside the "bit" macro because they are bits and not simple values.

And of course you use the usual approach for setting one bit:


  TCCR0A |= bit (COM0A0); // set toggle OC0A on match

... or ...

  bitSet (TCCR0A, COM0A0); // set toggle OC0A on match


And clearing one bit:


  TCCR0A &= ~bit (COM0A0); // clear toggle OC0A on match

... or ...

  bitClear (TCCR0A, COM0A0); // clear toggle OC0A on match


And testing one bit:


  if (TIFR1 & bit (TOV1))   // has the timer overflowed?
     ...

... or ...

  if (bitRead (TIFR1, TOV1))   // has the timer overflowed?
    ...


The code is much easier to read and maintain than just using numbers or binary bit patterns.






Style guide


Below are some suggestions for coding style. Opinions vary about style, so take this as a guide only.

Small example program:


// configuration
const byte    LED_PIN    = 12;
unsigned long DELAY_TIME = 200;
const int     ITERATIONS = 5;

void setup ()
  {
  Serial.begin (115200);
  pinMode (LED_PIN, OUTPUT);
  }  // end of setup

void loop ()
  {
  for (int i = 0; i < ITERATIONS; i++)
    {
    digitalWrite (LED_PIN, HIGH);
    delay (DELAY_TIME);
    digitalWrite (LED_PIN, LOW);
    delay (DELAY_TIME);
    } // end of for  
  }  // end of loop


I like to indent blocks of code as shown. This gives a visual representation of "blocks" like this:



Note that blocks of code (like functions) are indented so you can easily see where each one starts and ends. Also conditional statements (eg. "if") and loops (eg. "while", "for", and "do") have their body indented as in the example.

(Note: some people, including the auto-formatter in the IDE, do not indent the braces, but indent the body of the braces).

http://en.wikipedia.org/wiki/Indent_style

I think my personal preference is called "Whitesmiths style". If you read the link above you can see there are quite a few variations.

Other things in the example:


  • Use spaces. They are easy to type. Don't write stuff like: a=b+c
  • Use a space after a function name to make it easy to read.
  • Use a space after a comma, semicolon, or other punctuation. (Except at the end of a line)
  • Put a comment on closing braces to indicate what the brace "belongs" to.
  • Do not put code after an opening brace.
  • Closing braces should be on a line on their own.
  • Indent things at the same "level" by the same amount.
  • Put one statement per line
  • Don't overdo spaces or blank lines.
  • Don't overdo brackets, for example: "a = (0);" That just makes it look like you forgot a function call.


The IDE has an auto-format tool:



Use that to achieve similar results to the above.

Examples of bad style:


timeElapsed=lastTime+22*3;     // <---- more spaces!

if (a > 20)
b = 5;      // <--- indent this!

a = 5;
  b = 6;
    c = 7;   //  <--- these are all at the same "level" and should line up.

if (a > 20)
  { b = 5;  }  //  <--- no code after a opening brace, closing brace should be on its own line

a = 1; b = 2; c = 3;  // <--- one statement per line!

a = ( b + c ) / ( d - 3 ) ;   // <---- this is overdoing spaces!


Example of overdoing blank lines:


  for (int i = 0; i < ITERATIONS; i++)


    {


    digitalWrite (LED_PIN, HIGH);

    delay (DELAY_TIME);

    digitalWrite (LED_PIN, LOW);

    delay (DELAY_TIME);

    } // end of for  


You aren't writing an essay where everything is supposed to be double-spaced. That sort of stuff is hard to read.

For some more ideas about style, Google "C coding style".

Reading the indentation

The importance of good indentation is that experienced programmers tend to glance down code and assume that indented code is logically grouped. For example, everything under an "if" or "while", being indented, immediately gives the eye a visual clue. If you muck around with that by over or under-indenting, it is confusing for the reader, and can lead to subtle bugs, where you don't notice something because of bad indentation.

Comments


Comments should help the reader understand the code. Remember, in a year's time you yourself might be looking at your own code wondering "why did I do that?".

Don't state the bleeding obvious:


  TCNT2 = 0;     // set TCNT2 to 0


However this is good:

  TCCR2A = bit (WGM21) ;   // CTC mode


If things behave in an obscure way, a comment helps:


  OCR2A  = 124;            // count up to 125  (zero relative!!!!)


Comments like this don't do much:


Serial.begin (115200);   // set baud rate to 115200


They are worse if you change one and not the other:


Serial.begin (115200);   // set baud rate to 9600


Better would be to state what the number means:


Serial.begin (115200);   // baud rate


This is helpful, to clarify which pin is which:


SoftwareSerial thermalPrinter (2, 3);  // Rx, Tx


Try to avoid single-line "block" comments:


Serial.begin (115200);   /* baud rate */


That makes it hard to comment-out blocks of code later on.


Use a consistent 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").


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.




Further reading


Style

C Traps and Pitfalls (PDF document): http://literateprogramming.com/ctraps.pdf
C Style: Standards and Guidelines: http://syque.com/cstyle/




Arduino official documentation

Reference: http://arduino.cc/en/Reference/HomePage
Playground: http://playground.arduino.cc/
Hardware: http://arduino.cc/en/Main/Products
Tutorials: http://arduino.cc/en/Tutorial/HomePage
Libraries: http://arduino.cc/en/Reference/Libraries




Sequence points and undefined behavior

http://en.wikipedia.org/wiki/Sequence_point
http://c-faq.com/expr/seqpoints.html
http://en.wikipedia.org/wiki/Undefined_behavior




My own pages about Arduino and electronics

http://www.gammon.com.au/electronics

Popular topics:







Memory usage

Memories of an Arduino: http://learn.adafruit.com/memories-of-an-arduino
Debugging dynamic memory allocation: http://andybrown.me.uk/wk/2011/01/01/debugging-avr-dynamic-memory-allocation/
Find available memory: http://playground.arduino.cc/code/AvailableMemory




Libraries

Installing: http://learn.adafruit.com/adafruit-all-about-arduino-libraries-install-use
Installing: http://arduino.cc/en/Guide/Libraries
Installing: http://www.freetronics.com/pages/how-to-install-arduino-libraries
Writing: http://www.arduino.cc/en/Hacking/Libraries




General tips from Adafruit

http://learn.adafruit.com/arduino-tips-tricks-and-techniques

A lot of useful stuff in that link about bootloaders, flashing the USB chip, and so on.




C Macros

http://gcc.gnu.org/onlinedocs/cpp/Macros.html




Standard Template Library (STL)

http://andybrown.me.uk/wk/2011/01/15/the-standard-template-library-stl-for-avr-with-c-streams/




C++ static initialization order fiasco

What it is: http://www.parashift.com/c++-faq/static-init-order.html
How to prevent it: http://www.parashift.com/c++-faq/static-init-order-on-first-use.html




C++ classes

http://www.cplusplus.com/doc/tutorial/classes/






Info #1: How to disassemble generated code


If you have the sketch source, turn on verbose compiling (See Preferences):



Do a "verify" and you will then see the generated file names like this:



Right near the bottom, copy the name of the xxx.elf file to the clipboard, and then use a Console/Command/Terminal window like this:


avr-objdump -S xxx.elf


(where xxx.elf is the name you copied). You will see the disassembled code scroll by (this is assembler code, with the C code interspersed inside it).

It should look something like this:


	reg = portModeRegister(port);
 310:	90 e0       	ldi	r25, 0x00	; 0
 312:	88 0f       	add	r24, r24
 314:	99 1f       	adc	r25, r25
 316:	fc 01       	movw	r30, r24
 318:	e8 59       	subi	r30, 0x98	; 152
 31a:	ff 4f       	sbci	r31, 0xFF	; 255
 31c:	a5 91       	lpm	r26, Z+
 31e:	b4 91       	lpm	r27, Z+



Tip:

You may find that the source for your code is not in the disassembly (however the source to libraries is).

To fix this, find the path of your sketch folder (where the .ino file is) and add that with the -I option like this:


avr-objdump -S -I/path/to/the/sketch/folder  xxx.elf 


That's the folder name, not the .ino file itself.

Also, if your code contains NOPs (no-operation) you can see those as well as ordinary code by adding the -z option, eg.


avr-objdump -S -z xxx.elf



If you don't have the source you can still disassemble from a .hex file (for example if you got object code directly from a chip, or from a place that doesn't supply the source) like this:


avr-objdump -j .sec1 -d -m avr3 xxx.hex


The results will look similar, but you won't have the C source interspersed with the assembler code:


 310:	90 e0       	ldi	r25, 0x00	; 0
 312:	88 0f       	add	r24, r24
 314:	99 1f       	adc	r25, r25
 316:	fc 01       	movw	r30, r24
 318:	e8 59       	subi	r30, 0x98	; 152
 31a:	ff 4f       	sbci	r31, 0xFF	; 255
 31c:	a5 91       	lpm	r26, Z+
 31e:	b4 91       	lpm	r27, Z+


You can save output to a file by redirecting the output to a file, like this:


avr-objdump -j .sec1 -d -m avr3 xxx.hex > nick.txt


Now just edit that file in a text editor.


Info #2: Where is the source for the inbuilt libraries?


Depending on your operating system, most of the library source can be found inside the Arduino "install" directory (wherever you put that). For example on Windows it might be:



On the Mac you will need to RH-click on the application and select "Show Package Contents" like this:

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


98,313 views.

Postings by administrators only.

[Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

Quick links: MUSHclient. MUSHclient help. Forum shortcuts. Posting templates. Lua modules. Lua documentation.

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.

[Home]


Written by Nick Gammon - 5K   profile for Nick Gammon on Stack Exchange, a network of free, community-driven Q&A sites   Marriage equality

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( https://gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Hosted at HostDash]