Button Controlled LED with Serial Output

Button Controlled LED with Serial Output

Introduction

In the Button-Controlled LED project, you learned how to use a button to control an LED. In this project, a button is again used to control an on-board LED, but things get a bit more complicated. Instead of simply having the state of the LED correspond to the state of the button, in this project the LED's state will toggle every time the button is pushed (releasing the button does not change the LED's state). When a state is toggled, it simply changes to its opposite: if the state was HIGH (on), it changes to LOW (off). If it was LOW (off), it changes to HIGH (on).

Additionally, in this project a message is sent to the computer whenever the LED changes states. This message reports the current state of the LED as well as the amount of time (in seconds) that have passed since the LED last changed states. The message is displayed in the Serial Monitor window that MPIDE provides and is communicated between the chipKIT™ board and the computer via a serial port. (Serial communication entails communicating bits in a “series,” one after the other. This is in contrast to parallel communication, where multiple bits are communicated simultaneously.) Because the construction of a button-controlled LED circuit was considered in-depth elsewhere, the focus of this project is primarily on the code needed to accomplish the communication between the board and the computer.

Before you begin, you should:
After you're done, you should:
  • Understand how to toggle the state of a variable.
  • Understand how to perform simple timing (using the millis() function).
  • Understand the basics of a class.
  • Understand how to communicate information from the chipKIT board to a computer via the Serial interface and have information display in the Serial Monitor.

Inventory:

Qty Description Typical Image Schematic Symbol Breadboard Image
1 Two-port button
1 10 kΩ resistor

(10 kΩ = 10,000 Ω)

Step 1: Circuit Setup

The circuit shown in Fig. 1 requires a 10 kΩ resistor, a button, and three wires. The 10 kΩ resistor is used as a pull-down resistor so that the input associated with the button is LOW when it is not pushed and HIGH when it is pushed. The button circuit introduced in the Button-Controlled LED project is all we need for this project (the circuit is depicted slightly differently in Fig. 1 than it was before, but the two circuits are electrically equivalent).

Figure 1. Button circuit (with a pull-down resistor).
  1. Place the button so that it bridges the valley in the breadboard.
  2. Place the 10 kΩ resistor so that it connects to one of the legs of the button.
  3. Use a wire to connect the other end of the resistor to one of the ground pins of the chipKIT board (shown as the black wire in Fig. 1).
  4. Use a wire to connect pin 4 to the leg of the button directly across the valley from where the resistor is connected (shown as the blue wire in Fig. 1). Recall that the legs of the button that are directly across from each other are electrically connected at all times. Thus, when the button is not pressed, pin 4 is tied to ground through the 10 kΩ pull-down resistor.
  5. Finally, connect a wire from the 3V3 pin to the button leg that is on the same side as the one to which the resistor is connected, but do not connect it to the same leg as the resistor (shown as the red wire in Fig. 1).

Communicating through the USB

Before discussing serial communications, it is helpful to have an understanding of what a class is in the context of C++. For a brief overview of what classes are and how they work, click the red tab on the right. Moving forward, it will be assumed that you have at least a rudimentary knowledge of classes. If you are curious how a computer transmits messages through a wire, click the orange tab on the right.

MPIDE provides a HardwareSerial class that can be used to access the serial ports on a chipKIT board. (A port can be thought of as a pin or group of pins that are used for input or output, I/O. In other words, a port is the interface between a computer and the rest of the world.) More specifically, the HardwareSerial class is used to access what are known as the Universal Asynchronous Receive/Transmit (UART) ports. The number of UART ports on a chipKIT board can vary. For example, the Uno32™ has two serial ports and the Max32™ has four serial ports. Ports have associated with them an instance of the HardwareSerial class named Serial, Serial1, Serial2, and so on. The “first” serial port, named simply Serial, does double duty by communicating through either the USB (Universal Serial Bus) or through pins 0 and 1.

The first method that must be called when setting up serial communication using the Serial object is Serial.begin(speed). This method configures the hardware for serial communication at the speed provided as an argument. This speed is the baud (sometimes called the baud rate), which is taken to be synonymous with bits per second (even though this isn't strictly correct). When communicating with the computer, use one of the following rates: 300, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, or 115200. If you are communicating with other hardware, use the rate specified by the hardware.

Figure 2. Button that displays the Serial Monitor window.

To produce serial output, one can use either the the Serial.print() method or the the Serial.println() method. The output of these methods can be seen in the Serial Monitor window that MPIDE provides. To view this window, you must click on the button shown in Fig. 2 (this button is located in the upper right corner of an MPIDE window). After clicking on this button, a window should appear similar to the one shown in Fig. 3 (this particular window was taken from an Apple® Mac®).

Figure 3. Serial Monitor window (on Apple Mac).

Both Serial.println(message) and Serial.print(message) methods send the “message” given as an argument, but println() adds a newline to the end of the message (i.e., a “return” so that any subsequent message will start on a new line), whereas print() only sends the message. Figures 4 and 5 illustrate the difference between repeatedly sending the message “Hello World!” using either print() (Fig. 4) or println() (Fig. 5). Note that the output from print() is on a single line, with no space between the messages, while the output from println() has each separate message on a new line. The “message” argument passed to print() or println() can be various things including a character, a string, or a numeric value. The “message” can be literal value, such as the integer 123 or the string “Happy Cat,” or it can be a variable (that corresponds to a character, string, or numeric value).

Figure 4. Repeated calls to Serial.print() with an argument of “Hello World!.”
Figure 5. Repeated calls to Serial.println() with an argument of “Hello World!.”

The above screenshots are serial monitor windows running on Apple Mac.

Step 2: Sketch Overview and Determining the Variables

To accomplish what was described in the Introduction, we need to store the state of the LED (to tell us whether it is on or off), the state of the button (to tell us whether it is pushed or not), and the “time” when the LED last changed its state (what we mean by “time” will be made more clear in a bit). We also need to determine when the button was pushed. Based on this value and the time at which the LED last changed its state, we can calculate the amount of time the LED has been in its current state. This is the value that will be reported via the Serial Monitor.

When a sketch starts, the chipKIT board effectively starts a timer. We can obtain the value of this timer using the function millis(). This function takes no arguments and return the “time” as an integer number of milliseconds. This time is the amount of time since the sketch started to run. Because this time is stored as an (unsigned) integer which uses 32 bits, the maximum time that can be stored is 4,294,967,295 milliseconds. This corresponds to approximately 49 days and 17 hours. If your sketch runs longer than this, the “time” will return to zero and start counting up again. (Our sketch for this project won't worry about this “roll-over” or “overflow” of the time counter.)

In the sketch for this project, all times will be in milliseconds, but we will report the time between changes in LED states in seconds. Thus, we will need to convert from milliseconds (an integer, int) to seconds (which will be stored as a float).

A variable will be used to store the state of the LED, i.e., whether it is HIGH or LOW. When the button is pressed, the LED will toggle its state. Recall that HIGH and LOW are names that are equivalent to the integer values 1 and 0, respectively. Thus, the LED's state variable should be an integer. Let's call this stateLedGlb. Glb will be used in this sketch as part of a variable's name to identify it as a global variable.

We will use two variables to determine if the button has changed its state from being unpushed to being pushed: one variable to store the the current button state and another to store its previous state. Let's call these variables buttonCurrent and buttonPreviouGlb. Both of these variables will be integers because the button will be either HIGH or LOW. Note that the previous button state is stored in a global variable (as indicated by having Glb as part of its name) whereas the current button state is stored as a local variable. The reason for this is that the button's current state will be determined each time the loop() function is executed and hence buttonCurrent can be local to the loop() function. On the other hand, the button's previous state will first be determined outside of the loop() function. Additionally, we need the button's previous state to persist between successive executions of the loop() function. For these reasons, buttonPreviouGlb is a global variable.

Two variables will be used to determine the change in time from the previous button press to the current button press. We will call these two variables timePreviousGlb and timeCurrent, respectively. Both these variables are integers since we use the millis() function to obtain the time and this function returns an integer. As with the variables used to store the button's state, one of these variables is a global variable and the other will be local to the loop() function.

We want to report the time in seconds, rather than milliseconds. Since the time in seconds is likely to have a non-zero fractional part, we will store the time as a float variable that we'll call seconds. In our calculation of seconds, we will use what is known as explicit casting to tell the compiler to convert an int value to a float value. By doing this, we ensure we will obtain the fractional part. If we didn't do this, and we asked the computer to perform an arithmetic operation that involved only int's, the computer would return an int result (and any fractional part would be lost). In addition to explicit casting, where we specify the conversion that is to be done, there is also what is known as implicit casting. This is conversion that the compiler does as it follows certain rules. For example, if an arithmetic expression contains both an int and float value, the compiler will first implicitly convert the int to a float and then perform the operation. Reliance on implicit casting can be a source of bugs, so it is best to use explicit casting when casting is necessary.

Explicit casting is accomplished in a fairly straightforward way: you simply enclose the name of the desired type in parentheses and put that in front of the value you want to cast. Thus, to convert milliseconds to seconds, we would use a statement such as:

				seconds = (float) (timeCurrent - timePreviousGlb) / 1000;
			

Finally, we will also use constant integer variables to store the LED pin and the button pin. We will call these ledPinGlb and btnPinGlb, respectively.

Defining Global Variables

Given this description of the variables, we now want to write the code that defines these global variables. Recall that a global variable is created when a variable is defined outside of any function. Thus, our sketch starts with the following code:

// The LED state, initially set to LOW.
int stateLedGlb = LOW;
// Previous button state.
int buttonPreviousGlb;
// Time of previous button press/LED change.
int timePreviousGlb;
// Constants for the LED and button pins.
const int ledPinGlb = 13;
const int btnPinGlb = 4;

The setup() Function

In the setup() function, we set up serial communications by calling Serial.begin(), set the LED and button pins for output and input, respectively, initialize the “previous” variables, and set the LED to the state specified by stateLEDGlb (which is LOW). For the serial communication, we use a baud of 9600. The setup() function is:

void setup()
{
  // Start Serial Communications.
  Serial.begin(9600);
  // Set button pin for input.
  pinMode(btnPinGlb, INPUT);
  // Set LED output and specify its current state.
  pinMode(ledPinGlb, OUTPUT);
  digitalWrite(ledPinGlb, stateLedGlb);
  // Use initial state of button as "previous" state.
  buttonPreviousGlb = digitalRead(btnPinGlb);
  // Use initial time as "previous" time.
  timePreviousGlb = millis();
}

The loop() Function

Now we need to write the loop() function. This requires that we generate a message only when the pin reading the state of the button detects a change from LOW to HIGH, which happens when the button is pushed. We can use an if-statement to accomplish this. Prior to the if-statement, we determine the current state of the button, using digitalRead(), and assign that to the variable buttonCurrent. Then, in the condition that determines whether or not the body of the if-statement is executed, we check two things: if buttonCurrent is HIGH (the button is pushed) and if buttonCurrent is different from buttonPreviouGlb (the button wasn't pushed the last time we checked). The code to accomplish this looks like the following:

int buttonCurrent = digitalRead(btnPinGlb);
if ((buttonCurrent == HIGH) && (buttonCurrent != buttonPreviousGlb)) {
  // Code to execute if condition is true...
}

To produce the desired output (message), we must determine the difference between the current time and the time when the button was previously pressed (or, for the first time this executes, the difference between the current time and when timePreviousGlb was initialized). Furthermore, we need to convert this time to seconds. As shown in the code below, inside of the body of the if-statement we start by calling millis() to determine the current time and assign that to timeCurrent. Note that we are setting the value of the variable in the same statement in which we are defining it. Given the current time and the “previous” time (timePreviousGlb), we calculate the difference and convert that value, which is in milliseconds, to seconds. After doing that, we can forget about the “old” previous time. If the button is pressed again, we'll want the time between that event and what is now the current time. Thus, we reset timePreviousGlb to be timeCurrent. The code to accomplish this is:

// Get the current time.
int timeCurrent = millis();
// Convert the time from (integer) milliseconds to (float) seconds.
seconds = (float) (timeCurrent - timePreviousGlb) / 1000;
timePreviousGlb = timeCurrent;

Next, we toggle the variable representing the LED state using the “not” operator (!). The “not” operator returns zero when it operates on one, and returns one when it operates on zero. Finally, we use digitalWrite() to set the LED to this new state. The code fragment to accomplish these things is:

// Toggle the LED using the not operator (!).
stateLedGlb = !stateLedGlb; 
digitalWrite(ledPinGlb, stateLedGlb);

We can now generate an appropriate message that will appear in the Serial Monitor window. We want to report whether the LED is on or off and how long it has been since last changed its state. The following code accomplishes this:

Serial.print("The LED is now ");
if (stateLedGlb == HIGH) {
  Serial.print("on.  ");
}
else {
    Serial.print("off.  ");
}
Serial.print("It has been ");
Serial.print(seconds);
Serial.println(" seconds since the LED last changed.");

There is one more statement we need in the loop() and this appears outside of the if-statement. We update the current state of the “previous” state of the button to the whatever the current state is. This is necessary so that when we next read the button state from the pin we can determine if it has changed from what it was the last time we checked. So, we merely need to include this statement as the last line of our loop() function:

// Update the previous button state to reflect the current state.
buttonPreviousGlb = buttonCurrent;

Putting It All Together

We now have all the code necessary to accomplish what we set out to do. The complete sketch is shown below. Upload this code to the board and open the MPIDE Serial Monitor window. This is done by clicking on the button in the MPIDE window that is shown in Fig. 3. After the sketch starts to run and the Serial Monitor window has appeared, press and release the button at various intervals to see what appears in the Serial Monitor.

// The LED state, initially set to LOW.
int stateLedGlb = LOW;
// Previous button state.
int buttonPreviousGlb;
// Time of previous button press/LED change.
int timePreviousGlb;
// Constants for the LED and button pins.
const int ledPinGlb = 13;
const int btnPinGlb = 4;

void setup()
{
  // Start Serial Communications.
  Serial.begin(9600);
  // Set button pin for input.
  pinMode(btnPinGlb, INPUT);
  // Set LED output and specify its current state.
  pinMode(ledPinGlb, OUTPUT);
  digitalWrite(ledPinGlb, stateLedGlb);
  // Use initial state of button as "previous" state.
  buttonPreviousGlb = digitalRead(btnPinGlb);
  // Use initial time as "previous" time.
  timePreviousGlb = millis();
}

void loop()
{
  int buttonCurrent = digitalRead(btnPinGlb);
  if ((buttonCurrent == HIGH) && (buttonCurrent != buttonPreviousGlb)) {
    // Get the current time.
    int timeCurrent = millis();
    // Convert the time from (integer) milliseconds to (float) seconds.
    seconds = (float) (timeCurrent - timePreviousGlb) / 1000;
    timePreviousGlb = timeCurrent;
    // Toggle the LED using the not operator (!).
    stateLedGlb = !stateLedGlb; 
    digitalWrite(ledPinGlb, stateLedGlb);
    Serial.print("The LED is now ");
    if (stateLedGlb == HIGH) {
      Serial.print("on.  ");
    }
    else {
        Serial.print("off.  ");
    }
    Serial.print("It has been ");
    Serial.print(seconds);
    Serial.println(" seconds since the LED last changed.");
  }
  // Update the previous button state to reflect the current state.
  buttonPreviousGlb = buttonCurrent;
}

  • 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.