Functions - Programming Arduino Getting Started with Sketches (2012)

Programming Arduino Getting Started with Sketches (2012)

4
Functions

This chapter focuses mostly on the type of functions that you can write yourself rather than the built-in functions such as digitalWrite and delay that are already defined for you.

The reason that you need to be able to write your own functions is that as sketches start to get a little complicated, then your setup and loop functions will grow and grow until they are long and complicated and it becomes difficult to see how they work.

The biggest problem in software development of any sort is managing complexity. The best programmers write software that is easy to look at and understand and requires very little in the way of explanation.

Functions are a key tool in creating easy-to-understand sketches that can be changed without difficulty or risk of the whole thing falling into a crumpled mess.

What Is a Function?

A function is a little like a program within a program. You can use it to wrap up some little thing that you want to do. A function that you define can be called from anywhere in your sketch and contains its own variables and its own list of commands. When the commands have been run, execution returns to the point just after wherever it was in the code that called the function.

By way of an example, code that flashes a light-emitting diode (LED) is a prime example of some code that should be put in a function. So let’s modify our basic “blink 20 times” sketch to use a function that we will create called flash:

Image

So, all we have really done here is to move the four lines of code that flash the LED from the middle of the for loop to be in a function of their own called flash. Now you can make the LED flash any time you like by just calling the new function by writing flash(). Note the empty parentheses after the function name. This indicates that the function does not take any parameters. The delay value that it uses is set by the same delayPeriod function that you used before.

Parameters

When dividing your sketch up into functions, it is often worth thinking about what service a function could provide. In the case of flash, this is fairly obvious. But this time, let’s give this function parameters that tell it both, how many times to flash and how short or long the flashes should be. Read through the following code and then I will explain just how parameters work in a little more detail.

Image

Now, if we look at our loop function, it has only two lines in it. We have moved the bulk of the work off to the flash function. Notice how when we call flash we now supply it with two arguments in parentheses.

Where we define the function at the bottom of the sketch, we have to declare the type of variable in the parameters. In this case, they are both ints. We are in fact defining new variables. However, these variables (numFlashes and d) can only be used within the flash function.

This is a good function because it wraps up everything you need in order to flash an LED. The only information that it needs from outside of the function is to which pin the LED is attached. If you wanted, you could make this a parameter too—something that would be well worth doing if you had more than one LED attached to your Arduino.

Global, Local, and Static Variables

As was mentioned before, parameters to a function can be used only inside that function. So, if you wrote the following code, you would get an error:

Image

On the other hand, suppose you wrote this:

Image

This code would not result in a compilation error. However, you need to be careful, because you now actually have two variables called x and they can each have different values. The one that you declared on the first line is called a global variable. It is called global because it can be used anywhere you like in the program, including inside any functions.

However, because you use the same variable name x inside the function, as a parameter, you cannot use the global variable x simply because whenever you refer to x inside the function, the “local” version of x has priority. The parameter x is said to shadow the global variable of the same name. This can lead to some confusion when trying to debug a project.

In addition to defining parameters, you can also define variables that are not parameters but are just for use within a function. These are called local variables. For example:

Image

The local variable timesToFlash will only exist while the function is running. As soon as the function has finished its last command, it will disappear. This means that local variables are not accessible from anywhere in your program other than in the function in which they are defined.

So, for instance, the following example will cause an error:

Image

Seasoned programmers generally treat global variables with suspicion. The reason is that they go against the principal of encapsulation. The idea of encapsulation is that you should wrap up in a package everything that has to do with a particular feature. Hence functions are great for encapsulation. The problem with “globals” (as global variables are often called) is that they generally get defined at the beginning of a sketch and may then be used all over the sketch. Sometimes there is a perfectly legitimate reason for this. Other times, people use them in a lazy way when it would be far more appropriate to pass parameters. In our examples so far, ledPin is a good use of a global variable. It’s also very convenient and easy to find up at the top of the sketch, making it easy to change. Actually, ledPin is really a constant, because although you may change it and then recompile your sketch, you are unlikely to allow the variable to change while the sketch is actually running. For this reason, you may prefer to use the #define command we described in Chapter 3.

Another feature of local variables is that their value is initialized every time the function is run. This is nowhere more true (and often inconvenient) than in the loop function of an Arduino sketch. Let’s try and use a local variable in place of global variable in one of the examples from the previous chapter:

Image

Sketch 4-03 is based on the sketch 3-09, but attempts to use a local variable instead of the global variable to count the number of flashes.

This sketch is broken. It will not work, because every time loop is run, the variable count will be given the value 0 again, so count will never reach 20 and the LED will just keep flashing forever. The whole reason that we made count a global in the first place was so that its value would not be reset. The only place that we use count is in the loop function, so this is where it should be placed.

Fortunately, there is a mechanism in C that gets around this conundrum. It is the keyword static. When you use the keyword static in front of a variable declaration in a function, it has the effect of initializing the variable only the first time that the function is run. Perfect! That’s just what is required in this situation. We can keep our variable in the function where it’s used without it getting set back to 0 every time the function runs. Sketch 4-04 shows this in operation:

Image

Return Values

Computer science, as an academic discipline, has as its parents mathematics and engineering. This heritage lingers on in many of the names associated with programming. The word function is itself a mathematical term. In mathematics, the input to the function (the argument) completely determines the output. We have written functions that take an input, but none that give us back a value. All our functions have been “void” functions. If a function returns a value, then you specify a return type.

Let’s look at writing a function that takes a temperature in degrees Centigrade and returns the equivalent in degrees Fahrenheit:

Image

The function definition now starts with int rather than void to indicate that the function will return an int to whatever calls it. This might be a bit of code that looks like this:

Image

Any non-void function has to have a return statement in it. If you do not put one in, the compiler will tell you that it is missing. You can have more than one return in the same function. This might arise if you have an ifstatement with alternative actions based on some condition. Some programmers frown on this, but if your functions are small (as all functions should be), then this practice will not be a problem.

The value after return can be an expression; it does not have to just be the name of a variable. So you could compress the preceding example into the following:

Image

If the expression being returned is more than just a variable name, then it should be enclosed in parentheses as in the preceding example.

Other Variable Types

All our examples of variables so far have been int variables. This is by far the most commonly used variable type, but there are some others that you should be aware of.

floats

One such type, which is relevant to the previous temperature conversion example, is float. This variable type represents floating point numbers—that is, numbers that may have a decimal point in them, such as 1.23. You need this variable type when whole numbers are just not precise enough.

Note the following formula:

Image

If you give c the value 17, then f will be 17 * 9 / 5 + 32 or 62.6. But if f is an int, then the value will be truncated to 62.

The problem becomes even worse if we are not careful of the order in which we evaluate things. For instance, suppose that we did the division first, as follows:

Image

Then in normal math terms, the result would still be 62.6, but if all the numbers are ints, then the calculation would proceed as follows:

1. 17 is divided by 5, which gives 3.4, which is then truncated to 3.

2. 3 is then multiplied by 9 and 32 is added to give a result of 59—which is quite a long way from 62.6.

For circumstances like this, we can use floats. In the following example, our temperature conversion function is rewritten to use floats:

Image

Notice how we have added .0 to the end of our constants. This ensures that the compiler knows to treat them as floats rather than ints.

boolean

Boolean values are logical. They have a value that is either true or false.

In the C language, Boolean is spelled with a lowercase b, but in general use, Boolean has an uppercase initial letter, as it is named after the mathematician George Boole, who invented the Boolean logic that is crucial to computer science.

You may not realize it, but you have already met Boolean values when we were looking at the if command. The condition in an if statement, such as (count == 20), is actually an expression that yields a boolean result. The operator == is called a comparison operator. Whereas + is an arithmetic operator that adds two numbers together, == is a comparison operator that compares two numbers and returns a value of either true or false.

You can define Boolean variables and use them as follows:

Image

Boolean values can be manipulated using Boolean operators. So, similar to how you can perform arithmetic on numbers, you can also perform operations on Boolean values. The most commonly used Boolean operators are and, which is written as &&, and or, which is written as ||.

Figure 4-1 shows truth tables for the and and or operators.

From the truth tables in Figure 4-1, you can see that for and, if both A and B are true, then the result will be true; otherwise, the result will be false.

On the other hand, with the or operator, if either A or B or both A and B are true, then the result will be true. The result will be false only if neither A nor B is true.

In addition to and and or, there is the not operator, written as !. You will not be surprised to learn that “not true” is false and “not false” is true.

Image

Figure 4-1 Truth tables

You can combine these operators into Boolean expressions in your if statements, as the following example illustrates:

Image

Other Data Types

As you have seen, the int and occasionally the float data types are fine for most situations; however, some other numeric types can be useful under some circumstances. In an Arduino sketch, the int type uses 16 bits (binary digits). This allows it to represent numbers between −32767 and 32768.

Other data types available to you are summarized in Table 4-1. This table is provided mainly for reference. You will use some of these other types as you progress through the book.

Image

Table 4-1 Data Types in C

One thing to consider is that if data types exceed their range, then strange things happen. So, if you have a byte variable with 255 in it and you add 1 to it, you get 0. More alarmingly, if you have an int variable with 32767 and you add 1 to it, you will end up with −32768.

Until you are completely comfortable with these different data types, I would recommend sticking to int, as it works for pretty much everything.

Coding Style

The C compiler does not really care about how you lay out your code. For all it cares, you can write everything on a single line with semicolons between each statement. However, well-laid-out, neat code is much easier to read and maintain than poorly laid-out code. In this sense, reading code is just like reading a book: Formatting is important.

To some extent, formatting is a matter of personal taste. No one likes to think that he has bad taste, so arguments about how code should look can become personal. It is not unknown for programmers, on being required to do something with someone else’s code, to start by reformatting all the code into their preferred style of presentation.

As an answer to this problem, coding standards are often laid down to encourage everyone to lay out his or her code in the same way and adopt “good practice” when writing programs.

The C language has a de facto standard that has evolved over the years, and this book is generally faithful to that standard.

Indentation

In the example sketches that you have seen, you can see that we often indent the program code from the left margin. So, for example when defining a void function, the void keyword is at the left margin, as is the opening curly brace on the next line, but then all the text within the curly braces is indented. The amount of indentation does not really matter. Some people use two spaces, some four. You can also press Tab to indent. In this book, we use two spaces for indentation.

If you were to have an if statement inside a function definition, then once again you would add two more spaces for the lines within the curly braces of the if command, as in the following example:

Image

You might include another if inside the first if, which would add yet another level of indentation, making six spaces from the left margin.

All of this might sound a bit trivial, but if you ever sort through someone else’s badly formatted sketches, you will find it very difficult.

Opening Braces

There are two schools of thought as to where to put the first curly brace in a function definition, if statement, or for loop. One way is to place the curly brace on the line after the rest of the command, as we have in all the examples so far, or put it on the same line, like this:

Image

This style is most commonly used in the Java programming language, which shares much of the same syntax as C. I prefer the first form, which seems to be the form most commonly used in the Arduino world.

Whitespace

The compiler ignores spaces tabs and new lines, apart from using them as a way of separating the “tokens” or words in your sketch. Thus the following example will compile without a problem:

Image

This will work, but good luck trying to read it.

Where assignments are made, some people will write the following:

Image

But others will write the following:

Image

Which of these two styles you use really does not matter, but it is a good idea to be consistent. I use the first form.

Comments

Comments are text that is kept in your sketch along with all the real program code, but which actually performs no programming function whatsoever. The sole purpose of comments is to be a reminder to you or others as to why the code is written as it is. A comment line may also be used to present a title.

The compiler will completely ignore any text that is marked as being a comment. We have included comments as titles at the top of many of the sketches in the book so far.

There are two forms of syntax for comments:

• The single line comment that starts with // and finishes at the end of the line

• The multiline comment that starts with a /* and ends with a */

The following is an example using both forms of comments:

Image

In this book, I mostly stick to the single-line comment format.

Good comments help explain what is happening in a sketch or how to use the sketch. They are useful if others are going to use your sketch, but equally useful to yourself when you are looking at a sketch that you have not worked on for a few weeks.

Some people are told in programming courses that the more comments, the better. Most seasoned programmers will tell you that well-written code requires very little in the way of comments because it is self-explanatory. You should use comments for the following reasons:

• To explain anything you have done that is a little tricky or not immediately obvious

• To describe anything that the user needs to do that is not part of the program; for example, // this pin should be connected to the transistor controlling the relay

• To leave yourself notes; for example, // todo: tidy this - it’s a mess

This last point illustrates a useful technique of todos in comments. Programmers often put todos in their code to remind themselves of something they need to do later. They can always use the search facility in their integrated development environment (IDE) to find all occurrences of // todo in their program.

The following are not good examples of reasons you should use comments:

• To state the blatantly obvious; for example, a = a + 1; // add 1 to a.

• To explain badly written code. Don’t comment on it; just write it clearly in the first place.

Conclusion

This has been a bit of a theoretical chapter. You have had to absorb some new abstract concepts concerned with organizing our sketches into functions and adopting a style of programming that will save you time in the long run.

In the next chapter, you can start to apply some of what you have learned and look at better ways of structuring your data and using text strings.