Raspberry Pi Pico - Review

Table of contents

RoadTest: Raspberry Pi Pico

Author: tonygo

Creation date:

Evaluation Type: Semiconductors

Did you receive all parts the manufacturer stated would be included in the package?: True

What other parts do you consider comparable to this product?: Adafruit ItsyBitsy M4 Express featuring ATSAMD51

What were the biggest problems encountered?: Inaccurate Analog to Digital Conversion (ADC), especially at the zero end. Strange error messages and loss of USB connection to PC with Thonny. Random pixels at bottom of display after much program editing of large graphic program in MicroPython (Pimoroni UF2) Getting started/setting up with Visual Studio when trying to use C/C++. System crashes when using both cores. Why is garbage collection necessary? Lack of suitable driver modules for displays and sensors (I2C & SPI) with MicroPython No support for interrupts or second Core and poor implementation of PIO with CircuitPython.

Detailed Review:

Where am I coming from and what am I going to do?

 

I’ve been writing code since 1967. I used to teach Computer Science in Community Colleges in England, with students aged 14 to adult. I’ve been retired for several years but enjoy writing code and still get a buzz when a project I’ve been working on does exactly as I want. I am still very interested in Computer Education and helping people get started with coding. I’ve held ‘Show & Tell’ tables at Raspberry Jams and helped run a primary school after school Computer Club. More recently I’ve been writing web tutorials and helping people on forums. I’ve used Arduinos, Raspberry Pi computers, BBC micro:bits, CodeBugs, several Adafruit boards such as ItsyBitsys, Circuit Playground, PyPortal, EdgeBadge, CLUE and ESP boards. This new move from the Raspberry Pi Foundation in producing a microcontroller board with their own RP2040 chip has spiked my interest.

 

I’m going to be looking at the Raspberry Pi Pico as a contender in the currently crowded marketplace for users who want to improve their coding skills while building computer projects. They will often be hobbyists or students making the transition to a text-based language from a Scratch like environment, with basic to middling Python skills. Physical computing adds interest at this level.

 

image

What is a Pico?

Raspberry Pi produced the following specifications for their new board:

Raspberry Pi Pico is a low-cost, high-performance microcontroller board with flexible digital interfaces, built on silicon designed at Raspberry Pi. Key features include:

  • RP2040 microcontroller chip designed by Raspberry Pi in the United Kingdom
  • Dual-core ARM Cortex M0+ processor, flexible clock running up to 133 MHz
  • 264kB of SRAM, and 2MB of on-board Flash memory
  • Castellated module allows soldering direct to carrier boards
  • USB 1.1 Host and Device support
  • Low-power sleep and dormant modes
  • Drag & drop programming using mass storage over USB
  • 26 multi-function GPIO pins
  • 2×SPI, 2×I2C, 2×UART, 3×12-bit ADC, 16×controllable PWM channels
  • Accurate clock and timer on-chip
  • Temperature sensor
  • Accelerated floating point libraries on-chip
  • 8×Programmable IO (PIO) state machines for custom peripheral support

Getting Started with Raspberry Pi Pico

  • Pico C/C++ SDK - Libraries and tools for C/C++ development on the RP2040 microcontroller
  • The API level Doxygen documentation for the Raspberry Pi Pico C/C++ SDK is available as a micro-site.

 

They suggest two different methods of programming the board; C/C++ and MicroPython, neither of which I have used before. Adafruit, who manufacture a great variety of competing boards, are promoting the use of CircuitPython (a fork of MicroPython) as an additional method of programming. The Raspberry Pi Press also published the Official Raspberry Pi Pico Guide, “Getting started with MicroPython on Raspberry Pi Pico”. You can also get it as a free download. This is very useful as you can copy and paste code to cut down typing and typos! The programs can also be downloaded from Github here:

 

GitHub - raspberrypi/pico-micropython-examples: Examples to accompany the "Raspberry Pi Pico Python SDK" book.

 

I’ve used CircuitPython on a variety of Adafruit boards but have not tried MicroPython.

I’ve used the Arduino IDE with several Arduino boards and ESP32 and ESP8266 but have little other experience of C/C++.

 

In this RoadTest I propose to follow the recommended MicroPython route via the Pico Python SDK. I will then compare the CircuitPython route and follow up with the Pico C/C++ SDK, which looks quite daunting.

 

image

The photograph shows the top and bottom surfaces of the board. The large square chip is the new Raspberry Pi 2040 chip. You need to solder pins or sockets round the edge for connections to peripheral devices. Notice that some of the solder pads have square corners on the inside edge. These are the many GND connections. Unfortunately, once solder has been applied and a good joint made, I could not differentiate between the pads. The bottom surface has the pin labels which you cannot see once the board is pressed into a breadboard. You may have noticed in the previous picture that I have used black marker pen on the breadboard to show the position of the GND pins to help navigate round the edges. (I try to use orange wire for 3v3, red for 5v and black for GND in my projects.)

 

Near the top right of this picture there is a white button labelled BOOTSEL (Boot select) and nearby a built-in LED connected to pin 26. This will be very useful in initial setup testing.

 

I live in the UK and was lucky enough to purchase a couple of Picos and some add-on boards on the launch day, 21st January 2021. I have a Pico Decker which provides simple connection of a Pico to multiple sets of labelled pins and accepts plug in displays such as the Pico Display (240x135 colour pixels and RGB LED). I also got a Pico Explorer with a mini breadboard, 240x240 pixel colour display, motor driver and sockets for I2C breakout boards from Pimoroni.

 

image

 

Getting started

While waiting for my order to arrive I download all the documentation, which is in pdf format, and started reading. I began with the official Guide.

HackSpace magazine (raspberrypi.org) for the free download.

 

image

 

In addition to the Pico, you need some headers to solder to the board. Most people use male headers with the legs pointing down but female, long or short legged, are other options. A USB A to micro-B cable is needed to connect your Pico to a computer for programming and power. Think about how long you need – I find 1m cables a bit too long for my desk. You will also need a breadboard, LEDs, 330 Ohm resistors, button switches, piezo buzzer and jumper cables, m-m and m-f, for the first few tutorials.  (The full list is on page 41 of the Guide.) You will also have to download the latest version of the Thonny Editor, version 3.3.3 or later – it is currently v3.3.6 on 21 April 2021. It is available for Apple, PC and Raspberry Pi. I’ve tried it with a Windows 10 PC and a Raspberry Pi 4 4GB. You also need to download the firmware, a .UF2 file, and install it on the Pico. You can find it here:

MicroPython - Python for microcontrollers Pick the stable one.

 

The Pico guide is excellent (make sure you have the second impression as the first had many errors, which the publisher replaced free of charge.) I followed the instructions outlined in the guide and everything worked perfectly first time. It is written in language suitable for older children, with some school IT experience, and adults, who have never tried coding before, to follow. It covers:

  1. Soldering on the legs,
  2. Installing the Thonny editor
  3. Installing the UF2 file to the Pico
  4. Basic Python coding
  5. Electronic components identification
  6. Physical computing

 

If you have previously used CircuitPython you will be familiar with how to install a .UF2 file on a board. Installing MicroPython on a Pico is slightly different and it only has one button.

 

In file manager make sure you can see the downloaded Pico.UF2 file. Plug the small end of the cable into the Pico. Hold down the white button on the Pico while you insert the large end of the cable into the USB socket of your computer. A new drive appears (RPI-RP2).  Release the button. Drag the rp2-picoxxxxxv---.uf2 file and drop it on the new RPI-RP2 drive. You will see the data being copied and then the new drive disappears! This is correct and normal. You have done it right. You cannot use the file manager on your host computer to look at what is on your Pico. There is a way, which I will explain later.

 

Start Thonny and look at the bottom right corner of the window. It should show MicroPython(Raspberry Pi Pico). If it does not then go to Help –> About and check you have version 3.3.3 (or later). Update if necessary. Then go to Tools –> Options –> Interpreter and pick the Pico from the list. It may then give you an opportunity to update the firmware.

 

Click on the red STOP icon at the top of the Thonny window and you should see output from your Pico print the version of MicroPython installed and a >>> prompt. Type print("Hello world!") at the prompt and press return. If it prints your message your Pico is working.

Now we are ready to try a program – the traditional Blink. Save with File -> Save as:

 

image

Choose This Computer and save it – blink.py – somewhere suitable. At the top of the Thonny window click on the green arrow icon. The built-in LED on GP25 should blink. In the Shell window at the bottom of Thonny the Run is recorded and the prompt returns after the LED stops blinking. Try running it again but click on the red STOP icon after the second blink and the Shell resets.

 

Using Save as again, choose Raspberry Pi Pico and save it as main.py. Unplug the USB cable from your host computer. Thonny reports disconnection. Plug it back in again and the program runs immediately power is restored. To reconnect to Thonny just click on the STOP icon. Any program saved as main.py in the Pico will run on power-up, which could be from a battery or phone charger. This could be useful for robotics. I suggest that you always plug/unplug from the host computer with the large end of the cable. The small socket on the Pico is weaker and more likely to break off.

 

How can we see what we have stored on the Pico, and can we delete it? We can use Thonny. Using File -> Open -> Raspberry Pi Pico, we can see what is stored there. Right click on main.py and you can delete it. You can also create folders, examine properties of files. You can store several different programs on your Pico but only main.py will run at Power-on.

 

I prefer the first method, run from the green icon, while developing programs and save all your code on the PC.

 

At this point it is well worth working through the guide as it quickly moves on from blinking LEDs to using both cores, interrupts and timers. (Pretty advanced stuff in 70 odd pages!) It then moves quickly on to reading potentiometers, voltages and temperatures with the Analog to Digital converters. Data logging and files are covered in Chapter 9. This is followed by the use of I2C and SPI to connect to a rather expensive 3V3 LCD 1602 display. (Sparkfun SerLCD) I thought the price a bit steep so opted to install a SSD1306 instead but more on that a little display later. The appendices cover the Pico specification, a very useful pinout guide and using the Programmable I/O (PIO) to drive a short collection of WS2812B LEDs – more commonly called Neopixels. I’m not going to provide  a step-by-step commentary on these activities as they all worked perfectly as described and you are left with sufficient knowledge to start designing and building projects of your own.

 

A note for beginners

If you have not coded with microcontrollers before you probably do not box of electronic components to hand. Buying them in small quantities can be quite expensive plus the added cost of postage if you purchase from several suppliers. At this point it is useful to buy a starter kit of useful parts. I've just come across the Electronics Kit 1 for Pico from MonkMakes.com. This contains a unique Pico breadboard with the labels for the Pico GPIO pins printed on the surface. It also has jumper leads, resistors, LEDs, a potentiometer, photo transistor, buzzer, switches and a servo motor. If you buy a Pico with the legs already soldered on you can get started without any soldering needed. There is downloadable book of instructions provided with the kit and all the programs can also be downloaded to save time and errors.

 

image

 

 

One of the downloadable references is the Pico Python SDK, which you can get here:

            Raspberry Pi Pico Python SDK

This covers much of the same ground as the Pico Guide (which is suitable for older children and those with little experience of coding) but the language in this document is more technical, the pace quicker and better suited to those with more experience of coding. It does contain a section on installing a cheap SSD1306 display. These are easily available, simple to connect via I2C and although rather small are very clear, with high contrast, can display a great deal of information and support graphics. Be careful with the connections as the pins at the top can be in a different order, depending on the manufacturer.

image

 

 

I prefer the 128x64 pixel version as the area is twice the size of the 128x32 display.

 

Make the following connections:

VCC to 3.3V = Pin 36

GND to GND = Pin3

SCL to I2C0 SCL = Pin 2 = GP1

SDA to I2C0 SDA = Pin 1 = GP0

 

In Thonny click on Tools -> Manage Packages.

Search for SSD1306 and click on micropython-ssd1306 at the top, then on install.

 

Using Thonny go to File -> Open -> Raspberry Pi Pico

You will find a lib folder (library) which contains the required ssd1306.py library of 4740 bytes.

 

If you open it you will find the complicated code needed to communicate with the display.

 

image

 

At this point I built a small circuit board with stripboard containing:

4 LEDs with 330 Ohm current limiting resistors

  • Red on GP4
  • Yellow on GP5
  • Green on GP 6
  • Blue on GP7

 

2 button switches

  • sw1 connecting GP 26 to 3v3
  • sw2 connecting GP 27 to 3v3

 

10K Ohm potentiometer to GP 28 – ADC2

 

# Raspberry Pi PICO SSD1306 Test
# Tony Goodhew 17th March 2021
# 4 LEDs, 2 Buttons, 10K pot and SSD1306 display
# Digital and analog I/O + I2C connection to display
import machine
import utime
from ssd1306 import SSD1306_I2C
# Set up SSD1306 display
sda = machine.Pin(0)
scl = machine.Pin(1)
i2c = machine.I2C(0, sda=sda, scl =scl, freq=400000)
oled = SSD1306_I2C(128, 64,i2c)
# Set up potentiometer
p2 = machine.ADC(28)  # 10K potentiometer
#Set up 3 LEDs
red = machine.Pin(4, machine.Pin.OUT)    # Digital output
yellow = machine.Pin(5, machine.Pin.OUT)  # Digital output
green = machine.Pin(6, machine.Pin.OUT)  # Digital output
blue = machine.Pin(7, machine.Pin.OUT)  # Digital output
#Set up button switches                      
sw1 =machine.Pin(26, machine.Pin.IN,machine.Pin.PULL_DOWN)
sw2 =machine.Pin(27, machine.Pin.IN,machine.Pin.PULL_DOWN)

oled.text("Raspberry Pi", 20,15)
oled.text("Pico", 50,25)
oled.text(" Initial test", 10,35)

oled.show()
utime.sleep(2)
oled.fill(0)
oled.show()

for t in range(2):
    oled.text("RasPi PICO",0,20)
    for i in range(0,138):
        oled.scroll(1,0)
        oled.show()
#        utime.sleep(0.01)

oled.fill(0)
oled.show()
oled.text("Blink LEDs", 30,20)
oled.show()

for i in range(5):
    red.value(1)
    yellow.value(1)
    green.value(1)
    blue.value(1)
    utime.sleep(0.3)
    red.value(0)
    yellow.value(0)
    green.value(0)
    blue.value(0)
    utime.sleep(0.3)

oled.fill(0)
oled.show()
oled.text("Press buttons", 10,20)
oled.text("Both to HALT", 14,40)
oled.show()

running = True
while running:
    red.value(sw1.value())
    green.value(sw2.value())
    if sw1.value() + sw2.value() == 2:
        running = False
red.value(0)
green.value(0)

oled.fill(0)
oled.show()
oled.text("Turn the Pot", 8,0)
oled.text("Press a button", 0,10)
oled.text("to HALT", 35,20)
oled.show()
while sw1.value() + sw2.value() > 0:
    continue  # Wait for both switches OFF
# Set up PWM for Blue LED
blue = machine.PWM(machine.Pin(7))      # Analog potput
blue.freq(1000)
running = True
while running:
    pot =p2.read_u16()
    blue.duty_u16(pot)
    print(pot)
    oled.fill_rect(0,40,128,20,0)
    oled.text(str(pot), 50,50)
    oled.show()
    utime.sleep(0.2)
    if sw1.value() + sw2.value() > 0:
        running = False
# Tidy-up
blue.duty_u16(0)
swx = machine.Pin(7, machine.Pin.IN,machine.Pin.PULL_DOWN)
oled.fill(0)
oled.text("All Done!", 30,25)
oled.show()
utime.sleep(1.5)
oled.fill(0)
oled.show()

This code illustrates much of what we learned by following the early part of the Guide: buttons, a potentiometer, reading values and controlling the brightness of a LED with PWM.

 

It also shows an unfortunate feature of the ADCs on the Pico. As you turn the potentiometer the Blue LED changes brightness and the u16 value from the ADC is displayed. The maximum shown is 65535, as expected, but the LED never goes out completely as the value from the ADC always shows a value a several 100s above 0. The specification states that the ADCs are 12-bit but are shifted to supply unsigned 16-bit values, probably to fit with the duty value used with PWM. This allows us to read the ADC and pop the result straight into the value for a PWM duty. The minimum values shown were mostly 352, with a few random values thrown in. (ADCs connected to potentiometers often have a bit of randomness.) I added some ‘clean-up’ code to the final loop and this made the program work as I wanted with the LED switching right off.

 

running = True
while running:
    pot = p2.read_u16()
    # Clean up pot reading
    minn = 352 # most common lowest reading from pot
    temp = int(pot-minn)
    pot = int(temp/(65535-minn)*65535)
    if pot < 0 :
        pot = 0
    elif pot > 65535:
        pot = 65535
    print(pot)
    blue.duty_u16(pot)  
    oled.fill_rect(0,40,128,20,0)
    oled.text(str(pot), 50,50)
    oled.show()
    utime.sleep(0.2)
    if sw1.value() + sw2.value() > 0:
        running = False

 

 

I’ve written up two tutorials on using graphics with a Pi Pico and the SSD1306. They provide examples of graphical output and the routines to produce them. You can find them here:

 

SSD1306 With Raspberry Pi Pico : 6 Steps (with Pictures) - Instructables

 

Pico & SSD1306 Icons : 4 Steps (with Pictures) - Instructables

 

I now switched to the Pimoroni Explorer (240x240 pixels) and Display (240x135 pixels) boards to see how the Pico worked with better quality colour displays.

 

imageimageimage

 

 

To drive the extra features of this board you need a different UF2 file installed. Pimoroni have included the drivers for all of their Pico add-ons in this file, which can be downloaded from their site. It is built on top of the official Raspberry Pi Pico UF2. It is updated quite regularly to fix bugs and stay with the offical MicroPython version.

 

Releases · pimoroni/pimoroni-pico (github.com)

 

The graphical primitives provided were quite meagre:

  • Set pen colour
  • Screen fill
  • Draw pixel, character, text message, solid rectangle, solid circle and horizontal line.
  • Update screen

 

There were also a couple of example graphical programs to run. One for the Explorer and another for the smaller screen on the Display.

 

I wrote a series of graphical routines to the drive the screen and was very impressed with the performance - fast, bright, colourful and very clear.

 

I have since written up my findings as Workouts and published them on Instructables.com, with pictures, videos and code.

 

Tonygo2's Projects - Instructables

 

Hackspace Magazine also published a graphics article written by me in Issue 41, pages 74-77. You can download it here: 

Issues — HackSpace magazine (raspberrypi.org)

 

There is a reference in the text pointing to the page where the code can be downloaded.

 

24th May 2021 - Second article published today in Hackspace Magazine - Issue 43 pages 70 - 73

image

 

While working on these graphics routines the code got quite long – 300+ lines of python code – and as I continued to update and improve/test the code I started to see random pixels appearing at the bottom edge of the screens. Occasionally the PC lost contact with the Pico and random <stdin> error messages and strange characters appeared in Thonny. The Pi Pico and Pimoroni Forums supported discussions but I do not think the problem has been thoroughly resolved. I used the flash-nuke.UF2 to properly clear flash memory and reinstalled the UF2. I’m unsure about how well garbage collection is working as I keep modifying and replacing the code.

 

A longer Project involving Both Cores, Interrupts and PIO

 

After this long diversion into graphics, I thought it might be an idea to try to combine the three outstanding features of the Pico when programmed with MicroPython:

  • Drive both cores at the same time – Scrolling text on a curve + moving LEDs
  • PIO driving Neopixels round a 16-pixel ring
  • Interrupts to change the output and communicate between threads with global variables

Continuously scrolling text on a curve with the main core should keep it very busy.

An interrupt routine on the first button changes the direction of movement of the Neopixels in Core0 and LEDs controlled from Core1.

A second interrupt routine on the other button halts both cores and tidies up neatly when we have finished.

 

image

Here is the code:

 

# Uses both Cores, PIO and Interrupts
# Tony Goodhew - 22 March 2021
# 16 Neopixels on GP21, 5v and GND
# Button switches GB28 and GP27 (pull-down)
# SSD1306 on GP0 and GP1
#
# Lower button (GP27) controls direction of LEDs & Neopixels
# Upper button HALTs the program
from machine import Pin, I2C
import _thread
import utime
import array
import rp2
from ssd1306 import SSD1306_I2C
import framebuf
import math
WIDTH  = 128    # oled display width
HEIGHT = 64    # oled display height

# Explicit Method
sda=machine.Pin(0)
scl=machine.Pin(1)
i2c=machine.I2C(0,sda=sda, scl=scl, freq=400000)

#from ssd1306 import SSD1306_I2C
oled = SSD1306_I2C(128, 64, i2c)

# Configure the  WS2812 Neopixels
NUM_LEDS = 16
PIN_NUM = 21
brightness = 0.2

@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24)
def ws2812():
    T1 = 2
    T2 = 5
    T3 = 3
    wrap_target()
    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]
    wrap()

# Create the StateMachine with the ws2812 program, outputting on pin
sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(PIN_NUM))
# 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)])

BLACK = (0, 0, 0)
RED = (255, 0, 0)
YELLOW = (255, 150, 0)
GREEN = (0, 255, 0)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)
WHITE = (255, 255, 255)
COLORS = (RED, YELLOW, GREEN, CYAN, BLUE, PURPLE, WHITE)

def pixels_show():
    dimmer_ar = array.array("I", [0 for _ in range(NUM_LEDS)])
    for i,c in enumerate(ar):
        r = int(((c >> 8) & 0xFF) * brightness)
        g = int(((c >> 16) & 0xFF) * brightness)
        b = int((c & 0xFF) * brightness)
        dimmer_ar[i] = (g<<16) + (r<<8) + b
    sm.put(dimmer_ar, 8)
    utime.sleep_ms(10)

def pixels_set(i, color):
    ar[i] = (color[1]<<16) + (color[0]<<8) + color[2]

def pixels_fill(color):
    for i in range(len(ar)):
        pixels_set(i, color)

def blk():
    oled.fill(0)
    oled.show()
    
# Clear the oled display in case it has junk on it.
oled.fill(0) # Black
oled.show()

# Scrolling text on Sine curve
# Modified from a method by Tony DiCola
msg = 'Pico + SSD1306 is Magic!'
f_width  = 8  # Font width in pixels
f_height = 8  # Font Height in pixels
amp = 50  # Amplitude of sin wave
freq = 1    # Screen cycles (360 degrees)  
pos = WIDTH  # X position of the first character in the msg.
msg_len_px = len(msg) * f_width  # Pixel width of the msg.
# Extra wide lookup table - calculate once to speed things up
y_table = [0] * (WIDTH+f_width) # 1 character extra
for i in range(len(y_table)):
    p = i / (WIDTH-1)  # Compute current position
    # Create lookup table of  y co-ordinates 
    y_table[i] = int(((amp/2.0) * math.sin(2.0*math.pi*freq*p)) + (amp/2.0))
  
#Set up 4 LEDs
leds = [0,0,0,0]
for i in range(4):
    leds[i]= machine.Pin(i+4, machine.Pin.OUT)    # Digital output

def led_dirTask(pin):
    global led_dir
    led_dir = led_dir * -1
def HaltTask(pin):
    global running
    running = False
wait = 0.07
led_dir = 1
running = True
i = 0

#Set up button switches for interrupts
led_dirPin = machine.Pin(27, machine.Pin.IN,machine.Pin.PULL_DOWN)
led_dirPin.irq(trigger=machine.Pin.IRQ_RISING, handler=led_dirTask)
HaltPin = machine.Pin(26, machine.Pin.IN,machine.Pin.PULL_DOWN)
HaltPin.irq(trigger=machine.Pin.IRQ_RISING, handler=HaltTask)

def core_task():
    i = 0
    col = 0
    p0 = 0
    p1 = 8
    while running:
        # Update LEDs
        i = (i + led_dir)
        if i == 4:
            i = 0
        elif i < 0 :
            i = 3    
        leds[i].value(1)
        utime.sleep(wait)
        leds[i].value(0)
        # Update Neopixels
        pixels_fill(BLACK)
        p0 = (p0 + led_dir) % 16
        p1 = (p1 + led_dir) % 16
        pixels_set(p0, COLORS[col])
        pixels_set(p1, COLORS[(col +2) % 7])
        pixels_show()
        utime.sleep(wait)
    # Tidy up Neopixels on HALT
    pixels_fill(BLACK)
    pixels_show()
    # Core 1 Halts here
    _thread.exit()
    
# Run LEDs and Neopixels from Core 1
_thread.start_new_thread((core_task),())

# Main loop on Core 0
running = True
while running:
    # Start again if msg finished
    pos -= 1
    if pos <= -msg_len_px:
        pos = WIDTH
    # Go through each character in the msg.
    blk()
    for i in range(len(msg)):
        char = msg[i]
        char_x = pos + (i * f_width)  # Character's X position on the screen.
        if -f_width <= char_x < WIDTH:
            # If character is visible, draw it.
            oled.text(char, char_x + 5, y_table[char_x + f_width], 1)
    oled.show()
    utime.sleep(0.08)
# Tidy up - Core 0
for i in range(4):
    leds[i].value(0)
print("HALTED")
blk()
# Wait for Core 1 to stop first
utime.sleep(1)

This program appears to run perfectly for a short time. You can change the direction of the LEDs and Neopixels with the button and close it down neatly with the other button. However, if you let the program run for longer it eventually dies at exactly the same point on the SSD1306 screen while the other loop continues. I replaced the main loop with a continue statement, to turn off the scrolling, while running the LEDs and Neopixels on Core 1. While not touching the interrupt buttons it still dies after a similar interval. Unfortunately, the program just stops, without any error message.

 

There appears to be a problem here which I have not been able to solve on my own.

I tried the Pi Pico forum:

_thread problem in Pico Python SDK example - Raspberry Pi Forums

   

Occasionally Thonny gets confused and produces a long error report about a Management error. This is a know ‘feature’ and you can read the discussion with the author of Thonny here:

 

Pi pico multitasking causes Thonny Management Error · Issue #1586 · thonny/thonny · GitHub

   

There are one or two points to bear in mind when developing and testing a program which uses both cores.

 

If your script dies, or you halt the program running with the STOP icon in Thonny, the second core may keep on running. To clear this problem, you have to power down the Pico, pull the USB cable out of the USB port, push it back in and then click on the STOP icon to restart the REPL before you continue.

 

There can be a problem when combining interrupts with threads. I tried replacing the interrupts with normal button reads but the program still died.

The full documentation about the problem can be found here:

 

_thread — Low-level threading API — Python 3.9.2 documentation

 

I tried searching the web for answers and found that Andreas Spiess has produced an excellent video tutorial on threads and I recommend that you watch it.

 

(8) How to use the two Cores of the Pi Pico? And how fast are Interrupts? - YouTube

 

If I take out the Neopixel code it appears to work properly, even with the interrupt routines, but eventually dies, though much later. I’m now beginning to suspect the SSD1306 library. I tried running the scroll routine on its own and that eventually fell over. (I’ve been using the Pimoroni UF2 all this time which has the basic RaspPI UF2 included. I’ve updated every time a new version appeared  – to fix bugs.)

 

At this point I swapped the SSD1306 display for a Pimoroni Display module with a continuous count being displayed from the Core 0 loop. This lasted longer but still fell over.

 

I was getting more and more frustrated. “Perhaps it’s garbage collection,” I thought, so inserted a garbage collection routine every time the counter passed a multiple of 100. Much better. I eventually halted execution with the HALT button interrupt routine once the count exceeded 35500.

 

You can see my write-up here:

  Dual Cores & Interrupts on Pi Pico : 5 Steps (with Pictures) - Instructables

 

Here is the code:

 

# Cores test
import gc # garbage collection
import picodisplay as display
import utime, random
import _thread
width = display.get_width()
height = display.get_height()
display_buffer = bytearray(width * height * 2)
display.init(display_buffer)

#Set up 4 LEDs
leds = [0,0,0,0]
for i in range(4):
    leds[i]= machine.Pin(i+2, machine.Pin.OUT)    # Digital output

def led_dirTask(pin):
    global led_dir
    led_dir = led_dir * -1
    
def HaltTask(pin):
    global running
    running = False
    
wait = 0.07
led_dir = 1
running = True
i = 0

#Set up button switches for interrupts
led_dirPin = machine.Pin(27, machine.Pin.IN,machine.Pin.PULL_DOWN)
led_dirPin.irq(trigger=machine.Pin.IRQ_RISING, handler=led_dirTask)
HaltPin = machine.Pin(26, machine.Pin.IN,machine.Pin.PULL_DOWN)
HaltPin.irq(trigger=machine.Pin.IRQ_RISING, handler=HaltTask)

def core_task(): # To be run on Core 1 Moving LEDs
    i = 0
    col = 0
    p0 = 0
    p1 = 8
    while running:
        # Update LEDs
        i = (i + led_dir)
        if i == 4:
            i = 0
        elif i < 0 :
            i = 3    
        leds[i].value(1)
        utime.sleep(wait)
        leds[i].value(0)
        utime.sleep(wait)
        
    # Core 1 Halts here - no need to tidy up LEDs
    _thread.exit()
    
# Set the backlight to 50%
display.set_backlight(0.5)

def blk():
    display.set_pen(20,10,10)
    display.clear()
    display.update()
    
def title2(msg,r,g,b):
    display.set_pen(20,10,10)
    display.clear()
    display.update()
    display.set_pen(r,g,b)
    display.text(msg, 25, 25, 200, 8)
    display.update()
    utime.sleep(0.05)

display.set_pen(20,10,10)
display.clear()
display.update()
title2('Test Cores',0,0,255)
display.update()
#utime.sleep(1.5)
blk()
# Core 0 main loop
c = 0
while running:
    c = c + 1
    if (c == 30):
        # Run LEDs from Core 1
        _thread.start_new_thread((core_task),())
    ms = str(c)
    title2(ms,250,250,0)
    if (c % 100 == 0):
        gc.collect()
# Tidy up core 0    
title2("Done",0,0,200)
utime.sleep(2)
blk()

 

My tips when using the 2 cores

 

Only print from one core, preferably Core 0. Thonny jumbles up messages if you use both cores for printing. It can also produce strange output in the REPL.

 

Always finish Core 1 before Core 0. If you interrupt with the STOP icon in Thonny this may only halt core 0 and leave core 1 running. This will cause a problem when you try to run the script again. (Unplug the USB cable to power down and stop both cores.)

 

I’m not sure why but adding the occasional garbage collection routine appears to help keep things running longer.

 

Interrupt service routines must be very short. Do not attempt to print anything in a service routine as this is very slow.

 

Use global variables to pass values between cores.

 

 

At this point I went back to the SSD1306 program and inserted a similar garbage collect every 100 scroll moves – It still fell over!

 

I give up. There is some instability here which I cannot fathom. There may still be bugs in the UF2s. Enough time wasted (2 days), let’s look at sensors.

 

Reading Temperatures

 

Most users want to try to read the temperature of the room. There is a built-in temperature sensor inside the Pico. The Guide and Python SDK explain how to use it to read the temperature. I’ve had a certified mercury thermometer sitting on my disk for the last 5 hours. It is the only item left over from my ‘colour processing in a darkroom with chemicals’ days, and is marked at 0.5°C intervals. It shows the current temperature is 20.6°C.

 

import machine
import utime
sensor_temp = machine.ADC(4)
conversion_factor = 3.3 / (65535)
while True:
  raw = sensor_temp.read_u16()
  reading = raw * conversion_factor
  temperature = 27 - (reading - 0.706)/0.001721
  print(raw)
  print(temperature)
  utime.sleep(2)

I’ve inserted code to print the raw value from the ADC as well. The output was:

 

14419

15.3408

14435

14.87265

14435

14.87265

14435

14.87265

14435

14.87265

14419

15.3408

14451

14.40451

 

about 15°C – not very close!

 

Using a DS18B20 sensor with this program:

 

# Ds18B20 Temperature sensor
# Data/Signal on GP0 + on 3v3 and GND to GND
import machine, onewire, ds18x20, time

ds_pin = machine.Pin(0)
ds_sensor = ds18x20.DS18X20(onewire.OneWire(ds_pin))

roms = ds_sensor.scan()
print('Found a ds18x20 device')

while True:
    ds_sensor.convert_temp()
    time.sleep_ms(750)
    for rom in roms:
      print(ds_sensor.read_temp(rom))
    time.sleep(2)

 

Found a ds18x20 device

18.5625

18.5625

18.5625

18.5625

18.5625

 

Better and steady, but still out by 2°C.

 

At this time, I would normally try my sensor of choice, a BME 680, but here we hit the major problem with a Pico programmed with MicroPython – no sensor drivers!

 

Most of us who have been using other microcontrollers such as Arduinos, BBC micro:bits, and the Adafruit boards like Circuit Playground, ItsyBitsy, CLUE and EdgeBadge, or Raspberry Pi single board computers have a box full of sensors and are used to just downloading the driver library soon after the post containing a new sensor hits the mat or mailbox. This is not possible with MicroPython on a Pico – there are none. Reviews in RPi published magazines refer to ‘plugging in’ sensors rather than reading them!

 

I find this a major drawback. Most users want to measure things with sensors. Robotic vehicles and weather stations depend on sensors and they need drivers - which are pretty difficult to write. I tried it once, for a simple LCD2004 / LCD1602 with excellent, easy to read documentation. It took me quite a time. Most of us just want to use a sensor in a project and expect the manufacturer of the board to supply one.

 

Pimoroni have put 2 Breakout slots on their Explorer board, supply many sensors to plug in, but have not yet supplied a single MicroPython driver after 3 months. (They have a video in which they say they would like to develop a sort of ‘pick& mix’ from a menu of drivers to create a one-off UF2 file for their breakout boards. That would be great but in the mean time a couple of working MicroPython sensor driver modules would be very useful.)

 

We are forced to switch to Adafruit’s fork of MicroPython – CircuitPython if we are to use the sensors.

 

Getting started with CircuitPython

 

Kattni Rembor from Adafruit has written a great guide here:

Overview | Getting Started with Raspberry Pi Pico and CircuitPython | Adafruit Learning System

 

If you have used CircuitPython before you will still need to check out this guide because there are several major differences when using a Pico. There are no default board pin references; UART, I2C and SPI can be allocated to different pins.

 

For example, we need to specify the pins to be used:

import board
import busio

i2c = busio.I2C(scl=board.GP1, sda=board.GP0)
spi = busio.SPI(clock=board.GP2, MOSI=board.GP3, MISO=board.GP4)
uart = busio.UART(tx=board.GP4, rx=board.GP5)

What are the main differences between CircuitPython and MicroPython?

 

There's a few differences and they're all documented here, however for Pico users who are moving on from MicroPython guides the most important are...

CircuitPython was designed to have a USB disk drive that appears when you plug in the board. That disk drive is small (of the order of MB! – 2 MB on a Pico) and holds your code and files. You can treat it just like a disk drive - drag and drop files, delete and copy them. You do not need to use Thonny to 'upload' a file - simply drag any file you want to the USB drive. You can see what is on the Pico with File Manager.

CircuitPython will restart your code when you save files to the disk drive. That means when you write Python code, whenever you save it will auto-reload the code for you, for instant gratification. This is a little unusual for programmers who are used to 'edit-save-compile-upload-reset-run' - we go straight to 'edit-save & run'.

CircuitPython has a consistent API across all boards. That means that whether you're using a Pico, or an nRF52840 or an ESP32-S2 or SAMD51 for your project, the code for your hardware is identical. (Other than pin names which may vary depending on how many there are on the board itself and what they're called).

CircuitPython has a lot of examples and support!
There are 300 odd libraries for the standard CircuitPython API. Most of these will already work. Listed here

Tons of guides and tutorials are available at:  https://learn.adafruit.com/category/circuitpython

Use MicroPython for:

1) Advanced APIs such as interrupts and threading.
2) Complete PIO API (CircuitPython's support is incomplete)
3) Using existing MicroPython code

To get started:

 

Download the CircuitPython UF2 file for the Pico from circuitpython.org:

https://circuitpython.org/board/raspberry_pi_pico/

 

Download and install the Mu editor from:

Download Mu (codewith.mu)

 

(The latest version also supports MicroPython code)

 

The getting started guide to Circuit Python is here:

What is CircuitPython? | Getting Started with Raspberry Pi Pico and CircuitPython | Adafruit Learning System

 

While you at it download flash_nuke.uf2

This is used to deep clean all the flash memory of your Pico if it stops working. You then reload either CircuitPython or MicroPython UF2 and carry on. (This is safe because the bootloader is permanent on a Pico.)

 

The next section reproduces all of the example from the MicroPython Guide in CircuitPython.

 

In the REPL typing these commands lists the pin names.

 

>>> import board

>>> dir(board)

['__class__', 'A0', 'A1', 'A2', 'A3', 'GP0', 'GP1', 'GP10', 'GP11', 'GP12', 'GP13', 'GP14', 'GP15', 'GP16', 'GP17', 'GP18', 'GP19', 'GP2', 'GP20', 'GP21', 'GP22', 'GP25', 'GP26', 'GP26_A0', 'GP27', 'GP27_A1', 'GP28', 'GP28_A2', 'GP3', 'GP4', 'GP5', 'GP6', 'GP7', 'GP8', 'GP9', 'LED', 'SMPS_MODE', 'VOLTAGE_MONITOR']

>>>

 

At this point the REPL stopped working and my PC reported a problem – there was a fault with my drive and needed to be scanned and fixed. I let Windows get on with it and this fixed the problem. (Perhaps I should have flash_nuked the Pico before installing the CP UF2?)

 

This program names all the available pins in order:

 

"""CircuitPython Essentials Pin Map Script"""
import microcontroller
import board

board_pins = []
for pin in dir(microcontroller.pin):
    if isinstance(getattr(microcontroller.pin, pin), microcontroller.Pin):
        pins = []
        for alias in dir(board):
            if getattr(board, alias) is getattr(microcontroller.pin, pin):
                pins.append("board.{}".format(alias))
        if len(pins) > 0:
            board_pins.append(" ".join(pins))
for pins in sorted(board_pins):
    print(pins)

 

======== OUTPUT ==================================

Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.

 

 

Press any key to enter the REPL. Use CTRL-D to reload.

soft reboot

 

 

Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.

 

 

code.py output:

board.A0 board.GP26 board.GP26_A0

board.A1 board.GP27 board.GP27_A1

board.A2 board.GP28 board.GP28_A2

board.A3 board.VOLTAGE_MONITOR

board.GP0

board.GP1

board.GP10

board.GP11

board.GP12

board.GP13

board.GP14

board.GP15

board.GP16

board.GP17

board.GP18

board.GP19

board.GP2

board.GP20

board.GP21

board.GP22

board.GP25 board.LED

board.GP3

board.GP4

board.GP5

board.GP6

board.GP7

board.GP8

board.GP9

board.SMPS_MODE

 

 

Code done running.

 

To list the built-in modules within CircuitPython on a Pico

 

>>> help("modules")                  

__main__               board           microcontroller        storage

_bleio                 builtins        micropython            struct

_eve                   busio           msgpack                supervisor

_pixelbuf              collections     neopixel_write         sys

adafruit_bus_device    countio         os                     terminalio

analogio               digitalio       pulseio                time

array                  displayio       pwmio                  touchio

audiobusio             errno           random                 ulab

audiocore              fontio          re                     usb_hid

audiomp3               framebufferio   rgbmatrix              usb_midi

audiopwmio             gamepad         rotaryio               vectorio

binascii               gc              rp2pio                 watchdog

bitbangio              io              rtc

bitmaptools            json            sdcardio

bitops                 math            sharpdisplay

Plus any modules on the filesystem

>>>

 

At this point I worked through the examples, starting here:

Blinky and a Button | Getting Started with Raspberry Pi Pico and CircuitPython | Adafruit Learning System

 

Having used CircuitPython before I found this quite straightforward. Notice that you can use Neopixels without PIO assembler! (You need the neopixel.mpy module.)

 

What we need now is a display. The ssd1306 worked well in MicroPython so I will try it again.

 

This will need several libraries. CircuitPython has hundreds and you can download them here:

Libraries (circuitpython.org)

 

The documentation is found here:

Core Modules — Adafruit CircuitPython 6.1.0 documentation

 

The bundle, which is updated and added to each week, is a large compressed .zip file. You need to unzip it before you can use the contents.

 

To install a library on your Pico you use File Explorer to drag a module from the list to the lib folder on the Pico. I installed adafruit_framebuf.mpy and adafruit_ssd1306.mpy – the others are built-into the CP UF2.

 

This is the code that worked. Most of the Python code is the same as in the MicroPython example but the setup sequence is CP specific and different; as is the use of time.monotonic().

 

Initially, I had never needed the instruction displayio.release_displays() before and had never heard of it.

 

I built this program up is steps, just pixels, then text, then the graphics routines and finally the scrolling part. Each time I edited the script and tried to re-run the program with a save it died at once with an error message saying that GP1 was in use. The only way forward after each program edit was:

  1. Delete code.py from the Pico
  2. Unplug the USB cable from the PC
  3. Turn off the REPL
  4. Plug the USB back into the PC
  5. Restart the REPL
  6. Save the code to the Pico

This was driving me mad. So, I went to the Adafruit Forum and asked for support. (Adafruit is based in New York and several time zones West from where I live in UK. Within a few hours I had a solution from Tannewt, one of the employees at Adafruit in Support Forum Administration, with a solution. (The Raspberry Pi Forum and the Pimoroni Forum rely very heavily on users, with far less visible intervention from the companies.) Well done Adafruit!

 

Here is the code:

 

# SSD1306 GFX basic tricks on Pi Pico with CircuitPython
# Tony Goodhew 25th March 2021
# Import libraries
import board
import math
import time
import busio
import displayio
displayio.release_displays() # Saves a load of trouble
# Import the SSD1306 module.
import adafruit_ssd1306
WIDTH = 128
HEIGHT = 64
# Create the I2C interface.
i2c = busio.I2C (scl=board.GP1, sda=board.GP0) # This RPi Pico way to call I2C

display_bus = displayio.I2CDisplay (i2c, device_address = 0x3C) # The address of my Board
# Create the SSD1306 OLED class.
# The first two parameters are the pixel width and pixel height.  Change these
# to the right size for your display!
oled = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c)
# Alternatively you can change the I2C address of the device with an addr parameter:
# display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, addr=0x31)

# Clear the display.  Always call show after changing pixels to make the display
# update visible!
oled.fill(0)
oled.show()

oled.text("Raspberry Pi",5,5,1)
oled.show()
time.sleep(2)
oled.fill(0)
oled.show()

def blk():
    oled.fill(0)
    oled.show()
    
def horiz(l,t,r,c):  # left, right , top
    n = r-l+1        # Horizontal line
    for i in range(n):
        oled.pixel(l + i, t, c)

def vert(l,t,b,c):   # left, top, bottom
    n = b-t+1        # Vertical line
    for i in range(n):
        oled.pixel(l, t+i,c)

def box(l,t,r,b,c):  # left, top, right, bottom
    horiz(l,t,r,c)   # Hollow rectangle
    horiz(l,b,r,c)
    vert(l,t,b,c)
    vert(r,t,b,c)
    
def ring2(cx,cy,r,c):   # Centre (x,y), radius, colour
    for angle in range(0, 90, 2):  # 0 to 90 degrees in 2s
        y3=int(r*math.sin(math.radians(angle)))
        x3=int(r*math.cos(math.radians(angle)))
        oled.pixel(cx-x3,cy+y3,c)  # 4 quadrants
        oled.pixel(cx-x3,cy-y3,c)
        oled.pixel(cx+x3,cy+y3,c)
        oled.pixel(cx+x3,cy-y3,c)
        
# Clear the oled display in case it has junk on it.
oled.fill(0) # Black

# Basic stuff
oled.text("Raspberry Pi",5,5,1)
oled.text("Pico - Tony Goodhew",5,15,1)
oled.pixel(10,60,1)
oled.rect(5,32,20,10,1)
oled.fill_rect(40,40,20,10,1)
oled.line(77,45,120,60,1)
oled.rect(75,32,40,10,1)

ring2(50,43,20,1)  # Empty circle             
# Finally update the oled display so the image & text is displayed
oled.show()
time.sleep(3)

# Scrolling text on Sine curve
# Modified from a method by Tony DiCola
msg = 'Pico + SSD1306 is Magic!'
f_width  = 8   # Font width in pixels
f_height = 8   # Font Height in pixels
amp = 50   # Amplitude of sin wave
freq = 1    # Screen cycles (360 degrees)  
pos = WIDTH  # X position of the first character in the msg.
msg_len_px = len(msg) * f_width  # Pixel width of the msg.
# Extra wide lookup table - calculate once to speed things up
y_table = [0] * (WIDTH+f_width) # 1 character extra
for i in range(len(y_table)):
    p = i / (WIDTH-1)  # Compute current position
    # Create lookup table of  y co-ordinates 
    y_table[i] = int(((amp/2.0) * math.sin(2.0*math.pi*freq*p)) + (amp/2.0))
    
# Main loop:
stop = time.monotonic() + 30 # 30 seconds forward
while time.monotonic() < stop:
    # Start again if msg finished
    pos -= 1
    if pos <= -msg_len_px:
        pos = WIDTH
    # Go through each character in the msg.
    blk()
    for i in range(len(msg)):
        char = msg[i]
        char_x = pos + (i * f_width)  # Character's X position on the screen.
        if -f_width <= char_x < WIDTH:
            # If character is visible, draw it.
            oled.text(char, char_x + 5, y_table[char_x + f_width], 1)
    oled.show()
    time.sleep(0.08)
    
# Tidy up
blk()

 

 

The next thing to try is an I2C temperature chip. I’m going for the BME680 as it does far more.

Here is the code:

 

# BME680 with SSD1306 2 x I2C buses
# CircuitPython with Adafruit libraries
# Tony Goodhew 31 March 2021
import time
import board
import busio
import displayio
import adafruit_bme680
import adafruit_ssd1306
displayio.release_displays() # Saves a load of trouble

# Create the I2C interface.
i2c1 = busio.I2C (scl=board.GP1, sda=board.GP0)
i2c0 = busio.I2C (scl=board.GP7, sda=board.GP6)
# Buses alternate 0n1, 2n3, 4n5, 6n7 ...
display_bus = displayio.I2CDisplay (i2c1, device_address = 0x3C) # The address of my Board
# Create the SSD1306 OLED class.
oled = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c1)
# Alternatively you can change the I2C address of the device with an addr parameter:
# display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, addr=0x31)

# Clear the display.
oled.fill(0)
oled.text("BME 680",25,30,1)
oled.show()

# BME680
bme680 = adafruit_bme680.Adafruit_BME680_I2C(i2c0, debug=False)
# change this to match the location's pressure (hPa) at sea level
bme680.sea_level_pressure = 1019.0

#Temperature correction offset
temperature_offset = -0.6

while True:
    print("\nTemperature: %0.1f C" % (bme680.temperature + temperature_offset))
    print("Gas: %d ohm" % bme680.gas)
    print("Humidity: %0.1f %%" % bme680.relative_humidity)
    print("Pressure: %0.3f hPa" % bme680.pressure)
    print("Altitude = %0.2f meters" % bme680.altitude)
    oled.fill(0)
    oled.text("Temperature: %0.1f C" % (bme680.temperature + temperature_offset),0,5,1)
    oled.text("Gas: %d ohm" % bme680.gas,0,16,1)
    oled.text("Humidity: %0.1f %%" % bme680.relative_humidity,0,27,1)
    oled.text("Pressure: %0.2f hPa" % bme680.pressure,0,38,1)
    oled.text("Altitude: %0.2f m" % bme680.altitude,0,49,1)
    oled.show()
    time.sleep(1)

This worked as expected. I used 2 x I2C buses – to try this out. The pin pairs for each I2C bus alternate down the Pico:

0,1; 4,5; 8,9; … first bus

2,3; 6,7; 10,11;….. second bus

 

It is now 2 April 2021:

So CircuitPython does what I expected. The wonderful Adafruit library modules work so I can read data from a pretty complicated sensor, output information and graphics to a display screen, control LEDs and Neopixels, input and output on pins with digital and analog instructions. There is great support from the Adafruit company and the Forum. Unfortunately, CircuitPython does not support interrupts or Cores and has less support for PIOs.

 

Arduino IDE

Time to move on and try to program the Pico from the Arduino IDE.

At this time there is no official Arduino support except for the announcement of a forthcoming RP2040 Nano board and video demonstration of the Blink sketch on it.

 

We can do better than that as Earle F. Philhower, III (earlephilhower (Earle F. Philhower, III) · GitHub) has kindly provided an Arduino core for all RP2040 boards.

 

The instructions in the Read.me are easy to follow and very quick to set up:

 

GitHub - earlephilhower/arduino-pico: Raspberry Pi Pico Arduino core, for all RP2040 boards

 

I carried out the installation and got the Blink sketch working in under 10 minutes. Wonderful! I was very impressed.

 

Both installing the update to the Arduino IDE and compiling the sketch worked much faster than when I tried it for an ESP32.

 

The next test is to see how he deals with ADC and PWM.

 

/*
  Analog read a pot and control LED brightness with pot value
  Tony Goodhew 3 April 2021
  10K pot on pin 26
  LED and 330 Ohm resistor on pin 16
  
*/
//Initializing LED Pin
int led_pin = 16;

// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(11520);
  delay(1000); // Wait for Serial port
  //Declaring LED pin as output
  pinMode(led_pin, OUTPUT);
}

// the loop routine runs over and over again forever:
void loop() {
  // read the input on analog pin 0:
  int sensorValue = analogRead(26); // Range is 0 - 4095
  sensorValue = sensorValue - 26;
  // Correct ADC zero value offset - Top less important
  if (sensorValue < 0) {sensorValue = 0;}
  // print out the value you read:
  Serial.println(sensorValue);
  int pwmValue = int(sensorValue / 16); // Range 0 - 255
  analogWrite(led_pin, pwmValue);
  delay(200);
}

The after compiling the sketch the IDE reported:

Converting to uf2, output size: 443904, start address: 0x2000

Flashing F: (RPI-RP2)

Wrote 443904 bytes to F:/NEW.UF2

 

The ADC values range from 0 to 4095 but PWM uses the standard Arduino range 0 to 255. Once again, the Pico’s ADC errors were evident with a poor approximation for zero, which needed correction to get the LED to switch right off.

 

The only problems I had were:

The Serial monitor reported COM9 opening errors, but did start. I introduced a delay before writing to the Serial Monitor and that helped.

 

I also lost the connection between the Pico and the PC a few times. (Port was greyed out). Reinserting the USB with the BOOTSEL button down eventually fixed this but it sometimes took several tries.

 

The Arduino IDE produces a UF2 file, opens a link to the Pico, replaces the UF2 file and starts execution. Very neat – you do not have to drag and drop it as a separate action.

 

As a final test I tried to connect my SSD1306 using the Adafruit SSD1306 and GFX libraries. I hit a problem as the Arduino IDE expects designated pins and the Pico lets you put them more or less where you like. I used the Adafruit example script, which is quite a large sketch. It took a fair time to compile – expected, uploaded OK (478720 bytes) but the screen did not light up. I moved SDA and SCL from 0,1 to 8,9 and tried again. Same unlit screen. Probably a step too far.

 

I tried cutting down the example to the bare bones – just trying to display a single pixel. This compiled but lost the COM port while trying to open the Serial Monitor. (I think this is probably down to Windows 10 as I’ve had connection problem with other makes of board.) I tried several times to re-connect but gave up in the end.

 

I’m sure things will get better and the Arduino Organisation will eventually bring out an official version with I2C pins fixed, as normal, or a way to pick them on a Pico.

 

The main problem I have with the Arduino IDE is that it compiles. This takes a long time and is getting longer as the microcontrollers get more complicated. I’m a ‘bit-at-a-time’ incremental programmer. I plan the modules and basic structure, which I test first.  I make small additions to this working sketch and quickly test each step before moving on. I have pre-tested routines which I pull in when needed. This works very well with interpreted languages like MicroPython and CircuitPython but is really slow with the Arduino IDE. My projects now get to be quite long and complicated so slow compilations are a bit of a pain.

 

Those new to coding often work in a similar manner. They take an existing, working sketch, modify it and slowly expand and test. MP and CP are far more useful in this setting than being able to get into the finer workings of the chip. (Modern teaching methods for coding also follow this method. Students are given a piece of code, read it and try to explain what it does. They then run it, see what it really does and then modify it gradually to increase their knowledge.)

 

C/C++ SDK

 

It is with a fair degree of trepidation I start to investigate the C/C++ SDK. Initial reading of building the toolchain on a PC looked very hard work and probably prone to error. I have a Raspberry Pi 4B so I decided to use that as it has a wget command to do most of the heavy lifting.

 

I followed the instructions in the C/C++ guide and got to the step where I could see the list of PICO-EXAMPLES. At the bottom of the screen in the blue it said that I had ‘No Kit Selected’.

 

I looked on YouTube.com and found a useful video on setting up Visual Studio on Windows.

(30) How to Set Up Visual Studio Code to Program the Pi Pico (Windows) - YouTube

 

I skipped forward to 9 minutes, where the screen looked like mine on the Pi, and this told me how to continue. I right clicked the No Kit message and a list appeared. I picked:

        GCC for arm-none-eabi 7.3.1

 

At this point I got lost again.

 

What I need is a reasonably paced, step by step, clear video guide using a Raspberry Pi 4B and Visual Studio. At the moment I could not find one. They are all too fast and assume that you have used Visual Studio before with a different board. I hope some kind soul produces one soon; then I will give it another try. Perhaps another RoadTester can help here?

 

Further Investigation of the Cores Problem - 25 April 2021

 

Before finishing up I thought I would return to the Dual Cores Problem in MicroPython, which keeps falling over and see if I could fix it.

 

I moved the SSD1306 I2C connections from GP0 and GP1 to GP8 and GP9 because Thonny uses GP0 and GP1 to send and receive from the REPL.

 

I looked up the documentation about _thread.

 

_thread — Low-level threading API — Python 3.9.4 documentation

 

I thought I would try to implement a lock. This is supposed to prevent the two separate threads from accessing a global variable at the same time. In this case as Core0, with the interrupt routines, tries to write to either running or led_dir while Core1 is trying to read them. I also moved the LEDs to pins GP2,3,4 & 5. I used the latest pure UF2 from MicroPython.org (v1.15) without the Pimoroni added code for their display screens.

 

All started well. The screen scrolled the text along the sine curve, the LEDs and the moving Neopixels changed direction when the direction button was pressed and the program halted correctly with the second button.

 

However, if just left it to run, Core1 crashed leaving the text scrolling on Core0. This took about 23 seconds. I could halt the scrolling with the halt button so Core0 was still working. If I tried to just run the program again, from the green arrow icon, I got the this error message:

 

>>> %Run -c $EDITOR_CONTENT

Traceback (most recent call last):

  File "<stdin>", line 167, in <module>

OSError: core1 in use

 

Looking again at the Raspberry Pi Forum, where there has been some discussion about problems with running both cores, there was little more to do than to try garbage collections on Core0, as I’d used earlier on the much simpler project. I tried it again, inside the scrolling loop for the SSD1306.

 

Here is the final code:

 

# Uses both Cores, PIO and Interrupts
# Tony Goodhew - 25th April 2021
# 16 Neopixels on GP21, 5v and GND
# Button switches GB28 and GP27 (pull-down)
# SSD1306 on GP8 and GP9
#
# Lower button (GP27) controls direction of LEDs & Neopixels
# Upper button (GP28) HALTs the program
from machine import Pin, I2C
import gc # garbage collection
import _thread
import utime
import array
import rp2
from ssd1306 import SSD1306_I2C
import framebuf
import math
WIDTH  = 128    # oled display width
HEIGHT = 64     # oled display height

# Explicit Method
sda=machine.Pin(8)
scl=machine.Pin(9)
i2c=machine.I2C(0,sda=sda, scl=scl, freq=400000)

#from ssd1306 import SSD1306_I2C
oled = SSD1306_I2C(128, 64, i2c)

# Configure the  WS2812 Neopixels
NUM_LEDS = 16
PIN_NUM = 21
brightness = 0.2

@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24)
def ws2812():
    T1 = 2
    T2 = 5
    T3 = 3
    wrap_target()
    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]
    wrap()

# Create the StateMachine with the ws2812 program, outputting on pin
sm = rp2.StateMachine(0, ws2812, freq=8000000, sideset_base=Pin(PIN_NUM))
# 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)])

BLACK = (0, 0, 0)
RED = (255, 0, 0)
YELLOW = (255, 150, 0)
GREEN = (0, 255, 0)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)
WHITE = (255, 255, 255)
COLORS = (RED, YELLOW, GREEN, CYAN, BLUE, PURPLE, WHITE)

def pixels_show():
    dimmer_ar = array.array("I", [0 for _ in range(NUM_LEDS)])
    for i,c in enumerate(ar):
        r = int(((c >> 8) & 0xFF) * brightness)
        g = int(((c >> 16) & 0xFF) * brightness)
        b = int((c & 0xFF) * brightness)
        dimmer_ar[i] = (g<<16) + (r<<8) + b
    sm.put(dimmer_ar, 8)
    utime.sleep_ms(10)

def pixels_set(i, color):
    ar[i] = (color[1]<<16) + (color[0]<<8) + color[2]

def pixels_fill(color):
    for i in range(len(ar)):
        pixels_set(i, color)

def blk():
    oled.fill(0)
    oled.show()
     
# Clear the oled display in case it has junk on it.
oled.fill(0) # Black
oled.show()

# Scrolling text on Sine curve
# Modified from a method by Tony DiCola
msg = 'Pico + SSD1306 is Magic!'
f_width  = 8   # Font width in pixels
f_height = 8   # Font Height in pixels
amp = 50   # Amplitude of sin wave
freq = 1    # Screen cycles (360 degrees)  
pos = WIDTH  # X position of the first character in the msg.
msg_len_px = len(msg) * f_width  # Pixel width of the msg.
# Extra wide lookup table - calculate once to speed things up
y_table = [0] * (WIDTH+f_width) # 1 character extra
for i in range(len(y_table)):
    p = i / (WIDTH-1)  # Compute current position
    # Create lookup table of  y co-ordinates 
    y_table[i] = int(((amp/2.0) * math.sin(2.0*math.pi*freq*p)) + (amp/2.0))

lock = _thread.allocate_lock()

#Set up 4 LEDs
leds = [0,0,0,0]
for i in range(4):
    leds[i]= machine.Pin(i+2, machine.Pin.OUT)     # Digital output

def led_dirTask(pin):
    lock.acquire()
    global led_dir
    led_dir = led_dir * -1
    lock.release()
def HaltTask(pin):
    lock.acquire()
    global running
    running = False
    lock.release()
wait = 0.07
led_dir = 1
running = True
i = 0

#Set up button switches for interrupts
led_dirPin = machine.Pin(27, machine.Pin.IN,machine.Pin.PULL_DOWN)
led_dirPin.irq(trigger=machine.Pin.IRQ_RISING, handler=led_dirTask)
HaltPin = machine.Pin(26, machine.Pin.IN,machine.Pin.PULL_DOWN)
HaltPin.irq(trigger=machine.Pin.IRQ_RISING, handler=HaltTask)

def core_task():
    i = 0
    col = 0
    p0 = 0
    p1 = 8
    lock.acquire()
    while running:
        
        # Update LEDs
        i = (i + led_dir)
        lock.release()
        if i == 4:
            i = 0
        elif i < 0 :
            i = 3    
        leds[i].value(1)
        utime.sleep(wait)
        leds[i].value(0)
        # Update Neopixels
        pixels_fill(BLACK)
        p0 = (p0 + led_dir) % 16
        p1 = (p1 + led_dir) % 16
        pixels_set(p0, COLORS[col])
        pixels_set(p1, COLORS[(col +2) % 7])
        pixels_show()
        utime.sleep(wait)
        lock.acquire()
    # Tidy up Neopixels on HALT
    pixels_fill(BLACK)
    pixels_show()
    # Core 1 Halts here
    _thread.exit()
    
# Run LEDs and Neopixels from Core 1
_thread.start_new_thread((core_task),())

# Main loop on Core 0
c = 0 # garbage collection counter
running = True
while running:
    # Start again if msg finished
    pos -= 1
    if pos <= -msg_len_px:
        pos = WIDTH
    # Go through each character in the msg.
    blk()
    for i in range(len(msg)):
        # Garbage collection to stop crashes 
        c= c + 1
        if (c % 100 == 0):
            gc.collect()
            c = 0 # End of gc
        char = msg[i]
        char_x = pos + (i * f_width)  # Character's X position on the screen.
        if -f_width <= char_x < WIDTH:
            # If character is visible, draw it.
            oled.text(char, char_x + 5, y_table[char_x + f_width], 1)
    oled.show()
    utime.sleep(0.08)
# Tidy up - Core 0
for i in range(4):
    leds[i].value(0)
print("HALTED")
blk()
# Wait for Core 1 to stop first
utime.sleep(1)

Success! This works as I planned.

 

image

 

I fear that there is something not quite right here. You should not need to call for garbage collection in the main loop of your program. I do not know what is going on. I will post on the Raspberry Pi Forum to see if I can get some help.

 

Final thoughts:

 

Manufacturers all over the world are producing add-on boards for the Pico and developing their own microcontroller boards based on the same RP2040 chip, often with more flash memory than on the Pico, and different pinouts. Many have been produced for novice users providing clearly labelled pins and sockets (very helpful) and additional components such as relays,  display screens, motor controllers, a Neopixel or other RGB LED, LEDs, buzzer, earphone jack, buttons, Grove  ports and even a micro-SD card reader such as on the MAKER PI PICO from the Malaysian producer Cytron. Arduino, Adafruit, Sparkfun and Pimoroni all have new boards in the pipeline using this RP2040 chip.

image

Some of the kit on my desk: 4 Picos, Pico Explorer, Pico Display, BME680, SSD1306(128x64), Tiny2040, Pico Decker and Cytron Maker Pi Pico, Grove cables, breadboard and stripboard project boards

 

Looking more closely at the Pico hardware; it is unfortunate that the pins are not clearly identified on the top of the Pico. The poor accuracy of the ADCs, especially at the zero end, is a great disappointment.

 

The Pi Pico is a great little board – cheap, fast, enough memory for quite complicated programs and supports interrupts, dual cores and PIO.

It is easy to use in CircuitPython and Adafruit provides great support especially with driver modules and an excellent manned forum. Unfortunately, the user is limited to single core operation, without interrupts and downgraded PIO facilities.

It has extra facilities with MicroPython, but its users will then have difficulties with I2C and SPI peripherals. There appears to be a problem when dual core operation, interrupts and PIO programming are combined forcing the recourse to garbage collection routines.

 

Once an official Arduino update is published this will be taken up by the next level of users who are already familiar with the IDE. Visual Studio and the C/C++ programming method will remain unused by the majority of hobbyists until some more accessible video guidance is provided to explain, in simple steps, setup, programming and workflow.

 

The Pi Pico community has done a great deal since the launch, back in January, to help users overcome problems with guidance in on-line forums. (I expected and hoped for more intervention from the British hardware producers – not currently up to the standard set by Adafruit.) There is already a great deal of help and advice in internet blogs but searching for the answer to a problem is not always simple, quick or effective. There have been massive gains in knowledge since January but there is still a great deal more to be done to help users improve their skills to the upper levels.

 

 

The MicroPython and CircuitPython UF2 files together with the Thonny and Mu editors are regularly being updated and users need to keep up to date as bugs are fixed.

 

When you decide to purchase a Pi Pico, and hope you do, I suggest you also get a cheap SSD1306 128x64 display – it will add greatly to your enjoyment and is easy to program in either version of Python.

 

I hope you have found this RoadTest useful and informative. I’m happy to converse via the comments section. Above all, enjoy your coding.

 

=======================================================================================================================================

18th May 2021

 

The Pico Explorer display is quite small and so far, I’ve only used MicropPython to develop graphical routines. I thought it was time to have a go with a bigger screen and drive it with CircuitPython. No Pico specific larger screen is on offer so I decided to go for a cheap option and get something from Ebay. The 2.4” ILI9341 is a TFT 240x320 SPI display which is also available with a touch screen option. The chip for the Touch device is a xpt2046. Adafruit provide a driver for the ILI9341 display but not the xpt2046.

 

I eventually found what I needed to progress with Google:

Hello Raspberry Pi: Raspberry Pi Pico/CircuitPython + ILI9341 SPI Display with Touch

 

This blog explains how to wire things up and provides a library module to drive the Touch chip as well. (The unnamed writer modified the module from a MicroPython version which uses interrupts (not available with CP). There is also a link to using the board with MicroPython.

 

image

The one on the left has the Touch option – see top edge of screen.

 

image

 

Looking at the backs of the boards you can see the SD card readers and on the lower board the xpt2046 fitted.

 

Be careful when purchasing to get the Touch version, if you are going to make use of it, as it is easy to just get the display – the price difference is small. The pointer is very useful, but not always supplied.

 

Connecting up

ILI9341 TFT SPI                                 RPi Pico

VCC                                                       3V3

GND                                                     GND

CS                                                          GP13

RESET                                                   GP14

DC                                                         GP15

SDI (MOS)                                              GP7

SCK                                                       GP6

LED                                                        3V3

SDO(MISO)                                        -

 

TOUCH

T_CLK                                                   GP10

T_CS                                                     GP12

T_DIN                                                   GP11

T_DO                                                    GP8

T_IRQ   

 

image

 

I made up a carrier for a more permanent connection and to make it easy to add extra components to the project. There are quite a few crossed wires and several cut tracks under the board.

 

I followed the instructions in this excellent blog (Unnamed author – Thank you very much.) and quickly got the display working.

 

Moving on to the Touch example I found that my board was quite different and some major calibration was called for. I’ve used similar boards with an Arduino where you make use of the ADCs to find the position of the pointer on the touch screen. Each board is slightly different so you need to calibrate your own board. No calibration program was provided to I looked over the code and found that you need to know the RAW readings from the x and y voltage dividers to calculate the coordinates. You need the minimum and maximum raw values for x and y.

 

As a temporary, quick and dirty method, as you only need to do this once, I added a couple of lines to the library module:

 

    def raw_touch(self):
 """Read raw X,Y touch values.

        Returns:
            tuple(int, int): X, Y
        """
        x = self.send_command(self.GET_X)
        y = self.send_command(self.GET_Y)
# print(str(x)+" "+str(y)) #      <###################                               
#        sleep(1)
        if self.x_min <= x <= self.x_max and self.y_min <= y <= self.y_max:
            return (x, y)
        else:
            return None

 

and commented out the print lines in the main program.

 

I pressed screen the pointer in the four corners of the display and noted down the max and min values for RAW x and y.

 

I then went back to the original program (line 42) and changed the constants:

 

touch_cs = board.GP12
#touch_int = board.GP0

touch_x_min = 136                #64
touch_x_max = 1952               #1847
touch_y_min = 89                 #148
touch_y_max = 1864               #2047

touch_spi = busio.SPI(touch_spi_clk, MOSI=touch_spi_mosi, MISO=touch_spi_miso)
touch = Touch(touch_spi, cs=touch_cs,
 x_min=touch_x_min, x_max=touch_x_max,
 y_min=touch_y_min, y_max=touch_y_max)

 

and added a couple of tweaks as y was working in the opposite direction:

 

def validTouch():

    xy = touch.raw_touch()

    if xy == None:
        return None

    normailzedX, normailzedY = touch.normalize(*xy)
# Tony's fudge factor
    normailzedX = normailzedX +3
    normailzedY = 320 - normailzedY

    if (normailzedX < 0 or normailzedX >= scrWidth
            or normailzedY < 0 or normailzedY >= scrHeight):
            return None

    return (normailzedX, normailzedY)

 

This got things working pretty well. Perhaps not quite getting to the edges of the screen and exhibiting a bit of randomness, but useful enough if the screen buttons in a future project are not too small.

 

image

Now to try out the graphic commands in CircuitPython. These are much more complicated than the MicroPython routines I wrote for the displays used earlier (single layer). There is an excellent guide on the Adafruit Learn website:

 

Introduction | CircuitPython Display Support Using displayio | Adafruit Learning System

 

This is quite a long read, covers the essentials but is a more complicated multi-layered system.

Basically, graphical elements are built up on different layers above a background. The last is at the top. When the screen refreshes a single pixel may be defined at that moment on several layers. The uppermost definition is used. Individual layers may be moved horizontally between screen refreshes. The layers are held in a list, background first, and new layers can be appended (on the end) and popped/removed from the end of the list. Complicated but very powerful.

 

Project Aims

Display a title and instructions.

Use different text sizes and colours.

Use three 'Plus/Minus' rocker buttons to control the RGB component values of a colour.

Display the current values as numbers and as dynamic bar graphs.

Display the mixed colour in a rectangle.

Stop execution of the program with a HALT button.

 

Sounds pretty simple and I’ve done something similar several times before with other microcontrollers and displays. It was a different ball game with CircuitPython.

 

Normally bar graphs are a simple rectangle drawn on the background in the required colour. This normally takes two statements. The first to draw the rectangle and the second to update the screen. If you want to change the length you overdraw the rectangle in background colour and then redraw the new rectangle in whatever colour you now want. CP rectangles are a different kettle of fish. I eventually drew two full length bars. The first in the required colour and the second on the layer above, in the background colour. In CP you can slide the layers in the x and y directions. So, sliding the background layer along the top of the bar graph layer you can gradually expose the required bar.

 

To change the colour of a box you need it at the end of the layers list so that you can pop the box definition from the list and then append a newly defined box in the appropriate colour.

 

The whole exercise took far longer than I thought it would but I’ve got it working. I’m also amazed at the length of code needed.

 

I’ve separated the setting up of the display and the touch input to make things easy if you want to lift out code. There are plenty of comments so you can follow what is going on.

 

 

 

 

"""
Dynamic bar graphs - Cover method
Example of CircuitPython/Raspberry Pi Pico
on 320x240 ili9341 SPI display with xpt2046 touch addition
Tony Goodhew 18th May 2021
"""
import board
import displayio
import time
import terminalio
import busio
from adafruit_display_text import label
import adafruit_ili9341
from adafruit_display_shapes.rect import Rect
from cpy_xpt2046 import Touch
# ================Set up display=================
# Release any resources currently in use for the displays
displayio.release_displays()

# Setup ili9341 320x240 TFT display
TFT_WIDTH = 320
TFT_HEIGHT = 240
tft_spi_clk = board.GP6
tft_spi_mosi = board.GP7
tft_cs = board.GP13
tft_dc = board.GP15
tft_res = board.GP14
tft_spi = busio.SPI(tft_spi_clk, MOSI=tft_spi_mosi)
display_bus = displayio.FourWire(
    tft_spi, command=tft_dc, chip_select=tft_cs, reset=tft_res)
display = adafruit_ili9341.ILI9341(display_bus,
 width=TFT_WIDTH, height=TFT_HEIGHT)
display.rotation = 90 # Portrait orientation

# Make the display context
splash = displayio.Group(max_size=20)
display.show(splash)


# Set up the TOUCH facility
touch_spi_clk = board.GP10
touch_spi_mosi = board.GP11
touch_spi_miso = board.GP8
touch_cs = board.GP12
touch_x_min = 136        # These values may well be
touch_x_max = 1952       # different on your display
touch_y_min = 89
touch_y_max = 1864
touch_spi = busio.SPI(touch_spi_clk, MOSI=touch_spi_mosi, MISO=touch_spi_miso)
touch = Touch(touch_spi, cs=touch_cs,
 x_min=touch_x_min, x_max=touch_x_max,
 y_min=touch_y_min, y_max=touch_y_max)
  
EVT_NO = const(0)
EVT_PenDown = const(1)
EVT_PenUp   = const(2)
EVT_PenRept = const(3)
touchEvent  = EVT_NO

touchSt_Idle_0     = const(0)
touchSt_DnDeb_1    = const(1)
touchSt_Touching_2 = const(2)
touchSt_UpDeb_3    = const(3)
touchSt = touchSt_Idle_0

touchDb_NUM = const(3)
touchDb = touchDb_NUM
touching = False

def validTouch():
    xy = touch.raw_touch()
    if xy == None:
        return None
    normalizedX, normalizedY = touch.normalize(*xy)
# Tony's fudge factor - Y was mirroring (Different make of board?)
    normalizedX = normalized - 1
    normalizedY = 320 - normalizedY
    if (normalizedX < 0 or normalizedX >= 240
            or normalizedY < 0 or normalizedY >= 320):
            return None
    return (normalizedX, normalizedY)

def TouchDetTask():
    global touch
    global touching
    global touchSt
    global touchEvent
    global touchedX, touchedY
    global touchDb
    validXY = validTouch()
    if touchSt == touchSt_Idle_0:
        if validXY != None:
            touchDb = touchDb_NUM
            touchSt = touchSt_DnDeb_1
    elif touchSt == touchSt_DnDeb_1:
        if validXY != None:
            touchDb = touchDb-1
            if touchDb==0:
                touchSt = touchSt_Touching_2
                touchEvent = EVT_PenDown
                touchedX, touchedY = validXY
                touching = True
        else:
            touchSt = touchSt_Idle_0
    elif touchSt == touchSt_Touching_2:
        if validXY != None:
            touchedX, touchedY = validXY
            touchEvent = EVT_PenRept
        else:
 touchDb=touchDb_NUM
            touchSt = touchSt_UpDeb_3
    elif touchSt == touchSt_UpDeb_3:
        if validXY != None:
            touchSt = touchSt_Touching_2
        else:
 touchDb=touchDb-1
            if touchDb==0:
                touchSt = touchSt_Idle_0
                touchEvent = EVT_PenUp
                touching = False

def update():
    global r
    global g
    global b
    global running
    x = touchedX
    y = touchedY
    if y > 300:
        running = False
    if y < 295 and y > 260:
        if x < 40:
            r = r - 5
        elif x < 80:
            r = r + 5
        elif x < 120:
            g = g - 5
        elif x < 160:
            g = g + 5
        elif x < 200:
            b = b - 5
        elif x < 240:
            b = b + 5
        if r < 0: r = 0
        if r > 255: r = 255
        if g < 0: g = 0
        if g > 255: g = 255
        if b < 0: b= 0
        if b > 255: b = 255
        rtext.text = str(r)
        gtext.text = str(g)
        btext.text = str(b)
        rcover.x = int(r/2) + 30
        gcover.x = int(g/2) + 30
        bcover.x = int(b/2) + 30
        # Remove existing coloured mixerBox
        splash.pop()
        # Create replacement 
        mixerBox = Rect(80,140,80,35,fill=((0x010000 *r) +(0x0100 * g) + b))
 splash.append(mixerBox)
##########################################################################
# Make a background color fill
color_bitmap = displayio.Bitmap(240, 320, 50) # Full screen background WHITE
color_palette = displayio.Palette(10)
color_palette[0] = 0xFFFFFF # WHITE
bg_sprite = displayio.TileGrid(color_bitmap, x=0, y=0, pixel_shader=color_palette)
splash.append(bg_sprite)


# Build up screen in layers on top of background
text = "Touch Screen Demonstration"
font = terminalio.FONT
color = 0xFF0000
text_area = label.Label(font, text=text, color=color)
text_area.x = 36
text_area.y = 8
splash.append(text_area)

instr = "Gently press + and -, or HALT"
inst_area = label.Label(font, text=instr, color=color)
inst_area.x = 30
inst_area.y = 220
splash.append(inst_area)


# Bar Graph rectangles - colour bar and background cover
rbase = Rect(28, 15, 2, 30, fill=0x000000)
splash.append(rbase)

rbar = Rect(30, 20, 200, 20, fill=0xFF0000)
splash.append(rbar)

rcover  = Rect(30, 20, 200, 20, fill=0xFFFFFF)
splash.append(rcover)

gbase = Rect(28, 45, 2, 30, fill=0x000000)
splash.append(gbase)

gbar = Rect(30, 50, 200, 20, fill=0x00DD00)
splash.append(gbar)

gcover  = Rect(30, 50, 200, 20, fill=0xFFFFFF)
splash.append(gcover)

bbase = Rect(28, 75, 2, 30, fill=0x000000)
splash.append(bbase)

bbar = Rect(30, 80, 200, 20, fill=0x0000FF)
splash.append(bbar)

bcover  = Rect(30, 80, 200, 20, fill=0xFFFFFF)
splash.append(bcover)


# RED
color = 0xFF0000
rtext = label.Label(font, text="     ", color=color)
rtext.x = 8
rtext.y = 30
splash.append(rtext)

# GREEN
color = 0x00DD00
gtext = label.Label(font, text="      ", color=color)
gtext.x = 8
gtext.y = 60
splash.append(gtext)

# BLUE
color = 0x0000FF
btext = label.Label(font, text="      ", color=color)
btext.x = 8
btext.y = 90
splash.append(btext)


# Set up Touch buttons - rocker buttons - UP/DOWN
halt = Rect(0, 281, 240, 40, fill=0x000000)
splash.append(halt)

rbutton = Rect(0, 241, 80, 40, fill=0xFF0000)
splash.append(rbutton)

gbutton = Rect(80, 241, 80, 40, fill=0x00FF00)
splash.append(gbutton)

bbutton = Rect(160, 241, 80, 40, fill=0x0000FF)
splash.append(bbutton)


# Text labels for buttons
text_group1 = displayio.Group(max_size=1, scale=2, x=10, y=260)
color = 0x000000
rbtext = label.Label(font, text="-   +", color=color)
text_group1.append(rbtext)
text_group2 = displayio.Group(max_size=1, scale=2, x=90, y=260)
gbtext = label.Label(font, text="-   +", color=color)
text_group2.append(gbtext)
text_group3 = displayio.Group(max_size=1, scale=2, x=170, y=260)
bbtext = label.Label(font, text="-   +", color=color)
text_group3.append(bbtext)
text_group4 = displayio.Group(max_size=1, scale=2, x=90, y=300)
color = 0x00DD00
htext = label.Label(font, text="HALT", color=color)
text_group4.append(htext)
splash.append(text_group1)
splash.append(text_group2)
splash.append(text_group3)
splash.append(text_group4)


# Initial conditions
r = 180
g = 160
b = 190
mixerBox = Rect(80,140,80,35,fill=((0x010000 *r) +(0x0100 * g) + b))
splash.append(mixerBox)


rtext.text = str(r)
gtext.text = str(g)
btext.text = str(b)
rcover.x = int(r/2) + 30
gcover.x = int(g/2) + 30
bcover.x = int(b/2) + 30

print("Starting")
running = True
while running:
    TouchDetTask()
    # Handle touch event
    if touchEvent != EVT_NO:
        if touchEvent == EVT_PenDown:
            update()
        if touchEvent == EVT_PenUp:
            pass
        if touchEvent == EVT_PenRept:
            update()
        touchEvent = EVT_NO

print("HALTED")

 

I found using displayio a real pain, and I've been coding for ages. It got in the way and would probably put off those who have not tried to do simple graphics before.


I know displayio was built for writing games but I've not seen many finished published examples. Most users just want to use the screen to display the results obtained from sensors and at the moment you have to use CP to read the sensors. Simple, single layer writing to the screen buffer is sufficient for this, like we do with a SSD1306.

Is it possible to provide such a low-level library in CircuitPython which would link in to the screen driver libraries as GFX does for Arduino users?

 

This sounds to me like it could be done with framebuffer, but I’ve no knowledge of how it works.

 

I’ve added to an old thread on the Adafruit Forum if anyone can could help:

 

Adafruit customer service forums • View topic - CircuitPython and the GFX library

 

Let’s hope someone picks up.

 

=====================================================================================

 

Pico, MicroPython and SD card storage

 

A standard Raspberry Pi Pico has just 2MB of on-board Flash memory. Not a great deal to hold the UF2, libraries, your code and data. If your project needs to collect a great deal of data you will soon run out of space. You could opt for another board, still using the PR2040 chip, but with much more memory. In the last few weeks many more have reached the market. (The Pico Lipo from Pimoroni has 4/16 MB of SPI Flash memory and the same pinout as a standard Pico.) Alternatively, you could use a SD card to hold a great deal of data. This project shows how it is done.

 

Thanks to Jurij Orlow and contributors to this forum

https://www.raspberrypi.org/forums/viewtopic.php?f=146...

on how do it.

All you need is a passive microSD adaptor. Use a soldering iron to connect the gold pads directly with wires to your Pico GPIO pins. (I found that I had to scratch the gold contacts with the point of a penknife before I could get the solder to stick properly. Luckily the plastic has a high melting point and caused no problems.)

image

 

Adaptor Pin

Name

Pico GPIO

1

CS - Chip Select

9

2

MOSI - Master Out Slave In

11

3

GND

GND

4

VCC

3V3

5

SCK – SPI Clock

10

6

GND

GND

7

MISI – Master In Slave Out

12

8

Not Used

 

9

Not used

 

 

image

 

The library we need is here:

https://github.com/.../blob/master/drivers/sdcard/sdcard.py

Copy this to your Pico's filesystem, and name it "sdcard.py"

Format the card FAT32 and create a directory called “sd”.

The following program shows the system working in MicroPython.

 

import sdcard
import machine
import uos
sd_spi = machine.SPI(1, sck = machine.Pin(10, machine.Pin.OUT), mosi = machine.Pin(11, machine.Pin.OUT), miso = machine.Pin(12, machine.Pin.OUT))
sd = sdcard.SDCard(sd_spi, machine.Pin(9))

uos.mount(sd, "/sd")

print("Size: {} MB".format(sd.sectors/2048)) # to display card's capacity in MB
print(uos.listdir("/sd"))
print("\n=======================\n")
print("Basic SDcard Test \n")

with open("/sd/test2.txt", "w") as f: # Write - new file
    f.write("First Message\r\n")

with open("/sd/test2.txt", "a") as f: # Append
    f.write("Tony Goodhew\r\n")

with open("/sd/test2.txt", "a  ") as f:
    f.write("Leicester City Cup Winners!\r\n")
    
with open("/sd/test2.txt", "a  ") as f:
    for i in range(10):
        f.write(str(i) + ", " + str(i*i*i) + ", " + str(i*i*i*i) + "\r\n")


with open("/sd/test2.txt", "a  ") as f:
    f.write("Looping all done!\r\n")
        
with open("/sd/test2.txt", "r") as f:
    print("Printing lines in file: Method #1\n")
    line = f.readline()
    while line != '':   # NOT EOF
        print(line)
        line = f.readline()


with open("/sd/test2.txt", "r") as f:
    lines = f.readlines()
    print("Printing lines in file: Method #2")
    for line in lines:
        print(line)

uos.umount("/sd")

 

OUTPUT

 

>>> %Run -c $EDITOR_CONTENT

Size: 7579.999 MB

['overlays', 'bcm2708-rpi-b-plus.dtb', 'COPYING.linux', 'LICENCE.broadcom', 'bcm2709-rpi-2-b.dtb', 'bcm2708-rpi-b.dtb', 'bcm2708-rpi-cm.dtb', 'issue.txt', 'start.elf', 'bcm2710-rpi-3-b.dtb', 'bcm2710-rpi-cm3.dtb', 'bootcode.bin', 'cmdline.txt', 'config.txt', 'fixup.dat', 'fixup_cd.dat', 'fixup_db.dat', 'fixup_x.dat', 'kernel.img', 'kernel7.img', 'start_db.elf', 'start_x.elf', 'LICENSE.oracle', 'System Volume Information', 'start_cd.elf', 'test.txt', 'test2.txt']

 

=======================

 

Basic SDcard Test

 

Printing lines in file: Method #1

 

First Message

 

Tony Goodhew

 

Leicester City Cup Winners!

 

0, 0, 0

 

1, 1, 1

 

2, 8, 16

 

3, 27, 81

 

4, 64, 256

 

5, 125, 625

 

6, 216, 1296

 

7, 343, 2401

 

8, 512, 4096

 

9, 729, 6561

 

Looping all done!

 

Printing lines in file: Method #2

First Message

 

Tony Goodhew

 

Leicester City Cup Winners!

 

0, 0, 0

 

1, 1, 1

 

2, 8, 16

 

3, 27, 81

 

4, 64, 256

 

5, 125, 625

 

6, 216, 1296

 

7, 343, 2401

 

8, 512, 4096

 

9, 729, 6561

 

Looping all done!

 

>>>

 

This works very well and is quick, easy and cheap!

Anonymous