Digital sound sensor and the Raspberry Pico

There are a couple of different options to do sound detection. One provides a simple digital signal when the sound passes a threshold. The other provides a threshold digital signal as well as an analog amplitude signal.

Digital Sound Detection Option

You can pick these sensors up for about $1 apiece on Amazon.

The sound detection sensor detects whether sound has exceeded a threshold value. Sound is detected via microphone and fed into an LM393 op amp. The sound level set point is adjusted via an on board potentiometer. When the sound level exceeds the set point, an LED on the module is illuminated and the output is set low.

Specifications of sound detection sensor module:

  • Working voltage: DC 3.3-5V
  • Adjustable Sensitivity
  • Dimensions: 32 x 17 mm
  • Signal output indication LED
  • Single channel output in the form of digital switching outputs (0 and 1 high and low)
  • Outputs low level and the signal light when there is sound

Schematic Diagram

The circuit

The program

The Pico MicroPython program to access the signal via any GPIO port. In this case we will use GPIO 16.

from machine import Pin
import utime
di=Pin(16,Pin.IN)
def sound_handler(pin):
    utime.sleep_ms(100)
    if pin.value()==False:
        print("ALARM! Sound detected!")
di.irq(trigger=machine.Pin.IRQ_RISING,handler=sound_handler)

This is a simple program that prints “Alarm! Sound detected” when the interrupt is triggered. You could do the sensing in a main loop as well. You will want to adjust the pot on the sound sensor board to get the desired level of sensitivity. I looked at using the one that also had analog output, but it output an analog audio signal that is more difficult to sense effectively and those boards cost slightly more as well.

Raspberry Pico and GC9A01 Round display in MicroPython

I recently saw some interesting applications of an inexpensive round LCD display. In looking around I didn’t see any good explanations on how to use the display with the Pico and MicroPython, so I thought I’d write one up.

First, I found the hardware I was planning to use on Amazon. The model was the 1.28 Inch TFT LCD Display Module Round RGB 240 * 240 GC9A01 Driver 4 Wire SPI Interface 240×240 PCB. This version had an IC board that fits right into a breadboard with all the current limiting resistors… onboard. This model does not have a direct line to drive the backlight though. It is only $18 for three of them at the time I am writing this.

The pinouts for the board are:

  • VCC – 3.3 to 5VDC
  • GND – Ground
  • SCL – CLOCK
  • SDA – Data In
  • DC – Data/Command
  • CS – Chip Select
  • RST – Reset

I wired my module to the Pico as shown below:

The Fritzing model is not perfect but good enough to show what I was trying to do. VCC is on the left and RST is on the right.

Much of the work to connect via MicroPython was included in a MicroPython driver for GC9A01 available on GitHub. The developer of the library has a fairly extensive set of sample programs that exercise the various features. The developer also has a pre-built set of firmware for the Pico and the PicoW so you just need to install the ‘OS’ on the chip.

Armed with all this information, I dug into the project. The first issue I encountered was that the firmware didn’t load properly (who knows what I had on that Pico previously), so I did a hard reset and loaded in the developer’s firmware. Now Thonny worked correctly accessing the device.

I next had to figure out the correct pins to use on the Pico that had the features I needed. That breadboard illustration above was the result of overcoming those issues. I ended up with a working test program that looked like:

from machine import Pin, SPI
import gc9a01 as gc9a01
# hardware config
SCL_PIN = 14 #chip pin 19
SDA_PIN = 15 #chip pin 20
DC_PIN = 4   #chip pin 6
CS_PIN = 5   #chip pin 7
RST_PIN = 6  #chip pin 9
spi = SPI(1, baudrate=60000000, sck=Pin(SCL_PIN), mosi=Pin(SDA_PIN))
# initialize the display
tft = gc9a01.GC9A01(
    spi,240,240,
    dc=Pin(DC_PIN, Pin.OUT),
    cs=Pin(CS_PIN, Pin.OUT),
    reset=Pin(RST_PIN, Pin.OUT),
    )
tft.fill(gc9a01.BLACK)
print("black")
# x, y, width, height
# red
tft.fill_rect(50,  75, 50, 60, gc9a01.color565(255,0,0))
print("red")
# green
tft.fill_rect(100, 75, 50, 60, gc9a01.color565(0,255,0))
print("green")
# blue
tft.fill_rect(150, 75, 50, 60, gc9a01.color565(0,0,255))
print("blue")

You can see below that the program draws blue, green, and red squares on the display.

Now that I had a handle on getting the screen to display something, I needed to create the tft_config.py file used in all the examples.

It ended up looking like:

"""Generic 240x240 GC9A01

Generic display connected to a Raspberry Pi Pico.

"""
# hardware config
SCL_PIN = 14 #chip pin 19 
SDA_PIN = 15 #chip pin 20 
DC_PIN = 4   #chip pin 6 
CS_PIN = 5   #chip pin 7 
RST_PIN = 6  #chip pin 9 

from machine import Pin, SPI
import gc9a01

TFA = 0
BFA = 0
WIDE = 0
TALL = 1

def config(rotation=0, buffer_size=0, options=0):
    """Configure the display and return an instance of gc9a01.GC9A01."""

    spi = SPI(1, baudrate=60000000, sck=Pin(SCL_PIN), mosi=Pin(SDA_PIN))
    return gc9a01.GC9A01(
        spi,
        240,
        240,
        reset=Pin(RST_PIN, Pin.OUT),
        cs=Pin(CS_PIN, Pin.OUT),
        dc=Pin(DC_PIN, Pin.OUT),
        rotation=rotation,
        options=options,
        buffer_size=buffer_size,
    )
Now, I can pull in one of the examples and as long as the python code and the tft_config.py file are in the same directory the sample code should work. Here is the lines sample program:
"""
lines.py
========
.. figure:: /_static/lines.png
  :align: center

Benchmarks the speed of drawing horizontal and vertical lines on the display.
"""

import random
import time
import gc9a01
import tft_config

tft = tft_config.config(tft_config.TALL)
WIDTH = tft.width()
HEIGHT = tft.height()

def time_function(func, *args):
    """time a function"""
    start = time.ticks_ms()
    func(*args)
    return time.ticks_ms() - start

def horizontal(line_count):
    """draw line_count horizontal lines on random y positions"""
    for _ in range(line_count):
        y = random.randint(0, HEIGHT)
        tft.line(
            0,
            y,
            WIDTH,
            y,
            gc9a01.color565(
                random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
            ),
        )

def vertical(line_count):
    """draw line_count vertical lines on random x positions"""
    for _ in range(line_count):
        x = random.randint(0, WIDTH)
        tft.line(
            x,
            0,
            x,
            HEIGHT,
            gc9a01.color565(
                random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
            ),
        )

def diagonal(line_count):
    """draw line_count diagnal lines on random y positions"""
    x = 0
    for _ in range(line_count):
        x += 1
        x %= WIDTH
        tft.line(
            x,
            0,
            WIDTH - x,
            HEIGHT,
            gc9a01.color565(
                random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
            ),
        )

def main():
    tft.init()
    tft.fill(0)
    print("")
    print("horizonal: ", time_function(horizontal, 1000))
    print("vertical: ", time_function(vertical, 1000))
    print("diagonal: ", time_function(diagonal, 1000))

main()

The output generated was:

horizonal:  289
vertical:  283
diagonal:  2093

Meaning that the MicroPython version of this sample code took 289 ms to draw 1000 horizontal random color lines… Just as a test I took the random generator out and about 25ms of the time was taken generating the random colors for each type of line.

Now that I have this testing behind me, I can move on to something more interest…

For those who remember the invisible monster… or maybe the Robot Spy.

More Raspberry Pico material – Controlling a LED light strip

This is part of an ongoing effort to extend the material in the Getting started with MicroPython on the Raspberry Pi Pico book. I plan to put out each of the extension chapters and appendices, so others can review them without downloading the entire book I put together for the class. This example is a bit more complicated than most.

That’s because we’re having to deal with things at a lower level of the hardware. Most of the time, MicroPython can hide a lot of the complexities of how things work on the microcontroller. When we do something like:

print("hello")

…we don’t have to worry about the way the microcontroller stores the letters, or the format in which they get sent to the serial terminal, or the number of clock cycles the serial terminal takes.

This is all handled in the background. However, when we get to Programmable Input and Output (PIO), we need to deal with things at a much lower level.

We’re going to go on a brief tour of what is going on, and hopefully understand how PIO on Pico offers some real advantages over the options on other microcontrollers. However, understanding all the low-level data manipulation required to create PIO programs takes time to fully get your head around, so don’t worry if it seems a little opaque. If you’re interested in tackling this low-level programming, then this should give you the knowledge to get started and point you in the right direction to continue your journey. If you’re more interested in working at a higher level and would rather leave the low-level wrangling to other people, we’ll show you how to use PIO programs.

Data in and data out

We’ve looked at ways of controlling the pins on the Pico using MicroPython. However, what if we want to connect a device that doesn’t communicate in SPI and I2C? What if the device has its own special protocol?

There are a couple of ways we can do this. On most MicroPython devices, you need to do a process called bit banging, where you implement the protocol in MicroPython. Using this, you turn the pins on or off in the right order to send data.

There are three downsides to this.

  • It’s slow. MicroPython does some things well, but it doesn’t run as fast as natively compiled code.
  • We have to juggle doing this with the rest of our code that is running on the microcontroller.
  • Timing-critical code can be hard to implement reliably. Fast protocols can need things to happen at very precise times, and with MicroPython we can be precise, but if you’re trying to transfer megabits a second, you need things to happen every millisecond or possibly every few hundred nanoseconds. That’s hard to achieve reliably in MicroPython.

The Pico has a solution to this: Programmable I/O. There are some extra, really stripped back processing cores that can run simple programs to control the IO pins. You can’t program these cores with MicroPython – you must use a special language just for them – but you can program them from MicroPython. Here is an example:

#imports
from rp2 import PIO, StateMachine, asm_pio
from machine import Pin
import utime
#PIO routine definition
@asm_pio(set_init=PIO.OUT_LOW)
def led_quarter_brightness():
    set(pins, 0) [2]
    set(pins, 1)
@asm_pio(set_init=PIO.OUT_LOW)
def led_half_brightness():
    set(pins, 0)
    set(pins, 1)     
@asm_pio(set_init=PIO.OUT_HIGH)
def led_full_brightness():
    set(pins, 1)
#device definition       
sm1 = StateMachine(1, led_quarter_brightness, freq=10000, set_base=Pin(25))
sm2 = StateMachine(2, led_half_brightness, freq=10000, set_base=Pin(25))
sm3 = StateMachine(3, led_full_brightness, freq=10000, set_base=Pin(25))
#main loop
while(True):
    sm1.active(1)
    utime.sleep(1)
    sm1.active(0)
    sm2.active(1)
    utime.sleep(1)
    sm2.active(0)
    sm3.active(1)
    utime.sleep(1)
    sm3.active(0)

There are three methods here that all look unusual. These set the on-board LED to quarter, half, and full brightness. The reason they look a little strange is because they’re written in a special language for the PIO system of Pico. You can probably guess what they do – flick the LED on and off very quickly. The instruction set(pins,0) turns a GPIO pin off and set(pins, 1) turns the GPIO pin on. Each of the three methods has a descriptor above it that tells MicroPython to treat it as a PIO program and not a normal method. These descriptors can also take parameters that influence the behavior of the programs. In these cases, we’ve used the set_init parameter to tell the PIO whether the GPIO pin should start off being low or high.

Each of these methods – which are really mini programs that run on the PIO state machines – loops continuously. So, for example, led_half_brightness will constantly turn the LED on and off so that it spends half its time off and half its time on. led_full_brightness will similarly loop, but since the only instruction is to turn the LED on, this doesn’t change anything.

The slightly unusual one here is led_quarter_brightness. Each PIO instruction takes exactly one clock cycle to run (the length of a clock cycle can be changed by setting the frequency, as we’ll see later). However, we can add a number between 1 and 31 in square brackets after an instruction, and this tells the PIO state machine to pause by this number of clock cycles before running the next instruction. In led_quarter_brightness, the two set instructions each take one clock cycle, and the delay takes two clock cycles, so the total loop takes four clock cycles. In the first line, the set instruction takes one cycle, and the delay takes two, so the GPIO pin is off for three of these four cycles. This makes the LED a quarter as bright as if it were on constantly.

Once you’ve got your PIO program, you need to load it into a state machine. Since we have three programs, we need to load them into three state machines (there are eight you can use, numbered 0–7). This is done with a line like:

sm1 = StateMachine(1, led_quarter_brightness, freq=10000, set_base=Pin(25))

The parameters here are: The state machine number The PIO program to load The frequency (which must be between 2000 and 125000000) The GPIO pin that the state machine manipulates

There are some additional parameters that you’ll see in other programs that we don’t need here. Once you’ve created your state machine, you can start and stop it using the active method with 1 (to start) or 0 (to stop). In our loop, we cycle through the three different state machines.

Save your program and see if you receive the expected results.

A real example

The previous example was a little contrived, so let’s look at a way of using PIO with a real example. WS2812B LEDs (sometimes known as NeoPixels) are a type of light that contains three LEDs (one red, one green, and one blue) and a small microcontroller. They’re controlled by a single data wire with a timing-dependent protocol that’s hard to bit-bang.

Figure H-1 – Connecting an LED strip

Wiring your LED strip is simple. Depending on the manufacturer of your LED strip, you may have the wires already connected, you may have a socket that you can push header wires in, or you may need to solder them on yourself. Check the wire colors, because in this case, the green wire is not ground but the data signal?!?

One thing you need to be aware of is the potential current draw. While you can add an almost endless series of NeoPixels to your Pico, there’s a limit to how much power you can get out of the 5 V pin . Here, we’ll use eight LEDs, which is perfectly safe, but if you want to use many more than this, you need to understand the limitations and may need to add a separate power supply. You can cut a longer strip to length, and there should be cut lines between the LEDs to show you where to cut. There’s a good discussion of the various issues at: Powering NeoPixels | Adafruit NeoPixel Überguide | Adafruit Learning System

Now we’ve got the LEDs wired up, let’s look at how to control it with PIO:

import array, utime
from machine import Pin
import rp2
from rp2 import PIO, StateMachine, asm_pio
# variables definition
# Configure the number of WS2812 LEDs.
NUM_LEDS = 10 
@asm_pio(sideset_init=PIO.OUT_LOW, out_shiftdir=PIO.SHIFT_LEFT, autopull=True, pull_thresh=24) 
def ws2812():
    T1 = 2
    T2 = 5
    T3 = 3
    label("bitloop")
    out(x, 1)              .side(0)    [T3 -1]
    jmp(not_x, "do_zero")  .side(1)    [T1 -1]
    jmp("bitloop")         .side(1)    [T2 -1]
    label("do_zero")
    nop()                  .side(0)    [T2 -1] 
# Create the StateMachine with the ws2812 program, outputting on Pin(0).
sm = StateMachine(0, ws2812, freq=8000000, sideset_base=Pin(0)) 
# Start the StateMachine, it will wait for data on its FIFO.
sm.active(1) 
# Display a pattern on the LEDs via an array of LED RGB values.
ar = array.array("I", [0 for _ in range(NUM_LEDS)])
while True:
    print("blue")
    for j in range(0, 255):
        for i in range(NUM_LEDS):
            ar[i] = j
        sm.put(ar,8)
        utime.sleep_ms(10)
    print("red") 
    for j in range(0, 255):
        for i in range(NUM_LEDS):
            ar[i] = j<<8
        sm.put(ar,8)
        utime.sleep_ms(10)
    print("green")
    for j in range(0, 255):
        for i in range(NUM_LEDS):
            ar[i] = j<<16
        sm.put(ar,8)
        utime.sleep_ms(10)
     
    print("white")
    for j in range(0, 255):
        for i in range(NUM_LEDS):
            ar[i] = (j<<16) + (j<<8) + j
        sm.put(ar,8)
        utime.sleep_ms(10)

    print("off")
    for i in range(NUM_LEDS):
        ar[i] = 0
    sm.put(ar,8)
    utime.sleep(1)

The basic way this program works is that 800,000 bits of data are sent per second (notice that the frequency is 8000000 and each cycle of the program is 10 clock cycles). Every bit of data is a pulse – a short pulse indicating a 0 and a long pulse indicating a 1. A big difference between this and our previous program is that MicroPython needs to be able to send data to this PIO program.

There are two stages for data coming into the state machine. The first is a bit of memory called a First In, First Out (or FIFO). This is where our main Python program sends data to. The second is the Output Shift Register (OSR). This is where the out() instruction fetches data from. The two are linked by pull instructions which take data from the FIFO and put it in the OSR. However, since our program is set up with autopull enabled with a threshold of 24, each time we’ve read 24 bits from the OSR, it will be reloaded from the FIFO.

The instruction out(x,1) takes one bit of data from the OSR and places it in a variable called x (there are only two available variables in PIO: x and y).

The jmp instruction tells the code to move directly to a particular label, but it can have a condition. The instruction jmp(not_x, "do_zero") tells the code to move to do_zero if the value of x is 0 (or, in logical terms, if not_x is true, and not_x is the opposite of x– in PIO-level speak, 0 is false and any other number is true).

There’s a bit of jmp code that is mostly there to ensure that the timings are consistent because the loop has to take exactly the same number of cycles every iteration to keep the timing of the protocol in line.

The one aspect we’ve been ignoring here is the .side() bits. These are similar to set() but they take place at the same time as another instruction. This means that out(x,1) takes place as .side(0) is setting the value of the sideset pin to 0. Note that the spacing is important.

That’s quite a bit going on for such a small program. Now we’ve got it active, let’s look at how to use it. This section starts out with the comment

# Display a pattern…

Here we keep track of an array called ar that holds the data we want our LEDs to have (we’ll look at why we created the array this way in a little while). Each number in the array contains the data for all three colors on a single light. The format is a little strange as it’s in binary. One thing about working with PIO is that you often need to work with individual bits of data. Each bit of data is a 1 or 0, and numbers can be built up in this way, so the number 2 in base 10 (as we call normal numbers) is 10 in binary. 3 in base 10 is 11 in binary. The largest number in eight bits of binary is 11111111, or 255 in base 10. We won’t go too deep into binary here, but if you want to find out more, you can try the Binary Hero project here: Binary hero – Introduction | Raspberry Pi Projects.

To make matters a little more confusing, we’re storing three numbers in a single number. This is because in MicroPython, whole numbers are stored in 32 bits, but we only need eight bits for each number. There’s a little free space at the end as we only need 24 bits, but that’s OK.

The first eight bits are the blue values, the next eight bits are red, and the final eight bits are green. The maximum number you can store in eight bits is 255, so each LED has 255 levels of brightness. We can do this using the bit shift operator <<. This adds a certain number of 0s to the end of a number, so if we want our LED to be at level 1 brightness in red, green, and blue, we start with each value being 1, then shift them the appropriate number of bits. For green, we have:

1 <<16 = 10000000000000000

For red we have:

 1 << 8 = 100000000

And for blue, we don’t need to shift the bits at all, so we just have 1. If we add all these together, we get the following (if we add the preceding bits to make it a 24-bit number):

000000010000000100000001 The rightmost eight bits are the blue, the next eight bits are red, and the leftmost eight bits are green.

The final bit that may seem a bit confusing is the line:

ar = array.array("I", [0 for _ in range(NUM_LEDS)])

This creates an array which has I as the first value, and then a 0 for every LED. The reason there’s an I at the start is that it tells MicroPython that we’re using a series of 32-bit values. However, we only want 24 bits of this sent to the PIO for each value, so we tell the put command to remove eight bits with:

sm.put(ar,8)

Lesson challenge – Can you program each LED to display a rotating light that races back and forth along the strip? How about a thermometer where the temperature is displayed based on color and the number of lights. Blue for the 60s, Green for the 70s, Yellow for the 80s and Red for the 90s.
So 72 degrees would be 2 green lights, 85 degrees would be 5 Yellow lights.

All the PIO instructions

The language used for PIO state machines is very sparse, so there are only a small number of instructions. In addition to those we’ve looked at, you can use:

in () – moves between 1 and 32 bits into the state machine (similar, but opposite to out()).

push() – sends data to the memory that links the state machine and the main

MicroPython program.

pull() – gets data from the chunk of memory that links the state machine and the main MicroPython program. We haven’t used it here because, by including autopull=True in our program, this happens automatically when we use out().

mov() – moves data between two locations (such as the x and y variables).

irq() – controls interrupts. These are used if you need to trigger a particular thing to run on the MicroPython side of your program.

wait() – pauses until something happens (such as a IO pin changes to a set value or an interrupt happens).

Although there are only a small number of possible instructions, it’s possible to implement a huge range of communications protocols. Most of the instructions are for moving data about in some form. If you need to prepare the data in any particular way, such as manipulating the colors you want your LEDs to be, this should be done in your main MicroPython program rather than the PIO program.

You can find more information on how to use these, and the full range of options for PIO in MicroPython, on Raspberry Pi Pico in the Pico Python SDK document – and a complete reference to how PIO works in the RP2040 Databook. Both are available at Raspberry Pi Documentation – Raspberry Pi Pico.

More Raspberry Pico material – Adding Bluetooth

This is part of an ongoing effort to extend the material in the Getting started with MicroPython on the Raspberry Pi Pico book. I plan to put out each of the extension chapters and appendices, so others can review them without downloading the entire book I put together for the class.

Although the Pico does not have a Bluetooth interface included with it, it is easy to add one. The bigger problem will be getting the Pico program to talk with the Bluetooth program that is running on your phone. This example uses MicroPython and an Android phone to interface with the Pico.

To access Bluetooth, we’ll use the SH-HC-08 Bluetooth to serial port module with the Raspberry Pi Pico. This Bluetooth device uses serial communication to transmit and receive data serially over standard Bluetooth radio frequency. Therefore, we will use TX, RX pins of Raspberry Pi Pico to connect with the module. More detailed information can be found here: Microsoft Word – HC-08 USER MANUAL V2.4_20161208.doc (ecksteinimg.de)

The HC-08 is a Bluetooth 4.0 module, compatible with iPhone 4s/5/5s 6/6s and android phones 4.3 or later. The main chip is cc2541F256 manufactured by TI, whose default data rate is 9600 baud and has a working voltage 3.3V to 6V. You can modify the baud rate and the Bluetooth name via the AT command set.

Figure G-1 – HC-08 Pinout

The table below briefly describes the functionality of each pin.

PinDescription
VCCThe operating voltage range is 3.3 volts. But I/O pins can withstand voltage of up to 5 volts. Therefore, we can connect 5 volts power source to this pin.
GNDGround reference of both the microcontroller and HC-08 should be at the same level. Therefore, we should connect a power supply, HC05, and Raspberry Pi Pico ground pins to each other.
TxThe HC-08 Bluetooth module uses UART communication to transmit data. This is a transmitter pin. The TX pin will be the data transfer pin of the module in UART.
RxThis pin is a data receiving the pin in UART communication. It is used to receive data from the microcontroller and transmits it through Bluetooth.
StateThe state shows the current state of the Bluetooth. It gives feedback to the controller about the connectivity of Bluetooth with another device. This pin has an internal connection with the onboard LED which shows the working of HC05.
Enable/KeyUsing an external signal, Enable/Key pin is used to change the HC-08 mode between data mode and command mode. The HIGH logic (3.3V) input will transfer the device in command mode and the LOW logic input will change the mode to data mode. By default, it works in data mode.
ButtonThe command and data mode states are changeable through a button present on the module. You can apply voltage to the Enable/Key to perform the same function.
LEDThis pin shows the working status of module along with the State pin

Figure G-2 – Connecting HC-08 to the Pico

To connect the Bluetooth module, wire it up as shown. When you plug in the Pico, the HC-08 should start to flash.

Android application

We will use an Android smartphone to connect Raspberry Pi Pico. We will also use a Bluetooth terminal application to pair the two devices together.

Setting up the Android App

We will use an android smartphone to connect with our Raspberry Pi Pico. To do that you will have to perform a series of steps. For Android, go to the Play Store and download the application by the name: Serial Bluetooth terminal.

After you have installed the ‘Serial Bluetooth Terminal app, open it. On the top left corner of the screen, you will find three horizontal bars. Tap it.

Figure G-3 – Program Settings

This will bring up a settings window. Now click on the Devices option.

The device screen should now be displayed, and you can select Bluetooth LE and Scan. If everything is working correctly you should have the option of selecting SC-HC-08 (or whatever is equivalent for your model of Bluetooth device).

Once your device connects, you should be able to go back to the main screen and see a display like this:

At first, you will get the message: ‘Connecting to HC-08.’ After a successful connection, you will get the message: ‘Connected.’

Figure G-6 – Connection message

Now we will configure the buttons that will be used to toggle the LED. First long click the M1 button as shown below:

Figure G-7 – Message button M1

Add the configuration information for the button. Here we are configuring this button as “LED on” button with a value of 31 in HEX.

Figure G-8 – LED on definition

Likewise, create another button e.g., M2 as “LED off” button and associate a value (32 hex) with it as well.

Figure G-9 – LED off definition

Finally, we are going to add one more button to toggle the LED.

Figure G-10 – LED toggle definition

Now we will have three buttons through which we will be able to control the state of the LED.

Figure G-11 – Our buttons

MicroPython: Raspberry Pi Pico LED Control

Here is a program for the Pico that can be controlled from the Android application.

from machine import Pin,UART
#constant
LED_GPx = 2
#device definition
uart = UART(0,9600)
led = Pin(LED_GPx, Pin.OUT)
#main
while True:
    if uart.any():
        data = uart.readline()
        # print(data) # for debugging purposes
        if data== b'1':
            led.on()
            print("LED is now ON!")
        elif data== b'2':
            led.off()
            print("LED is now OFF!")
        elif data== b'3':
            led.toggle()
            if led.value() == False:
                print("LED is OFF!")
            else:
                print("LED is ON!")

A walkthrough of the program

We will start by importing the Pin and UART classes from the machine module.

from machine import Pin,UART

Then we will create an UART object by using UART() and specify the UART channel as the first parameter and the baud rate as the second parameter. We are using UART0 in this case with baud rate 9600 for the UART communication.

uart = UART(0,9600)

Next, we will define the Raspberry Pi Pico GPIO pin that we will connect with the LED. In this case GP2. You can use any appropriate output port for your application.

LED_GPx = 2

We will configure the led pin as an output through Pin() function and pass the GPIO pin as the first parameter and Pin.OUT as the second parameter inside it.

led = Pin(LED_GPx, Pin.OUT)

Inside the infinite loop we will first check if there is any data being received through the UART channel. If there is any data, it will get saved in the variable ‘data.’ This will be achieved by using uart.readline(). We will press each button and see what we obtain in the shell terminal of Thonny the first time.

while True:
    if uart.any():
        data = uart.readline()
        print(data)

In our case when we pressed the LED ON button, we obtained b’1′ in the shell terminal. Likewise, when we pressed LED OFF button, we obtained b’2′ in the terminal. We toggle the LED if we receive a b’3.

If LED ON button was pressed, then the LED will turn ON and print LED is now ON! in the shell terminal of Thonny.

If LED OFF button was pressed, then the LED will turn OFF and print LED is now OFF! in the shell terminal of Thonny.

Finally, if the LED Toggle button is pressed, we toggle the LED and check the state of the LED and print the appropriate message.

Hopefully, that is enough of an example that you can take it from here, to do what you want.

More Raspberry Pico material – Trouble shooting

This is part of an ongoing effort to extend the material in the Getting started with MicroPython on the Raspberry Pi Pico book. If there are any overlaps in the material covered (either from that book or from some of the previous posts on this site) they have been updated and Americanized. I plan to put out each of the extension chapters and appendices, so others can review them without downloading the entire book I put together for the class.

In my last post on troubleshooting I talked about some of the basic hardware issues that some encounter. In this post, I’d like to go into some tricks related to software debugging in the Thonny environment with MicroPython.

Some individuals who were taking the class ran into a situation where they really wished they had a debugger.

A debugger is a very specialized developer tool that attaches to your running app and allows you to inspect your code. It can look at line by line execution as well as all the variables along the way. Though Thonny has some commands under the Run menu that look like they facilitate debugging, they do not work using a single Pico. Thankfully, for most Python the input and print statement are sufficient. There are hardware debugging solutions available if you really need them. We’re not going to worry about them at this point.

For example, here is a voltage divider program:

#imports
import machine
import utime
#constants
POTENTIOMETER_ADCx =26
#define variables
conversion_factor = 3.3/(65535)
#define devices
potentiometer = machine.ADC(POTENTIOMETER_ADCx)
#main loop
while True:
    voltage = potentiometer.read_u16() * conversion_factor
    print(voltage)
    utime.sleep(2)

The circuit looks like:

Every 2 seconds it collects a voltage measurement. If we wanted to see the actual value collected from the AtoD converter, we could change the line after while True: to the following:

reading = potentiometer.read_u16()
i=input("potentiometer reading: " + str(reading))
voltage = reading * conversion_factor
# voltage = potentiometer.read_u16() * conversion_factor

This would give us the opportunity to see the actual reading before the conversion factor is applied – each time the program runs through this line of code (note the original line of code was commented out).

Now when I run the program, I get a line similar to the following:

potentiometer reading: 10274

and the program stops waiting for a carriage return in the Python Shell area. Not that you would want to replace every line of code in your program like this, but it can help you look inside. Once I enter return, the program continues to execute printing out the voltage value waiting the 2 seconds define and then we’re back around for another look at the reading.

Once I understand what is going on, I can either comment out all the debugging and uncomment the ‘production’ code and make any adjustments identified during the debugging process.

If you get desperate, you can always wire LEDs into your circuit and light them up in your code and see it flash when various lines are run.

In most cases, just adding in some textual messages can help you see what is going on under the covers.

More Raspberry Pico material – Trouble shooting

This is part of an ongoing effort to extend the material in the Getting started with MicroPython on the Raspberry Pi Pico book. If there are any overlaps in the material covered (either from that book or from some of the previous posts on this site) they have been updated and Americanized. I plan to put out each of the extension chapters and appendices, so others can review them without downloading the entire book I put together for the class.

One area that some of the people taking the class had trouble with was troubleshooting a microcontroller. They were used to larger machines that had ‘real tools’ to help debug. I wrote up this brief discussion of various techniques to help diagnose issues that Thonny cannot handle.

Adding a reset button

Sometimes (especially when your program is set to run at power up) it can have a bug and go into an infinite loop. Most of the IDEs can handle this by stopping the Pico, but I’ve found that with Thonny on the Raspberry Pi the Thonny Stop button doesn’t always work.

Figure A-1 – Adding a reset button

This little circuit that connects pin 30 (the Run button) to a switch that will ground out the pin — when pressed it will stop the current program. This allows you to reset the board without unplugging the device from the USB cable that is supplying the power.

Factory reset/delete files

If you need to delete all your programs from your Pico’s flash memory and start again from scratch, you can do that by downloading a special .uf2 file and copying it to your Pico, while it’s in bootloader mode. This can be useful when you have a malfunctioning main.py that is locking up the Pico to the extent that it may no longer communicate with Thonny. You’ll need to copy the MicroPython image across again afterwards, just like when you got the Pico out of the box

Problems with device recognition

Thonny doesn’t see the device

Figure A-2 – Thonny doesn’t see the device

If you see a screen similar to this when you bring up Thonny, it means that the Pico needs to have the MicroPython environment installed. Thonny is trying to start that process. This should only happen when you press the reset switch while plugging in the Pico.

If Thonny shows the following message:

Couldn't find the device automatically. Check the connection (making sure the device is not in bootloader mode) or choose "Configure interpreter" in the interpreter menu (bottom-right corner of the window) to select specific port or another interpreter.

This also means that Thonny cannot see the device. It could be because the cable is bad (or switched off). Try another cable and plug in the Pico again.

No lights come on when I power up the device

There is no physical power status light on the Pico. You can programmatically add one to your program if you desire.

I pasted my program in and it does not run

Indentation is critical to Python programming. Many times, when code is moved between programs, spaces can be replaced by tabs. This WILL NOT WORK reliably (especially on a Macintosh). Ensure that only spaces are used in your programming and that the code aligns to the required indentation. The colon is critical as well.

When you run your program and there is an error, you’ll see an error like:

The Python interpreter is telling you what line of code in your program it is confused by. In this case Line 2. You should be able to look at your code in Thonny and see something wrong either at the line in question (or earlier in the program). If you try and use a library that has not been loaded, you’ll see that the name of a method “isn’t defined”.

Traceback (most recent call last):
File "<stdin>", line 2
SyntaxError: invalid syntax

The Python interpreter is telling you the line of code in your program where it is confused. In this case line 2. You should be able to look at your code in Thonny and see something wrong either at the line in question (or earlier in the program).

If you try and use a library that has not been loaded, you’ll see that the name of a method “isn’t defined”.

Hopefully, this tips will help overcome some of the issues encountered when getting started with Thonny and the Pico.

More Raspberry Pico material – Adding sound

This is part of an ongoing effort to extend the material in the Getting started with MicroPython on the Raspberry Pi Pico book. If there are any overlaps in the material covered (either from that book or from some of the previous posts on this site) they have been updated and Americanized. I plan to put out each of the extension chapters and appendices, so others can review them without downloading the entire book I put together for the class.

There is currently no well supported way to add sound with MicroPython on the Pico. One simple way to play sound is using a relay and a dedicated sound board, like the WayinTop Sound Module (or one of the many others out on Amazon).

This sound board is triggered by a button and will play any MP3 data that is stored within it. Replace the button with a relay circuit and write your code to trigger the relay and you’re all set.

The Bill of Materials needed (in addition to the Pico):

  • Relay $2 (unless you want to use a transistor circuit mentioned in the previous post)
  • The WayinTop Sound Module is about $15

This approach will play mono sounds (it will play through the stored sound files sequentially if there is more than one). That’s nice, but a bit more expensive and less flexible, since there is relatively little programmatic control.

Instead, we are going to use a sound card that can be controlled using the UART serial port. Though the example plays through all the sounds stored on the card, you have the option to play specific songs on demand. It supports multiple songs, stored on a micro-SD card. It also will drive stereo sound, but the programming is a bit more complicated.

The components needed are:

The module has the following pin configuration:

Figure 14-1

Pin NumberPin NameDescriptionNotes
1VCCDC 3.2~5.0V 
2RXUART serial input3.3V TTL level
3TXUART serial output3.3V TTL level
4DAC_RAudio output right channelFor an earphone
5DAC_LAudio output left channelor an external amp
6SPK+Speaker+3W
7GNDGround 
8SPK-Speaker- 
9IO1Trigger port 1Short press to play previous
10GNDGround 
11IO2Trigger port 2Short press to play next
12ADKEY1AD port 1Trigger to play first segment
13ADKEY2AD port 2Trigger to play fifth segment
14USB+USB+ DPTo connect to USB flash
15USB-USB- DMDrive or PC
16BUSYBusy outputLow when playing, high when standby

We will only be using a small subset of these pins though. The wiring diagram for this project is:

Figure 14-2

The module needs to have the sound files stored on the micro-SD card in a very specific way, so look to the datasheet for the sound module for more details. In my case I called them 001.mp3…

The sound module supports numerous commands to perform a range of tasks. The serial communication format is the standard baud rate of 9600 bps with Data bits: 1 Check: none Flow Control: none.

The commands follow the following format:

Byte functionDescriptionContents
$$Start byte0x7E
VersionVersion0xFF by default
LengthNumber of bytes from version to Check_LSBTypically 0x06
CMDThe command codeDependent on what you are trying to do
FeedbackNeed feedback, send confirmation to the computer0x01 (no feedback needed)
Para_MSBMost significant byte of the parameterDependent on what you are trying to do
Para_LSBLeast significant byte of the parameterDependent on what you are trying to do
Check_MSBMost significant byte of the checksumDependent on buffer contents
Check_LSBLeast significant byte of the parameterDependent on buffer contents
$0End byte0xEF

You can leave out the checksum bits if you desire. To calculate the checksum:

 Checksum (2 bytes) = 0xFFFF–(Ver.+Length+CMD+Feedback+Para_MSB+Para_LSB)+1

For example, if we specify playback of SD card, we need to send the command “7E FF 06 09 00 00 00 EF”

Some of the commands supported by the device are:

Command descriptionCommand
Play next0x01
Play previous0x02
Specify the playback of a track0x03
Increase volume0x04
Decrease volume0x05
Specify volume0x06
Specify equalization0x07
Specify single repeat playback0x08
Specify playback of a device0x09
Set sleep0x0A
N/A0x0B
Reset0x0C
Play0x0D
Pause0x0E
Query status
(look at the datasheet for more detail)
0x42

There are many more commands that can play a specific sound file, play in a loop… To find out more look at the datasheet document for the sound module.

The code to play the next sound file:

def isPlaying(): #routine to get the playing status
statusBytes = [1,2,3]
while len(statusBytes)!=10: #sometimes you get double status
uart.write(STATUS_ARRAY) # ask for the status
time.sleep(0.1) #give it some time
statusBytes = uart.read()
time.sleep(0.1) #give it some time
if statusBytes[6] == 1:
return True
else:
return False
from machine import Pin,UART
import time
#constant
UART_TX = 0
UART_RX = 1
## command to play the next sound
PLAY_ARRAY = bytearray(5)
PLAY_ARRAY[0] = 0x7E
PLAY_ARRAY[1] = 0xFF
PLAY_ARRAY[2] = 0x03
PLAY_ARRAY[3] = 0x01
PLAY_ARRAY[4] = 0xEF
## command to define the device to play
DEVICE_ARRAY = bytearray(8)
DEVICE_ARRAY[0] = 0x7E
DEVICE_ARRAY[1] = 0xFF
DEVICE_ARRAY[2] = 0x06
DEVICE_ARRAY[3] = 0x09
DEVICE_ARRAY[4] = 0x00
DEVICE_ARRAY[5] = 0x00
DEVICE_ARRAY[6] = 0x02
DEVICE_ARRAY[7] = 0xEF
## command to set max volume
VOLUME_ARRAY = bytearray(8)
VOLUME_ARRAY[0] = 0x7E
VOLUME_ARRAY[1] = 0xFF
VOLUME_ARRAY[2] = 0x06
VOLUME_ARRAY[3] = 0x06
VOLUME_ARRAY[4] = 0x00
VOLUME_ARRAY[5] = 0x00
VOLUME_ARRAY[6] = 0x0E # 30 is the max?
VOLUME_ARRAY[7] = 0xEF
## command to get status
STATUS_ARRAY = bytearray(5)
STATUS_ARRAY[0] = 0x7E
STATUS_ARRAY[1] = 0xFF
STATUS_ARRAY[2] = 0x03
STATUS_ARRAY[3] = 0x42
STATUS_ARRAY[4] = 0xEF
#device definition
uart = UART(0, baudrate=9600, tx=Pin(UART_TX), rx=Pin(UART_RX),bits=8, parity=None, stop=1)
uart.write(DEVICE_ARRAY)
time.sleep(0.1) # give it some time to read the data
uart.write(VOLUME_ARRAY)
time.sleep(0.1) # give it some time to read the data
uart.write(PLAY_ARRAY)
time.sleep(1.0) # give it some time to read the data
while isPlaying():
print("Still playing")
time.sleep(1)
print("all done")

The DEVICE and VOLUME commands are not necessary since those are the default when the device powers up but wanted to show sending multiple commands.

Lesson challenge – How would you play a sound when a button is pressed? Go back to the circuit we used for the crosswalk lesson and make the circuit say ‘Walk’ ten times, instead of using the buzzer (a walk.mp3 file has been provided in the lesson program folder). If you wanted to make a flashing crossing gate circuit using the red LEDs, how would you make the lights flash for only as long as the sound is playing, using the Busy pin? Could you do the busy sensing only using UART commands? If so, think through how that would be designed?

More Raspberry Pico material – Stepper motors

This is part of an ongoing effort to extend the material in the Getting started with MicroPython on the Raspberry Pi Pico book. If there are any overlaps in the material covered (either from that book or from some of the previous posts on this site) they have been updated and Americanized. I plan to put out each of the extension chapters and appendices, so others can review them without downloading the entire book I put together for the class.

Stepper motors are DC brushless and synchronous motors. They rotate in discrete steps of predefined values and rotate both clockwise and anticlockwise. Unlike other DC motors, they provide a precise position control according to the number of steps per revolution for which the motor is designed. That means a complete one revolution of a stepper motor is divided into a discrete number of steps. They are commonly used in CNC machines, Robotics, 2D and 3D printers.

For this guide, we will use a 28BYJ-48 stepper motor and control it through ULN2003 motor driver.

28BYJ-48 Stepper Motor

This is the commonly used stepper motor in low power industrial and most famously in hobbyist projects. They can be purchased for a few dollars apiece.

The 28BYJ-48 is a uni-polar 5V stepper motor that takes electrical signals as input and rotates by converting these input signals into mechanical rotation. It consists of 4 stationary coils rated at +5V. These coils are known as a stator and make a ring around the rotor. Because of 5 volts operating voltage, we can easily drive this motor from any microcontroller such as ESP32, ESP8266, Arduino or TM4C123 Tiva Launchpad, etc. It has a 1/64 reduction gear set and therefore moves in precise 512 steps per revolution. These motors are silent in comparison to other DC motors and servo motors. You can achieve positional control easily without needing extra circuitry and components.

Stride Angle

This stepper motor has a stride angle of 5.625 degrees. That means 28BYJ-48 will complete one revolution in (360/5.625) 64 steps by taking one step at a time. However, the stepper motor can also be used in full-step mode. In full-step mode, the angle of each step is 11.25 degrees. That means the motor completes its one revolution in 32 steps instead.

Specifications

  • It is a unipolar 5 pin coil with a rated DC voltage of 5V.
  • Has 4 phases with a stride angle of 5.625°/64.
  • Speed variation ratio is 1/64
  • The frequency of this stepper motor is 100Hz and insulated power is 600VAC/1mA/1s.
  • The half-step method is recommended for driving this stepper motor.
  • The value of pull in torque for a stepper motor is 300 gf-cm.

Pinout

The following figure shows the pinout diagram of 28BYJ-48 stepper motor. It consists of 5 pins. Out of these 5 pins, four pins are used to provide sequence logic to the coils and one pin is a +5 volts supply pin.

Figure 13-1

Pin Configuration Details

Pin NumberCoil NumberColor
14Blue
22Pink
33Yellow
41Orange
5VccRed

Coil 1-Coil 4: These are coils used to control the step sequence of the stepper motor. One end of each coil is connected with +5V and the other end will be connected with ULN2003 driver output.

Vcc: Used to apply +5 volt supply to the stepper motor. This voltage appears across the coils when a specific coil is ground through a control sequence.

ULN2003 Stepper Motor Driver Module

To use a 28BYJ-28 stepper motor with the Pico we will be required to attach it with the ULN2003 motor driver. This is necessary because the current consumption of 28BYJ-48 is around 240mA. That means the current required to drive coils by applying a sequence of control signals is also almost 200mA. GPIO pins of the Pico cannot provide current of this magnitude. Therefore, we need a ULN2003 driver which translates low current output into higher current that meets the requirement of stepper motor control signals.

Figure 13-2

The ULN2003 breakout board has high current and voltage than a single transistor and therefore it can drive a stepper motor easily by enabling our ESP board.

PinDescription
1N1 to IN4These are input pins used to provide control signals to the stepper motor such as control sequences. We will connect these pins with the GPIO pins of ESP32.
Vcc and GNDVcc is a power supply pin and it is used to provide 5 volts power to the stepper motor from an external power source.

Interfacing the Pico with 28BYJ-48 Stepper Motor and ULN2003 motor driver

To connect the Pico with the stepper motor and driver, we will use the input pins IN1-IN4, the power supply pins, and the motor connection header. The 28BYJ-48 stepper motor already comes attached with the motor driver via the motor connector header.

The connection diagram is shown in the picture below.

Figure 13-3

Programming the stepper motor

We will start by importing the Pin class from the machine module and the sleep class from the time module.

from machine import Pin
from time import sleep

Configure the GPIO pins connected with IN1, IN2, IN3, IN4 pins as output pins. This is done by using the Pin() method and passing the GPIO number as the first parameter and Pin.OUT as the second parameter.

Next, create an array called ‘pins’ consisting of the four output pins IN1, IN2, IN3 and IN4.

IN1 = Pin(17,Pin.OUT)
IN2 = Pin(16,Pin.OUT)
IN3 = Pin(15,Pin.OUT)
IN4 = Pin(14,Pin.OUT)
pins = [IN1, IN2, IN3, IN4]

Then create a 2d array called ‘sequence’ to hold the sequence of values that we will provide to the stepper motor pins. These values are 1 and 0 indicating a high state and a low state.

sequence = [
    [1,0,0,0],
    [0,1,0,0],
    [0,0,1,0],
    [0,0,0,1]
    ]

Note: Reversing this sequence of numbers will move the stepper motor in the anti-clockwise direction.

Inside the infinite loop, we will step through the sequence and set each pin the value of the sequence in order. This will be done after a delay of 0.001 seconds to ensure proper movement of the stepper motor.

while True:
    for step in sequence:
        for i in range(len(pins)):
            pins[i].value(step[i])
            sleep(0.001)

The entire program looks like the following: 

from machine import Pin
from time import sleep
#constants
IN1 = Pin(17,Pin.OUT)
IN2 = Pin(16,Pin.OUT)
IN3 = Pin(15,Pin.OUT)
IN4 = Pin(14,Pin.OUT)
pins = [IN1, IN2, IN3, IN4]
sequence = [
    [1,0,0,0],
    [0,1,0,0],
    [0,0,1,0],
    [0,0,0,1]
    ]
#main program
while True:
    for step in sequence:
        for i in range(len(pins)):
            pins[i].value(step[i])
            sleep(0.001)

Lesson challenge – How would you make an analog clock that move its entire length over 24 hours and then resets back to zero? Whare are the key issues you would need to address?

More Raspberry Pico material – Transistor switch

This is part of an ongoing effort to extend the material in the Getting started with MicroPython on the Raspberry Pi Pico book. If there are any overlaps in the material covered (either from that book or from some of the previous posts on this site) they have been updated and Americanized. I plan to put out each of the extension chapters and appendices, so others can review them without downloading the entire book I put together for the class.

In the previous post I talked about using relays. You don’t always need to powerful capabilities of a relay when switching circuits. Many times, you can just use a transistor to switch. The following circuit is an example:

To use one of the Raspberry Pico’s GPIO pins as the triggering source and place your 5V load off the collector of the transistor. The process to determine the circuit is:

1. Determine the load that you want to switch: Before selecting the transistor, you need to know the characteristics of the load that you want to switch. This includes the voltage, current, and power requirements of the load.

2. Choose the appropriate transistor: Based on the load characteristics, choose a transistor that can handle the required voltage, current, and power. You can check the datasheet of the transistor to ensure it meets the specifications. The 2N2222 can handle about 600mA load at 12V.

3. Connect the transistor to the Raspberry Pi Pico: To use the transistor as a switch, connect the base of the transistor to a GPIO pin on the Raspberry Pi Pico through a current-limiting resistor (typically 1 – 12KΩ). Connect the emitter of the transistor to ground and the collector to the load.

4. Add a diode for protection: To protect the transistor and the Raspberry Pi Pico from voltage spikes when the load is switched off, you can add a diode in reverse bias across the load. This is known as a freewheeling diode (i.e., 1N4001). Probably not necessary for switching LEDs, but if there is a DC motor in the load, one would be required.

5. To control the transistor, write code on the Raspberry Pi Pico to set the GPIO pin connected to the transistor to either HIGH or LOW to turn the transistor on or off, respectively. This code will look like the code from back in the section on controlling an external LED.

There are cases where you would want to use a Field Effect Transistor (FET) rather than a Bipolar Junction Transistor (BJT) like the 2N2222. That situation is a more information than can be covered here though.

Here is some example code:

import machine
import time
# Define the GPIO pin to control the transistor
transistor_pin = machine.Pin(14, machine.Pin.OUT)
# Turn on the transistor to activate the load
transistor_pin.value(1)
time.sleep(1)
# Turn off the transistor to deactivate the load
transistor_pin.value(0)
time.sleep(1)

More Raspberry Pico material – Relays

This is part of an ongoing effort to extend the material in the Getting started with MicroPython on the Raspberry Pi Pico book. If there are any overlaps in the material covered (either from that book or from some of the previous posts on this site) they have been updated and Americanized. I plan to put out each of the extension chapters and appendices, so others can review them without downloading the entire book I put together for the class.

Sometimes you need to use relays to turn on higher current devices. This is actually easy to accomplish eith the Pico. For lower current devices you can use a transistor (which I will cover next) but this post will focus on using a relay. This post explains how to use a low cost relay (about $1) to switch just about anything on or off.

Relays are, in effect, electronic switches that can switch higher voltages and currents than the Pico can handle. In nearly all cases the working voltage being switched by the device is supplied externally – in this case it is the USB 5V.

Note that the voltages used in this class will not hurt you, but when you start working with the kind of voltages and currents that are possible to switch with the relay, you need to be very careful.

Many relays operate using a coil to switch mechanical contacts. There can be many relays mounted in one system. Many devices are available commercially with 1 to 16 relays.

Figure 12-1 Two SPDT relay module

A coil triggered relay has an operating voltage and current requirement. The Pico has a maximum current load for all pins at 50 mA, so keep that in mind when you design your circuit.

The relay we will be using have an Optocoupler (a FL-3FF) inside. These draw significantly lower current (as little as 5 mA) than the ones using coils, so our Pico can easily switch them.

Figure 12-2 – A board mount relay example

The two positions of relays are either considered Normally Open (NO) and Normally Closed (NC).

Figure 12-3 – Our relay logical diagram

The normal position is when the circuit is not energized. This is when the GPIO pin controlling the relay is low (and drawing no current). The pins on our relay are:

  • NO = normal open
  • COM = line being switched
  • NC = normal closed
  • IN = the voltage triggering the switch
  • DC- = ground
  • DC+ = 3V

For our circuit example, the wiring looks like:

Figure 12-4 Relay circuit

We wire the Normal Open to the yellow LED and the Normal Closed to the red LED. The COM line that is being switched goes to VBUS (5V). The controlling signal is connected to GP15 of the Pico and the DC- goes to ground and the DC+ can go to either VBUS or 3V3.

Using a program like the one from Lesson 5’s “External LED”, you can now control the relay and the LEDs.

With this code the relay should switch on the yellow LED for on 5 seconds and then the red LED on for 1 second, in a loop.

#imports
import machine
import utime
#constant2
RELAY_GPx = 15
#variables
relay = machine.Pin(RELAY_GPx, machine.Pin.OUT)
#main loop
while True:
    relay.value(1)
    utime.sleep(5)
    relay.value(0)
    utime.sleep(1)

Lesson challenge – Create a circuit for the burglar alarm to trigger a relay that can be hooked to a strobe, siren or any other device that can be powered through the relay. What other situations can you think of where this automation technique would be useful?

Session challenge – Think about how you would create a circuit for a crossing gate that uses a sensor to identify when the train is coming, flashing lights and servos to close the crossing gate, another sensor to recognize when the train has passed. Output all the status information on the LCD. Use a relay to start an electric motor while the crossing gate is down.