MicroPython Skill Builders - #2 LEDs & Lists
This is the second instalment of our MicroPython Skill Builders series by Tony Goodhew, aiming to improve your coding skills with MicroPython whilst introducing new components and coding techniques - using a Raspberry Pi Pico!
We continue the series today by looking at using multiple LEDs and Lists.
What you will need
We assume that you have installed Thonny on your computer and set up your Raspberry Pi Pico with the most recent MicroPython firmware (UF2). If not, check out our Raspberry Pi Pico Getting Started Guide where this is covered in detail.
You will need:
- Raspberry Pi Pico with header pins
- A full-size breadboard
- Micro-USB cable (for power and programming the Pico)
- A pack of male-male and male-female Jumper Wires (a mixed pack is available here)
- Tactile buttons (a mixed pack is available here)
- 10K Ohm potentiometer (we may use a few of these in future episodes)
- 10x Current limiting resistors (330 Ohms)
- 10x 5mm LEDs (a mixed pack is available here)
- OR a 10-segment light bar
- Current limiting resistors (330 Ohms)
The Circuit
We start with the breadboard circuit from the first tutorial and add two LEDs with current limiting 330 Ohm resistors on pins GP17 and GP18.
Follow the diagram below if you're constructing this circuit for the first time:
Testing the Circuit
Copy the program below, paste it into Thonny and save it as 3 LEDSv1.py
Now run the program. There are two loops in the program. The first flashes all three LEDS at the same time and the second flashes the LEDs one at a time. Pressing the button breaks the program out of a loop.
# Flash 3 LEDs - Method 1
# LEDS on GP15, GP16 and GP17
# Button on GP15 and 10K potentiometer on ADC0
from machine import Pin, ADC
import time
# === Set up section ===
# Set up button on GP15 as INPUT with PULL_DOWN
button = Pin(15, Pin.IN, Pin.PULL_DOWN)
# Set up LED on GP16 on Pi Pico
ledA = Pin(16, Pin.OUT)
ledB = Pin(17, Pin.OUT)
ledC = Pin(18, Pin.OUT)
# All LEDs on -- All off – loop 1
while button.value() == 0:
ledA.value(1) # LEDs ON
ledB.value(1)
ledC.value(1)
time.sleep(0.5)
ledA.value(0) # LEDS OFF
ledB.value(0)
ledC.value(0)
time.sleep(0.5)
# Wait for button to be released
while button.value() == 1:
continue
# One LED at a time – loop 2
while button.value() == 0:
ledA.value(1)
time.sleep(0.3)
ledA.value(0)
time.sleep(0.3)
ledB.value(1)
time.sleep(0.3)
ledB.value(0)
time.sleep(0.3)
ledC.value(1)
time.sleep(0.3)
ledC.value(0)
time.sleep(0.3)
Things to notice
- The LEDs each have a different name: ledA, ledB and ledC
- The LEDs appear to switch on and off together but they are actually turned on and off in sequence. This happens so fast that we cannot see the delay
- In each loop we need a different piece of code each time we change the state of an LED or pause
Imagine how long the code in the second loop would be it we had ten LEDs instead of just the three in this circuit...
Using Lists in Micropython
There is a more efficient way of naming the LEDs which makes turning them on and off much easier to code – we use a list and then point to items in the list.
In MicroPython, a list is a structure containing several items which can accessed individually by means of a pointer or index.
Here is a simple example program using a list of friends’ names. Copy and run the program before we go through it in detail:
# List example - a list of friends
friends = ["Joe", "Anne", "Phil"]
print(friends)
number = len(friends) # No of elements in the list
print("\n",number,"\n")
# Individual names
for p in range(number):
print(friends[p], end=' ')
print("\n")
# Individual names - reverse order
for i in range(number -1, -1, -1):
print(friends[i], end=' ')
print()
# Add a new friend with .append[] - to the end of the list
friends.append("Jenny")
number = len(friends) # No of elements in the list
print("\n",number,"\n")
# Individual names
for p in range(number):
print(friends[p], end=' ')
print("\n")
# Individual names - reverse order
# Index moves from last to first
for i in range(number -1, -1, -1):
print(friends[i], end=' ')
print("\n")
print("My 2nd friend is " + friends[1]) # Index/Pointer starts at 1
Once run, the output of the program should look like this:
>>> %Run -c $EDITOR_CONTENT
['Joe', 'Anne', 'Phil']
3
Joe Anne Phil
Phil Anne Joe
4
Joe Anne Phil Jenny
Jenny Phil Anne Joe
My 2nd friend is Anne
>>>
Let’s look at the code more closely
# List example - a list of friends
friends = ["Joe", "Anne", "Phil"]
print(friends)
number = len(friends) # No of elements in the list
print("\n",number,"\n")
We create a list of three names. The names, which are strings of characters, are enclosed in quotation marks, separated by commas and enclosed in square brackets. The list has the variable name, friends.
We print out the list and it appears as a single line with the brackets, quotes and commas.
The next line determines the number of items in the list using the len() function to count them. Finally, we print the number forcing a couple of new lines with the "\n" strings.
# Individual names
for p in range(number):
print(friends[p], end=' ')
print("\n")
Here we use the pointer, p, to point to each item in the list and print it out.
The end=' ' at the end of the print statement prevents the normal new line being generated by the print command, and the names are listed on the same line with a single space between them. The last print generates two line feeds.
# Individual names - reverse order
for i in range(number -1, -1,-1):
print(friends[i], end=' ')
print()
This time we move the pointer in the opposite direction and print the names in reverse order.
(n – 1) points to the last element in a list with n items; while 0 points to the first item.
# Add a new friend with .append[] - to the end of the list
friends.append("Jenny")
This line adds a new name to the end of the friends list with the .append() method.
number = len(friends) # No of elements in the list
print("\n",number,"\n")
Here we update the variable holding the length of the list and print it.
# Individual names
for p in range(number):
print(friends[p], end=' ')
print("\n")
# Individual names - reverse order
# Index moves from last to first
for i in range(number -1, -1, -1):
print(friends[i], end=' ')
print("\n")
Then we print the extended list in normal and reverse order
print("My 2nd friend is " + friends[1]) # Index/Pointer starts at 1
This line demonstrates that the index or pointer counts from zero. An index equal to 1 points to the second item in the list.
A List of LEDs
We are now going to use our code to control ten LEDs in a row. You can either use individual round LEDs, or a rather neat package with ten rectangular LEDS called a Light Bar / Bar Graph.
Each LED will need its own 330 Ohm resistor and an individual connection to a pin on the Pico. We are going to use GP2 to GP11 to control the LEDs.
Here is a picture of our circuit. Our 10 segment LED had the anode legs on the side with the printing and the cathode legs on the blank side:
The Raspberry Pi Pico has convenient pin numbers printed on its underside making it much easier to connect jumper wires to the correct pin.
We have kept the button on GP 15 and the potentiometer on ADC0 – GP26.
The LEDs have their anodes connected directly to the Pico’s GPIO pins (GP2 to GP11) while the cathodes are connected to the GND rail via 330 Ohm resistors.
Need a reminder on anodes and cathodes? Go back to episode #1 where this is covered.
Test the LEDs
Copy the code below over to your Thonny window and run it - we'll explain how it works in a moment:
# 10 Segment LED Bar Graph or 10 LEDs
# Author: Tony Goodhew 27 April 2023
# LEDs with 330 Ohm resistors on pins GP2 to GP11
# Button switch on GP15 - Pull Down
# Import libraries
from machine import Pin
import utime
import random
# Set up button on GP 15 with Pull Down
button = Pin(15,Pin.IN, Pin.PULL_DOWN)
# Set up the LEDs with a list
leds = [] # An empty list
for i in range(10):
led = Pin(2 + i, Pin.OUT) # Set up 1 LED
leds.append(led) # Add the new led to the list
leds[i].value(0) # Turn it off
# Turn LEDs ON, one at a time
for i in range(10):
leds[i].value(1) # Turn on a LED
utime.sleep(0.1)
utime.sleep(0.5) # Short delay
# Turn LEDs OFF, one at a time
for i in range(10):
leds[i].value(0) # Turn off a LED
utime.sleep(0.1)
# Flash, one at a time UP
for i in range(10):
leds[i].value(1) # Turn on a LED
utime.sleep(0.3)
leds[i].value(0) # Turn off a LED
utime.sleep(0.3)
utime.sleep(0.7)
# Flash, one at a time DOWN
for i in range(9,-1, -1):
leds[i].value(1) # Turn on a LED
utime.sleep(0.3)
leds[i].value(0) # Turn off a LED
utime.sleep(0.3)
utime.sleep(0.7)
# Random LEDs
for c in range(30):
p = random.randint(1,9)
leds[p].value(1) # Turn LED on
utime.sleep(0.1)
leds[p].value(0) # Turn LED off
utime.sleep(0.1)
How It Works
The first section imports the necessary libraries and sets up the button. We then set up the list of LEDs like this:
# Set up the LEDs with a list
leds = [] # An empty list
for i in range(10):
led = Pin(2 + i, Pin.OUT) # Set up 1 LED
leds.append(led) # Add the new led to the list
leds[i].value(0) # Turn it off
We start with an empty list called leds, using leds = [] # An empty list.
There are ten LEDs so we can use a counted loop to repeat a section of code ten times. In the loop we set up a new LED on GP pin 2 + i, where i is the loop counter, running from 0 to 9. Initially, this is pin GP2 and finally GP11.
We then append the newly created LED to the growing list and make sure that it is turned off.
Once the LEDs have been set up, we can start turning them ON and OFF in different pattens:
- Turn them all ON, one at a time
- Turn them all OFF, one at a time, in the opposite direction
- Flash them ON/OFF one at a time up then down the row
- Flash in random positions 30 times
Controlling a List of LEDs with a Potentiometer
Let's use the potentiometer (pot) in our circuit to control the LEDs. Copy the code below over to Thonny, run it, then read on for to learn about how it works:
# 10 Segment LED Bar Graph or 10 LEDs
# Author: Tony Goodhew 27 April 2023
# LEDs with 330 Ohm resistors on pins GP2 to GP11
# Button switch on GP15 - Pull Down
# 10K Ohm potentiometer on ADC0 = GP26
# Import libraries
from machine import Pin, ADC
import utime
import random
# Set up button on GP 15 with Pull Down
button = Pin(15,Pin.IN, Pin.PULL_DOWN)
# Set up potentiometer on ADC0 = GP26
pot = machine.ADC(26)
# Set up the LEDs with a list
leds = [] # An empty list
for i in range(10):
led = Pin(2 + i, Pin.OUT) # Set up 1 LED
leds.append(led) # Add the new led to the list
leds[i].value(0) # Turn it off
p = 0 # Set pointer at first LED
dir = 1 # pointer direction of movement
# Loop UP and DOWN until button pressed
while button.value() == 0:
leds[p].value(1) # Turn LED on
utime.sleep(0.1)
leds[p].value(0) # Turn LED off
utime.sleep(0.1)
p = p + dir
if p == 10: # Over the upper end
p = 8 # Set next position – back 2 places
dir = -1 # Reverse direction = move down
if p == -1: # Below the bottom end
p = 1 # Set next position – up 2 places
dir = 1 # Reverse direction = move up
# Wait for button release
while button.value() == 1:continue
# Single LED under pot control until button pressed
while button.value() == 0:
pot_in =int(pot.read_u16()/6500)
if pot_in != 0:
leds[pot_in - 1].value(1) # Turn LED on
utime.sleep(0.1)
leds[pot_in - 1].value(0) # Turn LED off
# Clear LEDS
for p in range(10):
leds[p].value(0) # Clear LEDS
# Wait for button release
while button.value() == 1:continue
# Bar graph under pot control until button pressed
while button.value() == 0:
pot_in =int(pot.read_u16()/6500) # Values 0 to 10
print(pot_in)
# Clear LEDS
for p in range(10):
leds[p].value(0) # OFF
for p in range(pot_in): # Values
leds[p].value(1)
utime.sleep(0.1)
# Clear LEDS
for p in range(10):
leds[p].value(0) # Clear LEDS
The lit LED should appear to move up and down the row.
Press the button to break out of the loop and then turn the knob of the potentiometer to control what is going on.
How It Works
Let's take a look at the two main code blocks:
# Single LED under pot control until button pressed
while button.value() == 0:
pot_in =int(pot.read_u16()/6500)
if pot_in != 0:
leds[pot_in - 1].value(1) # Turn LED on
utime.sleep(0.1)
leds[pot_in - 1].value(0) # Turn LED off
First a single LED moves as the potentiometer is twisted.
# Bar graph under pot control until button pressed
while button.value() == 0:
pot_in =int(pot.read_u16()/6500) # Values 0 to 10
print(pot_in)
# Clear LEDS
for p in range(10):
leds[p].value(0) # OFF
for p in range(pot_in): # Values
leds[p].value(1)
utime.sleep(0.1)
# Clear LEDS
for p in range(10):
leds[p].value(0) # OFF
After pressing the button again, we get a solid bar graph controlled by the pot. Press the button once more to finish and clear the display.
This loops while the button is released. The first line in the loop calculates an integer value from 0 to 10 inclusive from the value obtained from the potentiometer.
If the value is zero, we want the display OFF so that we do not turn any LEDs ON. We calculate the pointer value by subtracting 1 from pot_in, getting values 0 to 9 as required for 1 to 10 LEDs. The current LED is only on for 1/10th of a second in each loop but we do not see the flicker because of persistence of vision.
This is an example of nested loops - loops running inside an outer loop. Here the outer loop is button controlled - it keeps looping until the button is pressed.
Inside the loop we get a raw value from the potentiometer, scale it to the range 0 to 10 inclusive and print it in the Shell window.
The first inner loop turns off all the LEDs. The pointer, has the values: 0, 1, 2, 3, 4, 5, 6, 7, 8 and 9.
The second loop, which lights up some of the LEDs, only runs if the scaled value from the potentiometer is greater than 0. If pot_in is equal to zero it does not enter the loop. Otherwise, it lights up the number of the LEDs equal to pot_in. If pot_in equals 4, we need to light 4 of the LEDs and the pointer, p, has the values 0, 1, 2, 3, lighting up the first four LEDs (It’s all about humans counting from 1 and computers from zero).
Things to try
- Bounce a pair of adjacent LEDs up and down the display – always showing 2 lit LEDs
- Bounce two lit LEDs at the same time starting with one at each end of the display
If possible, please keep the current circuit as we'll be using it in the next tutorial when we learn about subroutines (procedures and functions).
About the Author
This article was written by Tony Goodhew. Tony is a retired teacher of computing who starting writing code back in 1968 when it was called programming - he started with FORTRAN IV on an IBM 1130! An active Raspberry Pi community member, his main interests now are coding in MicroPython, travelling and photography.
We used Fritzing to create the breadboard wiring diagram images for this page.
1 comment
Neil Hoskins
With the ten flashing LEDs, it should be p = random.randint(0,9) not p = random.randint(1,9) otherwise the first one never lights.
With the ten flashing LEDs, it should be p = random.randint(0,9) not p = random.randint(1,9) otherwise the first one never lights.