Embedded

View Original

Embedded Wednesdays: Program Flow Control Statements

Computers execute instructions one by one, in a strict order set out by the programmers, this is known as the flow of execution. But only having a straight flow would chew up our program memory really fast, what we need is some loops.

This week we will take a look at the flow control statements in the C language.  The two things that make computers useful are the ability to process data quickly and the ability to use data to influence the program flow. C provides a set of flow control statements very much like any other algebraic language. It gives if, switch, for, and do/while statements. Let’s look at each one.

if

See this content in the original post

There are two forms of the if statement, one is used to execute a block of code if a condition is true, the other executes one block of code if a condition is true and another if the condition is false.

The if statement looks like:

if (condition) {
  Statement;
}

Where the condition is some logical comparison, a bool variable, or function call that returns a bool. For comparison operators, C has a pretty standard set:

== - equal to. Beware a single = character is assignment.
!= - not equal to.
<  - less than
>  - greater than
<= - less than or equal to
>= - greater than or equal to
&& - logical AND
|| - logical OR. This is a pair of vertical bars, or stick characters.

If you wanted to compare some variable to a value, you would write:

if ( answerToQuestion == 42) {
   DoSomething();
}

So far we have seen how an if statement can be used to execute a block of code if the condition is true, bypassing it if it is false. If we wish to do one thing if the condition is true and another if it is false, we use the else clause.

if ( temperature < 0) {
  printf( “frozen\n”);
}  else  {
  printf( “not frozen\n”);
}

Within the curly braces of the if statement, we can put any number of statements, including other if statements. These are called nested ifs.

if ( temperature < 0) {
  printf( “frozen\n”);
} else {
   if ( temperature < 100) {
      printf( “liquid\n”);
   } else {
      printf( “gas\n”);
   } 
}

For our next example, assume that we want to classify characters as vowels or consonants. We could use something like:

#include <iso646.h>
#include <stdio.h>

char letter;

readin( letter);    // call a function that will get our character

if ((letter == 'A') or (letter == 'a')) {
   printf( "Vowel\n");
} else {
   if ((letter == 'E') or (letter == 'e')) {
      printf( "Vowel\n");
   } else {
      if ((letter == 'I') or (letter == 'i')) {
         printf( "Vowel\n");
      } else {
         if ((letter == 'O') or (letter == 'o')) {
            printf( "Vowel\n");
         } else {
            if ((letter == 'U') or (letter == 'u')) {
               printf( "Vowel\n");
            } else {
               if ((letter == 'Y') or (letter == 'y')) {
                  printf( "Sometimes a vowel\n");
               } else {
                  printf( "Consonant\n");
               }
            }
        }
    }
}

And you quickly go MAD!

switch

See this content in the original post

What we really need is a multi-way if statement, C has a statement called switch that provides this. It takes an integer or char as a parameter, and gives us a multi-way branch. Float values cannot be used as parameters since they cannot be reliably checked for equivalence.  The switch statement looks like:

switch (var) {
   case value1:   // note the colon after the case value
      statement;
      statement;
   break;

   case value2:
      statement;
      statement;
   break;

   default:
      statement;
      statement;
   break;
}

Over the years I have come up with my own style for formatting case blocks. I de-indent the break statement so that it lines up with the case. It's trivial, but it guides your eye and shows you that the break statement is present. The compiler doesn't care, so I do it for the future reader of my code.

You can have any number of statements in each case block, and up to 1023 cases. You do not need curly braces around your statements, the statement block will extend from the case to the next break. If you forget to put in your break statement your code will just continue through the next case until it hits a break in a trailing case or the closing curly brace of the switch. Falling through cases because of a missing break is a common bug, be careful to put break statements at the bottom of every case block. Falling through case statements can be useful if you have multiple cases that are handled identically.

Our example of consonant/ vowel sorting would turn into:

char letter;

readin( letter);   // call a function that will get our character

switch (letter) {
   case 'A':
   case 'a':
   case 'E':
   case 'e':
   case 'I':
   case 'i':
   case 'O':
   case 'o':
   case 'U':
   case 'u':
      printf( "Vowel\n");
   break;        // Andrei's style of de-indented breaks

   case 'Y':
   case 'y':
      printf( "Sometimes a vowel\n");
   break;

   default:
      printf( "Consonant\n");
   break;
}

If the switch value is not handled by any of the explicit cases, it is handled by the default case. If every possible case is explicitly handled, it is a good idea to put in a default case to handle all of the impossible cases that can arise from memory corruption, programming errors, or normal user inputs. Another option is to just put a comment where the default code would go explaining why you think the default case is not necessary.

for

See this content in the original post

For statements are used to iterate over, or individually processes each of, a bunch of data. The intent is to provide a loop with a way to terminate it, and a variable that you can use in calculations or as an index to an array.

The for statement has confused syntax made up of three parts separated by semicolons:

for ( initial; test; increment ) {
  statements;
}
  • Initial - this section is used to initialize your loop control variable.
  • Test - the for loop will continue as long as this test is true.
  • Increment - this section is used to alter the value of the control variable, typically increment or decrement it. (Wikipedia calls this part the afterthought, I think that's worse than calling it the increment.)

For example, here we have a very common pattern for going through a C array. Since C arrays starts at 0, we initialize our counter to 0 and continue the loop while the counter is less than the number of items in the array.

uint16_t total = 0;
uint16_t values[100];

...

for ( i = 0; i < 100; i++) {
  total = total + values[i];
}

This example goes from 0 to 99, which matches the element numbers of the array.

If you don’t need the various sections of the for statement, they can be left out. Your control variables may already be initialized, or something inside of your loop may change the control variable.

For embedded systems, you will typically have an infinite loop which can be set up as:

for ( ; ;) {
   DoSomething();
}

or

while ( true) {
   DoSomething();
}

I prefer the for (;;) version, but it looks a bit funny, and I’ve had a few people give me a hard time about it (right, Elecia?) so I put it in a define (which Wade hates):

#define FOREVER for (; ;)

Now my program will have a forever loop:

FOREVER {
   Body of the embedded program goes here.
}

 

do/while

See this content in the original post

The while loop executes a block of statements while a condition is true. There are two forms, one with the test at the top of the loop, and the other with the test at the bottom.

while ( moreDataToProcess) {
   moreDataToProcess = Process( data);
}

do {
   moreDataToProcess = Process( data);
} while (moreDataToProcess);

The major difference between the two forms is that the do-while will run at least once. Sometimes, you need that first loop to get things started, like when reading status registers.

While loops have a nasty habit of causing unwanted infinite loops. Unlike if, switch, and for loops with counters, there is no easy way to determine if a while loop will terminate. I’ve seen be waiting for the data ready bit on a UART to become active and it never does because of something happened.

A better idea is to add in a loop counter that will

uint32_t counter = 0;
while (( UARTDataReady == 0) and (counter < 1000)) {
  counter = counter + 1;
  someOtherCommands;
}

 

Watch Out!

There are a couple of things to watch out for; C's really terse syntax and underusing curly braces.

= | and &

C uses a single = for assignment and == for logical equivalence testing, making it really easy to write this:

if ( answerToQuestion = 42) {
   DoSomething();
}

What this will do is assign the value 42 to answerToQuestion, and always execute the block of code that follows (unless the value you are assigning is zero). The value of the assignment is also used in the if statement according to the rules of the bool type, anything non-zero will be considered true, so the block gets executed. Good C compilers will flag this with a warning that you are doing an assignment where a comparison would normally be expected. You can also strengthen you code by writing your conditions backwards:

if (42 = answerToQuestion) {   // Will not compile, can't assign to 42
   DoSomething();
}

if (42 == answerToQuestion) {   // Will compile
   DoSomething();
}

For doing bit manipulations, C provides the operators & and |. Unfortunately these look a lot like the && and || operators for logical relations and and or, and a lot of people can't remember which is which.

People mix these up all of the time, and good compilers and syntax checkers will spot the problems from their context, so always clear all of your warnings. These errors are so common that the header file iso646.h was introduced, providing the comparison keywords: “and”, and “or”, as well as the bit manipulation keywords "bitand" and "bitor". Unfortunately it does not help with the == versus = problem, you will have to deal with the warnings that your compiler generates.

With these keywords, we can write:

#include <iso646.h>
#include <stdio.h>

if ((value >= 0) && (value <= 100)) {
   printf( “The value is between 0 and 100\n”);
}

/* Can be written as */
if ((value >= 0)  and  (value <= 100)) {
   printf( “The value is between 0 and 100\n”);
}

Curly Braces

A suggestion from the old guy, always put curly braces on if statements. The compiler does not use your indenting to determine which instructions are part of the if statement. It is very easy to add an instruction in haste and inject bugs:

if (value == 42)         // DON'T USE THIS. EXAMPLE OF A BUG
   ReadPoetry( vogon);
   Panic( false);        // Hastily added line

To the compiler, this is the same as:

if (value == 42) {
   ReadPoetry( vogon);
}
Panic( false);

The extra statement is not a part of the if structure and will always be executed.  The compiler doesn't use the indentation for anything, it is only for the programmers. Curly braces are cheap (I can send you a whole bunch in an email if you run out), but they can save you a lot of work in the future.


We've looked at the C flow control statements if, switch, for, while, and do. They are pretty straight forward, but when combined with some statements to do calculations and save values, we can write real programs.

Next week, we’ll take a look at functions. These are sometimes known as subroutines, methods, or procedures, but in C they are all just functions.


This post is part of a series. Please see the other posts here.


Created using the program yEd.