Lab 2: Interfacing with the Pi

The questions below are due on Wednesday June 27, 2018; 05:30:00 PM.


You are not logged in.

If you are a current student, please Log In for full access to this page.

Goals:

Goals: In today's lab we're going to start programming on the Pi, using Python to control inputs and outputs and interface with some electrical components we worked with last time. Ultimately the overall goal is to build a music player with a limited set of functionality.

1) Big Picture

What we'll be doing today (and really in all of your work and projects going forward) is adding the "logic" aspect to our system. We got some experience messing with wiring/building circuits in Lab 01 and that will be invaluable, but the next step is to interface a computational device with that equipment and that requires programming.

For many years, we (humans) built stuff without programming. Everything can be done using electronics without programming, but the flexibility obtained through integrating programming and a computational device really has its benefits!

Flow of Information. Inputs feed data into our system. We process that information and then act upon it using Outputs.

Open up Python3 on your Raspberry Pi (Idle 3) by going to the Raspberry Symbol >Programming > Python3 (IDLE). This will open up Python. Immediately (before you forget) open up a new file (do not work directly in the Python console). Do this by going to File >New.

Please get in the habit of working in files in Python, and not within the Python shell. We'll use the Python shell for analyzing our code, but for writing the code, it isn't very good and doesn't really let us save stuff. Files are much better!

The Raspberry Pi has a number of external interfaces that we can use to generate inputs and outputs. Inputs on a computer can be specific things keyboards, but they can also be more "bare-bones." Among these inputs, the Raspberry Pi has a number of General Purpose Input Output (GPIO) Pins that we can use to obtain simple inputs and outputs. By "simple" we mean digital, which means they can either measure or produce On/Off voltages, depending on if they are an input or a output, respectively. (A pin cannot do both however!)
Many of the circuit components we've worked with so far can respond in an ON/OFF sort of way (think buttons, or LEDs being on or off), so these Genearl Purpose inputs and outputs from the Pi, allow us to interface with these components.

1.0.1) Breakout Board

We learned about the breadboard and used the blue "breakout" board to get power for our audio amplifier in Lab 01. The Raspberry Pi can do a lot more than just this, though, and today we'll start using all those other pins. The Pi is connected to the outside world through a ribbon cable that then connects to a board known as a "Breakout". The Breakout, shown in the image below, gives us nice, clean, readable access to all the Pi's pins.

A reminder of the breakout board. We have direct access to these pins from Python.

when we draw out the breakoutboard in this lab (and even the Pi) and later labs we'll often just represent it as a gray box.

2) The LED as an Output

OK so now let's grab a LED and hook up the LED to the output of GPIO4. You may remember from Lab 01 that we should never hook up an LED to power without some resistor in series like shown below:

A reminder about how to use LEDs (not what we're building here)

It turns out that our Raspberry Pi boards have an built-in resistor (shown in figure below) that let's us not need to add one ourselves when interfacing with the GPIO pins.

This is only because we've included an extra protection shield on the board. A Raspberry Pi off of the streets, will not have this, so if you're using your own hardware you'll need to include your own resistors.

This in-built protection resistor is only for the GPIO pins...not the 3.3V pins (3V3) or other pins) Try to hook the LED up to GPIO 4 in the correct direction and to the right pins such that if GPIO4 had a High voltage on it, the LED would glow. (Long leg to GPIO Pin 4 which is represented by the #4 label and the short leg of the LED to ground!

Connecting an LED to the Pi.

When you think you've added the part correctly, open a new file in Python (File>New) if you haven't already, and then copy-paste the code below into that file. Save it as led1.py and then run it by either going to Run>Run Code or pressing F5 on the keyboard (cool kids press F5, and you want to be cool, right? You should probably press F5).

#Simple LED Flasher
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)

led_pin = 4 #pin we'll use for the LED

GPIO.setup(led_pin,GPIO.OUT)
GPIO.output(led_pin,0)

time_pause = 0.25 #how long a standard pause is in this program

GPIO.output(led_pin,1)
time.sleep(time_pause)
GPIO.output(led_pin,0)
time.sleep(time_pause)
GPIO.output(led_pin,1)
time.sleep(time_pause)
GPIO.output(led_pin,0)
time.sleep(time_pause)
GPIO.cleanup()

When you run the code how many times does the LED flash?

Let's go through line-by-line what this code is doing.

In Python any line or part of a line that begins with a # is a comment, meaning it is meant for human reading.

The two lines below grant us access to other pieces of pre-written Python code. The first one gives us access to some Python "libraries" that let us interact with the actual Raspberry Pi hardware1 and what we're doing here is importing the RPi.GPIO library and just calling it GPIO for short. When we want to use a part from this library we'll access it by doing GPIO.thing. The second line imports the time library which gives us access to the sleep function which we'll use to add pauses in our program.

import RPi.GPIO as GPIO
import time

The next two lines configure the GPIO library so we can use it. That's about all you need to know for now:

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)

To make life easy it is better to use variables wherever possible. What we do next is create a variable called led_pin and assign a value of 4 to it. Then we can use that anywhere we want to refer to the LED. In the future if we want to change what pin we use for the LED we only need to change that number in one spot as a result.

Here we do our first pin-specific action for the LED. We set up Pin 4 to be an output (pins can be either inputs or outputs...to control an LED that means we need it to be an output).

led_pin = 4
GPIO.setup(led_pin,GPIO.OUT)

We then have a bunch of lines that look like the following. The first type of line tells the corresponding pin (dictates by the variable led_pin which has a value of 4 in this case) to be ON (1 or True == High OUTPUT which makes the LED go on), (0 or False == Low OUTPUT which makes the LED go off) The second line (time.sleep(time_pause)) has the program pause for 0.25 seconds because it is saying to sleep the computer time_pause amount where time_pause is another variable we specified in our code!

GPIO.output(led_pin,1)
time.sleep(time_pause)
GPIO.output(led_pin,0)
time.sleep(time_pause)
GPIO.output(led_pin,1)
time.sleep(time_pause)
GPIO.output(led_pin,0)
time.sleep(time_pause)

Finally the following line makes sure we shut down our hardware access ok. Just like you don't want to just unplug a running computer, sometimes you don't want to just stop controlling the outputs of the Raspberry Pi. This function call will make sure we end our time in Python gracefully.

GPIO.cleanup()

3) LED Tasks

We're now going to build on this code we started with by doing some exercises. Obtain a red, a white, and a blue LED from the parts supply (or any other colors if you want), and using the previous starter code as a starting point, do the following:

3.1) Assignment 1

Create a system that turns on each LED in sequence, then turns them off, and repeats this pattern three times. Use GPIO (#) pins 4, 17, and 27 ONLY!

Building a Red, White, and Blue Circuit

When you feel confident that you've built the circuit correctly and have code working properly, show a staff member and then submit your working code below for record keeping.


3.2) Assignment 2

We'd now like you to make two LEDs turn on together for 0.5 seconds, while the other LED is off, and then flip it (two LEDs off and the other on for 0.5 seconds). Have this repeat ten times. Your code needs to be less than twenty lines total. Think about how to do this. Ask staff for help!

When the system is working, show a staff member and then submit your code below for record keeping:


3.3) Assignment 3

Now make one LED (pick one...it is up to you) flash at 10 Hertz using a for loop. Don't know what "Hertz" means? Too bad. Look it up. When working, show a staff member.


4) Inputs

We just saw that we can control electrical signals with our Pi (and make LEDs go on and off). We can also have our Pi be sensitive to externally generated electrical signals. We'll start investigating this ability by looking at switches which we used in Lab 01. As a refresher, a switch is represented schematically as shown below:

A switch

Our switch looks like the following:

The switch we'll be using today

You can plug your switch into the breadboard as shown above. You've already gotten experience with these, but just keep in mind that their direction really matters. You all experienced that in Lab 01.

These types of switches have only two electrical nodes inside them, but four electrical contacts and legs. This means there is some redundancy in them. Pay attention/use that as you wire up circuits below.

The four legs of the switch mapped into two electrical connections.

Include your switch in the circuit/schematic below:

Switch as Input

Create a new file, call it switch_demo.py and paste the code into it below:

import RPi.GPIO as GPIO  #import library to use input/output pins
import time
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
input_pin = 5
GPIO.setup(input_pin,GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
while True:
    if GPIO.input(input_pin)==1:
        print("Pushed")
    time.sleep(0.05)

Some of these pieces should start looking familiar, but others are new...let's go over the chunks again:

The first four lines are like from earlier...import libraries and set some basic utility things up. You'll usually always use these four lines in a lot of what we do:

import RPi.GPIO as GPIO  #import library to use input/output pins
import time
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)

The next two lines set up a GPIO pin as an input. We create a variable input_pin and set it to 5 and then use that second line to establish that the pin is an input.

input_pin = 5
GPIO.setup(input_pin,GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

The last part takes some explaining:

When we generated outputs, our code was what was in charge of what happened. It could run and be done and could be very deterministic/pre-planned. If we instead want our code to react to inputs, we now need restructure how our code is designed and operates. Events are no longer triggered by code, but rather by an outsider, so we need a way to have our code wait.

A while True loop is a way to get a block of code to run forever. Normally a while loop runs as long as the condition it is checking is True. Since True is always True this loop will run forever (until we stop it with a Control-C).

To make sure the loop doesn't run 1000's of times per second we put a time.sleep command to pause the code for 0.05 seconds each time through, keeping our loop running no faster than 20 times per second.

We now have this ability to run and wait forever. This is exactly what we need in order to interact with our switch. Each time through the loop we check to see if our switch is pushed. We do that by checking the value of the switch with the command GPIO.input(input_pin). Calling this function will return a value that could be stored in a variable if we wanted, but in this case we check if it is 1 in value and if it is, we print "Pushed".

while True:
    if GPIO.input(input_pin)==1:
        print("Pushed")
    time.sleep(0.05)

We could further clean up our code by packaging that input check we're doing into a function like so:

def input_check(input):
    if input==1:
        print("Pushed")

We can then modify our main loop to look like the following:

while True:
    input_check(GPIO.input(input_pin))
    time.sleep(0.05)

Show this working modified code to the staff and briefly discuss with them what's going on.

5) State Machines and clicks

We're now going to build a simple LED ON/OFF controller. It will work using the following:

  • If the LED is off and you push and then subsequently release the button (connected to Pin 5), the LED will turn on.
  • If the LED is on and you push and then subsequently release the button, the LED will turn off

The net result is a Push-On-Push-Off sort of light controller. This is harder to create than you might think since the code needs to work not off of just what the button is, but rather what the button is doing over time. Below is an "attempt" at doing what is described, but it doesn't work. Load this code and compare/contrast what it is doing to what you need to do based on the specification above. Talk to staff.

import RPi.GPIO as GPIO  #import library to use input/output pins
import time
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
led_pin = 4
GPIO.setup(led_pin,GPIO.OUT)
input_pin = 5
GPIO.setup(input_pin,GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

def input_check(input):
    if input==1:
        GPIO.output(led_pin,1)
        print("Pushed")
    else:
        GPIO.output(led_pin,0)
        print("Unpushed")

while True:
    input_check(GPIO.input(input_pin))
    time.sleep(0.05)

In order to implement the Push-On-Push-Off functionality we want, we're going to need to add another conceptual tool to our toolbox. An important idea that can really help with handling problems is the idea of a state machine. To understand what a state machine is, we first need to understand what a state machine isn't...and that is a stateless machine. (note when we say "machine" we mean any sort of object (piece of code, robot, actual machine, etc...)

If you were to have a machine that always takes in a numerical input and immediately prints out three times that number regardless of what has happened in the past, we'd call this a "stateless" system. Basically the way it responds is based solely on its inputs and nothing else. For example...here's a stateless function in Python:

def slm(x):
    return 3*x

No matter how many times you call the function slm it will always do exactly the same operation. It lives in the moment with no concern for the past. If you were to do three simultaneous calls on slm of: slm(2), slm(2), slm(2), each time you'd get back 6.

Another stateless function is our function from earlier:

def input_check(input):
    if input==1:
        print("Pushed")
while True:
    input_check(GPIO.input(input_pin))
    time.sleep(0.05)

This function will print Pushed for as long as we are pushing it. How could we change this so that it only prints Pushed when the switch is first pushed? And then prints Released when the switch is first released (but not continuously afterwards)?

If you think about it, just reading the switch's currently value doesn't contain enough information for the code to decide when it is first pushed and first released. It needs something more, and that something more could be described as memory, history, or the more technical term: a system state. One way to model and design complex systems which act in ways more complex is as what's known as a state machine. It is important to realize that the term state machine refers to an abstract idea, namely systems which act based on current inputs and past inputs, and those past inputs are usually stored in some form (either individually or through some combination) in system states. The way these previous inputs influence the system's response manifests itself in the form of a "state" as in state of mind...as in New York State of Mind by Nas.

In order to accomplish such a functionality, state machines need to be able to remember...they need to have memory and ready access to what their states are and this memory generally takes the form of variables. As a result, when a state machine analyzes its inputs it can also use previous information to make an informed decision.

The following is a state machine that would accomplish what we described above, only printing stuff when the switch is first pushed or first released.

pushed = False #global variable to store what the state of the button is!

def input_check2(input)
    global pushed #tell function to refer to "global" pushed
    if input==1:
        if pushed==False: #means we see a push after not seeing one!
            pushed = True #change state so we remember it is now pushed
            print("Pushed")
    else: #(the button is not pushed)
        if pushed==True: # the button is not pushed but previously it was!
            pushed = False #change state so we remember it is now unpushed!
            print("Unpushed")

while True:
    input_check2(GPIO.input(input_pin))
    time.sleep(0.05)

Merge this code in with your Python file, and briefly meet with a staff member to discuss how it is operating.

Another state machine example is a human eating food. We put food in our mouth and eat, and then put food in our mouth and eat...but eventually our "state" changes and even if we were to put food into our mouth we might not want to eat any more or might throw up. Intelligence/Life can in some sense be characterized by the presence of state machines. A rock will always do the same thing when you yell at it...but a dog or a human will eventually tire of your anger and walk away or yell back or something.

5.1) Flow Diagrams

A good way to start thinking about (and ultimately designing) state machines is by sketching out how we want to respond inputs and putting it down in a flow diagram. An example one is shown below and is a graphical representation of how the input_check2 function operates from above. Different states of the system are in the blue boxes and different decision points are in the diamond shapes. One decision is made per time we run the function and in our code above, we are calling our function twenty times per second. See if you can map the decisions and states and how they are implemented in vivo in the code below.

A Flow Diagram of our Button State Machine

5.2) Finish LED

So now let's return to our LED task. As a reminder, we wanted a system that did the following:

  • If the LED is off and you push and then subsequently release the button (connected to Pin 5), the LED will turn on.
  • If the LED is on and you push and then subsequently release the button, the LED will turn off

Use the state machine above as the basis for a piece of code which will do this on-off functionality. When it is working, show it to a staff member!!!

6) Music Player

Your goal for this lab is to make a music player. The system should allow a user to start playing a song as well as pause and unpause the playing of a song and cycle through a selection of at least three songs.

  • If the system is playing a song (unpaused), it will pause the song upon pushing and releasing Button 1
  • If the system is paused, it will unpause (play from where it left off), upon pushing and releasing Button 1
  • Upon pushing and releasing Button 2, the system will skip to the next song in its queue (list of songs). If you are at the end of your songs, you need to restart in your list.

The code you need for this lab is going to take in all that you've learned so far. Whenever you start coding out a solution to a problem, often times the beginning is the worst since the problem can seem just so massive that you don't know where to start. What I usually do is try and sketch out an idea of my control flow...what paths will my code take as it runs...What ends up happening might not 100% match what you initially sketched out but at least this helps you get started. Here's the flow diagram I quickly sketched out when I was coming up with this lab.

The documentation for the pygame music player is found HERE, but you should be able to achieve all required functionality by studying the example use code below. An example of the necessary functionalities is included below. Move this file onto your computer and add two additional songs (From the internet...make them mp3's) below!

import pygame
import time

song1 = "/home/pi/Music/seeyouagain.mp3"
song2 = "" #you should add a song here
song3 = "" #you should add another song here

#Initialize the Mixer:
pygame.mixer.init()

#Load a Song:
pygame.mixer.music.load(song1)

#Start Playing the Song (always need to do after loading a song):
pygame.mixer.music.play()

#Wait for Ten Seconds:
time.sleep(10)

#Pause song:
pygame.mixer.music.pause()

#Pause for three seconds:
time.sleep(3)

#Unpause song (continue playing from where it left off)
#Note if you instead did music.play() here, the song would restart from beginning!
pygame.mixer.music.unpause()

#Load next song and start playing it (assumes song2 is defined):
pygame.mixer.music.load(song2)
pygame.mixer.music.play()

#Stop song being played 
#Different from pause() in that you lose position in song when you do this:
pygame.mixer.music.stop()

Some tips to keep in mind:

  • In order to start planning how to design this, first choose a few songs you like from a legal or quasi-legal download site (download MP3s), and place them on your Pi in the Music folder.
  • Break the assignment down into several parts:

    • First you need to get reliable readings for when the buttons have been pushed. Consider using the earlier button function from the previous section! In fact consider having two of those functions running in your loop so that you can get updated information on which button has been pressed recently.
    • After you can reliably get info on the two buttons use that information to determine how to drive the music player.
  • The pygame.mixer.music.pause() and pygame.mixer.music.unpause() are great ways to get the pause/unpause functionality we want in our syste. However these methods will only work on a song that has already played. This means that if you change songs, you'll need to first call pygame.mixer.music.load() on the song name, followed by pygame.mixer.music.play() in order to have things working.

Build up a working system using state machines and code we've provided. Sketch out your design using a flow diagram. Ask for help! When it is all working, show to a staff member! And then when all is good, upload your team's code below!



 
Footnotes

1GPIO stands for "General Purpose Input-Output" (click to return to text)