Overview
The Yoto Mini and Yoto Player toys are fun, screenless audio players aimed at kids. The devices use NFC cards to playback stories or music for kids to listen to without being attached to a phone or tablet. But what interested us the most about them was what was inside: an ESP32 processor, which means that it could in theory run CircuitPython.
In this guide, you'll see how to take your Yoto Mini apart and load and run CircuitPython firmware onto it with access to almost all of the peripherals onboard. As of writing, the speaker init sequence hasn't been fully cracked, but there is headphone output and every other peripheral is accessible, including the eMMC flash for chonky storage. There's also a page detailing the reverse engineering process, from using multimeters to looking at the shipped firmware with Ghidra.
Why Hack the Yoto Players?
It seems that every other day you hear about an IoT device no longer being supported or servers shutting down, essentially bricking your devices. Although this fate is not currently affecting the Yoto Players, you could say that about any IoT device on the market right now. This guide serves as an example of how you can approach a device, reverse engineer it and get firmware like CircuitPython running on it to save it from an e-waste future and to truly own your device.
Page last edited February 05, 2026
Text editor powered by tinymce.
Teardown
Use a spudger to gently pry open the back of the case open. There are two plastic latches that will release.
Towards the top edge of the PCB, to the left of the ESP32, is the JST connector for the RFID reader. Unplug it. There is a dot of adhesive that you will need to peel off or cut through with a razer.Â
Now flip the Yoto Mini over so that the encoders are facing up at you. Pry off the rotary encoder knobs. These can be tricky because they are clipped in around the edges. You'll want to use a spudger or pry tool to free them.
Lift the PCB out of the case. If you're programming the Yoto Mini, you can stop the teardown here. When I did the teardown, I had to see what ICs were under the display, so I had to remove it. I'm including the steps below for posterity.
The display lift is plastic and is held into holes on the PCB with plastic nubs. You can clip these nubs with flush cutters.
Page last edited February 05, 2026
Text editor powered by tinymce.
Building a Serial Programmer Breakout
The Yoto Mini does not have a USB to serial converter chip to access the ESP32 via USB. However, it does have the programming pins broken out at the top edge of the PCB. To make uploading firmware easier, you can solder together a quick programmer breakout.
ESP32
- Enable (EN) to BOOT button (red wire)
- Reset (RST) to 10K resistor to RESET button (black wire)
- TXD to FTDI RX (yellow wire)
- RXD to FTDI TX (green wire)
- GND to FTDI GND (blue wire)
- 3.3V to TLV62569 3V (red wire)
FTDI/USB Serial
- GND to TLV62569 GND (black wire)
- GND to BOOT and RESET button GND (black wires)
- 5V to TLV62569 Vin (orange wire)
Five of the six needed pins are broken out along the top edge of the Yoto Mini PCB:
- GND, RXD, TXD, RST and EN
Solder one cable pair to the five broken out pins along the edge.
The last wire in the cable bundle is for 3.3V. You will need to either solder to the 3.3V test point on the PCB or directly to the 3.3V pin on the ESP32. I recommend the 3.3V pin for stability.
Use
After connecting the Yoto Mini to the programmer board, connect the USB serial cable to the programmer. Plug the USB cable into your computer. You can now put the ESP32 into bootloader mode by holding down the BOOT button, pressing the RESET button and then releasing the BOOT button. Now you're able to access the ESP32 over USB serial.
Page last edited February 05, 2026
Text editor powered by tinymce.
Dump the Firmware
Although this guide is all about installing new firmware on the Yoto Mini, what about the stock firmware? You can use ESPTool to dump the flash contents of the ESP32 and save it as a .BIN file. You can then reinstall this .BIN file later if you ever want to go back to the Yoto firmware.
If you got too excited though and already installed CircuitPython, you can download the .BIN file below:
ESPTool
You can use ESPTool to read the flash over USB serial. I found this guide while I was researching what other folks had done with the Yoto Mini.
After getting the Yoto Mini into bootloader mode, use this command:
esptool.py -p COM34 read_flash 0 ALL yoto_mini_v2.bin
Replace COM34 with the serial port for the Yoto Mini on your system. You'll see the read_flash program start running. There are 8 MB of flash on the ESP32 module, so this will take a few minutes.
Detecting chip type... ESP32 Chip is ESP32-D0WD-V3 (revision v3.1) Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None Crystal is 40MHz MAC: 78:1c:3c:e7:48:50 Uploading stub... Running stub... Stub running... Configuring flash size... Detected flash size: 8MB 8388608 (100 %) 8388608 (100 %) Read 8388608 bytes at 0x00000000 in 786.3 seconds (85.3 kbit/s)... Hard resetting via RTS pin...
When its done, you'll be prompted to reset the board. You should see the .BIN file in your file directory. Now you have a backup of the stock firmware that you can refer back to.
Page last edited February 05, 2026
Text editor powered by tinymce.
Reverse Engineering Techniques
Scott and Liz tag teamed the reverse engineering for this project. This page goes over some tips and tricks that they used to get CircuitPython running on the Yoto Mini and Yoto Player.
Chip Markings
After getting the device open, the first step is to try and read the chip markings on all of the components. When you get the marking, you'll want to search for the marking followed by the word "datasheet" or "component". By doing this, you should get hits for what the name of the device actually is.
This process worked for almost every peripheral on the Yoto Players except for the speaker amplifier. That involved looking at the 3rd party licenses on the Yoto website and noticing the aw881xx mentioned in the list. Searching for that component name lead to seeing results for the Awinic company. Finally, Liz searched Awinic components on LCSC and found the matching amplifier IC.
Logic Analyzer and Multimeter
Now that the chips were identified on the board, the next step was to figure out which pins on the ESP32 module do what.
Liz used a multimeter to ring out the board. You put your multimeter in continuity mode and check for continuity between pins. For example, she could probe the SCL or SDA pin on an IC and see what pin that corresponded with on the ESP32 to figure out the I2C pins.
Scott set up the PCB on his PCBite and used the probes to capture a few signals at a time with a Saleae. This gave an idea which ones were I2C and SPI. The eMMC pins were one of two options that the ESP32 supported.
The two of us tag teamed our different approaches to put together a cohesive pin mapping for the Yoto Players. We both kept notes documents to share with each other as we worked.
Scott started by looking at the firmware dump using ImHex. It's his preferred hex viewer of choice, but any one will do. He tried and failed to find the display's initialization sequence. It wasn't a straight byte array. He did manage to find the display chip number though! Some web searching had led him to GC9 ICs and a search through the hex found the full part number.
Scott turned to Ghidra to try and find the display initialization sequence. It can section out parts of the hex into functions and then you can change the names of suspected functions.
After searching for a few different values in the logic capture, Scott found the init function. Below, the write_display_command was a rename to make the pattern clearer. The 0xFE then 0xEF is a common start to GC init sequences.
While searching through the firmware, Scott noticed mention of JSON files, including board-specific ones. Lo and behold, he found JSON files in the firmware that define pinouts and configuration for each hardware revision!
After reformatting the discovered JSON, it is very easy to see the exact pinouts of everything, including the functions of the IO expander pins! It also includes default settings for the IO expander. Anyone hacking the Yoto's should take a look at these files in their firmware. They were all we needed to set up the board module in CircuitPython.
To CircuitPython
While this was happening, Liz built a generic ESP32 CircuitPython install with all of the pins broken out. This made testing and initial interfacing really easy. She referenced the datasheets for the peripherals to write CircuitPython drivers for them and worked through them one by one. Eventually all of this work was combined into the helper library for the Yoto Players.
Scott worked on the core support in CircuitPython. He added native IO expander support, which had been a goal to add to CircuitPython, and built-in support for the display. He added two board definitions to the core; one for the Yoto Mini and one for the Yoto Player.
Page last edited February 05, 2026
Text editor powered by tinymce.
Pinouts
ESP32
The star of the Yoto Players, and the entire reason why this project was attempted, is the ESP32. This classic microcontroller with WiFi is supported in so many programming languages, including CircuitPython. This particular module has 8 MB of flash.
The pinout diagram shows the GPIO number and the pin name in CircuitPython (if applicable).
The TCAL6416 IO expander is used over I2C on address 0x20. Now I2C IO expanders can be accessed natively in CircuitPython with i2cioexpander . This means that all of the peripherals connected via the IO expander can be used in CircuitPython as DigitalInOut objects.
The following peripherals are attached to the IO expander:
- Display DC (
board.DISPLAY_DC) - Display CS (
board.DISPLAY_CS) - Display reset (
board.DISPLAY_RESET) - I2S DAC headphone detection (
board.HEADPHONE_DETECT) - Rotary encoder switches (
board.ENC1_BUTTONandboard.ENC2_BUTTON) - Charge and plug status for the battery monitor (
board.CHARGE_STATUSandboard.PLUG_STATUS) - Power button [also connected directly to the battery monitor chip, more details further down on this page] (
board.POWER_BUTTON) -
board.PACTRL- related to the speaker amplifierÂ
RFID/NFC Reader
The Yoto Mini uses the CR95HF RFID reader to read its cards. It is connected via UART on IO32 (NFC_IN/RX) and IO33 (NFC_OUT/TX). Datasheet.
RTC
The real time clock is a PCF8563 RTC module. It is connected via I2C on address 0x51. Datasheet.
Speaker Amplifier
The AW88194 amplifier is connected between the I2S DAC and the speaker output. Unfortunately the proper init sequence has alluded us, so speaker output in CircuitPython is not working yet. It is connected over I2C on address 0x64 and its datasheet is available.
Of all the components, this was the trickiest one to figure out. I assumed this was a radio module based on search results from the chip markings and the fact that it was a castellated pads module. While tracing the speaker pads, the module had continuity with it and an aw881xx was mentioned in 3rd party licenses on the Yoto site and in the .ELF file dump from the stock firmware. When you search for that though, nothing really comes up and the Awinic website is unreachable. Finally, I tried searching for Awinic parts on LCSC and got a hit. The final confirmation came by noting that the chip markings on the LCSC listing matched the markings on the module.
I2S DAC
The ES8156 I2S DAC handles the audio output to the headphone jack and the speaker amplifier. It is configurable over I2C on address 0x08. Datasheet.
Battery Charger
The SGM41513 battery charger is available over I2C on address 0x1A. The power button is connected directly to it on the nQON pin. When you press the button, the BATFET is enabled over hardware and powers up the device. Datasheet.
Page last edited February 05, 2026
Text editor powered by tinymce.
Install CircuitPython
You can use the serial programmer to install CircuitPython onto the Yoto player with the Adafruit ESPTool.
Click the link above to download the latest CircuitPython .bin file.
Save it wherever is convenient for you.
Connecting to the Web Flasher
To begin, plug your board into your computer via USB, using a known-good data-sync cable, directly, or via an adapter if needed.
You will have to use Firefox 151 or later, Chrome or a Chromium-based browser to install CircuitPython. For example, Edge and Opera are Chromium based.
Safari and some other browsers etc. are not supported - they have not implemented Web Serial!
In the Chrome browser visit https://adafruit.github.io/Adafruit_WebSerial_ESPTool/
You should remove all other USB devices so only the target board is attached. This eliminates confusion over multiple ports!
Press the Connect button in the top right of the web browser. You will get a pop up asking you to select the COM or Serial port. Look for USB Single Serial.
On some systems, such as MacOS, there may be additional system ports that appear in the list (as shown in the image).
The Javascript code will now try to connect to the board. It may timeout for a bit until it succeeds. On success, you will see that it is Connected and will print out a unique MAC address identifying the board along with other information that was detected.
Erasing the Board Contents
If you would like to erase the entire flash area so that you can start with a clean slate, you can use the erase feature. We recommend doing this every time before installing or updating CircuitPython.
To erase the contents, click the Erase button. You will be prompted as to whether you want to continue. Click OK to continue. If you do not wish to continue, click Cancel.
You'll see "Erasing flash memory. Please wait..." This will eventually be followed by "Finished." and the amount of time it took to erase.
Do not disconnect! Immediately continue on to Programming the Board.
You can click on Choose a file... from any of the available buttons. It will only attempt to program buttons with a file and a unique location. Select the  .bin file you downloaded at the beginning of this page from the file chooser dialogue.
Verify that the Offset box next to the file location you used is 0x0. The offset defaults to 0x0, so unless you changed it manually, it should be good to go.
Once you choose a file, the button text will change to match your filename. You can then click the Program button to start flashing.
You've now successfully programmed CircuitPython onto your board! As suggested in the output, press reset to run the new firmware.
As the ESP32 does not have native USB, no USB drive will show up on your computer when you reset. With CircuitPython firmware loaded, the REPL can be accessed over a serial/COM port.
For more details on installation, how to configure your ESP32, and info on getting started with CircuitPython on your ESP32 using the Web Workflow, check out the CircuitPython on ESP32 Quick Start guide.
Page last edited February 05, 2026
Text editor powered by tinymce.
Setting up Web Workflow
Web workflow is enabled when a file named settings.toml is added to the root folder of the CircuitPython file system. This file contains local wifi info and other settings. More info here.
Now, normally creating a file is super easy when the board shows up as a disk drive...but as mentioned before, that's not possible on the original ESP32 because it does not have native USB so it cannot show up as a disk drive. So we have to be a little more creative!
To start out, the minimal contents of this file are:
CIRCUITPY_WIFI_SSID = "wifissid" CIRCUITPY_WIFI_PASSWORD = "wifipassword" CIRCUITPY_WEB_API_PASSWORD= "webpassword"
with each item updated with local specifics.
-
wifissid- replace with local wifi network name -
wifipassword- replace with local wifi network password -
webpassword- used when connecting to the board via web browser, set to whatever
There are a couple of options for creating this file.
Option 1: Create settings.toml file via REPL
The settings.toml file is simple enough that it can be created with a few simple Python commands directly via the REPL.
Here are the basic commands to use. Note these need to be updated with local WiFi specifics. Note also where double quotes and single quotes are used.
f = open('settings.toml', 'w')
f.write('CIRCUITPY_WIFI_SSID = "wifissid"\n')
f.write('CIRCUITPY_WIFI_PASSWORD = "wifipassword"\n')
f.write('CIRCUITPY_WEB_API_PASSWORD = "webpassword"\n')
f.close()
- Replace
wifissidwith the name of your local WiFi network - Replace
wifipasswordwith your local WiFi password - The other password,
webpassword, is used when you access the board via a web browser. Set this to whatever you want.
If you have a terminal program like PuTTY, minicom, screen or similar and you know how to use them - connect to the ESP32 board at the correct port name and 115200 baud
Once connected, you can press the Reset button to kick the firmware, then hit return a few times to get to the REPL prompt. Now you can copy and paste the lines above (with your correct SSID/password!
Don't forget, ESP32 does not support 5 GHz networks, so use your 2.4 GHz SSID if you have two.
Now press the Reset button again, you will see the ESP32 reboot. This time, it should get an IP address. If you have a fairly smart terminal program, the IP address will appear in the title bar.
Alternatively, or if you want more details - you can always query the board over the REPL to ask it the MAC address and IP address:
import wifi
print("My MAC addr: %02X:%02X:%02X:%02X:%02X:%02X" % tuple(wifi.radio.mac_address))
print("My IP address is", wifi.radio.ipv4_address)
If that's not working either, try asking what SSIDs it thinks it's seeing to make sure you have no typos!
import wifi
print("Available WiFi networks:")
for n in wifi.radio.start_scanning_networks():
print("\t%s\t\tRSSI: %d\tChannel: %d" % (str(n.ssid, "utf-8"), n.rssi, n.channel))
wifi.radio.stop_scanning_networks()
Note that since this code has an indented part in it, you shouldn't just paste it in directly into the REPL because it won't handle the tab/spaces correctly. Instead, once you hit Return to get to the REPL, type in Control-E to enter paste mode . Then paste in the text and type Control-D to finish and run
Check that your SSID appears properly - if not, maybe its 5 GHz, maybe its not typed correctly, etc!
Option 2: Create settings.toml file using Thonny
This option requires installing additional software - the Thonny Python IDE. Thonny provides file access and a text editor which makes creating the settings.toml file a little more streamlined than using direct REPL commands. This technique requires Thonny 4.0.0 or later.
In Thonny, open the Tools -> Options dialog and select the Interpreter tab.
Set interpreter to CircuitPython (generic) and the COM port as needed.
Page last edited February 05, 2026
Text editor powered by tinymce.
Connecting via Web Browser
Once the web workflow is enabled and the board connects to the local wifi network with an IP address, the board can be accessed by opening a web browser and entering the board's network address. But what is the address to enter?
Connect using MDNS Address
The CircuitPython web workflow uses MDNS so that connecting to the board can be as simple as using the address circuitpython.local. Try using that first and if it works, great. Open a web browser and navigate to:
http://circuitpython.local
Or just click the button below:
Connect using IP Address
If MDNS does not work for some reason, the fallback approach is to use the actual IP address, like 192.168.0.121, the CircuitPython board was assigned when it connected to the local wifi network.
http://192.168.0.121
There are several options for determining this address.
Terminal Window Title Bar
The web workflow serial output includes some special escape sequences that are intended to update the window title bar of the terminal program being used.
For example, here's what a terminal window running screen to connect to the board looks like:
>>> import wifi >>> wifi.radio.ipv4_address 192.168.0.121 >>>
Page last edited February 05, 2026
Text editor powered by tinymce.
Using Web Workflow
Launch a web browser and navigate to circuitpython.local OR the numeric IP address like 192.168.0.121
Enter the password that was setup for CIRCUITPY_WEB_API_PASSWORD in the settings.toml file.
Leave Username blank.
Serial Terminal
From the Welcome! page, click the serial terminal link to access the serial output as well as REPL for entering commands.
Commands can be entered in the input field at the bottom.
The results will be shown above and scroll up.
File Browser
From the Welcome! page, click the file browser link on the Welcome! page to access files and folders.
For example, click the Edit button for code.py bring up a simple editor to allow changing the contents.
Library files and folders from the Bundle can be uploaded to the /lib folder using the Browse... buttons.
There's a separate button for files and folders (directories).
Page last edited February 05, 2026
Text editor powered by tinymce.
I2C Scan Test
Here's a simple test to make sure the Yoto Mini is working with CircuitPython. You can run an I2C scan to detect any peripherals connected over I2C. The Yoto Mini has quite a few, so it makes for a good test.
Download the Project Bundle
In the example below, click the Download Project Bundle button to download the necessary Python file in a zip file. Then, extract the contents of the zip file. For this example, no additional libraries are needed.
# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries
#
# SPDX-License-Identifier: MIT
# pylint: disable=broad-except, eval-used, unused-import
"""CircuitPython I2C Device Address Scan"""
import time
import board
import busio
# List of potential I2C busses
ALL_I2C = ("board.I2C()", "board.STEMMA_I2C()", "busio.I2C(board.GP1, board.GP0)")
# Determine which busses are valid
found_i2c = []
for name in ALL_I2C:
try:
print("Checking {}...".format(name), end="")
bus = eval(name)
bus.unlock()
found_i2c.append((name, bus))
print("ADDED.")
except Exception as e:
print("SKIPPED:", e)
# Scan valid busses
if len(found_i2c):
print("-" * 40)
print("I2C SCAN")
print("-" * 40)
while True:
for bus_info in found_i2c:
name = bus_info[0]
bus = bus_info[1]
while not bus.try_lock():
pass
print(
name,
"addresses found:",
[hex(device_address) for device_address in bus.scan()],
)
bus.unlock()
time.sleep(2)
else:
print("No valid I2C bus found.")
Upload the I2C Scanner Example with Web Workflow
Follow these steps to add the code.py file from the Project Bundle to the Yoto Mini with Web Workflow.Â
Copy and paste the I2C scanner code.py file into the Editor window. Then, click Save + Run to run the code.
Page last edited February 05, 2026
Text editor powered by tinymce.
CircuitPython Helper Library
With so many peripherals on the Yoto Players, from the display to the RTC to the I2S DAC and more, it seemed like a good idea to put together a helper library to use it with CircuitPython. The library is compatible with the PortalBase libraries and makes it easy to initialize and access all of the onboard peripherals without having to worry about getting all of the imports right.
Library Usage with Web Workflow
To use with CircuitPython, you need to first install the YotoPlayer library, and its dependencies, into the lib folder on the Yoto Mini. Then you need to update code.py with the example script.
Thankfully, we can do this in one go. In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file.
# SPDX-FileCopyrightText: 2026 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""Simple demo for the Yoto Mini Player"""
from adafruit_yoto import Yoto
yoto = Yoto(
default_bg=0x000000,
rotation=0,
debug=False,
auto_refresh=True,
)
title_index = yoto.add_text(
text="Hello World!",
text_position=(yoto.display.width // 2, yoto.display.height // 2),
text_color=0xFFFFFF,
text_scale=3,
text_anchor_point=(0.5, 0.5),
is_data=False,
)
if yoto.peripherals.nfc:
print(f"NFC: {yoto.peripherals.nfc.device_name}")
if yoto.peripherals.dac:
print(f"DAC: ES8156 (Chip ID: {yoto.peripherals.dac.chip_id:04X})")
if yoto.peripherals.battery:
part = yoto.peripherals.battery.part_info
print(f"Battery: {part['part_number']}")
if yoto.peripherals.rtc:
print(f"RTC: {'Valid' if yoto.peripherals.rtc_valid else 'Needs Set'}")
while True:
pass
Extract the contents of the zip file. You'll see the following contents in the extracted folder:
Next, you'll upload the code.py file to the Yoto Mini. Click the Upload button and then Upload Files.
After running the code, you'll see the peripherals recognized over I2C in the serial monitor:
The manual display refresh demo shows how you can load bitmap images to the onboard display and how to interface with the rotary encoder buttons. Pressing the buttons will change the Blinka image shown.
The MP3 player demo lets you use the Yoto Mini as a portable MP3 player. The code loads album folders from the /sd directory and can display the album artwork on the display. Then, the .mp3 audio files will play through the headphone jack. You can use the left rotary encoder to adjust volume and the right rotary encoder to advance or rewind tracks.
If you hack your own Yoto Player with CircuitPython, please write up your creations to the Adafruit Playground and bring them by Show and Tell!
Page last edited February 05, 2026
Text editor powered by tinymce.
Reassembly
To reassemble your Yoto Mini, you can reverse the steps on the Teardown page or follow along here.
Insert the PCB back into the case and secure it with the four screws. There is enough room inside the case to keep the cable harness soldered for programming.
Snap the back cover back onto the case. Start by inserting the top edge latches into the main case and pushing down around the edges.
Page last edited February 05, 2026
Text editor powered by tinymce.