RoadTest: Raspberry Pi Pico
Author: tonygo
Creation date:
Evaluation Type: Semiconductors
Did you receive all parts the manufacturer stated would be included in the package?: True
What other parts do you consider comparable to this product?: Adafruit ItsyBitsy M4 Express featuring ATSAMD51
What were the biggest problems encountered?:
Inaccurate Analog to Digital Conversion (ADC), especially at the zero end.
Strange error messages
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:
Getting Started with Raspberry Pi Pico
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:
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.
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:
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.
One of the downloadable references is the Pico Python SDK, which you can get here:
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.
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
2 button switches
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.
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:
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:
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:
(The latest version also supports MicroPython code)
The getting started guide to Circuit Python is here:
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:
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:
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:
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.
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.
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!
Top Comments
"An ADC has two key features: its resolution, measured in digital bits, and its channels, or how many analogue signals it can accept and convert at once. The ADC on RP2040 has a resolution of 12-bits,…
Hi Tony,
Great writeup! You have put a great deal of time into this and the information is greatly appreciated. I don’t understand what is going on with the ADC however
Very good road test report.
I am surprised you gave the Pico a good score given the issues you discovered.
That said, I was not surprised. The first version of any board usually has a lot of bugs and there…