Raspberry Pi Pico Tips and Tricks 2023
Raspberry Pi Pico Tips and Tricks 2023
Malcolm Maclean
This book is for sale at http://leanpub.com/rpitandt
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools
and many iterations to get reader feedback, pivot until you have the right book and build
traction once you do.
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Welcome! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
What are we trying to do? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Who is this book for? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
What will we need? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Why on earth did I write this rambling tome? . . . . . . . . . . . . . . . . . . . . . . . . 3
Where can you get more information? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Microcontrollers vs Computers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Microcontrollers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Computers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
What’s the difference to you? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Set up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
What is Thonny? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Install Thonny . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
MicroPython . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
What is MicroPython? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Connect our Pico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Automatically Installing the Firmware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Manually Installing the Firmware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Updating Firmware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Use the Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Blink the on-board LED . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Automatically run your program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Connectivity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Connecting using Dupont Connectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Connectivity via WiFi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
General Purpose Input / Output (GPIO) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Inter-Integrated Circuit (I2C) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
CONTENTS
Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
• A Raspberry Pi Pico. The standard Pico is okay, but I’m pretty much always going to be
using the wireless enabled version, the Pico W.
• A power supply for the Pico (almost any micro-USB charger will do the job).
• A remote computer (like your normal desktop PC) that you can use to program the Pico.
• An Internet connection for getting and updating the software.
As we work through the book we will be covering off the different aspects required and you
should get a good overview of what your options are in different circumstances.
Introduction 3
¹https://www.raspberrypi.org/
²https://raspberrypi.stackexchange.com/questions?sort=newest
Microcontrollers vs Computers
You might be thinking to yourself, surely all this IT stuff is the same? Well… from the perspective
of it being a bunch of highly integrated electronics designed to automate instructions and actions,
you’re exactly right. But there are differences in complexity and scale that make some methods of
carrying out tasks more complex or more capable than another, and that is where the distinction
between microcontrollers and computers comes in.
Microcontrollers
Microcontrollers are compact integrated circuits designed to operate embedded in a larger system.
Typical microcontrollers include a microprocessor, memory, timers, input/output connections
and converters (Analog-to-digital (ADC) and digital-to-analog (DAC)) on a single chip.
They are often referred to as an embedded controllers and can be found in in a huge number
of different areas. They are basically simple computers designed to control small features of a
larger component, without a great deal of complexity.
They are typically designed with a specific task (or a limited subset of tasks) in mind and as such
they can be simpler to use, but less flexible about their application.
There are a wide range of different options for microcontrollers depending on the users
requirements. Strictly speaking, the microcontroller is the highly integrated chip that provides
the function on a board, but typically people will refer to them by the manufacturer or model
of the board that carries the chip. In that respect the leader of the pack would be the Arduino
series of boards. Praised for their simplicity and small size, they have a range of boards for many
applications. Some microcontrollers are so ubiquitous that the boards that they are part of are
more broadly referred to by their chip name such as those based on the ESP32 or the ESP8266.
One of the more recent entrants to the world of microcontrollers is the Raspberry Pi Foundation.
They have released their RP2040 microcontroller chip which has been distributed on their
Raspberry Pi Pico boards.
Raspberry Pi Pico W
Computers
Computers are complex devices that are typically comprised of separate microprocessors,
memory, bus’s and connectivity for peripheral devices. They are designed to be able to carry
out a wide range of tasks and they can vary in size and complexity from large examples which
can take up a room to everyday laptop and desktop machines or even our phones.
Microcontrollers vs Computers 5
The feature that they share is that they are collections of discrete circuits that are combined to
create a functioning unit. This provides them with greater flexibility so that things like more or
less memory can be simply added or a different operating system can be loaded. Like all things,
with that capability comes the burden of greater complexity and ultimately cost.
The Raspberry Pi foundation has been manufacturing small, single board computers since 2012
and as such they have come to be a market leader in the supply of small computer boards for
computer and electronic hobbyists.
Raspberry Pi 4 B
The RP2040 is the first microcontroller released by the Raspberry Pi Foundation. It was designed
to deliver high performance, low power consumption and a wide variety of input / output
options to provide beginner and hobbyist users with access to a modern and capable option
for microcontroller based circuit boards.
It’s key features are;
The chip can be purchased separately and has been incorporated into a number of different
boards manufactured by organisations such as Arduino, Pimoroni, Adafruit, Sparkfun and Lone
Dynamics. But arguably the most obvious board manufacturer is the Raspberry Pi Foundation
itself.
• 21 mm × 51 mm form factor
• RP2040 microcontroller chip designed by Raspberry Pi in the UK
• 2MB on-board QSPI flash
• 2.4GHz 802.11n wireless LAN option
• Micro USB B port for power and data (and for reprogramming the flash)
• 26 multifunction GPIO pins, including 3 analogue inputs
• 2 × UART, 2 × SPI controllers, 2 × I2C controllers, 16 × PWM channels
• 12-bit 500ksps analogue to digital converter (ADC)
• 1 × USB 1.1 controller and PHY, with host and device support
• 8 × Programmable I/O (PIO) state machines for custom peripheral support
• Supported input power 1.8–5.5V DC and several options for powering the unit from micro
USB, external supplies or batteries
• The castellated module allows soldering direct to carrier boards
• Drag-and-drop programming using mass storage over USB
• Low-power sleep and dormant modes
• Accurate on-chip clock
• Temperature sensor
• Accelerated integer and floating-point libraries on-chip
The Pico provides minimum of external circuitry to support the RP2040 chip: flash memory,
a crystal, power supplies and decoupling, and USB connector. Four RP2040 I/O are used for
internal functions: driving an LED, on-board switch mode power supply (SMPS) power control,
and sensing the system voltages. The Pico W has an on-board 2.4GHz wireless interface using
The Raspberry Pi Pico 9
802.11n. The antenna is an onboard antenna formed as a resonant cavity by etching away copper
on each layer of the PCB structure. The wireless interface is connected via SPI to the RP2040.
All in all the Raspberry Pi Pico established itself as an immediate realistic option for users of
microcontrollers around the World. This in itself is a difficult thing in a dynamic market saturated
with options.
Pinout
The Pico W has been designed to make available as much of the RP2040 functionality as possible.
Apart from GPIO and ground pins, there are seven other pins on the main 40-pin interface;
• PIN40 VBUS is the micro-USB input voltage, connected to micro-USB port pin 1. This is
nominally 5V.
• PIN39 VSYS is the main system input voltage, which can vary in the allowed range 1.8V to
5.5V.
• PIN37 3V3_EN connects to the on-board SMPS enable pin, and is pulled high (to VSYS) via
a 100kΩ resistor. To disable the 3.3V (which also powers off the RP2040), short this pin low.
• PIN36 3V3 is the main 3.3V supply to RP2040 and its I/O, generated by the on-board SMPS.
This pin can be used to power external circuitry. It is recommended to keep the load on this
pin under 300mA.
• PIN35 ADC_VREF is the ADC power supply (and reference) voltage, and is generated on
Pico W by filtering the 3.3V supply. This pin can be used with an external reference if better
ADC performance is required.
• PIN33 AGND is the ground reference for GPIO26-29. There is a separate analogue ground
plane running under these signals and terminating at this pin. If the ADC is not used or
ADC performance is not critical, this pin can be connected to digital ground.
• PIN30 RUN is the RP2040 enable pin, and has an internal (on-chip) pull-up resistor to 3.3V
of about ∼50kΩ. To reset RP2040, short this pin low.
There is a pdf of the pinout available as an extra when you download the book from Leanpub³.
I recommend at the least printing out page size copy to have on the bench beside you when
working or have it printed to poster size for the wall!
³https://leanpub.com/rpitandt
The Raspberry Pi Pico 10
Powering from the USB connector is by far and away the simplest method, but not always
desirable because of limitations of space or supply types.
If we provide a supply to the VBUS pin our Raspberry Pi Pico can take a voltage of between 1.8
and 5.5V, as it has an internal buck-boost regulator (which can regulate the output to a higher or
lower voltage than its input). This will internally power VSYS via a Schottky diode, but we must
be sure not to connect another power supply to Raspberry Pi Pico’s USB connector at the same
time.
The VSYS pin is the main system power supply on Raspberry Pi Pico. From here the Raspberry
Pi Pico generates its own 3.3V supply which is used to power RP2040, and also the 3V3 output
pin (36). A safe way to add a second power source to Pico W is to feed it into VSYS via another
Schottky diode. This will ‘OR’ the two voltages, allowing the higher of either the external voltage
(or VBUS) to power VSYS, with the diodes preventing either supply from back-powering the
other.
Set up
Setting up our Raspberry Pi Pico for first use is a fairly simple task and I suggest that we should
approach it as an exercise in just getting going without too much of an eye to the future.
By that I mean that we should aim to get up and operating with a running program on the
Pico. We’ll ignore any plans for connecting peripherals or preparing for installing the device
somewhere separate. Our only aim is to get it working and along the way establish how easy it
is. We do this so that we can break down any mystique about the process being difficult. This
way, if we have a problem, we can work through it with a minimum of complexity.
Our aim therefore is to connect our Raspberry Pi Pico install ‘Thonny’ (which is the programming
environment we will use to interact with the Pico) and write a MicroPython program to blink
the onboard LED. This is a pretty common example program and should serve to demonstrate
that we can get things up and running and from there we can think about more complicated
adventures.
Hardware
The hardware requirements are pretty minimal. We will want the following;
• A Raspberry Pi Pico (I will strongly recommend a Pico W and there’s no need to solder any
headers onto the board just yet)
• A computer that can run the Thonny Integrated Development Environment (IDE). Pretty
much all will be able to.
• A micro USB cable to connect between the Pico and the computer
• A 5V micro USB power source (optional, but cool if we want to demonstrate the Pico
running independently from the computer)
Software
The project will guide you through the installation of:
What is Thonny?
Thonny⁴ is a simple Integrated Development Environment (IDE) that is designed to be the logical
interface between you (the programmer) and the Pico. This is the application where your can
⁴https://thonny.org/
Set up 12
write you code, run it and see the output (and any errors!). IDE’s can be incredibly complex
systems that support advanced software development. Thonny is designed for beginners who
want to use Python and as such it will more than adequately serve to get us started. It’s also
Open Source and as such there are few limitations on getting hold of a copy for use.
Install Thonny
To get hold of the software, go to the official Thonny web site and click on the ‘Download’
button. That will list out the different options that you can choose from depending on the type of
computer you are going to be using. Follow the instructions and you will have Thonny installed
in a couple of minutes. Open it up.
Thonny Start
The basic Thonny interface as shown provides us with a code editor in the top section, where
we will write all of your code. The bottom half is our ‘Shell’, where we will see any output when
we run our code.
In the classic manner of programmers everywhere we can test that things are working correctly
by writing a ‘Hello World’ program.
Type the following into the code editor;
print("Hello World")
Hello World
In the shell section of Thonny we should see that the program has run and it has printed out the
phrase ‘Hello World’! Congratulations! You’re a programmer! Although perhaps we shouldn’t
get ahead of ourselves ;-).
To get a feel for how Thonny can help us out, deliberately break your Hello World program by
deleting one of the parenthesis. When we press run again, we should be presented with feedback
in the shell that there in an error in the code and it should even provide some indication of where
in the code it has occurred. Have a bit of a play and see what changes you can make to both break
and expand the code.
What we have been doing above is writing Python code and having it run on our desktop. Now
we’re now ready to move on to the next step and connect our Raspberry Pi Pico to Thonny and
have the code run on the Pico.
MicroPython
What is MicroPython?
MicroPython is a programming language that is an implementation of the core of Python 3 and
includes a small subset of the Python standard library. The simplicity of the Python programming
language makes it an excellent choice for beginners who are new to programming and hardware.
However, in spite of its name, MicroPython is reasonably full-featured and supports most of
Python’s syntax so if you’re comfortable with Python you will be in familiar territory.
MicroPython is optimised for microcontrollers and microcomputers. It is a firmware solution
designed to run in constrained environments while allowing a small subset of standard libraries
into embedded programming.
MicroPython firmware can run in a footprint of 256 Kilobytes and 16 Kilobytes of RAM. The
means we can write clean and simple Python code to control hardware instead of having to use
complex low-level languages like C.
So let’s get started!
This portion of the exercise in getting our Pico working will change over time. Currently
(2022-09-23) the Raspberry Pi Pico W is so new that the firmware (which includes
MicroPython) for it needs to be applied manually (instructions below), but for the
standard Pico, they are nice and automatic (also described below). This means that
these instructions will change as firmware options change. Wherever practical, using
the default firmware would be the preference, but where necessary, don’t be concerned
about applying the firmware manually. It’s really easy to do.
Click on ‘Install’ and once complete we should see the notification in the lower right hand side
of the Thonny application indicating that we are running MicroPython on the Raspberry Pi Pico.
MicroPython 16
Then release the BOOTSEL button. This will make the Pico act like a mass storage device.
⁵https://micropython.org/download/rp2-pico-w/rp2-pico-w-latest.uf2
MicroPython 17
Copy the unstable firmware onto the Pico (just drag it and drop it). Wait for a moment and it
will install itself. Once completed, we should see a very modern version of the firmware noted
in the Thonny Shell.
Updating Firmware
Because the firmware for the Pico will improve over time, it’s generally a good thing to have it’s
firmware updated to the most recent version.
To do this, on the Thonny menu go to Tools >> Options and then select the ‘Interpreter’ tab
Assuming that we have the correct device selected, select the ‘Install or update firmware’ link.
The firmware update dialogue box will open.
Follow the instructions to plug in the Pico while holding the BOOTSEL button. Once the device
information appears (or at the least, the ‘Install’ button isn’t greyed out), click on ‘Install’.
The firmware should be automatically copied from MicroPython.org and installed. I have had
an error occur (‘socket.timeout’) in the past, but I just simply clicked on ‘Install’ again and it
proceeded without problem.
Close the Options dialog box and press the ‘Stop / Reset’ button on Thonny and we should see
our new version of MicroPython displayed at the bottom of the Shell.
In MicroPython (and Python), modules are just files with the ‘.py’ extension containing
other MicroPython code that can be imported inside another MicroPython Program.
We can consider a module to be the same as a code library or a file that contains a set
of functions that we want to include in our application. They act as a mechanism to
simplify code and to make common functions modular (hence ‘module’).
MicroPython 19
We can create a machine.Pin object to correspond with the on-board LED, which, on the Pico W
can be accessed using the reference LED in code.
The LED on the original Pico corresponds with GPIO pin 25, but this was changed to be
connected to one of the GPIO pins from the wireless chip (CYW43439) on the Pico W.
You will see tutorials mention GPIO 25 for the Pico, but LED as the designator in code on
the Pico W. All of our examples will be using the Pico W, but if you want to adapt any
of the code samples for the Pico, just change ‘LED’ for 25.
If we then press the ‘Run’ icon, a dialog box will come up asking where we want to save our
code. This time we’re going to save it to the Pico.
Give the code an appropriate name like led.py and save it. It’s important that we use the file
extension ‘py’ as this is what will help the Pico determine how to operate the file.
We should now see the on-board LED light up! Our code has had an effect on the physical world!!!
Edit the code to set led.value to 0 and press the run icon again’ This should turn the LED off.
Turn the LED on and off as many times as you like. Go on. You deserve it :-).
But really… That’s a pretty manual process right? Time to automate!
while (True):
led.toggle()
time.sleep(.2)
Click the Run button to run/save your code. Again, save onto the Pico and a file name like
blink.py seems appropriate
We should see the on-board LED turn on and off until we click the Stop button.
Now we’re really starting to cook. But we can do better! Let’s make the led start blinking
automatically whenever the Pico is powered on.
Off course the Dupont connector is just one half of a connector pair. The most common mating
platform for them is to a header pin. A header pin (or simply a header) is a form of electrical
connector. A male pin header consists of one or more rows of metal pins molded into a plastic
base, 2.54 mm (0.1 in) apart.
Connectivity 22
Header Pins
These can be straight, angled, single-in-line, dual-in-line and a myriad of other options.
The Dupont connector slips directly onto a header pin and because they share the same pitch
(distance apart of the pins) of 2.54mm they can be similarly ganged together in a myriad of ways.
By far and away the simplest method of utilising this method of connectivity is to purchase bulk
lots of the pre-made connectors. These can be male or female and commonly come joined to
what is called ‘Rainbow Cable’.
These are incredibly cheap and unless you have a very specific length that is required for a
project, they are so easy to use they will quickly become ubiquitous for your project work.
Connectivity 23
Re-using Connectors
One of the cool things about Dupont connectors is that they can be adjusted by slipping the
internal metal connectors out of their casings and placed into new casings. So if you have a set
of cables in a three way connector, but the header that you want to connect to doesn’t have the
connection points directly beside each other, not problem. Just use a small, flat bladed jewellers
screwdriver or similar to gently bend up the plastic flap that is keeping the connector shroud
in place. You can then slip the internal wire and connector out of the black plastic housing and
place it into three separate single housings. Easy peasy.
The antenna is a tuned cavity design which is licensed from ABRACON (formerly ProAnt).
The wireless interface is connected via a Serial Peripheral Interface (SPI) to the RP2040
microcontroller.
It’s possible to use a standard Pico connected to an ESP8266 or similar to enable WiFi connectivity,
but in enabling this I found there was more heartache than I cared to endure. With the release of
the Pico W with WiFi built in, this should be the go-to option for connecting to a WiFi network
if you’re using a Pico.
Connectivity 24
As an example of how the network module provides access consider the following code;
import network
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
print(wlan.scan())
When run on a Raspberry Pi Pico W it will enable the network interface, scan for wireless
networks and print them out
The import network line imports the network module.
wlan = network.WLAN(network.STA_IF) creates a WLAN network interface object with a client
interface type (as opposed to an access point type, which would use network.AP_IF) .
We then activate the network interface with wlan.active(True).
Lastly we print out the results of a scan of available wireless networks with print(wlan.scan()).
The type of output that we would see might look something like the following;
Here there are three access points returned with the information apparently separated as ssid,
bssid, channel, RSSI, security and hidden. Although, I’ll be honest and say that I know some of
those networks and some of those ‘security’ and ‘hidden’ values don’t appear to be correct. More
research could be required here.
‘bssid’ is the hardware address of an access point, in binary form, returned as a bytes object.
We could use binascii.hexlify() to convert it to ASCII form if we got excited (we will do this in a
future project collecting data from temperature sensors).
There are five values for security:
• 0 – open
• 1 – WEP
• 2 – WPA-PSK
• 3 – WPA2-PSK
• 4 – WPA/WPA2-PSK
Connectivity 25
• 0 – visible
• 1 – hidden
Serving up a web page is well within the Pico W’s capabilities. To do this we will need to active
the network interface, connect to a wireless network and then create an http server with a socket
connection and then listen for connections and serve up an HTML page.
This is definitely a more complicated process and we are going to make it slightly more so by
using two external files to our main.py file. Don’t worry, there’s good reasons for doing so.
Firstly, create a file called secrets.py on the Pico with contents as follows;
secrets = {
'ssid': 'Replace-with-WiFi-ssid',
'pw': 'Replace-with-WiFi-Password'
}
Edit the file and put the name of the network (ssid) that you’re going to connect to and it’s
password in the appropriate places. We are doing this so that when we write our main code, we
don’t have to expose things that we would rather not when and if we share our main code.
Next create a file with the contents below and save it as index.html. This will be the web page
that we will go to when connecting to the Pico W via the network.
<!DOCTYPE html>
<html>
<head>
<title>Pico W</title>
</head>
<body>
<h1>Pico W</h1>
<p>This is a very simple web page.</p>
<p>REALLY simple.</p>
</body>
</html>
Lastly, create the following file on the Pico and call it main.py. This will allow it to automatically
start when the Pico is connected to power.
Connectivity 26
import rp2
import network
import machine
import time
import socket
from secrets import secrets
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, pw)
wlan_status = wlan.status()
if wlan_status != 3:
raise RuntimeError('Wi-Fi connection failed')
else:
print('Connected')
status = wlan.ifconfig()
print('ip = ' + status[0])
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(1)
response = get_html('index.html')
cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
cl.send(response)
cl.close()
Now run the main.py program. We should see feedback from the shell showing us the connection
process and it should include the ip address of the Pico.
Connected
ip = 10.1.1.41
Listening on ('0.0.0.0', 80)
Put the IP address that appears in the shell into a browser that’s on our network and we should
see a page giving us the happy news that we have created a web page!
At the same time we should see an indication in the Shell of the connection requests coming in
each time we refresh the page.
Connected
ip = 10.1.1.41
Listening on ('0.0.0.0', 80)
Client connected from ('10.1.1.99', 57142)
Client connected from ('10.1.1.99', 57144)
Client connected from ('10.1.1.99', 57145)
There we go. We have just turned our Pico W into a web server! Not only that, but because we
have named our program that does it main.py it will operate as soon as we plug in power.
Connectivity 28
Enabling network access to the Pico is a really useful thing. This has allowed us to access our
device from a separate computer. But when we did it we relied on knowing what the IP address
of the Pico was in order to enter that into our browser. The allocation of the address is dynamic
and will be dependant on the configuration of our wireless network that is set up by our router.
However, we can set up that address so that we know what it is going to be before hand. This is
what is called a static IP address.
An Internet Protocol address (IP address) is a numerical label assigned to each device (e.g.,
computer, printer) participating in a computer network that uses the Internet Protocol for
communication.
This description of setting up a static IP address makes the assumption that we have a device
running on our network that is assigning IP addresses as required. This sounds complicated, but
in fact it is a very common service to be running on even a small home network and most likely
on an ADSL modem/router or similar. This function is run as a service called DHCP⁶ (Dynamic
Host Configuration Protocol). You will need to have access to this device for the purposes of
knowing what the allowable ranges are for a static IP address.
The Netmask
A common feature for home modems and routers that run DHCP devices is to allow the user to
set up the range of allowable network addresses that can exist on the network. At a higher level
we should be able to set a ‘netmask’ which will do the job for us. A netmask looks similar to an
IP address, but it allows you to specify the range of addresses for ‘hosts’ (in our case computers)
that can be connected to the network.
A very common netmask is 255.255.255.0 which means that the network in question can have
any one of the combinations where the final number in the IP address varies. In other words
with a netmask of 255.255.255.0, the IP addresses available for devices on the network ‘10.1.1.x’
range from 10.1.1.0 to 10.1.1.255 or in other words any one of 256 unique addresses.
The other service that our DHCP server will allow is the setting of a range of addresses that can
be assigned dynamically. In other words we will be able to declare that the range from 10.1.1.20
to 10.1.1.255 can be dynamically assigned which leaves 10.1.1.0 to 10.1.1.19 which can be set as
static addresses.
Because there are a huge range of different DHCP servers being run on different home networks,
I will have to leave you with those descriptions and the advice to consult your devices manual to
help you find an IP address that can be assigned as a static address. Make sure that the assigned
number has not already been taken by another device. In a perfect world we would hold a list of
any devices which have static addresses so that our Pico’s address does not clash with any other
device.
⁶http://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol
Connectivity 29
Be aware that if you don’t have a section of your IP address range set aside for static
addresses you run the risk of having the DHCP service unwittingly assign a device that
wants a dynamic address with the same value that you have already assigned for your
Pico. Such a conflict is not a good thing.
For the sake of this exercise we will assume that the address 10.1.1.110 is available.
Default Gateway
We will also need to find out what the default gateway is for our network. A default gateway is
an IP address that a device (typically a router) will use when it is asked to go to an address that it
doesn’t immediately recognise. This would most commonly occur when a computer on a home
network wants to contact a computer on the Internet. The default gateway is therefore typically
the address of the modem / router on your home network.
We can check to find out what our default gateway is from Windows by going to the command
prompt (Start > Accessories > Command Prompt) and typing;
ipconfig
This should present a range of information including a section that looks a little like the following;
Our network module and our WLAN class include an ifconfig method that uses all our gathered
information’. We will need to declare it in the format wlan.ifconfig([(ip, subnet, gateway,
dns)]).
When called with no arguments, this method returns a 4-tuple with the above information. To
set the above values, pass a 4-tuple with the required information. For example:
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
wlan.ifconfig(('10.1.1.110','255.255.255.0','10.1.1.1','8.8.8.8'))
Give it a try!
The observant reader will notice that the numbers above seem a little out of whack with the
numbered pins on the Pico pinout. Here they go from zero to 28, but numbers 23, 24 and 25 are
missing. Hence 26 total are exposed to the header pins.
The three missing pins (23, 24 and 25) in the sequence along with GPIO29 are used for internal
board functions;
On the original Pico those functions are;
The exposed GPIO pins can be utilised as inputs or outputs for a range of different protocols and
functions. These are in turn configured and enabled in software and can include;
Whatever we call them the important fact is that the signal level can be distinguished between
two different states.
This means that when we want to register when a high voltage occurs on a pin, first we need
to know what the low voltage is. And visa versa. If we are looking to try and read when a pin
drops to ground, first the pin needs to be able to recognise the 3.3V is the high point.
We accomplish this feat of knowing our reference points by using pull-up and pull-down resistors
on our Pico.
Connectivity 32
The answer to the question is kind of in the name, but that doesn’t necessarily make it obvious.
It’s also really useful to frame the question by using an example, and in our case (since this is
being written for a book about the Raspberry Pi Pico) we can use a GPIO pin on the Pico as our
case study.
We like to talk about our ability to read either a high or low signal on our GPIO inputs, but the
reality is that there are three states that our voltage measuring effort could result in. A low state
that will typically be ground or 0V, a high state that will typically be about 3.3V, and a mysterious
third state which is ‘floating’.
We can illustrate this by attempting to measure the voltages on our Pico.
Using a meter to measure the voltage on our Pico, we can first ensure that there is a common
reference point set by connecting our negative probe to a ground pin (here pin 23) and we can
then measure the amount of voltage or potential difference is present between that ground pin
and another (pin 33 shown here).
Reading Ground
Unsurprisingly, we should read 0V. This is because the two ground pins are connected together
on the circuit board and represent exactly the same voltage. Therefore the difference between
them is 0V.
With our voltmeter measuring the difference between our common reference point of ground
and the 3V3(OUT) pin (36), we are naturally going to read a voltage of 3.3V.
Connectivity 33
Reading 3.3V
Again unsurprisingly we see 3.3V because there is a potential difference between the ground pin
(23) and the 3V3(OUT) pin (36) of 3.3V
And now to our ‘floating’ state. with our red lead removed from our Pico and floating in mid
air, we have an uncertain reading on our voltmeter
This situation is a bit like Schroedinger’s cat in the quantum state analogy. We can’t confirm if
it’s alive or dead, so it’s both simultaneously. Except in this case, the reading is uncertain and
we cannot state what it will be since there the meter is not fully connected to the circuit.
This floating state is the initial configuration of our GPIO pins on the Raspberry Pi Pico. Each
one of them is essentially disconnected and as a result we can’t expect to read a steady voltage
off them.
So to set our GPIO pins to a state where they have a reliable reference voltage we need to ‘pull’
the voltage either up or down so that in a resting state they read high or low.
A pull-up or pull-down resistor is connected so that the GPIO pin is connected to either ground
or 3.3V via a resistance.
Connectivity 34
In some circuits this might be necessary to implement using discrete components, but the RP2040
microcontroller can configure a GPIO pin as either pull-up or pull-down internally and we just
need to instruct it to do so via software. The resistance value in the pico is specified as being
between a minimum of 50kΩ and a maximum of 80kΩ.
For example, in the PIR section of this book we set up our GPIO pin as an input and then we
configured the pin as pull-down via the following code;
We can test our thinking by considering reading the voltage at our nominal GPIO pin with the
Pin.PULL_DOWN configuration set. That will read 0V.
Reading Ground
Conversely with the GPIO pin set to Pin.PULL_UP we will have the following circuit where we
will read a voltage of 3.3V.
Connectivity 35
Reading Ground
Once we have set the GPIO pin to its default state of high or low we can then go about the job
of varying that pin to the alternate state via whatever input we choose. For example via a PIR
or a common switch.
Connectivity 36
Right from the outset, this does not mean that we can hook up multiple devices (or string of
devices) to multiple connection points that use the same controller. One device or string of devices
per controller only. That means that we are limited to a total of 127 daisy chained devices per
controller. That should be enough for a start :-).
I2C (Inter-Integrated Circuit) is a serial communication protocol that allows devices to communi-
cate with each other over a shared bus. It was developed in the 1980s by Philips Semiconductors
as a way to reduce the number of wires needed to connect devices, and has become widely used
in a variety of applications.
I2C uses a two-wire interface, consisting of a serial data line (SDA) and a serial clock line
(SCL). Devices that use I2C are called ‘slaves’, and they are connected to a ‘master’ device
(usually a microcontroller or processor) through these two wires. The master device controls
the communication by generating the clock signal and sending/receiving data on the SDA line.
I2C is a very useful protocol because it allows multiple devices to communicate with a single
microcontroller or processor using just two wires, making it well suited for use in embedded
systems and other applications where space and resources are limited. It is also relatively simple
to implement, making it a popular choice for many applications.
• The master device starts the communication by pulling the SDA line low while the SCL
line is high and then pulling the SCL line low as well, indicating the start of a new data
transfer.
Listen up everyone!
• The master device then sends a 7-bit slave address followed by a read/write bit, indicating
whether it wants to read data from or write data to the slave device.
• If the slave device recognizes its own address, it sends an acknowledgement (ACK) by
pulling the SDA line low. If the slave device does not recognize its own address, it does
not send an ACK and the communication ends.
Connectivity 38
I’m here!
• If the master device wants to write data to the slave device, it sends the data byte by byte,
followed by an ACK from the slave device after each byte. If the master device wants to
read data from the slave device, it sends a request for data and the slave device responds
by sending a byte of data followed by an ACK from the master device.
• The communication ends when the master device sends a stop condition by pulling the
SDA line high while the SCL line is high.
import machine
# Create an I2C object with the specified SDA and SCL pins
i2c = machine.I2C(sda=machine.Pin(22), scl=machine.Pin(23))
This code creates an I2C object using the SDA and SCL pins 22 and 23, respectively, and uses the
scan() method to detect slave devices on the I2C bus. It then prints the addresses of the detected
devices.
Connectivity 41
• The master asserts the chip select signal for the slave device it wants to communicate with.
• The master sends a command or data over the MOSI line to the slave.
• The slave receives the data and sends a response over the MISO line to the master.
• The master receives the response and de-asserts the chip select signal, signalling the end of
the communication cycle.
This process can be repeated multiple times to transfer multiple bytes of data. The clock signal
determines the speed of communication, with higher clock speeds allowing for faster data
transfer.
SPI is similar in function to I2C (Inter-Integrated Circuit), but where SPI is at a disadvantage in
terms of the number of wires used and a more limited number of connected devices, it has the
advantage of much higher data transfer speeds.
Connectivity 42
Looking at the pinout for the Pico we can see that there are three chip select connectors for SPI0
and two for SPI1.
In fairness, it is possible to use the process known as ‘bit-banging’ to simulate an SPI interface on
any GPIO pin, but we will need to take into account a slower speed and increased susceptibility
to noise.
Reed Switches with the Raspberry Pi
Pico
Reading the state of a switch is a pretty basic function for a microcontroller, but it’s an action
that is worth understanding and we can add a little bit of spice to it by using a switch that
is commonly used in security systems and to detect the state of items that need to determine
whether they are open or closed (fridges, laptops, washing machines).
Pull-up or pull-down?
The main driver for the connection type is understanding what the switch is going to be
switching. This might be dictated if you’re using a particular type of sensor, but for standard
switch, we have two choices depending on whether we want to connect our switch to the 3.3V
connection or the ground (the other end of the switch will be going to the GPIO pin).
For a deeper explanation of how pull-up and pull-down resistors are selected and implemented
in a circuit or a Pico, check out the section on pull-up and pull-down setting of GPIO pins earlier
in the book.
If we’re connecting a simple switch, either method is as good as the other. However, I would
normally opt for the pull-up option on the basis that it is easier to find a spare ground connector
than it is to try and connect to the sole 3.3V pin.
Reed Switches with the Raspberry Pi Pico 45
With that in mind, the example that follows will configure our GPIO pin with a pull-up resistor
and we will connect our reed switch between a ground pin (we’ll go for pin 23) and GPIO 22 (pin
29).
Code
The code below will designate the GPIO pin to be used as our input (GPIO22) and set it to a
default high state with a pull up resistor.
We will also need to set the GPIO pin to be an input (seen above as Pin.IN). Once set to input, the
GPIO line is high impedance so it won’t draw very much current, no matter what we connect it
to.
Our method to read the state of the switch is via the switch.value() parameter. Normally our
GPIO pin will be high since our switch is a ‘normally open’ switch, but once the magnet is
moved close to the main body of the reed switch, the mechanism closes, the switch is made and
the GPIO pin is then connected to ground and reads 0V.
Just to make things interesting, it increments a counter and loops repeatedly incrementing when
the switch is closed.
Reed Switches with the Raspberry Pi Pico 46
import time
from machine import Pin
time.sleep(1)
print('Ready to switch!')
while True:
if switch.value() != 1:
count = count + 1
print('Switch closed ', count)
time.sleep(4)
time.sleep(1)
Controlling a Servo from the
Raspberry Pi Pico
What is a Servo Motor?
Servo motors are types of motors that have been designed to rotate precisely in response to
control signals. They are commonly used in applications such as robotics and remote control
vehicles to provide a specific angle or distance of movement.
They are rated in kg/cm (kilogram per centimetre) which translates as how much weight the
servo motor can lift at a particular distance. For example: A 5kg/cm servo motor should be able
to lift 5kg when its load is suspended 1cm from the motors shaft, the greater the distance of the
weight from the shaft, the less the weight lifting capacity.
A typical servo motor will only turn 90° in either direction and will have a mechanical stop to
prevent further movement.
When a servos is commanded to move, it will move to the position specified by the pulse width
and hold that position. The maximum amount of force the servo can exert is called the torque
rating of the servo. As an example of this force, when we are running our code below and the
servo motor is connected and powered on, try (gently) to move the arm. There should be a
reasonable resistance. If you disconnect the power the servo can be moved relatively easily.
Controlling a Servo from the Raspberry Pi Pico 49
• The red wire (VCC) to the VBUS pin (40) on the Pico (this makes the assumption that we are
powering our Pico from a source connected to the micro USB connector with the standard
5V applied)
• The brown wire (Ground) to the ground pin (38) on the Pico
• The orange wire (the PWM signal) to GP28 (pin 34) on the Pico
All of the GP pins on the Pico can be used for pulse width modulation control. This is because
the RP2040 has 8 identical PWM ‘slices’, each with two output channels (A/B), where the B pin
can also be used as an input for frequency / duty cycle measurement. This means that each slice
can drive two PWM output signals, or measure the frequency / duty cycle of an input signal.
This provides a total of up to 16 controllable PWM outputs.
Since all of the GP pins can be driven by the PWM block, it’s just a matter of selecting which
one you would like to use.
I selected GP28 (pin 34) for no better reason than it made drawing the connection diagram
prettier!
While the SG90 comes with it’s wires terminated in a 3 way du-pont connector, in order to make
the connection to our header pins simple, replace the 3 way connector with three single pins.
Controlling a Servo from the Raspberry Pi Pico 50
Code
The code below will designate the GP pin to be used as an output (GP28) and the frequency of
the signal (50Hz (which equates to a period of 20ms)).
The aim is to sweep the servo through an arc of 180 degrees and then back again.
We then use two while loops to move the servo from a pulse width of 0.52ms to 2.6ms and then
back again in an endless loop. Now, I’ll be the first to admit that this does not equate with the
expected values of 1ms and 2ms. I have selected the values in the code, because that’s what
roughly equates to the -90 and +90 degrees of movement. Why is it not nore accurate? Good
question. I tried a few iterations including using duty_u16 (from 3277 to 6554) instead of duty_ns,
but it still didn’t seem to work accurately. I suppose my takeaway is that the servo is particularly
cheap and maybe I got what I paid for. Irrespective, it was possible to manually calibrate the
servo to discover appropriate values.
import time
from machine import Pin, PWM
pwm = PWM(Pin(28))
pwm.freq(50)
while True:
for pulse in range(520,2600,10):
pwm.duty_ns(pulse*1000)
time.sleep_ms(5)
for pulse in range(2600,520,-10):
pwm.duty_ns(pulse*1000)
time.sleep_ms(5)
Controlling a Servo from the Raspberry Pi Pico 51
Warning
I initially operated the servo in it’s sweeping motion while my cat was nearby. She quickly took
an interest and had a bit of a play with it.
Later I heard a strange noise from the office and found her up on the desk investigating it some
more. I have since found that I can operate it and she will quickly come and investigate what’s
happening. In short, she’s obsessed. I now have to keep the office door shut.
So fair warning.
Controlling a Motor with the
Raspberry Pi Pico
Being able to control a motor takes the principles of cross-over from a computing world to the
physical world into a different dimension. Almost literally, because it provides the mechanism
to induce movement and effect into the environment. Little wonder then at the excitement of
building a robot or driving a tracked vehicle since it represents a direct engagement into the way
that we (as humans) also interact with the world.
Motor
Any list of requirements should start with the thing that will drive subsequent selection criteria.
In this case we should start with the motor. We will use what are commonly called ‘TT Motors’.
These are a simple combination of motor and gearbox in a plastic housing. They may or may not
come with wires attached, and possibly a wheel(!) so select as appropriate. We will start with a
specification for using two, since ultimately it would be good to use our project to build a device
with the ability to drive two independent motors for direction control of a vehicle or similar.
TT Motor
These motors can be operated with anywhere from 3v to 12V although the recommended range
is from 3V to 9V. With 6V applied they will rotate at approximately 200rpm drawing 200mA. This
is all assuming a ‘no-load’ condition where the motor is just turning it’s own shaft and not being
put under strain (like driving a vehicle). When put under a load that resists the turning force of
the motor it will draw up to 1.5A (at 6V) when forced to to a stall (stop).
Power
Running a motor takes a reasonable amount of current. Our Raspberry Pi Pico is not designed
to pass significant amounts of current through it. The possible sources of electrical power from
a Pico would be via either the VBUS (40) or 3V3 (36) pins.
• VBUS represent the micro-USB input voltage, connected to micro-USB port pin 1. This is
nominally 5V, and the amount of current will be limited to the connected supply. While this
might technically be a possible source, for power for a motor, it would only be suitable in
situations where you were super careful about the type of motor and the USB power supply
used. I wouldn’t do it and I don’t recommend it.
• The other option is from the 3V3 pin which is also the main 3.3V supply to RP2040. This
pin can be used to power external circuitry, but the maximum output current that it can
supply should be kept under 300mA. This is not realistically enough to power even a tiny
DC motor.
Controlling a Motor with the Raspberry Pi Pico 54
In short, we won’t be taking the power supply for our motor from our Raspberry Pi Pico.
The sensible source for our power will be from an external battery or dedicated DC power supply.
For the sake of simplicity we will use a simple 4 AA cell battery pack. This will be able to supply
a voltage close to our 6V nominal identified for the motors. It should be able to supply over 1A,
although running it under that much load for an extended period will reduce the voltage quickly)
4 AA Battery Pack
If we were making this a stand-alone project (for a robot or vehicle), we would probably use this
as the source for the power for the Pico as well (with a suitable regulator). Likewise, if this was
for a project that was intending to be used a lot, we should consider some form of rechargeable
option.
Controller / Driver
So from our requirements gathering process above we want to have a motor driver / controller
that can supply two motors with a peak output of 1.5A per motor (just in case), it should be able
to accept 6V input and in an ideal world it would act as a supply for our Pico with 5V out.
With these requirements in mind I have selected the Maker-Drive board from Cytron⁷. It meets
our requirements nicely and is very reasonably priced.
Types of Motion
There are two objectives that we will want to achieve with our motor control. The first will be
simple movement forwards, backwards and stopped. The second will be to adjust the speed of
any movement.
Simple movement
The simplest form of control is making our motor turn clockwise, anticlockwise or to have it
stop.
Nost DC motor controllers will use two control signals to accomplish this. Either control signal
can be high or low and therefore, any combination of the two will provide us with with four
different signal combinations.
As we can see form the table above, whenever any of the two signals are both high or both low
the motor will be stopped and whenever the signals are different it will be rotating.
Variable Speed
To vary the speed of our motor we need to apply Pulse Width Modulation (PWM) to our control
signals. When the motor is turning in the simple example above, one of the control signals is set
low and the other high. To vary the speed we can ‘pulse’ (modulate) the high pin off and on very
quickly so that when combined with a smoothing effect, the voltage will appear reduced to the
motor. The way that we will control this voltage is by varying the ‘duty cycle’ of the pulses.
The duty cycle of a signal is commonly expressed at the ratio of the time that the signal is high
compared to the total period of one cycle. Thus a 70% duty cycle means the signal is on 70% of
the time but off 30% of the time.
Duty Cycle
Controlling a Motor with the Raspberry Pi Pico 56
In MicroPython (as we will see in the code that follows) the duty cycle can be set as a parameter
called duty_u16 where it varies between 0 (0% duty cycle) to 65535 (100% duty cycle).
Motor Connection
The connection above is assuming that we still have our Pico connected via USB to our computer
for programming and testing. If we get to a point where we want to operate the Pico and the
entire ensemble independently, we can also connect the Maker Drive 5VO pin to the VBUS pin
(40) on the Pico and the Pico will take it’s power from the Maker Drive board which is in turn
taking its power from the battery pack.
While the higher current connections to the Maker Drive board will go to the screw terminals,
the connections that carry lower power (like the signalling) can be connected using Dupont
connectors. A practical example of that can be seen below.
Controlling a Motor with the Raspberry Pi Pico 57
Code
The two different approaches to controlling the motors are outlined below.
import time
from machine import Pin
# Forward
motor1a.high()
motor1b.low()
time.sleep(2)
# Backward
motor1a.low()
motor1b.high()
Controlling a Motor with the Raspberry Pi Pico 58
time.sleep(2)
# Stop
motor1a.low()
motor1b.low()
import time
from machine import Pin, PWM
pwm_motor1b = PWM(Pin(5))
pwm_motor1b.freq(50)
# 1/4 speed
motor1a.low()
pwm_motor1b.duty_u16(16383)
time.sleep(2)
# 1/2 Speed
motor1a.low()
pwm_motor1b.duty_u16(32767)
time.sleep(2)
# FULL SPEED!!!!
motor1a.low()
pwm_motor1b.duty_u16(65535)
time.sleep(2)
# Stop
motor1a.low()
pwm_motor1b.duty_u16(0)
Using a Stepper Motor with a
Raspberry Pi Pico
The Stepper Motor
A stepper motor is a type of electric motor that rotates in precise increments or ‘steps’ when
electrical commands are applied. Stepper motors are commonly used in applications where
precise positioning is required, such as in printers, scanners, and 3D printers.
Stepper motors can be classified as either unipolar or bipolar. Unipolar stepper motors have a
single winding per phase and require a special type of drive circuit, while bipolar stepper motors
have two windings per phase and can be driven with a more common H-bridge drive circuit.
Stepper motors operate by energizing the windings in a specific sequence, causing the rotor to
rotate a precise number of degrees per step. The number of steps per revolution and the step
angle (the angle of rotation per step) are typically specified by the manufacturer. Stepper motors
can be controlled with a microcontroller or other type of controller, such as a motor driver or
stepper motor driver.
There are several key differences between a stepper motor and a normal (also known as a brushed
or brushless DC) motor:
• Precise positioning: Stepper motors are capable of more accurate positioning because they
rotate in precise increments or “steps” when electrical commands are applied. Normal
motors, on the other hand, are not capable of precise positioning because they rotate
continuously when powered.
Using a Stepper Motor with a Raspberry Pi Pico 60
• Torque: Stepper motors typically have less continuous torque than normal motors, but they
can deliver high torque in short bursts. Normal motors, on the other hand, can deliver a
more consistent level of torque over a longer period of time.
• Speed: Stepper motors have a limited speed range, typically up to several hundred RPM,
depending on the model. Normal motors, on the other hand, can reach higher speeds and
have a wider speed range.
• Control: Stepper motors can be controlled with a microcontroller or other type of controller,
such as a motor driver or stepper motor driver. Normal motors typically require a more
complex drive circuit, such as an H-bridge, to control the speed and direction of rotation.
• Cost: Stepper motors tend to be more expensive than normal motors of similar size and
power due to their precision and control capabilities.
• The controller sends electrical commands to energize the windings in a specific sequence.
For example, the sequence may be to energize winding A, then winding B, then winding
C, and finally winding D.
• When the windings are energized, the magnetic fields generated by the windings interact
with the magnetic field of the permanent magnets in the rotor, causing the rotor to rotate
a certain number of degrees.
• The controller sends the next set of electrical commands to energize the windings in the
next sequence. For example, the sequence may be to energize winding C, then winding D,
then winding A, and finally winding B.
• The process repeats, with the controller sending electrical commands to energize the
windings in the specified sequence, causing the rotor to rotate a certain number of degrees
each time. This results in the stepper motor turning in a precise, step-by-step manner.
The specific sequence of the windings being energized depends on the type of stepper motor
(unipolar or bipolar) and the specific drive circuit being used.
Using a Stepper Motor with a Raspberry Pi Pico 61
Torque
Rated torque is a measure of the maximum torque that a stepper motor can produce under
specified conditions. It is typically expressed in units of force times distance, such as Newton-
meters (Nm) or ounce-inches (oz-in).
Rated torque determines the amount of force that the motor can generate to move a load. In
general, a stepper motor with a higher rated torque will be able to move a larger or heavier load
than a motor with a lower rated torque.
It is important to note that the rated torque of a stepper motor is not constant and can vary
depending on factors such as operating voltage, current, speed, and temperature. In general, the
rated torque of a stepper motor decreases as the speed increases, and it may also be affected by
the type and size of the load being driven.
The 28BYJ-48
The 28BYJ-48 is a small, low-cost stepper motor commonly used in hobbyist and educational
projects. It has a 5V operating voltage and is driven by a ULN2003A driver board (or similar
stepper motor driver).
The 28BYJ-48 has a step angle of 5.625 degrees and a step resolution of 64 steps per revolution,
resulting in a total of 4096 steps per revolution. It has a rated torque of 44.4 g-cm (0.6 oz-in) and
a maximum no-load speed of approximately 15 RPM.
The 28BYJ-48 is a unipolar stepper motor, meaning it has a single winding per phase and requires
a special type of drive circuit to operate (hence the use of the ULN2003A driver board). It is
commonly used in applications such as small robotics, educational models, and DIY projects.
The 28BYJ-48 rotates by sending signals to its four coils in a specific sequence that switches the
coils on and off in a pattern that creates a magnetic field that rotates the motor. Those four coils
are controlled from four GPIO pins on the Pico. In our case we will use GPIO18, 19,20 and 21.
For no reason other than they make drawing the circuit diagram below slightly prettier.
The connections are as follows;
Code
The following code demonstrates the stepper motor turning in one direction;
IN1 = Pin(18,Pin.OUT)
IN2 = Pin(19,Pin.OUT)
IN3 = Pin(20,Pin.OUT)
IN4 = Pin(21,Pin.OUT)
sequence = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]
while True:
for step in sequence:
for i in range(len(pins)):
pins[i].value(step[i])
sleep(0.001)
Using a Stepper Motor with a Raspberry Pi Pico 63
The following code allows the motor to turn in different directions through a function call;
import time
from machine import Pin
# Main loop
while True:
# Move the stepper motor forward
move_stepper('forward', 400)
In turn they will be connected to one of the sets of SPI connections on the Raspberry Pi Pico. In
our case we will use controller 0 (the pico has two controllers and four sets of possible connections
to the GPIO pins) and GPIO pins 16 - 19
SD Card Connection
SD Card Connection
Code
In a new new document, enter the following code:
Connecting an SD Card to the Raspberry Pi Pico 67
import machine
import sdcard
import os
# Mount filesystem
vfs = os.VfsFat(sd)
os.mount(vfs, "/sd")
Make sure you have the SD card inserted into the breakout board and click the Run button. You
should see the contents of the file that is created (sdtest.txt) printed out in the shell. We can
go one step further and eject the SD card from the adapter and plug it into our desktop machine
where we can browse to the file and read it from the computer.
Our code above has the feature of creating the file sdtest.txt when it writes to it. This also
means that it will overwrite the file every time it is run. If we want to append information to an
already existing file we can use an a instead of a w. Something similar to the below would do the
trick placed between the write and read blocks;
Connecting an SD Card to the Raspberry Pi Pico 68
If we were utilising the SD card as a store for a data logging application, we would be appending
information.
Bonus Connection!
If you’re keen to DIY you can solder header pins to a more traditional SD to Micro SD card
adapter and connect that up to our Pico. I was a little sceptical before trying it out, but it worked
like a charm.
With that in mind, here is a connection diagram for when you have mastered your soldering
skills.
The Sensor
The MQ-2 is a commonly used gas sensor in MQ sensor series. It is what’s referred to as a
Chemiresistor⁹ as the detection is based upon change of resistance of the sensing material when
the gas comes in contact with a Metal Oxide Semiconductor (MOS). The value of the analog
signal output varies as the gas concentration varies.
Different metal oxides have different chemiresistive properties allowing them to sense different
gasses.
The most obvious feature of the sensor is the surrounding layer (actually two layers) of stainless
steel mesh called an ‘anti-explosion network’. This is present to make sure that the heater element
inside the sensor doesn’t cause an explosion while it is in the presence of flammable gasses. It
also acts as a filter to allow only gases to pass through to the sensor.
The MQ-2 sensor which we will be using can detect LPG, butane, propane, methane, alcohol,
Hydrogen and smoke concentrations from 200 to 10000ppm.
⁹https://en.wikipedia.org/wiki/Chemiresistor
Connecting MQ Series Gas Detectors to the Pico 70
There are a wide range of sensors in the MQ series that can detect the presence of different gasses.
The sensor we will be using is mounted on a circuit board for ease of connection. We provide
it with a 3.3VDC supply and it returns an analog signal that varies in proportion to the
concentration of our target gas. That signal can vary between 0VDC and 3.3VDC. The board
also includes a digital output option, but this is designed to provide a breakpoint level of gas,
rather than a value. The variable resistor (potentiometer) on the board allows this breakpoint to
be varied.
• VCC: Is the power input. This will require 3.3VDC in our case, but it can actually accept
anywhere from 2.5VDC to 5VDC)
• GND: Is the ground pin
• DO: Provides the digital output set by the potentiometer
• AO: is the analog output signal.
It is this analog voltage that is then digitised with our Analog to Digital Converter (ADC) that
is built into the microcontroller.
Connect Everything Up
We will want to connect;
The power and ground pins are fairly self explanatory, and because the MQ-2 has an analog
output that will vary from the applied voltage to 0V, we will apply this to one of our Analog to
Digital Converter (ADC) pins. In this case ADC0 on pin 31.
Connecting MQ Series Gas Detectors to the Pico 71
Connecting the sensor practically can be achieved in a number of ways. But because the
connection is relatively simple we can build a minimal configuration that will plug directly onto
the pins using Dupont header connectors and jumper wire.
Code
The following code will read the value from the MQ-2 and convert that to the effective voltage
that should be present on the analog pin. It will print this out every second.
import machine
import time
mq2 = machine.ADC(26)
conversion_factor = 3.3 / (65535)
while True:
voltage = mq2.read_u16() * conversion_factor
print("Output voltage is",voltage)
time.sleep(1)
Distance Measurement using Time
of Flight Sensor
What is a Time Of Flight Sensor?
A Time of Flight (ToF) sensor measures the time it takes for a signal to travel a distance through
a medium. This is a deliberately broad definition since there are different ways to carry this out
depending on the application. For the purposes of our explanation we are going to be describing
a sensor that measures the time elapsed between the emission of a pulse of light, its reflection
off an object, and its return to the ToF sensor.
In this case, the sensor itself is an extremely compact device that is popular for applications in
robotics and cameras.
The sensor we will be using is a VL53L0X Time-of-Flight laser-ranging module which can provide
an accurate distance measurement to objects up to 2m away.
The VL53L0X uses a 940nm (infrared) Vertical Cavity Surface-Emitting Laser which is invisible
to the human eye. The output is engineered to remain within Class 1 laser safety limits and as
such is safe under all operating conditions. Have a read of the vl53l0x Datasheet¹⁰ for all the good
info.
¹⁰https://www.st.com/resource/en/datasheet/vl53l0x.pdf
Distance Measurement using Time of Flight Sensor 73
The VL53L0X precisely measures how long it takes for emitted pulses of infrared laser light to
reach the nearest object and be reflected back to a detector, so it can be considered a tiny, self-
contained lidar system. The sensor can measure distances of up to 2m with 1 mm resolution, but
its effective range and accuracy depend on ambient conditions and target characteristics like size
and degree of reflectivity. The sensor’s accuracy can vary from ±3% at best to over ±10% in less
optimal conditions.
The beam of the emitted light is quite narrow and the orientation of the sensor and the measured
object will be factors in recording accurate values. This is also a positive thing since the narrow
light source is good for determining distance of only the surface directly in front of it. Unlike
audio based systems that utilise ultrasonic waves, the ‘cone’ of sensing is very narrow.
tof.set_measurement_timing_budget(100000)
Pulse Period
The other major adjustment that we can introduce is to the period of the pulse that is send out.
The shorter the pulse, the better for closer measurements, the longer the pulse, the better for more
distant measurement. There are two period ‘types’, Pre Range (Type 0) and Final Range (Type 1).
Longer periods increase the potential range of the sensor. Valid values are even numbers only.
These can be set in the MicroPython code via the lines;
tof.set_Vcsel_pulse_period(tof.vcsel_period_type[0], 18)
tof.set_Vcsel_pulse_period(tof.vcsel_period_type[1], 14)
The Pre Range settings can go from: 12 to 18 (default is 14) and the Final Range settings can go
from 8 to 14 (default is 10).
When selecting the I2C connections on the Pico, because the RP2040 microcontroller has two I2C
controllers we need to ensure that we define which controller we are using in the code. I2C0 =
id 0 and I2C1 = id 1. This is set in the following lines in the MicroPython code;
id = 1
The best place to ensure that we have the id correctly identified is on the pinout.
Assuming that we have header pins soldered onto our Pico and the ToF sensor, the easiest ways
to make a connection is via Dupont connectors.
Distance Measurement using Time of Flight Sensor 76
The only other point to note is that there are reports of some inconsistent measurements if the
XSHUT pin is left ‘floating’ (i.e, not tied to a low (ground) or high pin). I haven’t experienced this
myself, but if you’re seeing something that you can’t explain, this could be worth investigating
Code
The code below is largely that written by Kevin McAleer¹⁴ and published on GitHub¹⁵. However,
it is adapted to provide for the connection as described above and it is tuned to optimise for longer
distance readings. Likewise I have included a small piece of code to average out the readings to
improve consistency.
import time
from machine import Pin, I2C
from vl53l0x import VL53L0X
print("setting up i2c")
sda = Pin(26)
scl = Pin(27)
id = 1
print(i2c.scan())
# Sets the VCSEL (vertical cavity surface emitting laser) pulse period
# for the given period type (VL53L0X::VcselPeriodPreRange or
# VL53L0X::VcselPeriodFinalRange) to the given value (in PCLKs).
# Longer periods increase the potential range of the sensor.
# Valid values are (even numbers only):
while True:
# Start ranging
new_value = tof.ping()-50
if new_value != 8141 and new_value != 8140:
reading_group.append(new_value)
if len(reading_group) > n:
reading_group.pop(0)
print(sum(reading_group)/ len(reading_group), " ", new_value)
time.sleep(1)
Reading the on-board Temperature
of a Raspberry Pi Pico
As well as providing many marvellous ways of interfacing to the world via external sensors, the
Raspberry Pi Pico, or more accurately, the RP2040 microcontroller around which the Pico is built,
includes an on-board temperature sensor that we can access to get a feel (see what I did there)
for the environment.
Code
The code below is a very simple affair that declares our sensor then enters a loop where a reading
is taken, converted to a voltage, translated to a temperature and printed. This is repeated every
two seconds.
import machine
import time
sensor = machine.ADC(4)
while True:
reading = sensor.read_u16()
voltage = reading * ( 3.3 / 65535)
temperature = 27 - (voltage - 0.706) / 0.001721
print('Temperature: ', temperature)
time.sleep(2)
The output can be adjusted by carefully breathing on the Pico which should raise the temperature
slightly.
Temperature: 15.3408
Temperature: 14.87265
Temperature: 15.80894
Temperature: 15.3408
It should be noted that the large number of decimal places in the output is not indicative of a
commensurate level of accuracy!
Multiple Temperature
Measurements
This project will measure the temperature at multiple points using DS18B20 sensors. We will use
the waterproof version of the sensors since they are more practical for external applications.
The sensors can come with a couple of different wire colour combinations. They will typically
have a black wire that needs to be connected to ground. A red wire that should be connected to
a voltage source (in our case a 3.3V pin from the Pico) and a blue or yellow wire that carries the
signal.
The DS18B20 can be powered from the signal line, but in our project we will use the supply from
the Pico.
Hardware required
• 3 x DS18B20 sensors (the waterproof version)
• 4.7k Ohm resistor (I have used 10k Ohm resistor without problem)
• Jumper cables with Dupont connectors on the end
• Solder
• Heat-shrink
Connecting everything up
The DS18B20 sensors needs to be connected with the black wires to ground, the red wires to the
3V3 pin and the blue or yellow (some sensors have blue and some have yellow) wires to GP26
(pin 31). A resistor between the value of 4.7k Ohms to 10k Ohms needs to be connected between
the 3V3 and GP26 pins to act as a ‘pull-up¹⁶’ resistor.
We can actually use any of our GP pins to connect our sensors, as our code will will rely on a
software library to manage the communications, not one of the hardware implementations on
the microcontroller.
The following diagram is a simplified view of the connection.
Connecting the sensor practically can be achieved in a number of ways. But because the
connection is relatively simple we can build a minimal configuration that will plug directly
onto the appropriate GPIO pins using Dupont connectors¹⁷. The resistor is concealed under the
heat-shrink and indicated with the arrow.
¹⁶https://en.wikipedia.org/wiki/Pull-up_resistor
¹⁷http://www.instructables.com/id/Fitting-Dupont-Connectors/
Multiple Temperature Measurements 82
Code
The following code will read the temperature values from all the DS18B20 sensors that it finds
and print out the unique serial numbers of each sensor and the temperature that it is reading.
import machine
import onewire
import ds18x20
import time
import binascii
gp_pin = machine.Pin(26)
ds18b20_sensor = ds18x20.DS18X20(onewire.OneWire(gp_pin))
sensors = ds18b20_sensor.scan()
while True:
ds18b20_sensor.convert_temp()
time.sleep_ms(750)
for device in sensors:
s = binascii.hexlify(device)
readable_string = s.decode('ascii')
print(readable_string)
print(ds18b20_sensor.read_temp(device))
time.sleep(10)
The output will look something like the following (which has three sensors connected);
Multiple Temperature Measurements 83
AHT10 Details
The AHT10 is an accurate temperature and humidity sensor in a very small package that can be
accessed via an I2C interface. While it has a temperature measurement range of between -40°C
and 85°C, its typical error of ± 0.3 is achieved between around 0°C and 55°C. Normal operating
range for humidity is between 20 and 80% relative humidity. In that range it has a typical accuracy
of ±2.
Each sensor is calibrated and tested with a product lot number printed on the surface. be aware
that humidity sensors are not ordinary electronic components and should be carefully handled
and operated. Prolonged exposure to high concentrations of chemical vapour will cause the
sensor reading to drift.
If the sensor is exposed to adverse conditions or chemical vapours and the readings drift, it can
be restored to its calibrated state by the following process;
The devices address is 0x38 (which we will see when we scan it) and in spite of there also being
the address 0x39 also printed on the sensor PCB, there is no indication of how to enable that
address. As a result, only a single AHT10 can be used on an I2C bus.
¹⁸https://server4.eca.ir/eshop/AHT10/Aosong_AHT10_en_draft_0c.pdf
AHT10 Temperature and Relative Humidity 85
AHT10 Connection
Assuming that we have header pins soldered onto our Pico and the AHT10 sensor, the easiest
ways to make a connection is via Dupont connectors.
¹⁹https://github.com/targetblank
²⁰https://github.com/targetblank/micropython_ahtx0
AHT10 Temperature and Relative Humidity 86
An important point to note when we are connecting our Pico is that because the RP2040
microcontroller has two I2C controllers we need to ensure that we define which controller we are
using in the code. I2C0 = id 0 and I2C1 = id 1. This is set in the following lines in the MicroPython
code;
Code
As a neat method of confirming the address of our sensor we can run the following code on our
Pico that will scan the I2C bus;
if devices:
for d in devices:
print(hex(d))
0x38
import time
from machine import Pin, I2C
import ahtx0
temperature = round(sensor.temperature, 2)
humidity = round(sensor.relative_humidity, 2)
while True:
print("Temperature: ", temperature, "C")
print("Humidity: ", humidity, "%")
print()
time.sleep(5)
Just a reminder that if we use different I2C pin than GPIO26 and GPIO27, we will need to check
the pinout to know which I2C id should be used. In the case above it is 1.
Which produces something like the following;
Temperature: 21.98 C
Humidity: 55.41 %
Temperature: 21.98 C
Humidity: 55.41 %
Temperature: 21.98 C
Humidity: 55.41 %
PIR sensors are used in sensing applications, such as security alarms, motion detectors, and
automatic lights.
PIR Assembly
When there is no warm moving object in the sensors field of view it is idle and both slots detect
the same amount of radiation. However, when a warm body like a human or animal comes
into view, a signal is first detected by one of the pyroelectric sensors, which causes a positive
differential change between the two halves. When the warm body leaves the sensing area, the
reverse happens and the sensor generates a negative differential change. These changing pulses
are what determines that movement has been detected.
The PIR sensor is mounted on a printed circuit board which supports the electronics that interpret
the signals from the sensor itself. In a practical setting the complete assembly is usually contained
within a housing which is located to provide a view over the area to be monitored.
PIR
The white hemispherical lens is essentially a ‘window’ through which the infrared energy can
enter. The plastic lens acts as a focusing mechanism which condenses a large area into a small
one (in the same way that a camera lens works). To minimise the cost and size required the
covering is normally a fresnel lens.
The HC-SR501
I’m including the HC-SR501 description here because I have a few hanging around and I had
great plans to use one for a particular project involving a Raspberry Pi Zero some months ago.
However, in the process of testing I found that it would trigger at times through interference with
the WiFi signal on the Pi. This took quite a period to determine as I went through troubleshooting
which included multiple sensors and different Pis. Ultimately I came to the conclusion that the
Motion Sensing with the Raspberry Pi Pico 90
analog portion of the design of the HC-SR501 that allowed the device to trigger unintentionally
was not suitable for my application and I used the AM312 instead. That device uses digital signal
processing which was unaffected by the WiFi signal.
HC-SR501
The HC-SR501 has a 3-pin connector that interfaces it to the outside world. The connections are
as follows;
• VCC is the power supply for HC-SR501 PIR sensor which we can connect a 5V pin.
• Output pin is a 3.3V TTL logic output. LOW indicates no motion is detected, HIGH means
some motion has been detected.
• GND should be connected to the ground.
The HC-SR501 has a built-in voltage regulator so it can be powered by any DC voltage from 4.5
to 12 volts, typically 5V is used.
There are more than one model of this type of sensor. be careful to ensure that you have the
connections correct. The best mechanism (other than following the labels if there are any) is to
look for the protection diode as a reference. Failing that, if there aren’t any labels on the bottom
of the circuit board, check on the board, under the lens.
There are two potentiometers on the board to adjust a couple of parameters;
• Sensitivity: This sets the maximum distance that motion can be detected. It ranges from
3 meters to approximately 7 meters. The layout of the area being covered can affect the
range.
• Time: This sets how long that the output will remain high after detection is triggered. The
minimum is 3 seconds and the maximum is 300 seconds or 5 minutes.
• H: This is the Hold / Repeat / Retriggering setting. In this position the HC-SR501 will
continue to generate a high output while it continues to detect movement.
Motion Sensing with the Raspberry Pi Pico 91
• L: This is the Intermittent or No-Repeat / Non-Retriggering setting. Here the output will
stay high for the period set by the Time potentiometer.
As with most PIR sensors the HC-SR501 requires some time to adjust to the infrared environment
that it sees in any room. This will take from 30 to 60 seconds when the sensor is first powered
up. It also has a ‘reset’ period of about 5 or 6 seconds after making a reading. During this time it
will not detect any motion.
The AM312
AM312
As mentioned earlier, the AM312 utilises digital signal processing which removes one of the
reasons that interference can affect the HC-SR501.
AM312 is described as a new digital intelligent PIR sensor! It is a much simpler and smaller
device than the HC-SR501 with the digital detector and electronic circuitry built into the detector
housing.
This sensor also has the advantage of being ultra-low power with a quiescent current of only
8uA making it suitable for battery applications where a very long battery life is required.
The pin connections are as follows with the orientation of the sensor with the header pins
uppermost;
AM312 Pins
Motion Sensing with the Raspberry Pi Pico 92
PIR Connections
Connecting the PIR practically can be achieved in a number of ways. But because the connection
is relatively simple we can build a minimal configuration that will plug directly onto the pins
using Dupont header connectors and jumper wire.
Be aware that there are a few similar models of this type of sensor. Be careful to ensure that
you have the connections correct. The best mechanism (other than following the labels if there
Motion Sensing with the Raspberry Pi Pico 93
are any) is to look for the protection diode as a reference on the HC-SR501 and be aware that
the diagram that I have shown here for the AM312 is shown with the header pins uppermost.
For example the sensor I am using is not labelled, and the VCC and GND pins are in a different
location to those shone on at least one connection diagram on the Internet.
Code
The code below will designate the GPIO pin to be used as our input (GPIO28) and set it low with
a pull down resistor.
We can use any of the GPIO pins, so feel free to pick a convenient one. We use an internal pull
down resistor to avoid having a ‘floating’ input. This will set the pin to be a logic 0 so long as it
doesn’t have a signal applied.
It pauses momentarily to gather itself and then goes into an eternal loop where it prints out an
alert when movement is detected. It also pauses after detection to give the detectors signal output
an opportunity to return to a low state.
import time
from machine import Pin
time.sleep(1)
print('Ready to detect movement!')
while True:
if pir.value() == 1:
count = count + 1
print('Movement detected ', count)
time.sleep(4)
time.sleep(1)
Sensing vibration with a Raspberry
Pi Pico
Vibration sensors
Vibration sensors are designed to convert mechanical movement into a signal that can be
measured.
The underlying principles behind vibration sensors vary depending on the type of sensor, but
they generally rely on the conversion of mechanical energy (vibrations) into some other form
of energy that can be measured and analysed. The most common types of vibration sensors are
based on the following principles:
• Piezoelectricity: Piezoelectric sensors rely on the effect which occurs when certain materi-
als, such as quartz, generate an electrical charge in response to applied mechanical stress.
• Capacitance: Accelerometers and displacement sensors often use the principle of capaci-
tance, where changes in the capacitance between two electrodes is proportional to changes
in the distance between the electrodes. When the sensor is subjected to a vibration, the
distance between the electrodes changes, and this change in distance can be used to measure
the vibration.
• Inductance: Inductive sensors measure the change in inductance in a coil caused by the
movement of a ferromagnetic core inside the coil. When the ferromagnetic core is subjected
to a vibration, the inductance changes, and this change can be used to measure the vibration.
• Magnetic field: Magnetic sensors work when changes in a magnetic field is generated by
the movement of a ferromagnetic material inside a sensor.
• Light: Optical sensors use the principle of light and optics, where changes in the light that
is transmitted or reflected through the sensor are proportional to changes in the position of
a reflecting surface inside the sensor.
In each of these cases, the output signal from the sensor is proportional to the vibration being
measured, and the signal can be analysed to determine the frequency, amplitude, and other
characteristics of the vibration.
There are several types of movement that vibration sensors can be measure including:
• Acceleration
• Velocity
• Displacement
Each type of vibration sensor has its own strengths and weaknesses, and the choice of sensor will
depend on the specific application, including the type of vibration to be measured, the frequency
range and the operating environment.
Sensing vibration with a Raspberry Pi Pico 95
When a vibration is applied to the piezoelectric sensor, the piezoelectric material deforms and
generates an electrical charge proportional to the magnitude of the vibration. This electrical
charge can be measured and used to determine the frequency and amplitude of the vibration.
The piezoelectric sensor can be used in a wide range of applications, including measuring
vibrations in machinery, detecting structural movements in buildings, and measuring the impact
of shock or drop events. The sensors can also be used to monitor and control the vibrations of
musical instruments or in anti-tampering systems.
Piezoelectric sensors are generally rugged, reliable, and highly sensitive, making them a popular
choice for many applications that require the measurement of vibration or impact
The unit that we will test comes in two parts. There is an interface board which incorporates
two screw terminals which attach to the sensor proper.
The interface board will connect to the Pico as an analogue device with the addition of a power
supply connection that can be used at 5 or 3.3V.
Sensing vibration with a Raspberry Pi Pico 96
Connecting everything up
We will want to connect;
The power and ground pins are fairly self explanatory, and because the vibration sensor’s
interface board has an analogue output, we will apply signal output (S) to one of our Analogue
to Digital Converter (ADC) pins. In this case ADC0 on pin 31 (GPIO26).
Connecting the interface board to the Pico practically can be achieved in a number of ways. But
because the connection is relatively simple we can build a minimal configuration that will plug
directly onto the pins using Dupont header connectors and jumper wire.
Code
The following code takes 20 readings from the analogue connection on GPIO26 (ADC0) in quick
succession. It then discards the maximum and minimum values in the set and averages the
Sensing vibration with a Raspberry Pi Pico 97
remainder. This is done so that the sensor can get a ‘reading’ of a vibration that represents a
value over a slightly longer period of time than a single reading. This is useful since the sensor
can respond so quickly to a movement, that it may be possible for low frequency vibrations or
impacts to not get a true representation
while True:
reading_group = []
for x in range(n):
new_value = adc.read_u16()
reading_group.append(new_value)
time.sleep(.01)
print(vibration)
To test the code we can run it with the sensor taped to a suitably ‘vibrat-y’ device. I tried both
a electric knife and a battery powered jig-saw. Both produced good results. Ultimately I have a
plan in mind to attach them to my water pump to tell how long it is operating for and to the
aeration pump on my septic system (that runs continuously) so that I can be alerted if it fails or
if part of it fails (like a diaphragm) which would alter the nature of the vibration.
Using an Inertial Measurement Unit
(IMU) with a Pico
This project will connect a 6 Degrees of Freedom (DoF) IMU to our Raspberry Pi Pico and
demonstrate it’s use.
The IMU
An Inertial Measurement Unit, is a device that consists of one or more sensors that measure
specific types of physical quantities, such as acceleration, angular velocity, and magnetic field
strength. These sensors are typically arranged in a package that includes a microprocessor to
process the sensor data and output the measured quantities in a usable form. IMUs are often
used in applications that require precise measurements of motion, orientation, or position, such
as in drones, robots, and virtual reality systems
IMUs have been around for several decades and have undergone significant development over
time. Early IMUs were relatively simple devices that used mechanical sensors, such as gyroscopes
and accelerometers, to measure motion and orientation. These sensors were often large and
expensive, and the resulting IMUs were not very portable or practical for many applications.
In the last few decades, there has been a shift towards using microelectromechanical systems
(MEMS) sensors in IMUs. These sensors are much smaller and more affordable than their
mechanical counterparts, and they have greatly increased the portability and accessibility of
IMUs.
IMU’s are typically defined by the numbers of ‘Degrees of Freedom’ (DoF) they are capable of
measuring.
In addition to the sensors themselves, modern IMUs also typically include a microprocessor that
is capable of processing the sensor data and outputting it in a usable form. This allows the IMU
to provide real-time measurements of motion, orientation, and position, which can be used in a
variety of applications.
The unit we will connect to is capable of measuring 6 degrees of freedom.
3-axis Accelerometer
A MEMS (Microelectromechanical Systems) 3-axis accelerometer is a small, lightweight device
that measures acceleration along three orthogonal axes (typically labeled as x, y, and z). It can be
used to detect the magnitude and direction of gravitational acceleration, as well as any additional
acceleration that may be caused by movement or vibration.
Accelerometers are commonly used in a variety of applications and can be used to detect the
orientation of the device relative to a reference frame, to measure the acceleration of the device
during movement, or to detect and respond to impacts or other types of mechanical shock.
MEMS accelerometers typically use a small mechanical structure, such as a proof mass, that is
suspended on a flexible beam or membrane. When the device is subjected to acceleration, the
proof mass is displaced from its equilibrium position, and this displacement is detected by a
sensor, such as a capacitive or piezoresistive transducer. The sensor output is then processed by
an electronic circuit to determine the magnitude and direction of the acceleration.
Using an Inertial Measurement Unit (IMU) with a Pico 100
MEMS accelerometers are often small and inexpensive, and they can operate over a wide
temperature range. They are also relatively low power and have a fast response time, which
makes them well-suited for use in portable and mobile applications.
3-axis Gyroscope
A MEMS (Microelectromechanical Systems) 3-axis gyroscope is a small, lightweight device that
measures angular velocity along three orthogonal axes (typically labeled as x, y, and z). It can be
used to detect the rate of rotation around these axes, and can be used to determine the orientation
of the device relative to a reference frame.
Gyroscopes are commonly used in a variety of applications that require precise measurement of
motion or orientation, such as in drones, robots, and virtual reality systems. They can be used
to stabilize the motion of a device, to navigate through unknown environments, or to track the
orientation of the device over time.
MEMS gyroscopes typically use a small mechanical structure, such as a spinning rotor or a
vibrating element, that is suspended on a flexible beam or membrane. When the device is
subjected to angular velocity, the rotor or vibrating element responds by changing its position or
motion, and this change is detected by a sensor, such as a capacitive or piezoresistive transducer.
The sensor output is then processed by an electronic circuit to determine the magnitude and
direction of the angular velocity.
MEMS gyroscopes are often small and inexpensive, and they can operate over a wide temperature
range. They are also relatively low power and have a fast response time, which makes them well-
suited for use in portable and mobile applications. However, they are subject to drift over time,
which can limit their accuracy in certain applications.
Using an Inertial Measurement Unit (IMU) with a Pico 101
GY-521 Board
The accelerometer component of the MPU-6050 measures linear acceleration in the x, y, and z
axes. It can be used to detect the magnitude and direction of gravitational acceleration, as well
as any additional acceleration that may be caused by movement or vibration. The accelerometer
measures the rate of change of velocity over time. It is sensitive to changes in motion and can be
used to detect the direction and magnitude of acceleration, as well as changes in orientation.
The gyroscope component of the MPU-6050 measures angular velocity in the x, y, and z axes. It
can be used to detect the rate of rotation around these axes, and can be used to determine the
orientation of the device relative to a reference frame. The gyroscope measures angular velocity,
which is the rate of change of orientation over time. It is sensitive to changes in rotational
motion and can be used to detect the direction and magnitude of rotation, as well as changes in
orientation.
The MPU-6050 also includes a built-in temperature sensor and a digital motion processor (DMP)
that can be used to process the sensor data and output it in a usable form. The MPU-6050
communicates using an I2C or SPI interface, and it can be configured and controlled using
register settings.
Using an Inertial Measurement Unit (IMU) with a Pico 102
• Connect the GY-521’s SDA (data) pin to the Pico’s I2C1 SDA (pin 31, or GPIO26) on the
Pico (Brown).
• Connect the GY-521’s SCL (clock) pin to the Pico’s I2C1 SCL (pin 32, or GPIO27) on the
Pico (Orange).
• Connect the GY-521’s VCC (power) pin to the Pico’s 3V3(OUT) (pin 36) power pin.
• Connect the GY-521’s GND (ground) pin to the Pico’s GND (pin 38) pin.
IMU Connection
An important point to note when we are connecting our Pico is that because the RP2040
microcontroller has two I2C controllers, we need to ensure that we define which controller we are
using in the code. I2C0 = id 0 and I2C1 = id 1. This is set in the following lines in the MicroPython
code;
In our case, since we are using GPIO26 and GPIO27 for the SDA and SCK connections we are
using I2C Controller 1.
Code
The values from the IMU are accessed via two pre-built MicroPython modules (imu.py and
vector3d.py). These have been published on GitHub²¹. To make use of the module we will need
²¹https://github.com/micropython-IMU/micropython-mpu9x50
Using an Inertial Measurement Unit (IMU) with a Pico 103
to download them from GitHub and then copy them over to our Pico. I found this most easily
accomplished by first downloading the files to the main computer and then going File >> Open
on Thonny and selecting the appropriate file. From there go File >> Save as… and select the Pico
as the location to save the file (making sure to save it with the appropriate name).
The printed values from the code below represent the accelerometer and gyroscope readings of
the MPU-6050 IMU in 3-dimensional space.
In this code, the x, y, and z values represent the readings in the respective axes. Positive values
indicate that the IMU is accelerating or rotating in the positive direction of the respective axis,
while negative values indicate that it is accelerating or rotating in the negative direction.
import time
from machine import I2C, Pin
from imu import MPU6050
# Main loop
while True:
# Read the accelerometer and gyroscope values
accel = imu.accel
gyro = imu.gyro
The time and machine modules are imported, along with the MPU6050 class from the imu module.
The I2C interface is set up using the I2C class from the machine module, with the sda and scl
pins connected to pins 26 and 27 on the Raspberry Pi, respectively. The frequency of the I2C bus
is set to 400000 Hz.
Using an Inertial Measurement Unit (IMU) with a Pico 104
An MPU6050 object is created with the I2C interface. This object will be used to communicate
with the IMU and read the accelerometer and gyroscope values.
The full-scale range of the accelerometer is set to 2 g. This determines the maximum range of
the accelerometer readings, with higher values corresponding to a larger range.
The main loop begins and the accelerometer and gyroscope values are read using the accel and
gyro properties of the MPU6050 object. These properties are objects that contain the x, y, and z
components of the acceleration and angular velocity vectors, respectively.
The print section outputs the values to the terminal in a format that makes it easy to read (but
not necessairly easy to write!)
Using an OLED Display attached to a
Pico
This project will use an attached OLED display unit to present information from our Raspberry
Pi Pico.
The green tab on the side is simply there to make removal of the protective film on the screen
easy.
The SSD1306 micro-chip driver uses an I2C communications protocol and there is a PyPI library
available that can be used for basic controls. There are some models that will also include SPI
connectivity and these can be identified by having more than the four connecting pins that are
shown on the model above. For more information on the unit we can consult the datasheet²².
²²https://datasheetspdf.com/pdf/798762/SolomonSystech/SSD1306/1
Using an OLED Display attached to a Pico 106
OLED Connection
An important point to note when we are connecting our Pico is that because the RP2040
microcontroller has two I2C controllers, we need to ensure that we define which controller we are
using in the code. I2C0 = id 0 and I2C1 = id 1. This is set in the following lines in the MicroPython
code;
In our case, since we are using GPIO26 and GPIO27 for the SDA and SCK connections we are
using I2C Controller 1.
Code
The code below is incredibly basic and aimed at providing an example of how easy it it is to get
started using the display. There is a great deal more that can be done with the display to add
shapes, pictures and movement, but our aim is to get up and running and then from there we
can push on to greater things :-).
The code below will print ‘Pico’ five times staggered across the screen.
oled.text("Pico", 0, 0)
oled.text("Pico", 20, 10)
oled.text("Pico", 40, 20)
oled.text("Pico", 60, 30)
oled.text("Pico", 80, 40)
oled.show()
Using an OLED Display attached to a Pico 108
While the example above is limited, we can also use some of the code’s functions available for
the display to add greater complexity. Feel free to have a play with some of the options below;
For greater illustration of what can presented on the display, check out the MicroPython docs
page²³ for the ESP8266 which presents further options.
And as a final tribute to the MicroPython instructions, run the following code;
oled.fill(0)
oled.fill_rect(0, 0, 32, 32, 1)
²³https://docs.micropython.org/en/latest/esp8266/tutorial/ssd1306.html#ssd1306
Using an OLED Display attached to a Pico 109
oled.show()
Enjoy!
Using a Dot-Matrix Display Attached
to a Pico
A dot matrix display is a little like a compromise between the simplicity of a seven segment
display and a modern screen. They have a lot of flexibility and through widely available Python
modules can be easily used with a Raspberry Pi Pico.
the module we will need to download it from GitHub²⁵ and then copy it over to our Pico. I found
this most easily accomplished by first downloading the file to the main computer and then going
File >> Open on Thonny and selecting the appropriate file. From there go File >> Save as… and
select the Pico as the location to save the file (making sure to save it with the appropriate name
(max7219.py)).
Because of the abstraction afforded by the library, the reading of the sensor is nicely simplified.
The display module is labelled ‘DIN’, ‘SCK’ and ‘CS’, so the wiring should be relatively clear
(but look to the connection diagram if in doubt). As with so many of these connections, some
simple Dupont connecting wires will suffice.
Code
The code below rotates the words ‘RPi’ and ‘Pico’ on the display. Take the opportunity add your
own text and adjust the brightness settings to get a feel for the variations that are possible.
²⁵https://github.com/FideliusFalcon/rpi_pico_max7219
Using a Dot-Matrix Display Attached to a Pico 112
spi = SPI(0,sck=Pin(18),mosi=Pin(19))
cs = Pin(17, Pin.OUT)
display.brightness(10)
while True:
display.fill(0)
display.text('RPi',0,0,1)
display.show()
sleep(3)
display.fill(0)
display.text('PICO',0,0,1)
display.show()
sleep(3)
Displaying Pico
Scrolling
Showing text is one thing, but scrolling text is another :-). The code below takes a string of text
and moves it across the face of the display.
Using a Dot-Matrix Display Attached to a Pico 113
display.brightness(0)
scrolling_message = "RASPBERRY PI PICO SCROLLING DISPLAY"
length = len(scrolling_message)
column = (length * 8)
display.fill(0)
display.show()
time.sleep(1)
while True:
for x in range(32, -column, -1):
display.fill(0)
display.text(scrolling_message ,x,0,1)
display.show()
time.sleep(0.05)
In this instance, play with the message and the sleep time to adjust what is being displayed and
the speed of movement.
Controlling addressable LEDs
The Raspberry Pi Pico can be used to provide lighting control to create effects and custom
illumination via individually addressable LEDs that can be combined in a range of configurations.
The applications could range from a simple colour changing accent light to a wearable display
to a light sabre.
• WS2811
• WS2812B
• WS2813
Controlling addressable LEDs 115
WS2811
The WS2811 is normally found in 12V installations. 12V is preferable if we want to connect up
longer lengths of LEDs to reduce the effects of voltage drop with distance. This aids in providing
better colour consistency.
As we are going to be using the Pico, the added complexity of using a different voltage source
for the LEDs and the Pico is not going to be an advantage.
One of the most popular brands of addressable LEDs are made and distributed by Adafruit and
are called NeoPixels²⁶. They have put a lot of effort into developing and supporting this product
and I thoroughly recommend that you take a look.
Addressable LED
Each package has four pins – VCC, Ground, DIN (Data IN), and DOUT (Data OUT). The
controlling connection from our Pico goes to the DIN pin and follow on LEDs are daisy chained
from the initial devices DOUT pin to the follow on devices DIN. Thus, the controlling signal only
requires a single wire from our Pico (Although it will still require power in the form of VCC (5V)
and ground).
Each separate red, green, and blue LED in the package can be set to shine at one of 256 brightness
levels. The combination of those three colours at different levels of brightness allows for the
generation of the full colour spectrum. The signal sent from our Pico will be a sequence of RGB
combinations which will go to the first connected LED. This receives the first set of RGB levels
and passes the remainder through to the follow on LED. This in turn receives the next set of
levels and passes on the remainder etc, etc, until the end of the line (signals or LEDs) is met.
This brings us to a point where some people might want a bit of clarity. When we
are connecting up our addressable LEDs using a single signal wire it could be easy to
mistake this as another example of a ‘1-Wire’ connection. In short, it’s not. A 1-Wire
connection uses a single signal wire to communicate with connected devices, but the
communication mechanism is very different. In a 1-Wire system, each device uses a
unique ‘serial number’ that is the method of determining what signal goes where. For
the addressable LED’s the mechanism is reliant on the position of each device in the
connection chain.
Power Requirements
Each individual LED can draw up to 60mA of current. That’s not a huge amount in the scheme of
things, but because addressable LEDs are typically packaged as strips or matrices, if we multiply
that current draw by the possible number of LEDs in a connection, the value starts to be come
significant.
For instance, a 1m strip with 30 LED’s could potentially draw up to 1.8A of current. That’s quite
a lot and to be honest, it’s a worst case scenario. For general use where we would be varying our
colours and brightness to make pretty patterns, we can probably work on a rule of thumb that
20mA per LED is about right.
²⁷https://cdn-shop.adafruit.com/datasheets/WS2812.pdf
²⁸https://www.mouser.com/pdfDocs/WS2812B-2020_V10_EN_181106150240761.pdf
²⁹http://www.normandled.com/upload/201605/WS2813%20LED%20Datasheet.pdf
³⁰https://www.electronicshub.org/bidirectional-logic-level-converter/
Controlling addressable LEDs 118
Our Pico has a direct connection from the USB connector to the VBUS connector (where we are
taking our 5V supply from for our LEDs) so that means that we are dependant on our power
supply to our Pico in terms of managing the appropriate amount of power if we are feeding our
LEDs from the Pico.
If we were using a larger number of connected LEDs it might be necessary to divide your strip
into different sections and connect separate power supplies. This could be because you need a
certain amount of current to drive the LEDs or it could be because of the voltage drop that will
occur over longer distances due to the resistance of the copper connecting wires.
Important points to note when doing this is that the ground wires are all connected together and
the signal chain is contiguous, but the positive power line is separated into its different sections.
Pico LEDs
Voltage VBUS +5V
Ground GND GND
Signal GPIOxx DIN
Keeping in mind that we are utilising the GPIO26 pin in this example for simplicity’s sake. You
should be able to use any GPIO pin. You will need to adjust the pin number in the code samples
below however.
Controlling addressable LEDs 119
Code
Scroll a red LED through all the pixels
NUMBER_PIXELS = 64
STATE_MACHINE = 0
LED_PIN = 26
off = (0,0,0)
delay = .1
while True:
for i in range(0, NUMBER_PIXELS):
strip.set_pixel(i, red)
if i > 0: strip.set_pixel(i-1, off)
if i == 0: strip.set_pixel(NUMBER_PIXELS-1, off)
strip.show()
sleep(delay)
This code (led-red-run.py) is available to download (at no cost) as an extra from Leanpub when
you download the book
Run two LEDs around the outside of a 8 x 8 matrix.
NUMBER_PIXELS = 64
STATE_MACHINE = 0
LED_PIN = 26
delay = .1
while True:
for i in range(0, 8):
strip.set_pixel(i, red)
if i > 0: strip.set_pixel(i-1, off)
if i == 0: strip.set_pixel(63, off)
strip.set_pixel(i*8, red)
if i > 0: strip.set_pixel((i-1)*8, off)
strip.show()
sleep(delay)
for i in range(1, 8):
strip.set_pixel(56+i, red)
if i > 1: strip.set_pixel(56+(i-1), off)
if i == 1: strip.set_pixel(56, off)
strip.set_pixel(((i+1)*8)-1, red)
strip.set_pixel(((i)*8)-1, off)
Controlling addressable LEDs 121
This code (led-8x8-outside.py) is available to download (for free) as an extra from Leanpub
when you download the book
There are a couple more code samples available for download from Leanpub.
Prometheus³³ is an open source application used for monitoring and alerting. It records real-time
metrics in a time series database built using a HTTP ‘pull’ model.
It was was created because of the need to monitor multiple microservices that might be running
in a system. It employs a modular architecture and employs modules called exporters, which
allow the capture of metrics from a range of platforms, IT hardware and software.
Prometheus’s ‘pull model’ of metrics gathering means that it will actively request information
for recording. It collects metrics at regular intervals and stores them locally. These metrics are
pulled from nodes that run ‘exporters’. An exporter can be defined as a module that extracts
information and translates it into the Prometheus format.
Prometheus data is stored as metrics, with each having a name that is used for referencing and
querying. This is what makes it very good at recording time series data.
Prometheus is commonly used in combination with the Grafana platform which has a very
powerful visualisation capability.
I have written a separate book on installing and using Prometheus and Grafana here³⁴ and I would
recommend it to anyone who is interested in monitoring their physical or IT environment.
The good news is that when gathering metrics for use in a Prometheus / Grafana stack
installation, metrics can be made available from a device via a simple web query that details
various metric values for consumption.
The information presented on the web page is set out in the exposition format published here³⁵.
In its most simple form the information can take the format of a metric name and a value
separated by any number of blank spaces or tabs. If more than one line (metric) is being presented,
these must be separated by a line feed character (\n). The last line must end with a line feed
character. Empty lines are ignored.
For example;
weather_inside_temperature_C 21.7
weather_barometer_mb 1035.6
weather_sunshine_hours_hours 11.0
A great deal more complexity can be integrated into the metric values including label names,
and a time-stamp, but for the purposes of demonstrating the technique we will focus on a very
simple example. For guidance on best practices for naming conventions and metric formatting
in general, see the page on writing exporters here³⁶.
It is worth reinforcing here that this code is dependant on using the Pico W since it provides the
mechanism for connecting to the Prometheus platform via a web request.
Code
The astute reader will recognise the following as being heavily based on the example used earlier
in the book to serve a web page from the Pico W. Well spotted. You can also download this code
as an extra with the book. It is bundled with the code samples extra and is called prometheus.py.
import network
import socket
import time
import random
import rp2
ssid = secrets['ssid']
password = secrets['pw']
³⁵https://prometheus.io/docs/instrumenting/exposition_formats/
³⁶https://prometheus.io/docs/instrumenting/writing_exporters/
Using the Raspberry Pi Pico as a Prometheus Node 124
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
wlan.ifconfig(('10.1.1.161','255.255.255.0','10.1.1.1','8.8.8.8'))
# Open socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(1)
def temperature_reading():
reading = sensor.read_u16()
voltage = reading * ( 3.3 / 65535)
temperature = 27 - (voltage - 0.706) / 0.001721
Using the Raspberry Pi Pico as a Prometheus Node 125
return(temperature)
request = cl.recv(1024)
print(request)
temperature = temperature_reading()
rando = random.randint(0,99)
print(rando)
print(temperature)
first = html.replace("pico_random",str(rando))
last = first.replace("pico_temperature",str(temperature))
response = last
cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
cl.send(response)
cl.close()
except OSError as e:
cl.close()
print('connection closed')
This code combines several different components. It connects the Pico to a local network via
WiFi. It sets itself a static IP address. It makes content available via port 80 so that it can be read
by a browser. It serves content in an OpenMetric and Prometheus exposition format so that it
can be read by Prometheus.
One of the more important parts of that is the setting of the static IP address via the following
line;
wlan.ifconfig(('10.1.1.161','255.255.255.0','10.1.1.1','8.8.8.8'))
This is important so that we can tell Prometheus where to go to read the metrics. It’s important
to remember from the section earlier in the book that these settings need to be particular to your
network.
The HTML
The HTML section is where the metric information is recorded for presentation to Prometheus.
It took a bit of trial and error to get to the point where this was being presented in a format
Using the Raspberry Pi Pico as a Prometheus Node 126
where Prometheus could read it from a practical perspective and then it took a bit more effort to
ensure that the data was being presented correctly.
The first thing to notice is that it doesn’t include any HTML tags that we would expect for a
regular page. It turns out that this did not play well with Prometheus. It refused to connect,
showing the message "INVALID" is not a valid start token.
I then made the horrible mistake of thinking that the information on the lines with the # marks
were the equivalent of comments in code. Boy was I wrong and it was a classic case of RTFM.
The error response on Prometheus was invalid metric type "about the variable". So, after
reading the doc on the Prometheus exposition format³⁷ I could see that the HELP and TYPE lines
also have to be specifically formatted!
Lines with a # as the first non-whitespace character are comments. They are ignored unless the
first token after # is either HELP or TYPE. Those lines are treated as follows:
• If the token is HELP, at least one more token is expected, which is the metric name. All
remaining tokens are considered the docstring for that metric name. HELP lines may
contain any sequence of UTF-8 characters (after the metric name), but the backslash and
the line feed characters have to be escaped as \ and \n, respectively. Only one HELP line
may exist for any given metric name.
• If the token is TYPE, exactly two more tokens are expected. The first is the metric name,
and the second is either counter, gauge, histogram, summary, or untyped, defining the type
for the metric of that name. Only one TYPE line may exist for a given metric name. The
TYPE line for a metric name must appear before the first sample is reported for that metric
name. If there is no TYPE line for a metric name, the type is set to untyped.
So…. we could just omit the HELP and TYPE lines, but let’s persist.
The metrics
The astute reader (that’s you) will have noted that as well as the two metric names that we have
included in our HTML section (pico_temp and pico_rand) we have also included a couple of
place-holders that we will use in a few moments to substitute in our actual metric values. The
place-holders are pico_temperature and pico_random.
Because our temperature measurement takes a bit of code to read, that is mostly included in the
function temperature_reading.
³⁷https://prometheus.io/docs/instrumenting/exposition_formats/#exposition-formats
Using the Raspberry Pi Pico as a Prometheus Node 127
sensor = machine.ADC(4)
def temperature_reading():
reading = sensor.read_u16()
voltage = reading * ( 3.3 / 65535)
temperature = 27 - (voltage - 0.706) / 0.001721
return(temperature)
temperature = temperature_reading()
rando = random.randint(0,99)
The last piece of the puzzle is where we replace our place-holders with our metric values so that
the information can be served and read.
first = html.replace("pico_random",str(rando))
last = first.replace("pico_temperature",str(temperature))
With all of that complete, we are able to configure Prometheus and look at our target list to see
glorious success! From there we can make a simple graph to display our metrics.
The graph above shows a 24 hour read-out of the random number and temperature metrics. The
‘blip’ that we can see around 1600 hrs is actually when the sun came through the office and
passed over the Pico when it was sitting on the bench.
Using the Raspberry Pi Pico as a Prometheus Node 128
³⁸https://leanpub.com/rpcmonitor
Sending an email from a Raspberry
Pi Pico W
Sending emails programmatically is a useful function for those sort of events where you have
your Pico measuring something and when it needs some higher attention it sends a call for help.
Kind of the Pico version of shining the bat symbol in the sky. I’m sure that there are better
analogies, but that’s what I think of when I imagine a Raspberry Pi Pico sending an email.
Whatever the occasion, the Pico is up for the job.
If you’re not using Gmail you will need to determine the appropriate smtp server name for your
service and the port that they use. This will vary from provider to provider and might require
some googling.
We’ll also need some basics like our SSID name and username / password for connecting to the
WiFi connection that will be used. Just like we needed when setting up WiFi earlier.
Sending an email from a Raspberry Pi Pico W 130
All this good information above can be captured in our ‘secrets.py’ file that we can keep on the
Pico so that we don’t need to expose those more sensitive details in our main code. We first saw
this file used in the section where we were serving a web page using the Pico. For this example
it will look a little like the following (but with your secrets in the appropriate places);
secrets = {
'ssid': '<your ssid>',
'pw': '<your password>',
'ip': '<the static IP address for the Pico',
'netmask': '<the netmask for your network, but probably something like 255.25\
5.255.0>',
'gateway': '<Your gateway address>',
'dns': '<The DNS server you are going to use>',
'sender_email': '<the email address of the Gmail account you will use>',
'sender_name': '<Your sender name>',
'sender_password': '<the App / device password that you set up in Gmail>',
'recipient_email': '<the email address of the recipient>'
}
The Code
The email function is driven by the umail module. This has been published on GitHub by
shawwwn³⁹. To make use of the module we will need to download it from GitHub⁴⁰ and then
copy it over to our Pico. I found this most easily accomplished by first downloading the file to
the main computer and then going File >> Open on Thonny and selecting the appropriate file.
From there go File >> Save as… and select the Pico as the location to save the file (making sure
to save it with the appropriate name (umail.py)).
Our code is fairly straight forward in that it imports the appropriate modules, creates the WiFi
connection and then sends the email (not forgetting the secrets file) and looks like the following;
import network
import time
import umail
from secrets import secrets
# Set up Wifi
ssid = secrets['ssid']
password = secrets['pw']
rp2.country('NZ') # change to your country code
wlan = network.WLAN(network.STA_IF)
ip = secrets['ip']
³⁹hhttps://github.com/shawwwn
⁴⁰https://github.com/shawwwn/uMail
Sending an email from a Raspberry Pi Pico W 131
netmask = secrets['netmask']
gateway = secrets['gateway']
dns = secrets['dns']
# Email details
sender_email = secrets['sender_email']
sender_name = secrets['sender_name']
sender_password = secrets['sender_password']
recipient_email = secrets['recipient_email']
email_subject ='Test Email from Raspberry Pi Pico'
Obviously the content of the body of the email can be adjusted to accept information gleaned
from sensors or similar important information. Separate each line out with a newline character
and we’re good to go!
Integrating a Real Time Clock (RTC)
with a Raspberry Pi Pico
Just what is a RTC?
A Real Time Clock (RTC) is a crucial component for any microcontroller-based system that needs
to keep track of time. Ostensibly this would then allow the system to maintain time even when
the system is powered off or reset.
An RTC is a small, clock circuit that is designed to keep track of the current date and time. It
typically includes a clock crystal oscillator, a battery, and a small amount of non-volatile memory
for storing the time and date information.
Typically RTC’s will be separate modules that can interface with a microcontroller using a variety
of communication protocols such as I2C, SPI, or UART to read and write the date and time
information. The RTC provides accurate timekeeping for the microcontroller and can be used to
timestamp events, trigger time-based events, and schedule tasks.
An RTC is especially useful for systems that require time-sensitive actions such as data logging,
scheduling, and timing critical operations. It can also be used to implement features such as
alarms, timers, and watchdogs (used to facilitate automatic correction of temporary hardware
faults).
Overall, an RTC is an essential component for any microcontroller-based system that needs to
keep accurate track of time, even when power is lost or the system is reset.
From there we can use Greenwich Mean Time (GMT) (now referred to as Coordinated Universal
Time or Universal Time Coordinated (UTC)) to know what time it is.
Hang on a minute I hear you say. I want to know what the time is in the country where I am
running my Pico! I hear you and I acknowledge your concerns. Sadly this turns out to be way
more difficult that it seems at face value. It appears that keeping track of local times is a complex
job more suited to super computers and rooms full of frustrated programmers with sleeping
disorders. In short, we need to learn to embrace UTC and where required to convert to our
respective local times we do it as required (i.e programmatically in a script or spreadsheet. This
is the only way we preserve our sanity.
The Code
The following code connects to our local WiFi network (using the secrets file to set all the
appropriate network particulars (see the WiFi section for details)). From there it connects to
a NTP server, pulls a time value and sets the Pico’s RTC. Then it goes about logging a timestamp
every 30 seconds and flashing the on-board LED every time it writes a value to memory.
import network
import socket
import time
import struct
import machine
NTP_DELTA = 2208988800
host = "pool.ntp.org"
rtc=machine.RTC()
def set_time():
# Get the external time reference
NTP_QUERY = bytearray(48)
NTP_QUERY[0] = 0x1B
addr = socket.getaddrinfo(host, 123)[0][-1]
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.settimeout(1)
res = s.sendto(NTP_QUERY, addr)
msg = s.recv(48)
finally:
s.close()
tm = val - NTP_DELTA
t = time.gmtime(tm)
rtc.datetime((t[0],t[1],t[2],t[6]+1,t[3],t[4],t[5],0))
wlan = network.WLAN(network.STA_IF)
# Connect to Wifi
wlan.active(True) # activate the interface
if not wlan.isconnected(): # check if connected to an AP
print('Connecting to network...')
wlan.connect(ssid, password) # connect to an AP
wlan.ifconfig((ip,netmask,gateway,dns))
while not wlan.isconnected(): # wait till we are connected
print('.', end='')
time.sleep(0.1)
print()
print('Connected:', wlan.isconnected())
else:
print("Already connected!")
time.sleep(0.01)
led_onboard.value(0)
time.sleep(30)
For general use we would replace the data logger portion of the code to allow it to carry out
whatever task we desired that included accurate time!
However, as responsible engineers with an eye to the foibles of uncertain hardware changes, it
might be useful if we had some code that would allow us to illuminate the LED independent of
which board we were using. Well good news, we can utilise the board class of functions that will
allow us to determine just which type of Pico we are using and this will let us set the LED pin
appropriately. The following code demonstrates this;
BOARD_TYPE = Board().type
print("Board type: " + BOARD_TYPE)
if BOARD_TYPE == Board.BoardType.PICO_W:
led = Pin("LED", Pin.OUT)
elif BOARD_TYPE == Board.BoardType.PICO:
led = Pin(25, Pin.OUT)
while (True):
led.on()
time.sleep(.2)
led.off()
time.sleep(.2)