Skip navigation
> RoadTest Reviews

Raspberry Pi Pico - Review

Scoring

Product Performed to Expectations: 8
Specifications were sufficient to design with: 10
Demo Software was of good quality: 9
Product was easy to use: 8
Support materials were available: 9
The price to performance ratio was good: 10
TotalScore: 54 / 60
  • RoadTest: Raspberry Pi Pico
  • Buy Now
  • Evaluation Type: Semiconductors
  • Was everything in the box required?: No
  • Comparable Products/Other parts you considered: 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 <stdin> 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.

     

    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.

     

    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.

     

    Pimoroni Pico Explorer

     

    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.

     

     

    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:

     

    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.

     

    MonkMakes Kit 1 for Pico

     

     

    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.

    SSD1306

     

     

    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.

     

     

    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.

     

    GFX1GFX2

     

     

    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

     

    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.

     

    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.

     

    Dual Cores

     

    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.

    Pi Pico Kit

    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.

     

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

     

     

    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   

     

     

    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.

     

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

     

    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

     

     

     

    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!


Comments

Also Enrolling

Enrollment Closes: Nov 1 
Enroll
Enrollment Closes: Oct 31 
Enroll
Enrollment Closes: Oct 31 
Enroll
Enrollment Closes: Oct 31 
Enroll
Enrollment Closes: Oct 31 
Enroll
Enrollment Closes: Nov 1 
Enroll
Enrollment Closes: Oct 31 
Enroll