Raspberry Pi Pico W beginners’ components tutorial

raspberry pi pico w components tutorial

This Raspberry Pi Pico W tutorial will teach you how to interact with the microcontroller to interact with simple components such as LEDs, ultrasonic sensors and many other items found in a beginner’s kit.

If you are a complete beginner, this tutorial will help you understand MicroPython so that you can read, write and modify code. With this knowledge, you can combine code and components to create something that has a real-world use case.

If you are coming from a Pico, this tutorial will teach you how to control the Pico W wirelessly. Previously, you could only interact with the Pico through a switch, button or some physical interaction device. Not anymore! You can now control the components with your phone or desktop.

Table of contents

Tutorial goals in summary

  • Learn how to interact with fundamental components that make up larger projects
  • Control all of these wirelessly — no switches, buttons or other interaction devices.
  • Gain a better understanding of the Pico W’s strengths

Important links

Github repo for tutorial code (except secrets.py)

MicroPython documentation

Errors, suggestions, comments? Leave a comment in the comment box below, email me or Tweet me.

Soldering header pins

When you buy a Raspberry Pi Pico W, it might not come with headers that will allow you to connect components to your Pico.

At the time of writing, the Pico WH (H for headers) hasn’t been released. Our Pico W mega-article is keeping track of its release.

If, however, you can find a Pico W with pre-soldered headers, I’d advise you to buy that.

Nonetheless, for the rest of us, soldering headers onto the Pico W is a simple thing to do. You will need:

  • Breadboard
  • Soldering iron and solder
  • Headers

When you buy your headers, make sure to buy one that’s meant for the Pico W. Unlike headers for the Raspberry Pi Zero-series, the headers on the Pico W aren’t side-by-side. They are on opposite ends of the board.

A breadboard, the Pico W and 2×20 pin headers

The pins have a long side and a short side. You’ll want the longer pins to be on the side where you see the GPIO labels (GP0, GP1, GND, VBUS, VSYS, etc.)

You will want the longer pins to be on the side opposite where the USB connector is.

Therefore, insert the longer pins into the breadboard. You will need four holes’ and a gutter worth of spacing between them. If unsure, test with your Pico W.

Soldering is easy. Make sure your soldering iron is hot, and make sure to use the tip of the soldering iron.

What I found most effective was to get the soldering iron’s tip close to the pin, get the solder touching the tip and see it flow down the pin and create a connection.

After soldering, make sure to check and ensure there isn’t any extraneous solder that could be connecting two GPIO pins together or some leftover waste solder that’s on your board.

Use Thonny as your code editor

Thonny programming a Raspberry Pi Pico W

Thonny remains the easiest way to program your Raspberry Pi Pico W.

If you are using the Raspberry Pi OS, you would already have it installed.

However, if you are using Windows or Mac, you will need to download it and configure it.

Here’s a guide that will walk you through the steps.

Also make sure to refer to the guide on how to upload files onto your Pico W.

Update your firmware by uploading the latest UF2

When you buy your Pico W, you might have already have the outdated firmware.

Many changes are coming to the Pico W, so it’s ideal to update your firmware now.

Some changes that I have seen since the day of release are improvements in the WLAN’s access point function, and future updates could unlock the Bluetooth function on the board.

Update now! Here’s a guide in our mega-article.

1. Serve a web page that says “Hello World” on the Pico

The “Hello World” project of the Raspberry Pi Pico W

One of the most fundamental projects in all programming tutorials is the “Hello World” project.

A microcontroller’s “Hello World” usually involves blinking an LED. It’s super easy. Here’s how.

However, since the Pico W can serve a web page, let’s start by learning how to serve a web page that has a “Hello World” message.

The setup used here will form the most fundamental building block for the rest of the tutorials.

There are two ways you can connect to the Pico W. You can make it join a WiFi network or you can broadcast a SoftAP hotspot similar to what your smartphone does. If you want to try the latter, follow this link. However, for consistency throughout this tutorial, we will always connect to a WiFi network rather than broadcast one from the Pico W.

So, fundamentally, the steps for serving a web page involves:

  • Connecting to WiFi
  • Writing code to serve index.html to anyone who connects to the Pico W’s IP address

Let’s set up a few files. Save these and upload them to your Raspberry Pi Pico W. Here’s how to upload files, in case you missed it.

wifi.py

wifi.py is a boilerplate meant to help you to connect to your WiFi network. Creating a separate file and importing it into main.py file later will help reduce cluttered code.

Note that you should change your country’s code in the line rp2.country(‘DE’) if your country is not Germany.

import rp2
import network
import ubinascii
import machine
import urequests as requests
import time
from secrets import secrets


def init_wifi():
    # Set country to avoid possible errors
    rp2.country('DE')

    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)

    # Load login data from different file for safety reasons
    ssid = secrets['ssid']
    pw = secrets['pw']

    wlan.connect(ssid, pw)

    # Wait for connection with 10 second timeout
    timeout = 10
    while timeout > 0:
        if wlan.status() < 0 or wlan.status() >= 3:
            break
        timeout -= 1
        print('Waiting for connection...')
        time.sleep(1)

    # Define blinking function for onboard LED to indicate error codes    
    def blink_onboard_led(num_blinks):
        led = machine.Pin('LED', machine.Pin.OUT)
        for i in range(num_blinks):
            led.on()
            time.sleep(.2)
            led.off()
            time.sleep(.2)

    wlan_status = wlan.status()
    blink_onboard_led(wlan_status)

    if wlan_status != 3:
        raise RuntimeError('Wi-Fi connection failed')
    else:
        print('Connected')
        status = wlan.ifconfig()
        print('ip = ' + status[0])

secrets.py

wifi.py imports secrets.py, where you store your WiFi network’s info.

secrets.py is a simple JSON file which contains your WiFi SSID and password.

secrets = {
    'ssid': 'SM-A520W9371',
    'pw': 'starting',
    }

serve_webpage.py

As the name suggests, this page serves websites to users who connect to the Pico W.

Upon receiving a connection, the Pico W finds a file called index.html and sends it to the connected client, as seen in the line response = get_html(‘index.html’).

import socket

def serve_webpage():
    #Function to load in html page    
    def get_html(html_name):
        # open html_name (index.html), 'r' = read-only as variable 'file'
        with open(html_name, 'r') as file:
            html = file.read()
            
        return html

    # HTTP server with socket
    addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

    s = socket.socket()
    s.bind(addr)
    s.listen(1)

    print('Listening on', addr)

    # Listen for connections
    while True:
        try:
            cl, addr = s.accept()
            print('Client connected from', addr)
            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()
            
        except OSError as e:
            cl.close()
            print('Connection closed')

Finally, we need to create the index.html file that will be sent to a connected client.

<!DOCTYPE html>
<html>
    <head>
        <title>Pico W</title>
    </head>
    <body>
        <h1>Hello World</h1>
    </body>
</html>

This is a simple HTML boilerplate with two changes: one to the <title> tag which outputs “Pico W” as the title and the <h1> tag which says “Hello World”.

main.py

Since we have placed all the code elsewhere, our main.py file just has to import and call these functions in order to serve the Hello World web page.

As you can see, we initialize WiFi first before serving the web page.

from wifi import init_wifi
from serve_webpage import serve_webpage

init_wifi()
serve_webpage()

You’re almost there!

Referring to the screenshot below, make sure:

  • You have uploaded five files into your Raspberry Pi Pico W (see bottom left red box)
  • Make sure your interpreter is set to MicroPython (Raspberry Pi Pico) (see bottom right box)
  • Then, highlight the main.py in your code editor, and click the green run button (top left red box)
  • Once you run this, you will see your IP address in the Shell. Go to your browser and type in this address and you will see the Hello World web page.
  • If your Shell is not open, go to View -> Shell.

If all is successful, you will see the page below.

2. Controlling a LED wirelessly

Now that you have the basics set up, let’s go one step forward.

One of the most fundamental Raspberry Pi projects involves blinking an LED.

Let’s up that a notch by controlling the LED wirelessly. We want to be able to blink, turn on and off the LED.

In order to do this, you will need to set up a circuit and a web server with three buttons — ON, OFF, BLINK.

For this project, you will need an LED, a 330 ohm resistor, one jumper wire and a breadboard.

We will use a red LED because most kits will have it. Do note that if you use an LED of any other colour, you will need to adjust the resistor.

Here’s how to connect the components

  • GPIO 2 -> LED’s long leg (anode/positive)
  • GND -> 330 ohm resistor -> LED’s short leg (cathode/negative)

Code to control LED on Pico W

Let’s build upon what we did in the previous tutorial. The only two files we will need to modify are index.html to add buttons and main.py to interact with the LED based on the input from index.html.

The parts that are bold indicate the new lines that were added to index.html. They add three buttons and a paragraph that says “Control the LED”.

<!DOCTYPE html>
<html>
    <head>
        <title>Pico W</title>
    </head>
    <body>
        <h1>Pico W</h1>
        <p>Control the LED</p>
        <a href=\"?led=on\"><button>ON</button></a>&nbsp;
        <a href=\"?led=off\"><button>OFF</button></a>
        <a href=\"?led=blink\"><button>BLINK</button></a>
    </body>
</html>

Notice when you press the buttons, you will see a parameter added to your Pico W’s IP address (e.g. http://192.168.43.134/%22?led=blink\). These parameters are captured by the Pico W’s backend and controls the LED.

We are going to move serve_webpage.py‘s code into the main.py file.

Here’s main.py:

from wifi import init_wifi
import socket
import machine
import time

init_wifi()


#LED controls
led = machine.Pin(2, machine.Pin.OUT)
      
def blink_led():
    led.on()
    time.sleep(0.2)
    led.off()
    time.sleep(0.2)

#Function to load in html page    
def get_html(html_name):
    # open html_name (index.html), 'r' = read-only as variable 'file'
    with open(html_name, 'r') as file:
        html = file.read()
        
    return html

# HTTP server with socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

s = socket.socket()
s.bind(addr)
s.listen(1)

print('Listening on', addr)

# Listen for connections
while True:
    try:
        cl, addr = s.accept()
        print('Client connected from', addr)
        request = cl.recv(1024)
        print(request)
        
        request = str(request)
        led_on = request.find('?led=on')
        led_off = request.find('?led=off')
        led_blink = request.find('?led=blink')
        print('led_on = ', led_on)
        print('led_off = ', led_off)
        print('led_blink = ', led_blink)
        if led_on > -1:
            print('LED ON')
            led.on()
            
        if led_off > -1:
            print('LED OFF')
            led.off()
            
        if led_blink > -1:
            print('LED BLINK')
            blink_led()
            
        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()
        
    except OSError as e:
        cl.close()
        print('Connection closed')

The first, and key, segment is below:

request = cl.recv(1024)
        print(request)
        
        request = str(request)
        led_on = request.find('?led=on')
        led_off = request.find('?led=off')
        led_blink = request.find('?led=blink')
        print('led_on = ', led_on)
        print('led_off = ', led_off)
        print('led_blink = ', led_blink)
        if led_on > -1:
            print('LED ON')
            led.on()
            
        if led_off > -1:
            print('LED OFF')
            led.off()
            
        if led_blink > -1:
            print('LED BLINK')
            blink_led()

The variable “request”, when printed, outputs the first block of text below. The last three lines are the print statements that check for LED on, off or blink:

b'GET /%22?led=blink\\%22 HTTP/1.1\r\nHost: 192.168.43.134\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nReferer: http://192.168.43.134/%22?led=on\\%22\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: en-US,en;q=0.9,es;q=0.8,zh-TW;q=0.7,zh-CN;q=0.6,zh;q=0.5\r\n\r\n'
led_on =  456
led_off =  -1
led_blink =  10

The code above will try to search out specific strings such as “led=on“. If it exists, the value will be more than -1, therefore triggering the relevant if statement.

For example, if led=on exists in the parameters, then the variable led_on will be more than -1, therefore the if led_on > -1 statement triggers and runs led.on();

The only complicated bit here is the led_blink function which will trigger the function:

    def blink_led():
        led.on()
        time.sleep(0.2)
        led.off()
        time.sleep(0.2)

Finally, here’s how you initialize GPIO 2 to operate the LED:

import machine
import time
    
#LED controls
led = machine.Pin(2, machine.Pin.OUT)

We import machine to interact with the GPIO pin. As you can see in the variable led, we want the Pico W to power GPIO 2.

We import time so that we can have a pause of 0.2 seconds in blink_led().

3. RGB LED on Pico W

To light up your RGB LED, you need:

  • Three 330-ohm resistors
  • One RGB LED
  • One jumper wire

On the RGB LED, you will find four legs. One leg will be the longest. That is either a cathode (negative) or anode (positive). My RGB LED had a shared cathode so here’s the connection:

Image shows R, G, B legs on a common cathode RGB LED. (Image from Raspberry Pi Foundation CC-BY-SA)
  • GPIO 15 -> 330-ohm resistor -> red LED
  • GPIO 17 -> resistor -> green LED
  • GPIO 16 -> resistor -> blue LED

Here’s some code to see if you wired it up right:

import machine

red_led = machine.PWM(machine.Pin(15))
green_led = machine.PWM(machine.Pin(17))
blue_led = machine.PWM(machine.Pin(16))

red_led.duty_u16(65534)
green_led.duty_u16(65534)
blue_led.duty_u16(65534)

In this example, we are using PWM, which allows you to vary the brightness of the R, G, B LEDs.

You can change the values passed to [colour].duty_u16 to a value between 0 and 65534. Theoretically, you should be able to pass 65535, but somehow that doesn’t seem to work for me.

Think of passing “0” as saying you want zero per cent brightness. If you pass it 65534, you want 100 per cent brightness.

If you do 65534 for one colour and zero for the rest, you will be able to tell whether you have connected the right GPIO pins to the right LED colour.

So why are we using PWM? Because it will help you get more colours. If you just use an “on-and-off” method, you can get red, green, blue, white-ish and no light. With PWM, you can vary the intensity of the R, G, B LED and create as many colours as you can imagine.

Now, let’s create something that you can control wirelessly!

index.html

The main change here is to have a <form> which has three sliders. These sliders have a value between zero and 100.

Using values zero to 100 helps you visualize brightness as a percentage. It also reduces the amount of code needed to parse out the value from the parameters (seen later in main.py)

When you have set the values for the R, G, B LEDs, you would press submit, and this data is captured by the Pico W.

<!DOCTYPE html>
<html>
    <head>
        <title>Pico W</title>
    </head>
      <body>
        <h1>Pico W RGB LED</h1>
        <form id="form">
            
            <input type="range" min="0" max="100" value="slider_value" name="red" id="red">
            <label for="red">R</p>
            
            <input type="range" min="0" max="100" value="slider_value" name="green" id="green">
             <label for="green">G</p>
            
            <input type="range" min="0" max="100" value="slider_value" name="blue" id="blue">
            <label for="blue">B</p>
            
            <input type="submit" id="submit">
        </form>
        <script>
        
            submit.addEventListener('click', function ()  {
                form.submit();
            }, false);
        
        </script>
</html>

main.py

from wifi import init_wifi
import socket
import machine
import time

init_wifi()

#LED controls
red_led = machine.PWM(machine.Pin(15))
green_led = machine.PWM(machine.Pin(17))
blue_led = machine.PWM(machine.Pin(16))

#Function to load in html page    
def get_html(html_name):
    # open html_name (index.html), 'r' = read-only as variable 'file'
    with open(html_name, 'r') as file:
        html = file.read()
    return html

def find_intensity(color, request_str):
    index = request_str.find(color) + len(color)
    offset = 0
    if request_str[index].isdigit():
        offset = 1
        if request_str[index+1].isdigit():
            offset = 2
            if request_str[index+2].isdigit():
                offset = 3

    intensity = int(request_str[index:index+offset])
    return intensity
    
    
# HTTP server with socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

s = socket.socket()
s.bind(addr)
s.listen(1)

print('Listening on', addr)

# Listen for connections
while True:
    try:
        cl, addr = s.accept()
        print('Client connected from', addr)
        request = cl.recv(1024)
        
        request_str = str(request)
       
        #find intensity ONLY if the params exist in request
        if request_str.find('red') > -1 :
            #int = get rid of decimal
            #/100*65534 = find_intensity returns something 0 to 100,
            # so x/100 = proportion of 65534 that you want to send to LED
            # 65534 = max number you can use for PWM
            red_intensity = int(find_intensity('red=', request_str) /100 * 65534)
            green_intensity = int(find_intensity('green=', request_str) /100 * 65534)
            blue_intensity = int(find_intensity('blue=', request_str) /100 * 65534)
            
            #print('r=' + str(red_intensity))
            #print('g=' + str(green_intensity))
            #print('b=' + str(blue_intensity))
            
            red_led.duty_u16(red_intensity)
            green_led.duty_u16(green_intensity)
            blue_led.duty_u16(blue_intensity)
                        
        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()
        
    except OSError as e:
        cl.close()
        print('Connection closed')

As you press submit on the index.html web page, the Pico W will take the parameters and process it.

Let’s look at some key points in the code, bolded in the code block above.

find_intensity function

This function takes two params: color and request_str. color takes in “red”, “green” or “blue” and finds the value after the equals (=) sign.

For example, your URL after you submit the form is “http://192.168.1.142/?red=89&green=50&blue=50”.

If you pass “red” to find_intensity, it will return 89.

intensity parser

The second bolded block of code represents the code that tells the Pico W’s GPIO how much brightness you want from each LED.

First up, we must ensure that the params exist in the URL. This is done by the if statement request_str.find(‘red’) > -1. I just used ‘red’ because the params will 100% contain the string ‘red’ if you use the form.

If you are visiting your Pico W’s IP address (e.g. http://192.168.1.142/) for the first time, you won’t have the params, so the program will crash if you ran find_intensity.

If params exist, we find the intensity for each LED according to the values submitted. Let’s take a look at red_intensity.

red_intensity = int(find_intensity('red=', request_str) /100 * 65534)
...
red_led.duty_u16(red_intensity)

find_intensity returns an integer zero to 100. We divide this by 100 so you can get a percentage. This percentage divides the maximum value that the duty_u16 method can take.

However, we need an int function to wrap this because you can get a float sometimes and duty_u16 needs an int. For example, say you wanted 41% brightness — 41% of 65534 is 26,868.94. You can’t pass this float as the program will crash.

Buzzer operation on the Pico W

A buzzer is a pretty important component for basic notifications, just like how an LED can tell you about the state of something, a buzzer give you an auditory notification.

Connect the buzzer’s positive terminal to GPIO 16 and ground to a GND pin on the Pico W.

Rather than implementing my own code, I’ve chosen to use Giuseppe Cassibba’s implementation. If you ran his code on the Pico W, you will hear beeps on your buzzer.

But since this code is meant for the Pico, it won’t have any wireless interactivity features.

So, let’s get the fork out!

First, let’s modify the index.html to implement the buttons. Let’s have a button for “ON”, “OFF”, “SCALE”, “MUSIC”.

The first two buttons are self-explanatory. The third button plays a C-scale and the last one plays a piece of music.

<!DOCTYPE html>
<html>
    <head>
        <title>Pico W</title>
    </head>
    <body>
        <h1>Pico W Buzzer</h1>
        <p>Control the buzzer</p>
        <a href=\"?buzzer=on\"><button>ON</button></a>&nbsp;
        <a href=\"?buzzer=off\"><button>OFF</button></a>
        <a href=\"?buzzer=scale\"><button>SCALE</button></a>
        <a href=\"?buzzer=music\"><button>MUSIC</button></a>
    </body>
</html>

Just like before, we create a few <button> tags with <a> tags surrounding them. Upon clicking the buttons, the URL will have a parameter such as /buzzer=on. The Pico W reads this and turns the buzzer on.

Now, let’s look at Giuseppe’s code for the Pico:

from machine import Pin, PWM
from time import sleep

buzzerPIN=16
BuzzerObj=PWM(Pin(buzzerPIN))

def buzzer(buzzerPinObject,frequency,sound_duration,silence_duration):
    # Set duty cycle to a positive value to emit sound from buzzer
    buzzerPinObject.duty_u16(int(65536*0.2))
    # Set frequency
    buzzerPinObject.freq(frequency)
    # wait for sound duration
    sleep(sound_duration)
    # Set duty cycle to zero to stop sound
    buzzerPinObject.duty_u16(int(65536*0))
    # Wait for sound interrumption, if needed 
    sleep(silence_duration)


# Play following notes by changing frequency:
#C (DO)
buzzer(BuzzerObj,523,0.5,0.1)

#D (RE)
buzzer(BuzzerObj,587,0.5,0.1)

#E (MI)
buzzer(BuzzerObj,659,0.5,0.1)

#F (FA)
buzzer(BuzzerObj,698,0.5,0.1)

#G (SOL)
buzzer(BuzzerObj,784,0.5,0.1)

#A (LA)
buzzer(BuzzerObj,880,0.5,0.1)

#B (SI)
buzzer(BuzzerObj,987,0.5,0.1)

#Deactivates the buzzer
BuzzerObj.deinit()

His code plays a C-scale when run using the function buzzer(), which takes in four parameters: Buzzer object, frequency in Hertz, sound duration and pause duration before playing the next sound.

Let’s modify the code so that we can activate the ON, OFF, SCALE and MUSIC.

Here’s how we integrate Giuseppe’s code into our main.py:

from wifi import init_wifi
import socket
import machine
import time

init_wifi()

# Buzzer
buzzerPIN = 16
BuzzerObj = machine.PWM(machine.Pin(buzzerPIN))


def buzzer(buzzerPinObject, frequency, sound_duration, silence_duration):
    # Set duty cycle to a positive value to emit sound from buzzer
    buzzerPinObject.duty_u16(int(65536*0.2))
    # Set frequency
    buzzerPinObject.freq(frequency)
    # wait for sound duration
    time.sleep(sound_duration)
    # Set duty cycle to zero to stop sound
    buzzerPinObject.duty_u16(int(65536*0))
    # Wait for sound interrumption, if needed
    time.sleep(silence_duration)

# Function to load in html page


def get_html(html_name):
    # open html_name (index.html), 'r' = read-only as variable 'file'
    with open(html_name, 'r') as file:
        html = file.read()

    return html


# HTTP server with socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

s = socket.socket()
s.bind(addr)
s.listen(1)

print('Listening on', addr)

# Listen for connections
while True:
    try:
        cl, addr = s.accept()
        print('Client connected from', addr)
        request = cl.recv(1024)

        request = str(request)
        buzzer_on = request.find('?buzzer=on')
        buzzer_off = request.find('?buzzer=off')
        buzzer_scale = request.find('?buzzer=scale')
        buzzer_music = request.find('?buzzer=music')

        if buzzer_on > -1:
            BuzzerObj.duty_u16(int(65536*0.2))
            BuzzerObj.freq(440)

        if buzzer_off > -1:
            BuzzerObj.duty_u16(0)

        if buzzer_scale > -1:
            #C (DO)
            buzzer(BuzzerObj, 523, 0.5, 0.1)

            #D (RE)
            buzzer(BuzzerObj, 587, 0.5, 0.1)

            #E (MI)
            buzzer(BuzzerObj, 659, 0.5, 0.1)

            #F (FA)
            buzzer(BuzzerObj, 698, 0.5, 0.1)

            #G (SOL)
            buzzer(BuzzerObj, 784, 0.5, 0.1)

            #A (LA)
            buzzer(BuzzerObj, 880, 0.5, 0.1)

            #B (SI)
            buzzer(BuzzerObj, 987, 0.5, 0.1)

            BuzzerObj.deinit()

        if buzzer_music > -1:
            pass

        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()

    except OSError as e:
        cl.close()
        print('Connection closed')

In the above implementation, we haven’t written the code for MUSIC.

The code is very similar to the red LED tutorial above, where the Pico W captures the parameters after the Pico W’s IP address. If the param buzzer=ON, it will play a 440Hz sound. If buzzer=scale, it will play the scale that’s taken from Giuseppe’s code.

What about implementing music? Implementing music is a bit more complicated so we should create a new file called constants.py and add a few lines into our main.py.

This code is a fork from Rowan Packard’s Arduino code.

constants.py

A3F = 208  # 208 Hz
B3F = 233  # 233 Hz
B3 = 247  # 247 Hz
C4 = 261  # 261 Hz MIDDLE C
C4S = 277  # 277 Hz
E4F = 311  # 311 Hz
F4 = 349  # 349 Hz
A4F = 415  # 415 Hz
B4F = 466  # 466 Hz
B4 = 493  # 493 Hz
C5 = 523  # 523 Hz
C5S = 554  # 554 Hz
E5F = 622  # 622 Hz
F5 = 698  # 698 Hz
F5S = 740  # 740 Hz
A5F = 831  # 831 Hz

main.py (additions bolded)

from wifi import init_wifi
import socket
import machine
import time
from constants import *

init_wifi()

# Buzzer
buzzerPIN = 16
BuzzerObj = machine.PWM(machine.Pin(buzzerPIN))


def buzzer(buzzerPinObject, frequency, sound_duration, silence_duration):
    # Set duty cycle to a positive value to emit sound from buzzer
    buzzerPinObject.duty_u16(int(65536*0.2))
    # Set frequency
    buzzerPinObject.freq(frequency)
    # wait for sound duration
    time.sleep(sound_duration)
    # Set duty cycle to zero to stop sound
    buzzerPinObject.duty_u16(int(65536*0))
    # Wait for sound interrumption, if needed
    time.sleep(silence_duration)

# Function to load in html page


def get_html(html_name):
    # open html_name (index.html), 'r' = read-only as variable 'file'
    with open(html_name, 'r') as file:
        html = file.read()

    return html


# HTTP server with socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

s = socket.socket()
s.bind(addr)
s.listen(1)

print('Listening on', addr)

# Listen for connections
while True:
    try:
        cl, addr = s.accept()
        print('Client connected from', addr)
        request = cl.recv(1024)

        request = str(request)
        buzzer_on = request.find('?buzzer=on')
        buzzer_off = request.find('?buzzer=off')
        buzzer_scale = request.find('?buzzer=scale')
        buzzer_music = request.find('?buzzer=music')

        if buzzer_on > -1:
            BuzzerObj.duty_u16(int(65536*0.2))
            BuzzerObj.freq(440)

        if buzzer_off > -1:
            BuzzerObj.duty_u16(0)

        if buzzer_scale > -1:
            #C (DO)
            buzzer(BuzzerObj, 523, 0.5, 0.1)

            #D (RE)
            buzzer(BuzzerObj, 587, 0.5, 0.1)

            #E (MI)
            buzzer(BuzzerObj, 659, 0.5, 0.1)

            #F (FA)
            buzzer(BuzzerObj, 698, 0.5, 0.1)

            #G (SOL)
            buzzer(BuzzerObj, 784, 0.5, 0.1)

            #A (LA)
            buzzer(BuzzerObj, 880, 0.5, 0.1)

            #B (SI)
            buzzer(BuzzerObj, 987, 0.5, 0.1)

            BuzzerObj.deinit()

        if buzzer_music > -1:
            pause = 0.05
            # pauses between notes
            t = 0.125
            # time that music note plays

            music_notes = [B4F, B4F, A4F, A4F,
                           F5, F5, E5F, B4F, B4F, A4F, A4F, E5F, E5F, C5S, C5, B4F,
                           C5S, C5S, C5S, C5S,
                           C5S, E5F, C5, B4F, A4F, A4F, A4F, E5F, C5S,
                           B4F, B4F, A4F, A4F,
                           F5, F5, E5F, B4F, B4F, A4F, A4F, A5F, C5, C5S, C5, B4F,
                           C5S, C5S, C5S, C5S,
                           C5S, E5F, C5, B4F, A4F, A4F, A4F, E5F, C5S, C5S]

            rhythm = [1, 1, 1, 1,
                      3, 3, 6, 1, 1, 1, 1, 3, 3, 3, 1, 2,
                      1, 1, 1, 1,
                      3, 3, 3, 1, 2, 2, 2, 4, 8,
                      1, 1, 1, 1,
                      3, 3, 6, 1, 1, 1, 1, 3, 3, 3, 1, 2,
                      1, 1, 1, 1,
                      3, 3, 3, 1, 2, 2, 2, 4, 8, 4]

            for i in range(len(music_notes)):
                buzzer(BuzzerObj, music_notes[i], rhythm[i]*t, pause)

        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()

    except OSError as e:
        cl.close()
        print('Connection closed')

As you can see, there are two arrays, music_notes and rhythm. You would then run a for loop to put these values into the buzzer() function. Also, at the top of the code, we import all variables from constants.py.

The buzzer() function also uses two new variables — t and pause. These two variables help you tune tempo of the music. t defines how long each note should be played and pause defines how long of a silence there should be between notes.

Pico W and HC-SR04 ultrasonic sensor

pico-w-ultrasonic-sensor
`

For the previous tutorials, we’ve been using the Pico W to send commands to the LEDs and buzzers.

But what if we’re using the Pico W to receive information?

In this case, we’re talking about the HC-SR04 ultrasonic distance sensor.

When you plug in your ultrasonic sensor, be sure to know if it’s a 5V or 3.3V component. I plugged my 5V version into the 3.3V pin and got no response from the program, which I will share below. Once I moved the power source to the 5V pin, I immediately got a response.

Apparently, some newer versions of the HC-SR04 can take both 3.3V to 5V. Perhaps it’s best to just try the 3.3V pin first and see if you get a result. If not, try the 5V.

Here’s the schematics and the program you can run in Thonny. If you get a response, that means you have plugged everything in the right way, including the voltage.

Wiring

The wiring from the labelled pins of the HC-SR04 sensors are as follows:

  • VCC to 3.3V or 5V pin (if in doubt, try 3.3V first)
  • TRIG to GPIO 16
  • ECHO to GPIO 15
  • GND to GND

Test program for HC-SR04

In order to test if your wiring is correct, try this code that will print out the distance on the Thonny Shell.

from machine import Pin
import time
trigger = Pin(16, Pin.OUT)
echo = Pin(15, Pin.IN)
def ultrasonic():
   trigger.low()
   time.sleep_us(1)
   trigger.high()
   time.sleep_us(10)
   trigger.low()
   while echo.value() == 0:
       signaloff = time.ticks_us()
   while echo.value() == 1:
       signalon = time.ticks_us()
   timepassed = signalon - signaloff
   distance = (timepassed * 0.0340) / 2
   print("Distance = ",distance,"cm")
while True:
   ultrasonic()
   time.sleep(1)

Your output should be:

Distance =  9.707001 cm
Distance =  9.707001 cm
Distance =  8.619 cm
Distance =  8.415001 cm
Distance =  8.551001 cm
Distance =  8.551001 cm
Distance =  8.619 cm

If the program runs and quits without any output, you probably wired something wrongly. When I plugged the sensor to a 3.3V pin instead of a 5V, the program quit with no response.

Getting data sent to your browser

Let’s whip up the files that we used to serve a web page and modify it so that we can see the distance values.

Here’s the final code:

main.py

from wifi import init_wifi
import socket
from machine import Pin
import time

init_wifi()

# ultrasonic sensor pins and functions
trigger = Pin(16, Pin.OUT)
echo = Pin(15, Pin.IN)


def ultrasonic():
    trigger.low()
    time.sleep_us(1)
    trigger.high()
    time.sleep_us(10)
    trigger.low()
    while echo.value() == 0:
        signaloff = time.ticks_us()
    while echo.value() == 1:
        signalon = time.ticks_us()
    timepassed = signalon - signaloff
    distance = (timepassed * 0.0340) / 2
    return distance

# Function to load in html page

def get_html(html_name, distance):
    # open html_name (index.html), 'r' = read-only as variable 'file'
    with open(html_name, 'r') as file:
        html = file.read()
    content = html.replace(
        "<h2 id=\"ultrasonic\"></h2>", f"<h2 id=\"ultrasonic\">{distance}cm</h2>")

    return content


# HTTP server with socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

s = socket.socket()
s.bind(addr)
s.listen(1)

print('Listening on', addr)

# Listen for connections
while True:
    try:
        cl, addr = s.accept()
        print('Client connected from', addr)
        request = cl.recv(1024)
        print(ultrasonic())
        response = get_html('index.html', ultrasonic())
        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')

index.html

<!DOCTYPE html>
<html>
    <head>
        <title>Pico W</title>
    </head>
      <body>
        <h1>Pico W Ultrasonic Distance</h1>
        <h2 id="ultrasonic"></h2>
        <script>
            setInterval(() => location.reload(), 500)
        </script>
</html>

Let’s look at some snippets of the code to understand what’s happening.

Code to get ultrasonic distance

def ultrasonic():
    trigger.low()
    time.sleep_us(1)
    trigger.high()
    time.sleep_us(10)
    trigger.low()
    while echo.value() == 0:
        signaloff = time.ticks_us()
    while echo.value() == 1:
        signalon = time.ticks_us()
    timepassed = signalon - signaloff
    distance = (timepassed * 0.0340) / 2
    return distance

The code block above triggers the emission of an ultrasonic wave. The time.sleep between the lows and highs are necessary for the functioning of the ultrasonic sensor.

To measure how much time it takes between the emission of the ultrasonic wave and the time for the sound wave to return, we use time.ticks_us to measure the time of emission and the time for the echo to be detected.

time.ticks_us is an arbitrary number, so we will have to subtract signalon from signaloff to get the time passed.

To get the distance, we use the formula distance = (timepassed * speedofsound) / 2. Speed of sound is 340m/s, which therefore is 0.0340.

The reason why we have to divide it by two is because the sound wave travels to the object and returns back.

Code to serve web page with distance value

def get_html(html_name, distance):
    # open html_name (index.html), 'r' = read-only as variable 'file'
    with open(html_name, 'r') as file:
        html = file.read()
    content = html.replace(
        "<h2 id=\"ultrasonic\"></h2>", f"<h2 id=\"ultrasonic\">{distance}cm</h2>")

    return content

...

response = get_html('index.html', ultrasonic())

This function has been modified a little from the previous tutorials, taking in an additional parameter distance, which runs ultrasonic().

The function opens the index.html file normally but uses the replace method to find the <h2> and inserts the distance variable.

index.html pings server every 500ms to get new value

index.html

The main web page receives a function that reloads the web page every 500ms. Every reload pings the Pico W to get the new ultrasonic distance value.

    <script>
            setInterval(() => location.reload(), 500)
        </script>

A work in progress…

Building a mega-tutorial takes a bit of time so I thought it’s best to publish whatever I have here and add onto it as I create more content.

Still to come:

  • AJAX on the Pico W
  • Controlling a relay/transistor
  • Servo control
  • DHT22 weather web server

In the meantime, your suggestions are welcome. Leave a comment in the comment box below, email me or Tweet me.

Leave a Comment