EE109 – Fall 2022 Introduction to Embedded Systems

EE109 – Fall 2022: Introduction to Embedded Systems

Lab 3

Arduino Input and Output

Honor Statement

This is an individual assignment. The code you write, use for the demo and submit should be wholly your own and not produced by working in teams or using, in part or in whole, code written by someone else (found online, from a fellow student, acquaintance, etc.). You will be asked to verify this when you submit your code on Vocareum by signing your name in the honor.txt file on Vocareum. Penalties for violation include a 0 on the assignment and a potential further deduction or referral to Student Judicial Affairs.


In this lab exercise you will build a signaling device that makes an LED flash the Morse code signal for a letter of the alphabet. Rather than have 26 input buttons it will only have three, each assigned to one of three letters of the alphabet. When one of the buttons is pressed, an LED will flash the Morse code signal for the corresponding letter.

For a copy of the Lab 3 grading sheet, click here.

Morse Code

Morse code is a method of signaling letters and numbers by sending on and off patterns of light or audible tones. It was first developed in the 1830's and for many years was the standard way of communicating via telegraph and radio. It is still used today by amateur radio operators, the military, and sometimes for emergency communication.

Characters in Morse code consist of a unique set of short or long signals, known as "dots" and "dashes". The codes for letters A through Z and 0 through 9 are shown below. The dots and dashes of a character are separated by a short gap, and a longer gap separates characters. The most widely known Morse code sequence is the distress call "SOS" of three dots, three dashes, three dots.

The length in time of a dot or dash in Morse code is not specified directly, only the relationship between the dot and dash length:

The speed of the transmission of the characters can cover a wide range, but the above ratios should be adhered to in order to make the transmission understandable by the receiving party.

To see a short video demonstrating the operation of this lab, click here.


The buttons you will be using are the same ones you used in Labs 1 and 2. These are classified as momentary action, single pole, normally open buttons. These terms define how the button works.

Momentary action
The button is in the ON state only when it is held down. Once you release pressure on the button, it returns to the OFF state on its own.
Single pole
The button has one on-off circuit inside it. Buttons can be found with 2, 3, or more circuits that all turn on and off together when the button is operated.
Normally open
When the button is NOT being pressed the switch contacts are OPEN so the two terminals of the switch are not connected. Pressing the switch closes the contacts. Switches are also available as "normally closed" models meaning that the contacts are connected (closed) until the switch is pressed.

The buttons have four pins on the bottom that are spaced so they will fit in the holes in the breadboard. When the button is held with two pins at the top and two at the bottom as shown in the diagram on the left below, the left two pins are permanently electrically connected together and the same for the right pair. When the switch operates, it opens and closes the connection between the two pairs of pins as shown below.

When installing the button on the breadboard, it should be oriented so it straddles the center channel with the two upper pins in two different 5-hole rows, and the same for the two lower pins. When done that way, the switch opens and closes the connection between the two rows of the breadboard holes. Since the upper and lower pins are wired together for both contacts, you can make electrical connections to either one.


We used LEDs in Labs 1 and 2 connecting them to electronic circuits. In this lab you will be connecting one to a port bit of the Arduino configured to be an output and your program will determine whether it is on or off. As before, the LED will need a resistor in series with it to limit the amount of current flowing through the LED. Recall that in Lab 1, the current through the LED and resistor was given by

I = (Vs - VLED)/R

where Vs is the source voltage, VLED is the voltage drop across the LED and R is the resistance. When an output bit is in the high state the voltage on the pin is close to 5V so we can assume Vs = 5V, and VLED is usually close to 2V. If we use a 240Ω resistor this will result in a current through the LED of

I = (Vs - VLED)/R = (5 - 2)/240 = 12.5mA

The LED must be oriented so the anode (positive end) is towards the Arduino and the cathode (negative end) is towards ground. The resistor can be in series with the LED either between the Arduino and the LED, or between the LED and ground.

The Circuit

Use your Arduino Uno and breadboard to build the circuit shown below.

  1. The three switch inputs should be wired to the Arduino inputs D11, D12 and D13, which are connected to Port B, bits 3, 4 and 5 of the microcontroller.
  2. The LED and current limiting resistor should be connected to pin D2 of the Arduino, which is Port D, bit 2 of the microcontroller.
  3. The ground wire from the Arduino to the breadboard should go to one of the columns of holes that forms the bus that runs the length of the breadboard. Use one of the buses by the blue line for ground. The ground connections to the switch and the resistor should all be made to that bus column.

The Program

Do the following steps to get started.

  1. In your ee109 folder create a lab3 subfolder.
  2. To the lab3 folder download the file "" from the class web site. Extract the files from it:
    • lab3.c - A template C program to be used to start your Lab 3 program.
    • Lab3_Answers.txt - Edit to answer the review problems at the end of this web page.
  3. Copy the generic Makefile from your ee109 folder into the lab3 folder. The lab3 folder should now contain three files: Makefile, lab3.c and Lab3_Answers.txt.
  4. Edit the Makefile to change the name of the project object file on the OBJECTS line to be "lab3.o". Make sure you are editing the one that is the lab3 folder. Also edit the PROGRAMMER line as done before to be compatible with your computer.

Initializing input and output ports

You're now ready to starting adding code to lab3.c. The first part that we want to add is the code before the main "while" loop that initializes various things.

  1. Configure the appropriate Port D bit as an output (for the LED).
  2. Configure the three Port B bits the buttons are connected to as inputs. This step is optional since all the Port bits default to inputs, but it doesn't hurt to add code to do it.
  3. Configure the appropriate Port B bits that are inputs to have their pull-up resistors enabled.

At this point the program is ready to initialize things so let's try it.

  1. Run the "make" command to compile it and check that there are no errors.
  2. If it compiles cleanly run "make flash" to download the program.
  3. With the program running, use the DMM to measure the input voltage from the three buttons. Remember to attach the negative lead of the DMM to ground in order to make the measurement. If the internal pull-up resistor was configured properly, it should have around 5 volts when the button is not pressed, and close to 0 volts when the button is pressed. Repeat the test for all the buttons on ports PB3, PB4 and PB5. If the input ports do not have these voltages, you need to fix this before moving on.

Input and output functions

Now that we know the input signals are working correctly we can add more code to read the inputs and generate the output. We want to write the program in a way that is "clean" and easily understandable. To achieve this we will hide the ugly details about how input bits are read, and the output bit is changed in as few a places a possible, and the main part of the program just calls functions to check the buttons and generate the proper output. The lab3.c template file includes empty skeleton functions for a few routines that you will have to finish writing.

First add code to the two routines that handle the inputs and outputs.

This routine checks the state of an input bit in I/O port B (which is where the three buttons are connected). This routine should be the only place in the program where the PINB register is read. The argument to this function is a number from 0 to 7 that tells which bit to check. Think about what value the bit in the PINB register will be when a button is pressed, and what your function should return when that happens. It's important that your code in the main part of the program and the checkInput function agree on how to communicate the fact that a button press was pressed.

This routine sets or clears the output bit the LED is connected to (Port D, bit 2). If the argument is zero, the PD2 bit is cleared to 0, otherwise it is set to 1. This routine should be the only place in the program where the PORT bit is modified in order to change the output value on Port D, bit 2.

Before adding code for the rest of the functions, this is a good time to test the two you just wrote.

  1. For testing purposes add code to the main "while" loop to have the program turn the LED on (by using the makeOutput function) if button 1 is pressed and turn it off if the button 1 is not pressed. Make sure to use the checkInput function to see if the button has been pressed.
  2. Compile and download your code. If things are working correctly you should be able to turn the LED on and off by pressing button 1.
  3. If this works, change the code to have it use button 2, and then try it with button 3.

Your goal in debugging is to eliminate possible sources of problems by testing parts of the whole thing before they all have to work together. If you just build the final circuit and try it with the full program, what do you do if nothing works? Is it a problem with the buttons, with the LEDs, with the program, or what?

Generating dots and dashes

Next add the code to the two routines that will generate the dots and dashes for the Morse code.

This routine controls the LED to generate a dot. It should turn the LED on (by using the makeOutput function described above), delay the correct amount of time and then turns the LED off. See below for how to make the program delay a specified amount of time. Important: All dots also have to be followed by a delay as described in the definition of Morse code at the top of this web page. This delay should be part of the dot function.
Similar to the dot routine but generates the longer dash.

To create the dots and dashes of the correct lengths, the program will have to execute delays during execution. The avr-gcc software includes two functions for implementing delays: _delay_ms() for millisecond delays, and _delay_us() for microsecond delays. When one of these functions is called with a numeric argument, the program will delay that long (in milliseconds or microseconds) before function returns and the program can continue. Important: In order to use these functions your program will need the following line near the top of the lab3.c file.

#include <util/delay.h>

For this lab we will use the _delay_ms function to give delays suitable for sending the dots and dashes. For example the following line would cause a 300msec delay.


The argument to the _delay_ms function must be an integer value. In addition, it should be a constant value, not a variable. The avr-gcc compiler allows variables on some types of systems but on others they can not be used.

Put it all together in the main loop

Now that you have the tools available to read the inputs and generate the output, you can write the code in the main "while" loop of the program that uses these functions. The main loop of your program should do the following operations.

  1. Use the checkInput function to examine the state of button 1 to see if it has been pressed. If so, send the pattern of dots and dashes for the character 'U' by making calls to the dot() and dash() functions. Do not directly set or clear the PORT bit at this point in the program. Your code calls dot() and dash(), and these functions call makeOutput which makes changes to the PORT bit. Also ensure there is the appropriate delay after the character is finished and before the next character can begin. The program does not have to read the buttons for the next character until after the current character is done being signaled.
  2. Do the same thing for button 2 to send an 'S'.
  3. Do the same thing for button 3 to send a 'C'.

When generating the patterns of dots and dashes make sure to use the proper lengths as defined above. In your program you should choose what delay amount to use for the length of the Morse code dot. All the other timing amount (dashes, gaps between dots and/or dashes, gaps between characters, etc.) are based on the length of the dot. Select a dot length that makes the output blinking readable, not too fast and not too slow. Your program must include the delay after a character is generated. The program should not begin sending the next character until after the proper delay from the end of the previous character.

To help with this, we have defined the fundamental time unit as DOT_LENGTH at the top of your program. Please put all delay lengths in terms of this constant so that it can easily be changed later.

#define DOT_LENGTH  250         /* Length of a "dot" time in msec */

If you feel 250msec is too fast or slow, experiment a bit and come up with one you prefer.

You can add whatever other functions you find useful to the program. Try to make the code easy to understand and modify. Make sure to add comments to the code so someone looking at it can easily see what you are doing.


Once you have the program working and the LEDs are blinking the Morse code for 'U', 'S' and 'C', use the oscilloscope to examine the output signal and confirm that the timing relationships between the dots, dashes, and the spaces between them is correct.

  1. Turn on the scope and connect the two probes to channels 1 and 2.
  2. Turn on both channels by pressing the '1' and '2' button until they are lit.
  3. Set the vertical control for both channels for 2 volts/division.
  4. Use the vertical position controls to put the yellow channel 1 line in the upper part of the display just above the center of the screen, and the green channel 2 line in the bottom part.
  5. Set the horizontal control to 500ms per division. This can be changed later to get a good view of signals.
  6. Channel 1 will be used to view the button press signal. Connect probe 1 to the 'C' button output where the wire is going to the PB5 input bit.
  7. Channel 2 will show the output signal going to the LED. Connect probe 2 to the anode of the LED where the wire from the PD2 output bit is connected to the LED.
  8. You need the scope to trigger when the button is pressed so adjust the trigger settings so the scope triggers on a falling edge on channel 1:
    • Set "Trigger Type" to "Edge".
    • Set "Source" to "1".
    • Set "Slope" to a falling edge.
    • Adjust the trigger level to about 2 volts.
  9. The scope should now be ready to trigger when the 'C' button is pressed. Press the "Single" button in the upper right to make it wait for the next trigger signal, and then press the 'C' button on your breadboard.

If everything is working, you should get a display like that below. Depending on your choice of the dot delay time the display may be compressed or stretched out so use the horizontal controls to adjust the scope timing and position to get a good view of the signal. Check that the timing is correct for the dots, dashes and the gaps between them.

Show the scope display to one of the instructors so they can check off that this task has been completed.

Propagation Delay

We want to observe and measure the propagation delay from when a button is pressed to when the output starts changing as the dot and dashes are sent. If you followed the instructions above to view the 'C' character then the scope is already mostly configured to make this measurement.

  1. Press the small knob (labeled "Push to Zero") in the horizontal section of the scope controls to bring the trigger point back to the center of the screen. We want to look at the signals right after the triggering point.
  2. Adjust the horizontal timing control to 5μs per division
  3. Press the "Single" button in the upper right to make it wait for the next trigger signal, and then press the 'C' button on your breadboard.

You should get a display similar to the one below that shows the button input going from the 1 to the 0 state, and then some time later, the LED output going from the 0 to the 1 state.

From the scope display, how long did it take from the time the button input went to zero before the LED output went high? Repeat the test 10 times and record the times on the Lab 3 grading sheet. For each test you will have to wait for the Arduino to finish outputting the 'C' character before you can start a new one.

In Lab 1 we built a simple combinational circuit with a button, NOT gate and LED output. In Lab 2 we measured the propagation delay through that circuit from button to LED and found it to be on the order of 10ns. In our circuit this week using software to turn the LED on the delay is much longer, on the order of 1-10 microseconds. That is about two orders of magnitude difference. Software takes much longer because our loop is composed of many software instructions that the processor must fetch from memory, decode and then execute. You can see that if computation needs to be done quickly, hardware has advantages!

In our hardware-only circuit from Labs 1 and 2, since the gates never change the delay time from when the button was pressed to the LED lighting up was fairly consistent, always about 10ns. However, with the Arduino you should be seeing different delays every time the button is pressed. If you aren't zoom in to a slightly smaller time scale. Can you explain why the propagation delays vary from one test to another?


Once you have the assignment working demonstrate it to one of the instructors including showing the scope display of the timing of the "C" character and your measurements of the propagation delay. Have them fill out a grading rubric which you should keep in your files for the rest of the semester.

The answers to the review questions below should be edited into the "Lab3_Answers.txt" file. The "Lab3_Answers.txt" file and all source code (lab3.c and Makefile) must be uploaded to the Vocareum web site by the due date. See the Assignments page of the class web site for a link for uploading.

Review Questions

Answer the following questions and submit them in a separate text file ("Lab3_Answers.txt") along with your source code.

  1. Suppose we relocated the three buttons from group B, bits 5-3 to group D, bits 5-3, noting that the LED is still attached to group D, bit 2. Assuming appropriate DDRD values, consider the following method of turning on the LED.

    PORTD = 0x04; // turn on the LED

    Explain the problem with this approach and, in particular, what would stop working after the execution of that line of code.

  2. Briefly explain why the delay between the press of the 'C' button and the start of the LED on/off sequence varied when you took multiple measurements? Think about the timing of how software executes.