Overview
By now we all get it, cows say moo. Sure. But what about subway car doors and jackhammers? What do they say? Build your own city-themed See N Say to teach the children!
This project replaces the brains of a classic talking toy with a modern, CircuitPython-powered KB2040 microcontroller, with a collection of typical urban sounds and custom illustrations.
illustrations by Brian Kesinger. Explore more of his work at Briankesinger.com
Fisher Price Classic Farmer Says See 'N Say
Available at toy stores and online, such as here. Note, this project uses the modern electronic toy, not a vintage mechanical one.
Shottky Diode
You can use a regular power diode such as the one below, but a proper Shottky diode is even better, it only has a ~0.3V drop vs. closer to ~0.7V.
Optional
You can replace the toy's graphics with your own, or the handy downloadable graphics included in the guide. Vinyl sticker paper for laser printers works well, such as this.
Page last edited March 08, 2024
Text editor powered by tinymce.
CircuitPython
CircuitPython is a derivative of MicroPython designed to simplify experimentation and education on low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront desktop software downloads. Simply copy and edit files on the CIRCUITPY drive to iterate.
CircuitPython Quickstart
Follow this step-by-step to quickly get CircuitPython running on your board.
Click the link above to download the latest CircuitPython UF2 file.
Save it wherever is convenient for you.
To enter the bootloader, hold down the BOOT/BOOTSEL button (highlighted in red above), and while continuing to hold it (don't let go!), press and release the reset button (highlighted in red or blue above). Continue to hold the BOOT/BOOTSEL button until the RPI-RP2 drive appears!
If the drive does not appear, release all the buttons, and then repeat the process above.
You can also start with your board unplugged from USB, press and hold the BOOTSEL button (highlighted in red above), continue to hold it while plugging it into USB, and wait for the drive to appear before releasing the button.
A lot of people end up using charge-only USB cables and it is very frustrating! Make sure you have a USB cable you know is good for data sync.
You will see a new disk drive appear called RPI-RP2.
Drag the adafruit_circuitpython_etc.uf2 file to RPI-RP2.
The RPI-RP2 drive will disappear and a new disk drive called CIRCUITPY will appear.
That's it, you're done! :)
Safe Mode
You want to edit your code.py or modify the files on your CIRCUITPY drive, but find that you can't. Perhaps your board has gotten into a state where CIRCUITPY is read-only. You may have turned off the CIRCUITPY drive altogether. Whatever the reason, safe mode can help.
Safe mode in CircuitPython does not run any user code on startup, and disables auto-reload. This means a few things. First, safe mode bypasses any code in boot.py (where you can set CIRCUITPY read-only or turn it off completely). Second, it does not run the code in code.py. And finally, it does not automatically soft-reload when data is written to the CIRCUITPY drive.
Therefore, whatever you may have done to put your board in a non-interactive state, safe mode gives you the opportunity to correct it without losing all of the data on the CIRCUITPY drive.
To enter safe mode when using CircuitPython, plug in your board or hit reset (highlighted in red above). Immediately after the board starts up or resets, it waits 1000ms. On some boards, the onboard status LED (highlighted in green above) will blink yellow during that time. If you press reset during that 1000ms, the board will start up in safe mode. It can be difficult to react to the yellow LED, so you may want to think of it simply as a slow double click of the reset button. (Remember, a fast double click of reset enters the bootloader.)
In Safe Mode
If you successfully enter safe mode on CircuitPython, the LED will intermittently blink yellow three times.
If you connect to the serial console, you'll find the following message.
Auto-reload is off. Running in safe mode! Not running saved code. CircuitPython is in safe mode because you pressed the reset button during boot. Press again to exit safe mode. Press any key to enter the REPL. Use CTRL-D to reload.
You can now edit the contents of the CIRCUITPY drive. Remember, your code will not run until you press the reset button, or unplug and plug in your board, to get out of safe mode.
Flash Resetting UF2
If your board ever gets into a really weird state and CIRCUITPY doesn't show up as a disk drive after installing CircuitPython, try loading this 'nuke' UF2 to RPI-RP2. which will do a 'deep clean' on your Flash Memory. You will lose all the files on the board, but at least you'll be able to revive it! After loading this UF2, follow the steps above to re-install CircuitPython.
Page last edited March 08, 2024
Text editor powered by tinymce.
Code the See N Say
Text Editor
Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide.
Alternatively, you can use any text editor that saves simple text files
Download the Project Bundle
Your project will use a specific set of CircuitPython libraries, and the code.py file. To get everything you need, click on the Download Project Bundle link below, and uncompress the .zip file.
Drag the contents of the uncompressed bundle directory onto your KeeBoar board's CIRCUITPY drive, replacing any existing files or directories with the same names, and adding any new ones that are necessary.
Upload the Code and Libraries to the KB RP2040
After downloading the Project Bundle, plug your KB2040 into the computer USB port. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called CIRCUITPY. Unzip the folder and copy the following items to the KB2040's CIRCUITPY drive.Â
- lib folder
- wavs folder
- code.py
# SPDX-FileCopyrightText: 2023 John Park for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import os
import audiocore
import board
import audiobusio
import audiomixer
from digitalio import DigitalInOut, Pull
from adafruit_debouncer import Button
button_pins = (
board.D2, board.D3, board.D4, board.D5,
board.D6, board.D7, board.D8, board.D9,
board.D10, board.MOSI, board.MISO, board.SCK,
)
buttons = [] # will hold list of Debouncer button objects
for pin in button_pins: # set up each pin
tmp_pin = DigitalInOut(pin) # defaults to input
tmp_pin.pull = Pull.UP # turn on internal pull-down resistor
buttons.append(Button(tmp_pin, value_when_pressed=False))
# get the filenames in aplha order from files in the 'wavs' directory
sounds = []
for filename in sorted(os.listdir("/wavs")):
filename = filename.lower()
if filename.endswith(".wav") and not filename.startswith("."):
sounds.append(filename)
audio = audiobusio.I2SOut(bit_clock=board.A1, word_select=board.A2, data=board.A3)
mixer = audiomixer.Mixer(voice_count=1, sample_rate=11025, channel_count=1,
bits_per_sample=16, samples_signed=True)
audio.play(mixer)
mixer.voice[0].level = 0.5
def play_sound(sound_number):
wave_file = open(("wavs/" + sounds[sound_number]), "rb")
wave = audiocore.WaveFile(wave_file)
mixer.voice[0].play(wave, loop=False)
while True:
for i in range(len(buttons)):
buttons[i].update()
if buttons[i].pressed:
play_sound(i)
Make Your Own
Here's a great guide on prepping your own audio files for use on microcontrollers.
To convert your own .wav files, use audio software such as Audacity to save them with these settings:
- bits per sample: 16
- sample rate: 11kHzÂ
- channels: mono
A great place to start is freesound.org. The sounds used in this project are:
https://freesound.org/s/86938/
https://freesound.org/s/125557/
https://freesound.org/s/200338/
https://freesound.org/s/339918/
https://freesound.org/s/353180/
https://freesound.org/s/405628/
https://freesound.org/s/417281/
https://freesound.org/s/434085/
https://freesound.org/s/523952/
https://freesound.org/s/612857/
https://freesound.org/s/619087/
https://freesound.org/s/633976/
How It Works
The code does two key things -- wait for button presses and play audio. Here's how it does it.
First, we import libraries, including os for reading the file system, audiocore, audiobusio, and audiomixer for audio playback/mixing, and digitalio and adafruit_debouncer for button input.
import os import audiocore import board import audiobusio import audiomixer from digitalio import DigitalInOut, Pull from adafruit_debouncer import Button
button_pins = (
board.D2, board.D3, board.D4, board.D5,
board.D6, board.D7, board.D8, board.D9,
board.D10, board.MOSI, board.MISO, board.SCK,
)
buttons = [] # will hold list of Debouncer button objects
for pin in button_pins: # set up each pin
tmp_pin = DigitalInOut(pin) # defaults to input
tmp_pin.pull = Pull.UP # turn on internal resistor
buttons.append(Button(tmp_pin, value_when_pressed=False))
Sound Setup
The sounds list is created by alphabetically sorting the .wav files found in the wavs/ directory. This means you can place track numbers in front of your file names to assign them to the desired spot on the wheel.
Then, the audio object is created to send digital audio over I2S to the amp. The mixer object is created to play the chosen wav, and the audio mixer is set to play with a half-volume voice level of 0.5.
sounds = []
for filename in sorted(os.listdir("/wavs")):
filename = filename.lower()
if filename.endswith(".wav") and not filename.startswith("."):
sounds.append(filename)
audio = audiobusio.I2SOut(bit_clock=board.A1, word_select=board.A2, data=board.A3)
mixer = audiomixer.Mixer(voice_count=1, sample_rate=11025, channel_count=1,
bits_per_sample=16, samples_signed=True)
audio.play(mixer)
mixer.voice[0].level = 0.5
play_sound() Function
The play_sound() function is defined to receive an integer number which it then uses to open the corresponding wav file and play it once.
def play_sound(sound_number):
wave_file = open(("wavs/" + sounds[sound_number]), "rb")
wave = audiocore.WaveFile(wave_file)
mixer.voice[0].play(wave, loop=False)
Main Loop
The main loop of the program checks for button presses and when one is pressed it calls the play_sound() function to play the sound.
while True:
for i in range(len(buttons)):
buttons[i].update()
if buttons[i].pressed:
play_sound(i)
Page last edited March 08, 2024
Text editor powered by tinymce.
Build the See N Say Circuit
Open the Case
Flip the See N Say over and unscrew the battery door, then remove the batteries.
Unscrew the seven screws and set them aside, then carefully pull open the case.
Sound Button Press
Most of the pull string mechanism is there to spin the selector arrow, the only part in the front case section that causes sounds to play is the small plastic nub on the central gear -- when the string is fully pulled out that nub raises up slightly and presses the associated rubber dome switch.
Button Pad
The electronics are all on a single PCB. The center of the PCB is a set of 12 open contact buttons that are closed by pressing rubber domes with conductive pills in them. This is the same as what you'll find in a video game controller or rubber dome switch keyboard.
Carefully remove the rubber ring, then unscrew the two screws holding the PCB in place.
Glue Removal
You'll desolder the battery wires and speaker wires from the board, since we only need the button contacts and common contact test points, the rest of our electronics will be transplanted.
Flip over the PCB and you'll see the battery wires and speaker wires have hot glue blobs on them for strain relief.
Dab on some isopropyl alcohol to loosen and then remove the hot glue.
Wire Desoldering
Heat the two gray speaker wire solder points and the two (one red, one black) battery wire solder points with a soldering iron while gently pulling down on the wires with tweezers or pliers.
KB2040 Prep
Jumper the USB+ > RAWÂ pads with some solder. This will allow us to use 5V power from the MiniBoost (which takes the ~3V AA battery input and converts it). The I2S works better with 5V as well.
KB2040 on PermaProto
Solder the KB2040 to the PermaProto board as shown.
You can use the castellated pads instead of adding pins if you like, this keeps it a bit more svelte.
Amp
Solder the MAX98357A I2S amplifier board to the PermaProto as shown.
You can use bare header pins as shown to keep things low profile.
MiniBoost Prep
Add some Kapton tape or electrical tape to insulate the MiniBoost mounting hole from the PermaProto.
This isn't normally necessary, but since we're keeping it super low profile, the mounting hole plating could short some pads otherwise.
Solder the MiniBoost to the PermaProto board.
Button Pad Wiring
Conveniently, there is a test pad next to each pair of switch contacts! Solder a thin wire, approximately 5" long, to each test pad as well as the GND pad.
Wire wrap wire, enamel coated magnet wire, and 28-30 gauge silicone covered wire all work well for this. Make an effort to run the wires inward so they won't disrupt the elastomer pad.
Button Connections
Place the button board and PermaProto into the back case temporarily to get a sense of the wire lengths and trim/strip as needed.
Then, one-by-one, feed a wire through the PCB's center hole and solder the twelve button wires to their respective GPIO pins, as well as the GND from the button board to the PermaProto.
KeeBoar Ground
Since the KB2040 is hanging off the edge of the board for space considerations, you'll need to free-wire its GND to the PermaProto. Run a wire from each side to the ground rails, so both sides of the PermaProto have common ground.
Battery Box Connections
Place some insulating tape over the battery box contacts so nothing is shorted to the PermaProto accidentally.
Solder the battery box ground (black) wire to PermaProto ground rail, and the battery box +V (red) wire to the Vin pad on the MiniBoost.
Solder the MiniBoost GND to the PermaProtoGround rail.
To prevent any issues in case the KB2040 is plugged into USB when the batteries are also supplying power, solder a Shottky diode from MiniBoost 5V to PermaProto power rail as shown.
Run a wire from one PermaProto power rail to the other, then wire the KB2040 RAW pin to the PermaProto power rail.
Power Enable Switch
You'll turn the toy on and off with a DPST toggle switch that shorts the MiniBoost enable pin to ground.Â
Solder one wire to the center common pin and another to either outer pin of the switch.
Ream out the packaging key hole a bit and then insert the switch through it. Tighten the nut from the other side to secure.
Solder the center wire to ground and the other wire to the MiniBoost En pin.
I2S Audio Amp Wiring
Wire the MAX 98357A I2S Amp as follows:
- KB2040 A1 to BCLK (bit clock)
- KB2040 A2 to LRC (word select)
- KB2040 A3 to DIN (data)
- Ground to GND
- 5V power rail (from MiniBoost output post-diode) to Vin
- Speaker wires to speaker output
Place the elastomer button pad ring back on the PCB, pulling the rubber nub legs through their respective holes.
Test It
This is a great time to test it out! Put the two AA batteries back in the case, flip on the toggle switch, and press each rubber dome button to hear the audio.
Secure the Brain
Use double-stick foam tape to secure the PermaProto and elecronics to the inside of the case on the back of the battery box.
Screw the original PCB back in place.
Wire Dressing
Use Kapton tape to secure wiring in any places where it may be a bit loose in order to avoid any snags.
Close the Case
Pull and hold the pull-string to get the arced gear out of the way (it will slide under the transplanted PermaProto board during operation), then close the case.
Release the string slowly to make sure there are no obstructions.
Screw the Case in Place
Refasten the seven case screws, then insert two AA batteries and close the battery door, securing it with the captive screw.
Page last edited March 08, 2024
Text editor powered by tinymce.
See N Say Graphics
You can print out this wonderful set of illustrations by the incredible Brian Kesinger and use them to customize your city-themed See N Say!
Matte finish vinyl sticker paper for laser printers worked very well for me -- I used the "heavy paper" setting in the print dialog to make sure the toner bonded well with the paper.
If you'd like to use other graphics, import the graphic above into a graphics editor at 100%, change things, then save.
Print at 100% size, cut carefully with a hobby knife, and peel and stick, being sure to smooth out bubbles as you go.
Page last edited March 08, 2024
Text editor powered by tinymce.