Aberdeen Art Gallery Micro:Bit workshops 2024

We have some workshops coming up in January/February 2024 in the Aberdeen Art Gallery, thanks to our successful application to the “Creative Funding Programme” from Aberdeen City Council in the summer!

We’ll be running free “drop in” sessions, where you can find out about Microbit s & “break out boards” (adding more inputs & outputs) to make really cool interactive things…

And we’re also running some “inventor” workshops (£45 per person) – but you get to keep £45 worth of kit! (Micro:bits, Breakout boards, sensors, LEDs, wires, breadboards, electronics etc). – Some spaces are sponsored by SRCN Solutions – so if you need financial assistance & would love to come to the workshop, get in touch to secure a sponsored space.

you can sign up via the #AAGM website

Supported by SRCN Solutions

#YesNo (“Do you care?”)

Step By Step PDF of how to build a “Yes/No” custom buttons display with a Raspberry Pi Pico, 2 x Custom Made Buttons & 2x7segment 4 digit LED backbacks

We have uploaded a PDF of the steps to build a “Yes/No” Custom Button + LED Digit displays on a raspberry Pi Pico , with step by step guide on how to make the custom wooden buttons, and the easy implementation of the adafruit_ht16k33 library for the LED Backpacks.

You can also download the zip file of the code  here.

#optimisticOracle

Step By Step PDF of how to build an “Optimistic Oracle” with a tiny2040, PIR & speaker / Amp.

We have uploaded a PDF of the steps to build an “OptimisticOracle” using a pimoroni Tiny2040 a PIR sensros & a Speaker / Amp.

You can also download the zip file of the code & supporting MP3s here.

The Optimistic Oracle giving out helpful predictions for a fairer & healthier future for all.
Inspired by Sam Bentley’s Good News Feed

Number to Money counter with CircuitPython on Tiny2040

Phil’s working on some interactive electronics for a project, and he was asked to create a counter that could show how much money you could make from 20p recycling deposits using CircuitPython & a Tiny2040

Phil’s working on some interactive electronics for a project, and he was asked to create a counter that could show how much money you could make from 20p recycling deposits. The counter + button in a #circuitPython was easy to implement (see other “weekly project” posts for a “how to”) – But playing an MP3 file has become incredibly easy with CircuitPython, having the library / code built in to the system! so no need to add a new library or even extra electronics to your CircuitPython Powered Board of Choice! (We’ve been using the Raspberry Pi #Pico & the Pimoroni #Tiny2040 )

Below is a short video of the Tiny2040 + button + mono speaker counting up when the counter value is sent to a function that converts it to a list of MP3’s to play.

Tiny2040 + Button + Mono Speaker playing MP3s

Phil took a while to create an algorithm to create a “sentence” that stitches together the necessary files needed… using small MP3 files saved to the 8mb Tiny2040. The numbers 1 to 20 were recorded, then, 30, 40, 50, 60, 70, 80 & 90. Phil also created with a speech synthesiser, “pence”, “and”, “pound”, “pounds” – which can then create any number up to 999 (Thousands + were unnecessary at the moment!) When it reaches “£100” – a clip saying “you’ve made a lot of money, perhaps donate it to charity?” is spoken, then the counter resets !

The code below is Phil’s “Robust” programming… which works nicely, and can even cope with increments of 5p – but single counts break it (his “logic” should be improved – but the 5 / 10 / 20p increments work!

# SPDX-FileCopyrightText: 2020 Jeff Epler for Adafruit Industries
# Custom Code by Philip Thompson / Digital Maker CIC
# SPDX-License-Identifier: MIT

"""CircuitPython Essentials Audio Out MP3 """
import board
import digitalio
from time import sleep

from audiomp3 import MP3Decoder

try:
    from audioio import AudioOut
except ImportError:
    try:
        from audiopwmio import PWMAudioOut as AudioOut
    except ImportError:
        pass  # not always supported by every board!

button = digitalio.DigitalInOut(board.GP0)
button.switch_to_input(pull=digitalio.Pull.DOWN)


audio = AudioOut(board.A0)
# You have to specify some mp3 file when creating the decoder
mp3 = open("none.mp3", "rb")
decoder = MP3Decoder(mp3)
decoder.file = mp3
audio.play(decoder)

# calculate money from number stuff

def convertArrayToString(a):
    return(' '.join(map(str, a)))

def last_digit(num):
    if 10 < num < 20:  # it's a teen!
        return [str(num)+".mp3"]
    else:
        last_digit_unsigned = abs(num) % 10
        return [str(num)+".mp3"] if last_digit_unsigned == 0 else [str(num)[0]+"0.mp3", "5.mp3"]

# used in making a sentence of the value of the number
ppp = ['pence.mp3']
lb = ['pound.mp3']
lbs = ['pounds.mp3']

def numToMoney(v):
    if v < 20:  # unique "quick returns"
        return([(str(v)+".mp3")] + ppp)
    elif (v > 9999):  # no one will make this much money from recycling!?
        return(["too_much.mp3"])
    elif (15 < v < 100):  # just pence
        return(last_digit(v) + ppp)
    else:
        tupV = str(v)
        if len(tupV) == 3:
            if (tupV[0] == "1"):
                lll = lb
            else:
                lll = lbs
                    
            if (tupV[1] + tupV[2] == "00"):  # flat £                
                return([tupV[0]+".mp3"] + lll)
            else:
                if tupV[1] != "0":  # not a "x pounds tens pence
                    return([tupV[0]+".mp3"] + lll + ["and.mp3"] + last_digit(int(tupV[1]+tupV[2]))+ppp)
                else:
                    return([tupV[0]+".mp3"] + lll + ["and.mp3"] + ["5.mp3"] + ppp)
        else:
            if (tupV[1] + tupV[2] + tupV[3] == "000"):  # flat £
                return([tupV[0]+"0.mp3"] + lbs)
            else:  # complicated number x pounds y (z) pence
                tA = tupV[0] + tupV[1]
                tB = tupV[2] + tupV[3]
                if (tB == "00"):
                    s = [tA+".mp3"] + lbs
                elif (tB == "05"):
                    s = [tA+".mp3"] + lbs + ["and.mp3"] + ["5.mp3"] + ppp
                else:
                    s = [tA+".mp3"] + lbs + ["and.mp3"] + last_digit(int(tB)) + ppp
                return(s)


# res = arr[::-1] #reversing using list slicing
def money(n):
    moneyArray = []
    moneyArray.extend(numToMoney(n))
    #print(moneyArray)
    for w in moneyArray:
        decoder.file = open(w, "rb")
        audio.play(decoder)
        #print("playing", w)
        while audio.playing:
            sleep(0.1)

counter = 0

while True:
    
    while audio.playing:
        pass
    
    if button.value:
        counter += 20
        money(counter)
        sleep(0.3)
        if counter > 9999:
            counter = 0

The Button is in Pin GP0 & the Speaker Signal Out is in pin A0 … you can’t get simpler! Amazing! MP3 files played with CircuitPython on a Tiny2040 – Done!

Sonic Pi : “live sounding” drums

Phil showcases “live drum” samples & structures in Sonic Pi.

So, Phil is always playing around with the amazing Sonic Pi (By Sam Aaron)… if you’ve not got it, you really should download it… it’s an amazing & FUN tool to make music with AND learn / push your Ruby programming…

So! Phil has been working on making “live sounding” drums in Sonic Pi for a while, as well as an ability to create different patters & the ability to create structures (“songs”) – he’s come up with this:

One of the helpful aspects to this set up is, the beautifully recorded free drum samples from Judd Madden ( http://juddmadden.com/drum-samples.html ). The drum sounds in Sonic Pi are great, but Judd’s samples are absolutely superb.

Phil has commented the code as best he can, if you do try to reproduce this, you’ll need to save the drum samples into the right folders…

use_debug false
use_cue_logging false
use_bpm 120

d_root = "C:/Drums/twi"
drums_bass = "Kick/"
drums_snare = "Snares/"
drums_hat = "Hats/"
drums_cymbal = "Cymbals/"
drums_tom = "Toms/"

#......[1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8,1,2,3,4,5,6,7,8]
# some patterns for bass / snare
bds0 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
bds1 = [1,0,0,0,0,0,0,0,0,0,0,0]
bds2 = bds1 + [2,0,0,0,0,0,0,0,0,0,0,0,0,1,0,2,0,0,0,0,2,0]
bds3 = [1,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0]

sns0 = bds0
sns1 = [0,0,1,0,0,0,0,0]
sns2 = sns1 + [2,0,2,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,3]
sns3 = [0,0,1,0,0,0,0,0,0,0,2,0,0,0,3,0]

#some constants (rr = random range to draw from)
@rr = 12
@mc = 0
@sec = "intro"

# sections is an Array that holds arrays with "section" and a range as to when they should play.
# we later use the values to calculate how long the "main loop" sleeps for before moving to the next position/section
sections = [
  ["intro", (0..0).to_a],
  ["space", (1..1).to_a],
  ["build", (2..5).to_a],
  ["filler", (6..7).to_a],
  ["main", (8..15).to_a],
  ["filler", (16..17).to_a],
  ["main", (18..25).to_a],
  ["end", (26..26).to_a]
]

# change to true to introduce the various drums to the mix
# "parts" is a HASH that holds info on what drums to play for each section
parts = {
  "intro" => {'bds' => false,
              'sns' => false,
              'hts' => false,
              'cym' => true,
              'spl' => false,
              'tms' => true,
              'rnd' => false},
  "build" => {'bds' => true,
              'sns' => false,
              'hts' => true,
              'cym' => true,
              'spl' => false,
              'tms' => false,
              'rnd' => false},
  "main" =>  {'bds' => true,
              'sns' => true,
              'hts' => true,
              'cym' => true,
              'spl' => true,
              'tms' => false,
              'rnd' => false},
  "filler" =>  {'bds' => true,
                'sns' => true,
                'hts' => true,
                'cym' => true,
                'spl' => false,
                'tms' => true,
                'rnd' => true},
  "space" =>  {'bds' => false,
               'sns' => true,
               'hts' => true,
               'cym' => true,
               'spl' => true,
               'tms' => false,
               'rnd' => false},
  "end" => {'bds' => false,
            'sns' => false,
            'hts' => false,
            'cym' => false,
            'spl' => false,
            'tms' => false,
            'rnd' => false}
}
# a HAS that holds info on what patterns to play for what section
drumsForSections = {
  "intro" => {"bds" => bds0, "sns"=> sns0},
  "build" => {"bds" => bds1, "sns"=> sns0},
  "main" => {"bds" => bds2, "sns"=> sns2},
  "filler" => {"bds" => bds2, "sns"=> sns3},
  "space" => {"bds" => bds3, "sns"=> sns3},
  "end" => {"bds" => bds0, "sns"=> sns0},
}

# main counter keeps track of what section we should use
# we "sleep" for bar * (end - start) & pt in "8 beats" if the value of e-s = 0
live_loop :mainCount do
  bar = 8
  tick
  # get the name of the section from the sections array via "look" (the value of tick on this loop)
  @sec = sections[look][0]
  puts @sec #  show the current section name
  if @sec == "end"
    stop
  else
    if @sec == "main"
      # each loop if "main" starts with a Crash Cymbal
      sample d_root+drums_cymbal, "Crash C #{rand_i(4)+1}"
    end
    # calculate how long we should sleep for from this sections start / end numbers * 8 beats
    s = sections[look][1][0]
    e = sections[look][1][-1]
    zzz = (e-s)*bar
    if zzz == 0
      zzz = 8
    end
    
    sleep zzz
  end
end

# ========== drum tracks in separate live_loops

live_loop :ride do
  # simple ride cymbal playing each beat, if "true"
  if parts[@sec]["cym"]then
    # randomise the ride sample used & vary the amp value
    sample d_root+drums_cymbal, "Ride Cymbal #{rand_i(2) + 1}", amp: rand(0.4) + 0.8
  end
  sleep 1
end

live_loop :bd do
  tick
  # bass drum plays, if "true"
  if parts[@sec]["bds"] then
    # we look in the "drumsForSections" in the current section (e.g. "intro") then the "bds" for that section
    # if the beat ISN'T a 0, we play the BD sample.
    if drumsForSections[@sec]["bds"].look != 0 then
      sample d_root+drums_bass, rand_i(3) + 1, amp: rand(0.4) + 0.8
      # if it's the start of a bar (8 beats) we add in a bd_boom
      if look % 8 == 0 then
        sample :bd_boom
      end
    end
  end
  sleep 0.25
end

# an array for different types of snares
snlist = [0, "Off", "On", "Flam"]

live_loop :sn do
  tick
  with_fx :pan, pan: -0.25 do
    # we look in the "drumsForSections" in the current section (e.g. "intro") then the "sns" for that section
    if parts[@sec]["sns"] then
      # if the beat ISN'T a 0, we play the Snare sample.
      # and also use the number to pull in the specific snare style (Off, On, Flam)
      if drumsForSections[@sec]["sns"].look != 0 then
        sample d_root+drums_snare, snlist[drumsForSections[@sec]["sns"].look], pick, amp: rand(0.4) + 0.6
      end
    end
    sleep 0.25
  end
end

live_loop :splash do
  tick
  p = 0
  # if splashes are to play ( in parts : 'spl' => true )
  if parts[@sec]["spl"] then
    sample d_root+drums_cymbal, "Crash", "A", pick, amp: rand(0.4) + 0.8, pan: 0.3
    # add a random china bell hit for variance
    if (1..3).include? rand_i(@rr) then
      sleep 1.5
      p = 1.5
      sample d_root+drums_cymbal, "China", "Bell", pick, amp: rand(0.4) + 0.8, pan: 0.3
    end
  end
  sleep 8 - p
end

# set up some arrays for the Tom  Filler
# tom spread will sequentially play the specific tm type (2xhigh 2xmid, 2xFloor here)
tomspread = ["high", "high", "mid", "mid", "floor", "floor"].ring
# we can also pan the tom hits from left to right
tompan = [-0.8, -0.6, -0.4, -0.2, 0, 0.2, 0.4, 0.6].ring
# a counter to keep track of what tom to play in tomspread
tomtick = 0

live_loop :tm do
  # if "tms" is true for this section
  if parts[@sec]["tms"] then
    # check for "rnd" for potential tom hit randomisation
    if parts[@sec]["rnd"]
      if rand_i(@rr) == 0 then
        sample d_root+drums_tom, tomspread[tomtick], pick, amp: rand(0.4) + 0.4, pan: tompan[tomtick]
        tomtick = tomtick+1
      end
    else
      sample d_root+drums_tom, tomspread[tomtick], pick, amp: rand(0.4) + 0.4, pan: tompan[tomtick]
      tomtick = tomtick+1
    end
  end
  sleep 0.25
end

If you have any questions or comments, do get in touch!

Tiny2040 + Old Valve + NeoPixel

Phil is working away on the “Oop Tree” on the Pico, but is getting into “slow progress” with getting to grips with the implementation of how the tree will be affected by various inputs (water, food, light).

So as a “quick show n tell” – he’s put together a quick project using a #pimoroni #tiny2040 microcontroller, again, running the latest version of #circuitPython. Having found a box of old blown Valves (Beautiful objects!) Phil thought he would try to convert them into a useful light, using an RGB led, a button and a sliding potentiometer.

Phil did wire up a classic 4 legged RGB led & got it working, but had some very neat, single “NeoPixels” from Ada Fruit lying around, so soldered some pins onto one & used that, as instead of needing 3 separate pins to control the Red Green & Blue pins, you can do that all through one pin on the NeoPixel. There’s even a handy library to help ease the controls of changing the light colours / intensity.

First, Phil wired the push button into a digital pin (GP7) + Ground, then added a sliding potentiometer (you need 3 connections for this input, a constant 3v, a ground connection, then your “reading” (analogue) connection (A3)). Lastly, phil connected the 3 wires (5v, Gnd + A0) to the NeoPixel. Because you can “chain” NeoPixels, be sure to use the “in” side of the NeoPixel. Phil then drilled a hole into the base of the old Valve, he did this outside (well ventilated) and wore a mask, just in case there are any nasty old chemicals inside the housing or valve itself.

The full code for Phil’s RGB Led can be found here and a wee explanation below:

This project uses a single pushbutton to “cycle through” different states the NeoPixel uses. There is a Dictionary (Dict) that keeps information for LED levels (a “Base” RGB colour + “mix” (potential new levels to be added to the base colour). Each Dict Key is a string that should make the values obvious! The last setting is to make the slider decide what colour the RGB led should be…

Using a few functions Phil wrote for previous projects, you can now pass two tuples (r,g,b) and add them together to return the “mixed” (r,g,b) value – and apply it to the NeoPixel with the simple library method “pixels[0] = (r,g,b)” and “pixels.show()” (to update the NeoPixel).

The slider affects the possible “maximum mix” value by first converting its raw value (130 to 65000+) to 0 & what ever the current maximum value is for that “mix” (stored in the dictionary) this is done for each value (r, g, b). So when the slider is at “0” the maximum possible random number is between 0 & 0 (no new colour added to the base colour!) the higher the slider goes, the more randomness for each Red, Green, Blue “mix” value is possible… so the light starts to flicker within the range of the “base + mix” values…

The Slide setting uses a “wheel” method from Ada Fruit, and we “just” need to map the slide value (again from 130 to 65000+) to 0 & 255. When the slider is moved, it takes its position & passes an RGB value to the Neopixel to cycle from Green to Green over the spectrum. Nice.

NVM : Non Volatile Memory

Not much to show this week from Phil, he’s been getting deep into learning more about OOP in Python, and running into several problems that led him to more exploration, experimentation & fails (so, as we said, not much to show). He also sadly lost his 22 year old cat “Link” this week…

He did however, last week get into “NVM” (Non Volatile Memory) – A very elegant way of writing data to the Pico, so you can save info & recall it when the Pico Starts up again… We had tried using it for the Dinky OSC, but the older versions of CircuitPython didn’t support it, or at least were buggy with the ability to save without hanging the Pico (and forcing “Nuke ReFlashes!”).

Martin had read on the fantastic CircuitPython discord channel that the NVM issues had been addressed in the latest version. And so Phil re-opened his exploration of the use of NVM on a Pico… Phil did a quick search on discord & found :

Neradoc22/05/2022

“you might want to look into foamyguy’s NVM helper in the community bundle https://github.com/FoamyGuy/Foamyguy_CircuitPython_nvm_helper

Phil followed the link, downloaded the library from FoamyGuy and within minutes, had a fully working, easily updateable way of reading & writing info to NVM ! Like a dream you can save a dictionary via NVM_helper & all the byte conversions are sorted in that amazing script… So! Phil is back on track to keep track of the digital Tree , so we can implement the Real Time Clock & apply the effect of time to the tree depending on how often the tree is watered & fed & given sunlight! We will then hopefully see the fully working Tamagochi-esque digital tree…

Week 9 : Adding a Real time clock to a Raspberry Pico

For our new project we think at some point we are going to need to have an accurate measure of time that has passed and for this we will need a real time clock.

For our new project we think at some point we are going to need to have an accurate measure of time that has passed and for this we will need a real time clock.

The Pico doesn’t have it’s own clock, and for that matter non of the Raspberry Pi family do, a quick google search revealed a few different boards that could offer this facility.

I decided to go with one that was based on a DS1307 and communicated via I2C. I ordered three of them from Amazon at a cost of £4.99. They arrived complete with installed batteries, with a claimed running time of a few years which would be more than enough for our project.

DS1307 Module

Another Google search was undertaken to find out how to connect them to a Raspberry Pi Pico. Most articles suggested that they were primarily a 5V device as they were originally designed for use with an Arduino and so would need to have two pull-up resistors moved from the board for use with the 3.3V system of the Pico. The resistors are R2 and R3 in the above image. The boards also needed to have headers installed.

Connections

Only four connections were needed:
GND: Ground
VCC: 3.3V
SDA: GP0
SCL: GP1

Luckily CircuitPython has a library for use with the DS1307, complete with an example code listing.

I installed the required libraries and ran the example code. An error came up complaining about a lack of pull-up resistors.

Martin’s RTC Set up with Resistors…

I installed a couple of resistors, anything around 3.3k should be fine, between the SDA/SCL pins and VCC (3.3V) and ran the code again.

This time there were no errors. The code, see below, has a part where you can set the time by changing the ‘if False’ statement to ‘if True’. Once this has been done change it back to False. The RTC clock will continue to keep the time even after the Pico has been powered off.

Code listing

import time
import board
import adafruit_ds1307


# To create I2C bus on specific pins
import busio
i2c = busio.I2C(board.GP1, board.GP0)    # Pi Pico RP2040

rtc = adafruit_ds1307.DS1307(i2c)

# Lookup table for names of days (nicer printing).
days = ("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")

if False:  # change to True if you want to set the time!
    #                     year, mon, date, hour, min, sec, wday, yday, isdst
    t = time.struct_time((2022, 5, 6, 15, 2, 15, 5, -1, -1))
    # you must set year, mon, date, hour, min, sec and weekday
    # yearday is not supported, isdst can be set but we don't do anything with it at this time
    print("Setting time to:", t)  # uncomment for debugging
    rtc.datetime = t
    print()
# pylint: enable-msg=using-constant-test

# Main loop:
while True:
    t = rtc.datetime
    # print(t)     # uncomment for debugging
    print(
        "The date is {} {}/{}/{}".format(
            days[int(t.tm_wday)], t.tm_mday, t.tm_mon, t.tm_year
        )
    )
    print("The time is {}:{:02}:{:02}".format(t.tm_hour, t.tm_min, t.tm_sec))
    time.sleep(1)  # wait a second
Readouts from the RTC

Realisation

After doing this I realised that because I was connected to pin 36 (3V3 out) that I hadn’t actually needed to remove the on board resistors and wouldn’t need the added pull-up resistors either.

I soldered headers to the remaining two boards to test that I was correct and indeed I was. So we now have three working realtime clocks, but one of them needs pull-up resistors.

Week 8/9 : Oop Tree

Object Orientated Programming in CircuitPython to make Digital Trees

We think we’ve taken the Asteroids development as far as we can just now (looking at speeding up the FFT calculations)… but, in the mean time, Phil has started re-visiting an old passion… OOP Trees!

Object Orientated Programming in CircuitPython to make Digital Trees… Phil used to play with Macromedia Director 8 / 8.5 (!) and made some digital trees. Phil’s had an idea of re-visiting making OOP trees, but on the tiny Raspberry Pi Pico using CircuitPython…

Over 2 days, Phil’s successfully written a Tree Class, thinking of a tree as a series of “nodes” – He passed several key parameters to the Tree Class, an X,Y location, how many nodes the tree will have (“the Trunk”), how much those nodes can vary in direction from each other & how far they will be from each other. t = Tree() # Params cut out for ease here

Phil had a brainwave, to define all the lengths & angles “at the start” – and then “simply” multiply the values by a scale (from 0 to 1) – the “age” of the tree… so the entire tree is drawn but “scaled down” (to 0) at the start & in a loop, slowly increase the tree’s “age” by a tiny increment… giving the impression of growth! (it all worked smoothly! Not bad for a self taught N00B(!) – Phil’s sure there are probably a lot of pythonic conventions that need to be learned! ).

So, having a “working trunk” growing & stretching nicely, Phil wondered about adding branches… The thing is, they are nearly identical to a Trunk, just that the base of the 1st node isn’t “at a fixed point” (in the “ground”) – but a node on the Trunk (or even another Branch!). With a quick addition of “fNode” (“follow node”) – the Trunk having fNode = None & a branch having fNode = parentNode, he added in the code.py t.newBranch([2,3,3,4, 10]) The array passed, are positions of nodes in t (duplicates just add more branches to that node!). He even put in an error check to see if a node requested is in the range of the parent’s nodes! IT ALL WORKED! woohoo! The Branch grows just like the Trunk, but its base node follows the position of its parent node!

With the branches & trunk now growing nicely (and stopping growing when it reaches the scale of 1), Phil turned to add in leaves… a new Class “leaf” was added to the script & contains info of a colour of leaf, the radius of the leaf (using vectorIO.Circle objects) . Phil thought that the control of when / where & how many leaves appear should be decided by each node (on the trunk or branches). Passing several random numbers to determine how many, how big, how far away from the node they should be was passed in to the object. A few functions Phil’s made (“utility scripts”) like getting an X,Y position based on a known x,y position + length & angle (using Pythagoras) & adding a random range of R G B values to a “base R G B” colour to get variations of greens / browns (any colour!) – Phil rendered out leaves a hoy! But, things were slowing down again on the tiny Pico… So Phil decided to only render leaves when their parents reached specific criteria (age / length away from root) – and this gave a lovely effect of the leaves slowly blinking into existence, as the tree aged!

Phil was inspired & energised , getting into a “playful” development state…”I’ll try this!” he thought… boom! it works! And so, he added a few playful things, like a random “gust of wind” that jiggles the nodes occasionally, increasing & decreasing the scale quickly with the 2 buttons from week 1 … so many possibilities …

Anyway, if you want to have a play (& probably hone / improve the OOP!) download the scripts for the Pico set up here. Have fun!

Week Seven: OOP Asteroids and tone detection

Working (but slow) Sound Controlled “Asteroids” with PDM mic on Raspberry Pi Pico by Digital Maker CIC

Using Martin’s Ada Fruit FFT experiment (week 5) and Phil’s OOP Asteroids, we started to get some sound controlling the Spaceship!

This week has proven to be quite a challenge.

We wanted to combine the knowledge gained previously from Phil’s Asteroids and Martin’s FFT code to make a game controlled by sound. Phil continued his work on the Asteroids and control of a small rocket capable of firing projectiles.
Martin looked at isolating individual tones, these could then be used for the control of the rocket. Three tones were needed for, boost, rotate and fire. He chose an online tone generator https://www.szynalski.com/tone-generator/ to generate the three required tones.

A tutorial https://learn.adafruit.com/light-up-reactive-ukulele/software found online was particularly helpful. This led to the following CircuitPython code:

"""FFT Tone detection
to work with ulab on Raspberry Pi Pico"""

from time import sleep
import audiobusio
import board

from ulab import numpy as np
from ulab.scipy.signal import spectrogram
import array

#---| User Configuration |---------------------------
SAMPLERATE = 16000
SAMPS = 256

fft_size = 256

# ============== make the PDM mic object =============== #

mic = audiobusio.PDMIn(board.GP27_A1, board.GP0,
                                sample_rate=SAMPLERATE, bit_depth=16, startup_delay=0.05, mono=True)

#use some extra sample to account for the mic startup
samples_bit = array.array('H', [0] * (fft_size+3))

# Main Loop
def main():
    max_all = 10
    while True:
        mic.record(samples_bit, len(samples_bit))
        samples = np.array(samples_bit[3:])
        spectrogram1 = spectrogram(samples)
        # spectrum() is always nonnegative, but add a tiny value
        # to change any zeros to nonzero numbers
        spectrogram1 = np.log(spectrogram1 + 1e-7)
        spectrogram1 = spectrogram1[1:(fft_size//2)-1]
        peak_idx = np.argmax(spectrogram1)
        peak_freq = peak_idx * 16000 / 256 / 2
        #print((peak_idx, peak_freq, spectrogram1[peak_idx]))
        
        if peak_freq > 1000 and peak_freq < 1150:
            print("Fire")
        elif peak_freq > 500 and peak_freq < 550:
            print("Rotate")
        elif peak_freq > 225 and peak_freq < 275:
            print("boost")
        else:
            print(peak_freq)
        sleep(0.1)

main()

The code worked reasonably well so the next task was to incorporate it into Phil’s asteroid code.

This proved quite challenging as once the two were combined together. The frequency returned by the microphone was always 0.0.

Various methods were tried, adding the Mic as object, calling it as a function and directly adding it to the main code.py. Adding it to directly to code.py was the final stage but still wasn’t providing the desired results.

Next we resorted to commenting out elements of code, the final conclusion was that some of the print statements were causing timing errors that caused the spectrogram to return values of 0.0. We aren’t quite sure why but once these were reduced to a minimum the code started working.

The final code with libraries is available as a zip file here.

It’s quite slow – the FFT calculations might be pushing the Pico to the edge of its computing power! Phil is exploring how to hone the OOP too, to see if he can eek out a few more milliseconds