Subroutines

(file: Subroutines.htm)

Subroutines make your code more manageable.

Arduino In terface- start new tab

It pays to create a new tab for them, put them there. The image shows you the button to click to start a new tab. When you've done that, name the tab in the box provided at the bottom of the IDE window. (IDE: "Integrated Development Environment"... the "thing" you use to write Arduino "stuff".)



The simplest type of subroutine just "gathers up" a few (or more!) commands, gives you a "new word" which will cause them to happen. That's called "calling the subroutine".

Suppose you already had everything set up to make the following loop() routine...

digitalWrite(13,HIGH); delay(20): digitalWrite(13,LOW); delay(40); digitalWrite(13,HIGH); delay(20): digitalWrite(13,LOW); delay(40); digitalWrite(13,HIGH); delay(20): digitalWrite(13,LOW); delay(40); delay(1000);

... cause an LED to wink briefly three times, and then pause for 1000 milliseconds.

If you had enough to make the above work, then, if you put the following on your subroutines tab...

void Wink() { digitalWrite(13,HIGH); delay(20); digitalWrite(13,LOW); delay(80); }//end of Wink()

... you would be able to make your loop() routine just...

Wink(); Wink(); Wink(); delay(1000);

A lot neater. A lot easier to understand, I think you'll agree?

Re-cap: There are TWO parts... The part where you use the subroutine, and part where you declare it.

Notice this: To call (i.e. "use") a subroutine named "Wink", you need "Wink()". Those brackets will always be there. Sometimes there will be "stuff" between them... but not for simple subroutines like this one. But even empty, they are important.

"... named it Wink": You choose the name. The rules are very like the rules for variable names. Letters. Digits. Underscore. Don't start with a digit. Case sensitive. Be consistent about case... I tend to start simple functions with an upper case letter, as I have done with "Wink".

To declare a simple subroutine, you use the following syntax....

void name for subroutine (discussed above) () {block-stmts}
  where "block-stmts" is one or more statements, with a semi-colon after each, including the last..

It is Good Practice to put a comment after the last { saying "end (whatever the subroutine is called)". If you put the "()" after the name, it helps you remember to use that.

It isn't the word "void" that makes this a subroutine declaration. We will put other things there, in due course. But for the first two types of subroutine, we always use "void".

A little nuisance....

Sometimes, when you have "split out" subroutines to a separate tab, during compile, the system moans. Let's say that you've created a subroutine called MySubroutine. If the "little nuisance" is cropping up, the system will say "MySubroutine was not declared in this scope".

It that happens, copy the first line of the subroutine, i.e. "void Wink()" for the one we did not long ago, to the main part of the program. At the highest level, i.e. "outside"/ "above" both setup() and loop(). Add a semi-colon to the end. It may help to place it above any place where you use it.

(You can get "not in scope" errors other ways. Some of these get discussed further down this page.)

Fancier subroutine...

Starting with the one we just used, we can make a slightly fancier subroutine...

In the place where we declare it...

void Wink(int iHowLongOn) { digitalWrite(13,HIGH); delay(iHowLongOn); digitalWrite(13,LOW); delay(80); }//end of Wink()

I put "int iHowLongOn" between the brackets. That ("int iHowLongOn") looks just like declaring a variable of data-type int, called iHowLongOn. Which is essentially what we are doing. It is WHERE we have done it that makes the magic.

The only other difference between this and the Wink() we made earlier is in the first "delay" statement. Previously, we had "delay(20)". Now we have "delay(iHowLongOn)". That will create a delay. How long will the delay be? If we have 100 in iHowLongOn, the delay will be 100ms.

How do we put a number in iHowLongOn?

We do that when we call the subroutine. Modify what's in the loop() part of the program....

Wink(10); Wink(300); Wink(10); delay(1000);

... and run the program again. What happens should make sense, if you think about it. Stick with it until it DOES make sense. Once you "understand" how this "passing of values to a subroutine" works, make some changes in the stuff in loop(), and see if the program does what you thought it would. When it always does, then you DO understand it!

You should at some point say "Wait a minute! There are TWO "Wink"s! How can that be right?"

Well spotted. But the language is very clever. It treats the Wink with someting in the brackets as different from the Wink with nothing in the brackets. Even so, I tend not to use a name more than once in a program. It just gets confusing!

The "stuff in the brackets" is called the argument(s) of the subroutine. Our first Wink() had no arguments. The second version had one argument. Also sometimes called a parameter.

You use arguments (aka parameters) to "pass data to" the subroutine.

Moving on

What you pass can be in a variable...

void loop() { int iOnFor;//Declares the variable iOnFor=10; Wink(iOnFor);//Wink on for 10ms iOnFor=iOnFor * 16; Wink(iOnFor);//Wink on for 160ms iOnFor=iOnFor * 16; Wink(iOnFor);//Wink on for 2560ms (16*16=256) delay(1000); }//end of loop()

Moving on again...

One arguement is good... wouldn't two be better? Nothing could be simpler. Change the declaration of the subroutine to...

void Wink(int iHowLongOn, int iHowLongOff) { digitalWrite(13,HIGH); delay(iHowLongOn); digitalWrite(13,LOW); delay(iHowLongOff); }//end of Wink

... and now you can, in loop(), do...

iOnFor=10; Wink(iOnFor,200);//Wink on for 10ms, with 200ms off afterwards

A word about "Scope"

Once you get the hang of it,it isn't hard. But it's easy to trip over "scope" issues.

Everything has a "scope". It is particularly important with variables.

In the example we have been working on, the of iHowLongOn is "local to the definition of Blink()". That means we can only use it inside of Blink(). This is annoying at first, but a Very Good Thing that you will eventually get used to.

If you declare a variable in the "top" levels of the sketch...

//------------------- int iLeaveOnFor=100; //------------------- void setup() { pinMode(13,OUTPUT); }//end of setup() //-------------------- void loop() { NewWink(); delay(1000); }//end of loop()

... with NewWink declared as follows...

void NewWink() { digitalWrite(13,HIGH); delay(iLeaveOnFor); digitalWrite(13,LOW); delay(80); }//end of NewWink()

... it will work... but that would be a Bad Way. The variable iLeaveOnFor is valid everywhere in the program. And thus can be changed everywhere, and is underfoot everywhere, just waiting for you to trip on it.

Doing things the way they were done earlier means that iHowLongOn (for example) only appears in the part of the program where you actually need to have it appear. And it is "safe" from "stuff" happening elsewhere.

If you get error messages like "xxx was not declared in this scope", it will be to do with where you declared a variable you want to use. Remember that the whole program is made of blocks within blocks within blocks. Delcare variables a "deep" as you can in this system of blocks.

An aside...

Would you believe that a program as simple as...

//------------------- int iLeaveOnFor=100; //------------------- void setup() { pinMode(13,OUTPUT); }//end of setup() //-------------------- void loop() { NewWink(); delay(1000); }//end of loop() //-------------------- void NewWink() { digitalWrite(13,HIGH); delay(iLeaveOnFor); digitalWrite(13,LOW); delay(80); }//end of NewWink()

... could fail to work? And yet give no error messages? Leave out the () after the "NewWink" in the loop() section, and you can see it happen. The program compiles. The program uploads. And it does do something... but you won't see the LED winking. What the program is doing is NOT making the LED wink. Put the ()'s in, and it all works as intended. Sigh. Be brave.



Much bigger, much better

We're now going to leap forward, and look at the fancy sort of subroutine.

We are "building on" the simple and slightly fancier subroutines we've seen before... but now we're adding something powerful.

Previously, we "passed" values to the subroutine.

Now we're going to do a subroutine that passes values back

For our example, you have to imagine (or set up) an Arduino with an LED on pin 13, and a potentiometer sending a voltage to analog input 0, a voltage that changes when you turn the pot's knob.

Just to make sure all is well, enter the following. It (should) just check that you've got the potentiometer connected correctly. Watch the serial monitor while the program is running, and you change the potentiometer's setting... the number appearing in the serial monitor should change.

//------------------- const byte pAnIn0=A2;//Leave in //------------------- void setup() { Serial.begin(9600); pinMode(13,OUTPUT); }//end of setup() //-------------------- void loop() { int iAnIn0; //This is where we "declare" the variable. iAnIn0=analogRead(pAnIn0); Serial.println(iAnIn0); delay(800); }//end of loop()

Once that's showing the potentiometer correctly connected, we will move the "analogRead" into a subroutine... a subroutine that returns a value to the place from which the subroutine was called. The subroutine looks like...

int FetchAnalogValue() { int iLocal; iLocal=analogRead(pAnIn0); return iLocal; }//end of FetchAnalogValue()

Note the line that says "return iLocal"... THAT's what sends a value to the calling routine. Once the subroutine is in place, the main program can be changed to...

void loop() { int iAnIn0; //This is where we "declare" the variable. iAnIn0=FetchAnalogValue(); Serial.println(iAnIn0); delay(800); }//end of loop()

(Most of what we had before remains unchanged, so I only showed you loop(). Even most of that is unchanged.) Only a little change! But it illustrates a Very Powerful capablilty. You will use subroutines which return values a lot.

You CAN program without subroutines. If you are hard working and brilliant. It is just, once you have the hang of it, Much Easier to program with subroutines.

They let us "wrap" "stuff" up. Get it out from underfoot.

General form of a subroutine which returns a value

To declare a subroutine which returns a value, you use the following syntax....

data-type name-for-subroutine () {block-stmts}
  where "block-stmts" is one or more statements, with a semi-colon after each, including the last..

For "data-type" you would put byte or int. (Or void, for a simple subroutine, not returning a value.) Or one of some of the other data-types. (We'll discuss that in a moment!).

Within the block of statements, there needs to be a line...

result  ... followed by something that boils down to a datum which is right for the data-type of the sub-routine..

A moment ago, I said "Or one of some of the other data-types."

You can't make sub-routines to return every type of data. But you can make them to return many types of data.

A type you may not have met is the boolean data-type.

if I do....

void loop() { boolean boTmp; boTmp=true; if (boTmp) {Serial.println("boTmp is true"); }//end of If delay(400); }//end of loop()

... it will run just fine... if a bit pointlessly. And it doesn't use a subroutine.

But if I still have a potentiometer on the system, as used in previousl example, and make the following subroutine...

boolean boPotAbove250() { boolean boTmp; int iLocal; iLocal=analogRead(pAnIn0); Serial.println(iLocal); boTmp=false; if (iLocal>250) {boTmp=true;}; return boTmp; }//end of boPotAbove250()

... and change the if statement in loop() to...

void loop() { boolean boTmp; boTmp=true; if (boPotAbove250()) {Serial.println("boTmp is true"); }//end of If delay(400); }//end of loop()

Be sure to include the () that should follow the "buPotAbove250". Compare the two versions. They both "work" the same, as far as anyone using the programmed Arduino can see. But, for the programmer, see how much "junk" is taken OUT of the main part of the program, put "away", out from under foot, when you use the magic of subroutines?

-------
And yes, you can make subroutines which both receive a value, and return one.



Abrupt end! Sorry!


Here is how you can contact this page's author, Tom Boyd.