Lab 6: Raspberry Pi

Learning Goals

In this lab you will be introduced to the basics of electronics using a a credit-card sized computer called the Raspberry Pi. You will build simple electronic circuits and write programs to control them. Because this lab requires special hardware, which we need to share amongst groups, you only have limited time to work on this assignment. If you are not able to get all the way to the end, don’t worry. The main goal is to learn and have fun.

Getting Started

Take a look at the Raspberry Pi (or RPi for short) on your workbench. You are looking at the insides of a computer. Your gut feeling may be that it all looks very complex. Your premonitions are not misplaced. The circuit that you are looking at is not called the motherboard for nothing.

The green base on which all the electronic components (which you see as bulgy entities) are laid out is a non-conductive substrate. This means that the green parts of the board don’t conduct electricity. However, to connect the various electrical components, conductive tracks made of copper are etched on the green base. At the very center of your RPi sits the brain of your RPi: the processor (a Broadcom BCM2711 CPU with 64-bit Quad core ARM). It is actually covered by a heat sink, which consists of a set of metal fins. This is to radiate heat away from the processor (it can hot when doing a lot of work). Two other smaller components are also covered by heat sinks, the RAM (Random Access Memory) and the USB controller (which is the larger of the two unlabeled black boxes in the diagram below). 


Observe the components marked in this diagram on your RPi. We will focus on a few important components on the diagram:

Observe the connections from your RPi to other peripherals

In the setup provided to you, the RPi has been wired to work as a desktop computer. Actually, it’s even more than a desktop computer, because it is set up to connect to an electronic circuit that you will soon create (something you can’t do with standard desktops). For the robotics projects during project week, we will also operate the RPi in an untethered fashion (i.e., without monitor, keyboard, mouse, charger attached) so that the robot can be mobile. However, for Lab 6, we stick to this desktop setup. Let's now observe the following wired connections from the RPi to other peripherals:

Note: It is best for the RPi to be shut down when you plug or unplug peripherals. This minimizes the risk of the device resetting, which in turn could causes corrupting the SD card.

Now let’s take a closer look at the breadboard and the GPIO breakout board that sits on it. You will need this information to complete the later exercises in this lab.

The Breadboard

To attach an electronic components to the RPi, we will be using a breadboard – which is designed to make it possible to experiment with circuits by extending the available space. For electricity to flow you need connections. Think of a breadboard as a way of giving you more space to make those connections. The main feature of the breadboard is that it comes with a set of in-built electrical connections which significantly simplifies the wiring involved in complicated circuits. Let’s try to understand the electrical connections on the breadboard using the figures below:

The figure on the left shows a picture of a smaller version of the breadboard as visible to you, while the one on the right shows in internal electrical connections when the back of the breadboard is uncovered. The rows are numbered 1 through 30 (based on this picture of the breadboard) and the columns ‘a’ through ‘j’. In the figure on the right observe how the conductive metal strips run across the rows. This means that any two grids that are on columns ‘a’ through ‘e’ are at the same potential as long as they are also on the same row. Similarly, any two grids that are on columns ‘f’ through ‘j’ are at the same potential as long as they are also on the same row. However, the two halves of a row are electronically disconnected. As you will see, this helps you out a lot. Although your breadboard might be a bigger size, the pattern of the metal strips are still the same but just extended more.

Are each of the following grids internally connected on the above shown breadboard?

Finally, the breadboard has two vertical lines running up and down on both sides. The pins on each of these lines are electrically connected but the lines are electrically disconnected from each other. The convention is to connect a positive voltage (e.g. 3.3V pin) to the line that has a ‘+’ sign above it (red) and connect the ‘ground’ (GND pin) to the line with the ‘-‘ sign above it (blue).

The GPIO pin numbering conventions

The Raspberry Pi 4, model B (which is the one we are using) has 40 GPIO pins that are laid out as two rows of 20 pins each, also depicted in the diagram below:

The pins that are labeled "GPIO #" are available for any general purpose and can be referred to and configured in your python programs. Some of these pins have an ‘alternate function’ as well (labeled with the parentheses); this means they may not be available if you are using that alternate function. In case you are not sure, avoid those pins and only use the ones that do not have an alternate function labeled with the parentheses. There are two different conventions used to refer to these pins:

Either of the above conventions is okay to use in your programs as long as you define which scheme you are using before you start referring to the pins. Defining which scheme you are using is done in your Python code, as we will see later.

Note that physical pins 1 and 17 provide 3.3V, while physical pins 2 and 4 provide 5V. Physical pins 6, 9, 14, 20 and 25 provide the ground (GND) signal which is also very important in your circuits. The pins that provide the same signal are all internally connected, and you can just use any of the pins to access that signal.

The GPIO breakout board shown below makes all these pins available to you on a breadboard where you will be doing your electrical wiring.

The labels on the breakout board use the BCM naming convention. However, we have connected the breakout board in such a way that you can easily derive the physical pin number of any pin by looking at the row number that it is connected to on the breadboard.

Getting to know your RPi desktop environment

The operating system on the RPi is a version of Linux known as Raspbian. The picture above shows the Pixel desktop environment under Raspbian. Feel free to poke around and see what all of these icons mean.

Terminal

A terminal is a program where you can type in commands (you have similar command line tools in Windows and iOS as well). You can open a terminal when by clicking on the black icon on the top bar showing >_. You can also find it under the Accessories submenu of the main menu (the raspberry icon). When you open a terminal, notice the prompt after which you can type your command. The purple part specifies the location in the directy (i.e., folder) structure you are currently in.  If you just opened a new terminal window, you will see ~$, which stands for the home directory.

We will use the terminal to type in commands to execute on our RPi; more on this later.

WiFi

Before we do anything else, we want to make sure that the RPi is connected to Wifi.  Look on the right side of the top menu bar, to the left of the speaker icon. If you see the wireless connectivity icon there (the one depicted below on the left), you can click on it to verify that you are connected to UCSD Protected. 

On the other hand, if you see the icon depicted above on the right, it means you are not yet connected to Wifi. In that case, follow the steps below (you do not need to do this if you are already connected).  

cd Desktop

sudo ./connect-wifi.sh

This will prompt you for a username and password. Use the same username and password you use for UCSD-Protected (and the basement work stations).  Note that the password will be visible when you type it, so your partner should best be looking away at this point. Once the script has run, you should now be connected to the Wifi and the connected icon should be visible in the top menu bar.

Note: While not necessary for this lab, if you want to know the IP address of your RPi, open a terminal window and type: ifconfig. It will show you a bunch of information, organized in three sections. In the bottom section, labeled wlan0, look for inet addr. The numbers that follow (something like 192.168.1.233) are your IP address. Knowing this will come in handy if you want to log in remotely using ssh.

Command Line

When working in Linux, it is common to type in commands directly into a terminal window. For example, in the section above, the procedure to connect to Wifi if you were not already connected, involved such terminal commands. Similarly, executing Python code or connected to GitHub may have to be done this way, which is also known as "working on the  command line".

We have talked earlier about how to open a terminal window. Below are a few useful commands (these will be useful in your future classes as well). You an open a terminal window and try them out:

ls

This will list all the files and folders in the current folder (note: in Linux, folders are also called directories, and we will use these two names interchangeably). When you open a new terminal window, it will start in the home folder.

cd Desktop

From the current directory, go to the subfolder named "Desktop". This is just an example and assumes there is a subfolder called "Desktop". Note that if you type the first few letters of the subdirectory name and then press tab, the name will get autocompleted. Once you moved to a new folder, type ls again to show the content of that folder. Also note that the command prompt has changed from ~$ to now also show the folder name you are in.

pwd

This prints out which folder you are currently in.

cd ..

From the current folder, go one folder up in the directory structure.

cd ~

From any folder, go back to the home folder.

For more information these (and other) Linux commands, you can also check out this online tutorial.

Getting the starter code

Now that you are a little more familiar with the Raspberry Pi and the Linux desktop environment, let's get ready to do some coding.

In this lab, you will no longer be using Colab. Instead, you will write and execute code on the Raspberry Pi using some code development tools there. However, we will still be using GitHub as before, as a central place to share your code with the mentors/instructors and your pair partner.

As in some earlier labs, we will give you some starter code to get started.


Creating a new repo on GitHub

First, you need to create a new GitHub repo and repopulate it with the starter code. It is easiest to do this in a web browser on the RPi itself (as you will allow you to simply copy-paste information in later steps rather than having to type them out). To open a web browser, click on the globe-like icon on the left side of the top menu bar.

In that web browser, go to your GitHub account. Only one partner needs to do these steps, as we will be sharing the GitHub repo with the other partner later on anyway.

You will be asked to create a new repo called spis23-lab06-Name-Name (with Name replaced by our pair partner names as in the prior labs). Also, this repo should import the follow starter code: https://github.com/ucsd-cse-spis-2023/lab06-startercode. Follow the steps here to create this new repo with this starter code: link (make sure you import the starter code)

Once the repo has been created, share it with your pair partner as you did in previous labs.


Importing the code from GitHub to your RPi

Next, you will need to get the starter code from your GitHub repo onto your RPi. This is part of the more general process of exchanging code between the RPi and GitHub.

The following page describes how you can exchange code between GitHub and RPi: link. Follow one of the procedures described there to get the starter code from GitHub to your RPi. Once that is done, you can proceed and start developing your own code on the RPi.

However, remember to periodically push your code back to GitHub! Definitely do not forget to do this at the end of the lab!

Running Python programs on the RPi

There are two options to execute your Python code: inside the text editor program or on the command line. They are explained below.  You can try it out on 00_test.py, which is part of your starter code.


Click on the yellow "File Manager" icon on the left side of the top menu bar. There, navigate to the folder with your code. Right click on the file you want to open and select "Thonny".

This will open your file in the Thonny IDE (integrated development environment). There you can edit the file, etc. Note that you need to explicitly save the file (unlike in Google Colab where this was done automatically). To run the code, press the green play button. The red stop button ends the code in case you are in an infinite loop.


You can open and edit the file as above, or with another editor (e.g., Geany or nano). To run your code from the command line, open a terminal window, navigate to the correct folder (using cd as explained earlier). You can use ls to check that you see the file there. To execute a file, use the following command (replace <filename> by the actual Python file you want to execute):

python <filename>

Making an LED blink

Now that we are done with all the basics, let's work with some hardware!

In this exercise you will create a circuit consisting of an LED and a resistor connected to the RPi. You will then periodically blink the LED using the example program provided to you in the starter code.

For all your work with Raspberry Pi, it is extremely important you do not make short circuit connections on the GPIO pins. A short circuit is when a supply or GPIO pin set to HIGH is directly connected to a GND or GPIO pin set to LOW. If you are unsure, ask a mentor or instructor to check your circuit before running your program. Always be careful to avoid short circuits. It may reset the RPi, which can corrupt the SD card or possibly even destroy the RPi.

Understanding the code

Open the file 01_blinking_LED.py. Let’s begin by trying to understand the given code.

The first two statements import the modules needed for this exercise:

import RPi.GPIO as GPIO

import time

The RPi.GPIO module provides routines for configuring the GPIO pins on the RPi and for sending and receiving signals on these pins. Since the GPIO pins are digital we can only send high or low voltages.

The time module provides routines that make use of the clock on the RPi. Using the time module you can make the RPi wait for some time before executing the next python command in your program. For example, time.sleep(0.5) makes your program wait for half a second before moving on to the next line of code.

The lines immediately after the import statements take care of configurations related to using the GPIO pins. The first one specifies that we are using the physical numbering scheme.

GPIO.setmode(GPIO.BOARD)

If we chose to use the BCM scheme, that line should be replaced by the following:

GPIO.setmode(GPIO.BCM)

Before proceeding further, identify pin number 11 on your breadboard (remember, we are using physical numbering, so you will need to count the pins to find the location of pin 11). You will need this for wiring your circuit.

The Python GPIO library allows configuring certain pins to be either input or output pins using the setup(Pin, mode) function, where Pin is the pin number and mode is either GPIO.OUT (for output) or GPIO.IN (for input). If we want our program to generate high or low voltages on a pin that potentially drives other electronic components (such as LEDs or servos), then configure the pin to be an OUTPUT pin. If we want to read the signal generated by sensors into our program, then configure the pin to be an INPUT pin.

In our code here, pin number 11 is configured to be an output pin using the command GPIO.setup(LedPin, GPIO.OUT). It also sets the initial state of that pin to be LOW via the command GPIO.output(LedPin, GPIO.LOW).

After these inital settings and definitions comes the main program. For now ignore the try …except structure. The key part of the code is the while True: loop. The code in this loop contains the main functionality and runs forever. Any ideas why we are doing this?

Wiring the circuit

To create the circuit you will need the following components

The picture of an LED and the symbol used for it in circuits is shown below:

The longer leg of the LED is called the ‘anode’ and the shorter leg is called the ‘cathode’. When there is a positive voltage difference between the anode and the cathode, the LED is forward biased or “on state” and current flows through the circuit lighting it up, otherwise the LED is in the “off state”.

Wire up the circuit shown by the following diagram. Remember: we are using physical pin numbering for the RPi. You should use jumper wires and the breadboard to make the physical connections.

Ask an instructor or mentor is you don’t understand the diagram. For a more in depth information on configuring GPIO pins as outputs refer to this website.

Now, run the 01_blinking_LED.py program. Hopefully you should have a blinking LED. To stop the program, press ‘Ctrl+C’ on the keyboard. This will force the code the execute the KeyboardInterrupt: portion.

If you were unsuccessful, the problem is most likely with your wiring. If you were successful, take a close look at the code.

Now, modify the program (and rename it to 01_blinking_LED_sol.py) to double the frequency at which the LED blinks. Make sure you commit this code to your github repo.

Note: It may happen at some point that you get an error or warning that says “This channel is already in use”. This is Python telling you that one of the GPIO pins you are trying to use is already assigned to another program. This can happen when you ran your code before and it was exited without executing the GPIO.cleanup() line in the KeyboardInterrupt portion (e.g., it stopped because there was an error in the code). If it doesn’t execute GPIO.cleanup(), it never gives up control of the GPIO pins, and so when the next program tries to use it, there may be a conflict. If all you get is a warning, you can ignore it. If you get an error and your code cannot be executed, put GPIO.cleanup() in your code immediately after importing the libraries. This should resolve the issue, and you can then comment out this line going forward.

When you are finished with your code, make sure you push it to GitHub.

Controlling an LED using a button

Next, we are going one step further and we’ll use a button to control the LED. You don’t need to undo the wiring you already created for the previous problem. This can stay the same. We will just add circuitry for a button.

Buttons are switches that are used to connect or disconnect circuits. In the figure below, pins pointed out by the arrows of the same color are internally connected. When the button is pressed, the pins indicated by the blue arrow will connect to the pins indicated by the red arrow. Note which way the legs of the button are facing and how to differentiate between the red and blue pairs of pins of the button. This is important because faulty direction of the button will make your circuit not work.

Understanding how to use a button

Now, let’s start wiring the circuit. You should keep the LED circuit from the precious exercise. Then add the button as per the circuit diagram below.

Next, open the program 02_buttonLED.py. One of the key differences in this exercise compared to the previous one is that you will configure the pin connected to the button as an INPUT pin. Here is a nice excerpt from a website explaining such a configuration:

A GPIO pin configured as an input is used to read (to input) the value of one digital signal. In this case the pin is converting the voltage being delivered to the pin into a logical value of 0 or 1 for subsequent use in the program. By convention, when the voltage on the pin is ‘high’ (near Vcc), reading the pin will result in reading a logic 1, while when the voltage is ‘low’ (near GND), reading the pin will result in reading a logic 0.

So, the bottom line is that when we configure a GPIO pin as an input, we can read the voltage at that pin in our Python program.

But, what is the expected voltage if the pin is not connected to anything at all? This would occur in the button circuit we just built and the button is not pressed. The answer is that we really can’t say. When a GPIO pin is set as an input but is not connected to anything it is ‘floating’ and has no defined voltage level. For us to be able to reliably detect whether the input is high or low, we need to tie it so that it is always connected to something and either reads high or low. This can be done by using a pullup resistor, as shown in the diagram below. (If you are interested in exactly how this work, you can ask the instructors).

Controlling the LED

Now, it’s time to write some code of your own.

(1) Modify the program such that the LED is ON when the button is pressed and OFF when the button is not pressed. Name it 02_buttonLED_sol1.py and commit it to the github repo.

(2) Now change the functionality: toggle the LED every time the button is pressed. So, if the LED is ON and the button is pressed, it should turn OFF. If the LED is OFF and the button is pressed, it should turn ON. So pressing the button changes the state of the LED, from ON to OFF to ON to OFF etc. This functionality is trickier to obtain. You will need to think carefully how you can accomplish this. Name this version of your code 02_buttonLED_sol2.py and commit it to the github repo. When you have a working solution, please have a mentor verify the correct functionality before moving on to the next section.

When you are finished with your code, make sure you push it to GitHub.

Controlling a servo motor

A servo is an electrical motor that comes with built-in control circuitry. There are two types of servos: standard servos and continuous rotation servos. In this lab, we will be working with standard servos. A stardard servo can move its arm half a rotation, from an angle of 0 degrees to an angle of 180 degrees, and back. Servos are used in a lot of applications, for example to control flaps and rudders of RC aircraft or to rotate camera turrets.

Keep the circuitry from the previous problems (the wiring of the LED and button), and add a servo. The extra wiring is shown in the diagram below. Note that even if the wiring does not exactly come out the place that is shown in the diagram, hold the servo like the diagram and the bottom wire should be ground while the top wire should be pin 7.

Testing the servo

Now look at the starter code for the servo in 03_servo.py. The first few lines of code should look familiar to you: we are configuring pin 7 as an output as it will be used to control the servo. However, the control signal we will send to the servo is a PWM or Pulse Width Modulation signal. What this means is that it alternates between HIGH and LOW at a certain frequency. The percentage of time the signal is HIGH is called the duty cycle. By changing this duty cycle, we can signal to the servo to which angle we want it to move.

The program includes code between the comments #--- Start of the PWM setup --- and #--- End of the PWM setup --- that takes care of this signaling. It is not important to understand the details here. Just leave the code as is and realize that it takes care of the PWM signal generation. The only thing you really need to know is that it defines a function set_duty_cycle(angle). This function generates the PWM signal to tell the servo to move to the position indicated by ‘angle’ (between 0 and 180).

Try out the starter code. You should see the servo move between angle 0 and angle 180.

You will notice that the pwm_servo.start() command tells the servo to start moving to the new angle. The actual motion will take some time. This is why there are the time.sleep() commands: we want to halt the code execution long enough so that the servo can move to its desired destination. Specifically, time.sleep(x) is a function in the time library that halts execution for x seconds.

What happens when you reduce this sleep time? Try decreasing it to 0.8 seconds, 0.5 seconds, 0.1 second, etc. Can you explain what you observe?

Modifying how we implement delays

Next, open the starter code in 04_timing.py. This shows an alternative way for a program to delay execution without explicitly adding in sleep time with time.sleep(). Execute the code to verify its functionality. It prints a comment every second, but the way this delay is implemented is much more flexible than when using time.sleep(). Go through the code in detail and make sure you understand how it works.

Now, let’s use this knowledge to change the servo example code. These last two coding assignments will challenge your ability to think about real-time coding. If you are struggling, don’t despair. Part of the goal is to realize the challenges that result from dealing with the concept of time in embedded systems. Thinking about functionality as a collections of states (as in Picobot) is very useful here.  Give it a shot and see what you can come up with.

(1) Modify the code from 03_servo.py to perform the same functionality, but without using time.sleep(). Instead, use the technique shown in 04_timing.py. Name this version of your code 03_servo_sol1.py and commit it to the github repo. The easiest way to get started may be to begin with the 04_timing.py code and copy in the servo functionality from 03_servo.py.

(2) Now, let’s modify the functionality a bit. The code you wrote in part (1) will allow you to do this. We want the servo to move as we did before, but we also add a button. As soon as the button is pressed, the servo should move back to position 0 and stay there as long as the button is pressed. Why would this be hard to implement if you use time.sleep()? Name this version of your code 03_servo_sol2.py and commit it to the github repo.

Reading from a distance sensor

An ultrasound distance sensor is able to detect the distance to an object by sending out an ultrasonic pulse and measuring how long it takes for the reflection to come back. With this two-way travel time and knowledge of the speed of sound, it can calculate the distance. This is pretty much how echo-location for bats works as well.

Keep the circuitry from the previous problems, and add an ultrasound sensor. The extra wiring is shown in the diagram below.

Testing the ultrasound sensor

Run the starter code 05_ultrasound.py. If successful, it will print the distances measured by the sensor every 0.5 seconds. If it prints -1, it means it was not able to detect an object in front of the sensor. The reason is that it only has a limited range.

Now have a close look at the code. Most of the interfacing with the sensor is in a helper function, which you shouldn’t modify. This function reads from the sensor and uses this reading to calculate the distance. If you are interested in the details, you can go through the code and also have a look at this tutorial. It is written for another embedded board, the Arduino, but the most interesting part for you is the description of how the sensor sends out sound signals to measure distance.

Implementing your own functionality

Now it is time to be creative with the ultrasound sensor. Modify the code to include an LED and have the LED blink at different rates based on the distance measured by the sensor. When the distance is smaller, the LED should blink faster. Make sure you include comments in your code to explain how the blinking rate depends on the distance. Save this code as 05_ultrasound_sol1.py and commit it to the github repo.

Next, add code that also moves the servo to a position of x degrees (with x between 0 and 180), based on the measured distance. Again, make sure your comments explain this mapping. Save this code as 05_ultrasound_sol2.py and commit it to the github repo.

Trying out the picam camera

Very important: When you attach or remove the picam, your RPi needs to be shut down!!! Otherwise, you may damage the device.

We can attach a miniature camera that was specifically designed for the RPi, called the picam. You need to connect it to dedicated camera port on the RPi with a flex cable (check the RPi diagram in the beginning). You should match the metal side of the flex cable with the metal side withingm the port. Remember, do not attach or remove the picam when the RPi is powered on. Shut it down first. Ask the mentors or instructors for help with this.

Once you have the picam attached, try out the program 06_picam_preview.py. This will open a preview window that automatically closes after 5 seconds. It is a good test to see if the camera is connected correctly and working properly. If you get an error stating something like “Camera is not enabled”, you need to go into the RPi configuration and enable it there and reboot. A mentor can help with this.

Next, run the program 06_picam_video.py. It will open two video windows. One with the original video, and one where the center part has inverted colors. Basically, this example shows how we can apply what we have learned about image processing to the RPi. All the image manipulations we did in that lab, we could implement here as well, but now on video streams. Note that for efficiency reasons, we are no longer using the PIL library here. Instead, we use the very powerful OpenCV (which stands for Open Computer Vision) library cv2. To store images, we will not do this in Image objects anymore (as PIL does), but instead we will be using numpy array objects. Calculations on these numpy arrays are much faster. Essentially, a numpy array is a 3-dimensional array of pixels: the first two dimensions are the x and y of the pixel and the third dimension is of size 3, containing the values of B, G, and R for that pixel. Numpy assumes the color channels are in the order BGR. For example, if img_np is a numpy array containing an image, then img_np(2,5,0) is the B value of the pixel at location x = 2, y = 5. Similarly, img_np(2,5,1) is the G value for the pixel at (2,5) and img_np(2,5,2) is the R value. For detailed information on numpy and how to manipulate these arrays, you can check out this website.

Now you can try to modify the picam video starter code. What you decide to do is totally up to you. You could convert to grayscale, black our certain regions, mirror the image, look for a particular color, etc. Save your code as 06_picam_video_sol1.py and make sure you add comments to explain what it does. Upload to github.

We hope you enjoyed this lab and got a bit of a taster of what is possible with RPis and robotics…