“Marching” LEDs

An Introduction to Binary Numbers

“Marching” LEDs

An Introduction to Binary Numbers

Introduction

This project introduces binary numbers, bit-wise operators, for loops, and an MPIDE function(bit()). We will use these new concepts to create “marching” LEDs. An example of “marching” LEDs is shown in Fig. 1.

Figure 1. Example of “marching” LEDs.
Before you begin, you should:
  • Understand how to wire an LED as shown in Blink External LED
  • Understand how to download a sketch from MPIDE to a chipKIT™ board.
After you're done, you should:
  • Understand the basics of binary numbers.
  • Understand what bit-wise operators are.
  • Understand the modulo operator and its typical uses.
  • Understand how to use the for loop.

Inventory:

Qty Description Typical Image Schematic Symbol Breadboard Image
6 LEDs
6 Resistor (3.3 kΩ)

Step 1: Circuit Setup

This circuit is rather straight forward. It is simply six LEDs with resistors connected to six different pins. Figure 2 displays the circuit diagram, and the process is explained in the steps below.

Figure 2. Circuit diagram.

  1. Connect the six wires to pins 2 through 7.
  2. Connect the other end of the wires to the breadboard, but with a row between each wire.
  3. Connect the LEDs across the valley of the breadboard, with the anode (long end) connected to the wires to the pins.
  4. Place the resistors so that they connect to the cathode (short end) of the LEDs and directly to the ground rail.
  5. Finally, use a wire to connect ground on the board to the ground rail that is connected to the resistors.

Binary Numbers

You have probably heard of binary numbers and that computers use them for everything. This is completely true; computers represent everything in binary. Binary numbers can be used to represent regular numbers, letters, pictures, and movies in one way or another.

Though, the question is of course, “what is a binary number?” Simply put, a binary number is just a number that is composed of ones and zeros instead of the digits zero to nine. It should be made clear that binary numbers are just numbers using two symbols, 1 and 0, just like decimal which uses ten symbols, 0 through 9. It might help to think of numbers having different “languages,” where decimal is a language that has ten “letters” to create words, and similarly, binary is a language that has two “letters” to create words. Just like with different languages, two words that look completely different can mean the exact same thing. In short, binary numbers are just numbers written with two symbols, instead of the more common ten symbols.

Binary numbers work the same as our normal base 10 system where numbers are comprised of digits which can represent a number from 0 to 9. Binary numbers instead have “bits,” which is short for “binary digits” and each bit is one of two numbers, 1 or 0.

Because of symbol reuse in binary (1 and 0), we can end up in a situation where both binary and decimals numbers look the same, but have very different values. One good example is 4 and 100. The 4 is clearly a decimal number, but the 100 could be decimal or binary. If the 100 is binary, then it is equivalent to 4 despite looking like one hundred.

Converting to and from a binary number can be somewhat involved since a lot of numbers must be tracked and manipulated. The process for both converting to, and converting from, is covered in the link that can be found above.

One thing that is very helpful when working with both decimal and binary numbers is to use notation that is clear. In C, prefixes are used to denote a binary number instead of the default decimal number. The prefix is 0b, so that binary 4 looks like 0b100, instead of just 100. There is also base notation which is more useful when writing. base notation is easy to use since every number has its base (10 for decimal and 2 for binary) written as a subscript at the end of a number. Using the binary 4 example again, it would look like 1002.

If you don't want to convert back and forth by hand, or simply want check your work, check out this binary converter website. The site also includes a hexadecimal(base 16) conversion as well, but hexadecimal is for another topic.

Bit-wise Operators

A bit-wise operator does one of the logical operations shown to the left and described below, but with each bit from the input number being an input for the logical operator.

The tables below are truth tables which give the result of a logic operation when given some input, A and B. There are four main logical operators, three of which are the primary operators. The three primary logical operators are: AND, OR, and NOT.

AND
A B Result
0 0 0
0 1 0
1 0 0
1 1 1
OR
A B Result
0 0 0
0 1 1
1 0 1
1 1 1
NOT
A Result
0 1
1 0

 

XOR
A B Result
0 0 0
0 1 1
1 0 1
1 1 0

The AND operator returns 1 if both inputs are 1, and it will return 0 if either input is 0. The symbol in C for a bit-wise AND is '&', which looks like the logical operator AND '&&'.

The OR operator returns 1 if either inputs are 1, and it will only return 0 if both are 0. The symbol in C for a bit-wise OR is '|', which looks like the logical operator OR '||'.

The NOT operator returns the opposite of the input, it returns 0 if the input is 1, and 1 if the input is 0. The symbol in C for a bit-wise NOT is '~'.

The fourth operator is the XOR operator, which is called the eXclusive OR, and it returns 1 only if one input is 1 and the other is 0, otherwise it returns 0 if both inputs are the same. It is not considered a primary operator since it can be created from the three primary operators. The symbol in C for the bit-wise XOR operator is '^'.

Using the bit-wise AND operator on two numbers will return a number where each bit is the result of the AND logical operator on the same bit from each input numbers. Figure 3 shows an example of bit-wise AND with two different binary numbers, Fig. 4 is an example of bit-wise OR with the same binary numbers in the bit-wise AND example, Fig. 5 is an example of bit-wise NOT with one of the same numbers in the previous figures, and Fig. 6 is an example of bit-wise XOR with the same binary numbers in bit-wise AND and OR examples.

Figure 3. Bit-wise AND.
Figure 4. Bit-wise OR.
Figure 5. Bit-wise NOT.
Figure 6. Bit-wise XOR

There is another bit-wise operator: the bit shift operator. There are two versions of the bit shift operator, one for shifting right and one for shifting left. The number to the left of the symbol is the number whose bits are going to be shifted. The number to the right of the symbol is the number of bits to shift. The symbol for shift left is '<<' and the symbol for shift right is '>>'.

We will do a simple example to show how bit shifting works. Let's use 0011 1100 as the binary number we are going to shift. If we do the operation 00111100 >> 2, that means we shift the bits of the number to the right by 2. This will give the result 0000 1111. And similarly, 00111100 << 2 will shift the bits to the left by 2, which gives the result 1111 0000. Also, if you look back at how binary numbers work, you'll see that shifting bits is equivalent to multiplying or dividing by a power of 2. Try writing a few numbers, shifting their bits and converting back, and compare to the original number.

Modulo Operator

When we divide integers, we get two results. One is the actual division and the other is the remainder of the division. The remainder is simply what is “left over” after the division. For example, if we divide 17 by 8, 8 goes into 17 two times, since 2 times 8 is 16, then we subtract 16 from 17 and get 1. Thus, 17 divided by 8 is 2 with a remainder of 1.

The operator that returns the remainder is called the modulo operator. In C/C++, the modulo operator uses the '%' symbol. In programming, the modulo operator has many uses, but here are a few common examples. We can determine whether a number is even or odd by finding the remainder of dividing by 2 (since even numbers are divisible by 2), which means the remainder of an even number will be 0. Another use is to keep a number within a range. For example, if you want to keep some number in between 0 and 63, we find the remainder of a division by 64 since the remainder can be any number from 0 to the number that is dividing, minus one.

For Loop

There are three types of loops in, where a loop automatically repeats any code inside the loop. Which means that to run some code for three different inputs, instead of writing the same code three times, we can write that code just once. The first loop covered was the while loop in Blinking LED with “Trainable” Delay and the loop we will cover in this project is the for loop. The last type of loop is the do while loop. The for loop is often used when we need to loop over code for a known number of times. The for loop is different from the other two loops, in that it has its own loop variable that is initialized and incremented automatically by the loop. It should be noted that each type of loop can be converted to another.

Figure 7 shows an example of a for loop that is labeled. In the loop setup (the section in the parentheses), there are three sections that are each separated by a semicolon. The first section is the initialization, which is used to initialize the increment variable that the for loop will use (the variable can be declared outside the loop if it is needed outside, or inside the loop as shown in the example, but it will only exist in the loop's body). The second section is the condition, which the loop uses to know when to stop, using a boolean or conditional expression or variable. The final section is the afterthought, which is how the for loop should change the increment variable every iteration, since sometimes you'll want it to change differently than just incrementing. The most important part of any loop is the code that is looped. It is the reason why we use a loop in the first place. Any code to be looped should go between the loop's curly braces '{' and '}.'

Figure 7. Labeled for loop.

Let's compose a for loop for an example. This example is to find the sum of the numbers from 1 to 10, since this will be easy to check. The first thing to do when making a loop is to determine what the increment variable should be named. Traditionally, these variables are often named i, j, or k, but the increment variable is just like any other variable, and thus can be called whatever, but keep in mind that this variable is local only to the body of the loop. The increment variable should be short but clear that it is the increment variable. Once we have the variable name, we then set its initial value to the number we wish to start at, and in this example, that number is 1.

The next step is to determine the stopping point. We can either use an inclusive or exclusive conditional statement for the end point. The inclusive conditional uses the <= operator and the exclusive conditional uses the < operator. The choice is dependant on your coding style or what makes the most sense for that situation. In this example, we will use the inclusive operator so that it is clear we count until 10 and then stop.

The final step in setting up a for loop is the afterthought, where we increment the variable. For most cases, you likely just use the increment (++) operator, although it can be any other operation that increases (or decreases) the value of the increment variable. It can be any assignment statement as well, so i += 3 would increment i by 3 every iteration, or we can use function or any other operators.

Now, let's construct a for loop that adds up the numbers from 1 to 10. We'll need a variable outside the loop for the sum, since the increment variable only exists inside the loop. The loop should start at 1 and end at 10, and we need the increment variable to increment by 1 every loop cycle. Thus, the resulting for loop should look like this:

                  int sum=0;
                  for(int i=1;i<=10;i++){
                      sum += i;
                  }
                  // sum = 1+2+3+4+5+6+7+8+9+10 = 55

Step 2. Software Design

Global Variable

We only need a single variable, which we will use to determine which LED to illuminate. It will be an integer (int) and we will call it stateGlb.

The setup() Function

Since the LEDs are connected to pins that are in sequential order (2 through 7), we can use a for loop to initialize the pins without having to write the same two lines for each pin. We will initialize the pins as OUTPUTs and initially set them to LOW. The for loop below initializes pins 2 through 7.

                  for(int i=2; i<=7; i++){
                    pinMode(i,OUTPUT);
                    digitalWrite(i,LOW);
                  }

After setting the pins, all we need to do is set stateGlb to 1 so that it illuminates the first LED.

The loop() Function

This time, we will start at the end of the loop() function, so that we know how stateGlb changes (this will make explaining/understanding the first part of the loop() function a lot easier).

We end with a delay() so that the LEDs stay illuminated long enough for us to see each state. 500 milliseconds (half a second) is a good choice. We will then use a bit-wise operator to shift the bit to the left by 1 (same as multiplying by 2) and then use the modulo operator to ensure that the variable stays within six bits, since we have six LEDs. We will also need to set stateGlb to 1 when stateGlb is 0 so that the bit “rolls over” back to the beginning. The end of the loop() function should look like this:

                  delay(500);
                  stateGlb = (stateGlb<<1)%64;
                  if(stateGlb == 0){
                      stateGlb = 1;
                  }

Since we know that only one bit will be 1 at a time due to how bit shifting works, we can use the bit() function to see which bit is set in stateGlb by comparing the two numbers, and then change the LED that corresponds to that bit. We will use a for loop to iterate through the first six bit positions of stateGlb. Then we check to see if stateGlb is equal to the value of bit(). If that is true, then we write the corresponding LED to HIGH and the previous LED to LOW with a special condition when the first bit is 1, since the previous LED is the highest LED. The code should help clarify:

                  // We iterate through the first six bits
                  // (recall first bit position = 0 and last bit position = 5)
                  for(int i=0; i<6; i++){
                      if(bit(i) == stateGlb){
                          /* Because the LEDs start at pin 2
                          *  we must add 2 to i (the bit position)
                          *  get the corresponding pin position
                          */
              
                          // Set the correct LED to HIGH
                          digitalWrite(i+2,HIGH);
                          
                          // Special condition when the first bit is 1
                          if(i == 0){
                              // The first LED is lit, thus we must
                              // turn off the last LED
                              digitalWrite(7,LOW);
                          }else{
                              /* Since we need to add 2 to convert the 
                              *  bit position to pin location,
                              *  to get the previous pin we just add 1 instead of 2
                              */
                              digitalWrite(i+1,LOW);
                          }
                      }
                  }

Step 3. Compiling the Sketch

Combining everything we disscussed above, we get the resulting code that should look something like this:

                  // This variable is used to determine which LED to light.
                  int stateGlb;
                  
                  void setup()
                  {
                      // Since the LEDs are in order, we can use a for loop.
                      for(int i=2;i<8;i++){
                          pinMode(i,OUTPUT);
                          digitalWrite(i,LOW);
                      }
                      // Initialize stateGlb
                      stateGlb = 1;
                  }
                      
                  void loop()
                  {
                      // Check which bit is 1 in stateGlb, and then light the corresponding LED.
                      for(int i=0; i<6; 6++){
                          if(bit(i) == stateGlb){
                              digitalWrite(i+2,HIGH);
                              // Special condition for the first bit.
                              if(i == 0){
                                  digitalWrite(7,LOW);
                              }else{
                                  digitalWrite(i+1,LOW);
                              }
                          }
                          }
                      delay(500);
                      // Shift by 1 and ensure its in the first 6 bits.
                      stateGlb = (stateGlb<<1)%64;
                      if(stateGlb == 0){
                          stateGlb = 1;
                      }
                  }

Summary:

In this project we covered binary numbers, bit-wise operators, for loops, and an MPIDE function called bit() in order to create “Marching” LEDs. Binary numbers are used by computers for everything; they are used to represent regular numbers, letters, pictures, and movies using only two symbols, 1 and 0.

There are four main logical operators, three of which are primary operators: AND, OR, and NOT. XOR is the fourth and not considered to be a primary logical operator since it can be created from the three primary operators.

The for loop that we covered in this project is often used when we need to loop over code for a known number of times.

Core Concepts:
  • Binary numbers
  • Bit-wise operator use
  • For loops
Functions Introduced:
  • bit()
  • Bit-wise operators

Test Your Knowledge!

Now that you've completed this project, you should:

  • Try replacing the modulo operator with a bit-wise AND (Recall that we want only the first six bits of the number).
  • Show that shifting bits is equivalent to multiplying or dividing by a power of 2 for integers (Since floating point numbers have bit fields and bit shifting will change the number in interesting ways).
  • Implement your own bit() function that uses bit-wise operators to return the same result as the bit() function (Recall exactly what the bit() function does).

  • Other product and company names mentioned herein are trademarks or trade names of their respective companies. © 2014 Digilent Inc. All rights reserved.
  • Circuit and breadboard images were created using Fritzing.