• Skip to main content
  • Shop
  • Learn
  • Blog
  • Forums
  • IO
  • LIVE!
  • AdaBox
Adafruit Logo
0
  • Sign In | Create Account
  • Playground
  • Shop
  • Learn
  • Blog
  • Forums
  • IO
  • LIVE!
  • AdaBox
Sign In
0
Adafruit Logo
  • Learn Categories
    • 3D Printing
    • AdaBox
    • Adafruit Products
    • Arduino Compatibles
    • Breakout Boards
    • Circuit Playground
    • CircuitPython
    • CLUE
    • Community Support
    • Components
    • Crickit
    • Customer & Partner Projects
    • Development Boards
    • Educators
    • EL Wire/Tape/Panel
    • Feather
    • Gaming
    • Hacks
    • Internet of Things - IOT
    • LCDs & Displays
    • LEDs
    • Machine Learning
    • MakeCode
    • Maker Business
    • micro:bit
    • Microcontrollers
    • Programming
    • Raspberry Pi
    • Robotics & CNC
    • Sensors
    • STEMMA
    • Tools
    • Trellis
    • Wearables
    Explore, Groups to get your gears turning
    Explore
    Groups to get your gears turning
    Explore Groups
  • New Guides
  • Playground
Sign In to create your own notes.

Getting Started

Adafruit Playground is a wonderful and safe place to share your interests with Adafruit's vibrant community of makers and doers. Have a cool project you are working on? Have a bit of code that you think others will find useful? Want to show off your electronics workbench? You have come to the right place.

The goal of Adafruit Playground is to make it as simple as possible to share your work. On the Adafruit Playground users can create Notes. A note is a single-page space where you can document your topic using Adafruit's easy-to-use editor. Notes are like Guides on the Adafruit Learning System but guides are high-fidelity content curated and maintained by Adafuit. Notes are whatever you want them to be. Have fun and be kind.

Click here to learn more about Adafruit Playground and how to get started.

New
Featured
  • Image for user tyeth
    By tyeth
    TPL5111 - Low Power projects (WipperSnapper, Arduino, CircuitPython, etc)

    Overview

    The TPL5110 (with driven output line) or the TPL5111 (with toggling enable line) allow some really simple low-power projects. Use the zero to sever and supply power, or the one to control enable pins (for LDO/MCU).

    Just adjust the potentiometer to tweak the turn-on time, and then with the TPL5111 you send a low high transition to the Done pin to go to sleep. The Enable pin goes low and MCU sleeps until the next cycle.

    All of that means you can easily create a no-code low-power project, just by hooking up the TPL5111 to a WipperSnapper compatible board, and combine that with an E-ink display for the ultimate low power setup.

    The board wakes up, connects to WiFi and Adafruit IO, get's the latest messages (E-Ink Display) and sends some sensor data. Two small Actions on Adafruit IO switch the Done pin to a high level, and then ten seconds later back to low. A third action updates the screen message with time + weather every 10mins.

    Add a third action to update the display or whatever is appropriate (like acting on some sensor data).

    Use \n to force a line break when working with Displays on WipperSnapper

    image.png
  • By Crolla
    Fizzgig

    I built Fizzgig and would like to share a few notes on my build (made in October 2025). I hope that this is helpful to anyone attempting to build it as well. I had a lot of fun building it!

    Speaker

    I tried using an external speaker connected with the headphone jack cable. It turned out that this speaker is not deactivated when no sound is played, and it produces very loud white noise (this happens with any speaker connected via this connection). Thankfully, the Adafruit support team reproduced this, and it seems that this behaviour cannot be resolved. So I went along with the oval mini speaker that is connected to the board via PicoBlade. To improve its sound a little, I attached the speaker to a Tic Tac box as a resonator, which worked quite well.

    Servo / Jaw

    I used lots of hot glue to attach all the hair, so the jaw became rather heavy. I attached some cardboard to the jaw to stabilise it, but this was not enough to support the weight. As a result, the jaw would not close properly. To resolve this, I stabilised the jaw with ice cream sticks. I also extended the servo horn with a piece of carbon tube. I could securely fix this to the servo horn using heat shrink tube with hot glue. My extended servo horn ends in a grommet made out of another piece of carbon tube with a larger diameter that I glued to the jaw. This makes the placement of the servo a lot easier and the connection more stable.

    I thought about also adding a spring taken out of a retractable pen to the servo horn extension to help the servo along. In my build, this was not possible to integrate, but there may be setups where adding a spring is possible and makes sense.

    Since I could not use a spring, I added a counterweight in the part where the servo horn ends. I glued in a screw and added two nuts for added weight. This helps the servo along.

    Code

    I used the UF2 file provided in the instruction. I would have liked to change the code to reduce the jaw opening angle (this would have enabled me to use a spring). Adafruit support pointed me to the code, and I was able to identify the place where the servo angles are defined. In my current Arduino IDE, the code would compile successfully, and the IDE even told me that the code had been successfully transferred to the board. However, CIRCUITPY was still visible as a drive after this supposedly successful transfer, and I had to re-upload the original UF2 file to go back to that version. I re-tried with several different methods, but I was not able to make the compiled code transfer to the board successfully. This may be due to my inability, since I am not very experienced using microcontrollers at all. If you are planning to alter the code, do check if it works for you before making your build.

    Wig

    It makes a lot of sense to buy a wig with really long hair. The instructions advise to cut off the hair along the longest section. I did this, but it would have made more sense to plan ahead to first check where the nose will go. Most hair under the nose will not be needed, as that's where the mouth goes. Depending what your particular wig looks like, it may be better to keep the long hair in the back and trim under the nose. 

    Also, the hair will be all over the place when you're done.

    I styled my Fizzgig's hair with transparent lacquer from a spray can. This was necessary especially around the eyes and the nose.

    Frame

    I built a wooden frame for my Fizzgig. I had originally used a cardboard box, but for more stability I replaced it with a wooden frame that basically had the same dimensions. My frame is about 31 x 18 x 10 cm. The required dimensions very much depend on the size of the wig and the length of the hair.

    Back side of Fizzgig, total view
  • Image for user jepler
    By jepler
    USB, XT, PS2 keycode cross reference in CSV

    Recently for the Fruitjam-286 emulator, I needed to create a good mapping from USB HID codes to XT codes.

    There are many resources online including Keyboard Scancodes (Andries Brouwer) which is a nice HTML table.

    I converted the table into a csv file, and then used a bespoke Python program to convert it to a format for the emulator.

    First, my program opens the csv file and checks its structure

  • By Speedy_VI
    2025-1017 Speedy-Vi Note

    I created this Note so I can link a section of CircuitPython Essentials to it. Unfortunately, when I click "Link Note" it still says "You do not have any notes available to link to this guide. To create a new note please visit the Adafruit Playground and create a note." I just created this note so I could link the section of CircuitPython Essentials to it. I must be doing something wrong...

  • Image for user danak
    By danak
    CircuitPython Functions to Pretty Up Strings

    Here's a short one...

    Sometimes I find myself wanting to pretty up a string. For example, a value might come in all in lower case letters and I want to capitalize the first letter or maybe go for capitalizing all first letters. Another use case is using variable names (conventionally all lower case) for captions.

    • 'this is my string'  ===>  'This is my string'
    • 'top values' ===> 'Top Values'

    Python has title() and capitalize() functions but these are not implemented in CircuitPython. 

    Also, strings may be in camel case that I want to fix - the Weather Powerup on AdafruitIO sends text data back this way:

    • 'mostlyClear' ===> 'Mostly Clear'

    When there is a limited set of data I've used if/elif/else statements or used look-up dictionaries, but in some cases the inputs may not be known. Also, who wants to write all of the repetitive code! 

    Since CircuitPython does include upper() and lower() functions we can build functions to do this. So with the help of CoPilot I drafted and now include these three functions in my code when I am doing anything with strings:

    In these sample functions I only apply to strings that are greater than three characters long as generally when I have short strings, I want them to keep the case (such as 'MPH'). But you can change this if you want it to make the change to all input strings. In the camel_to_title() function I also replace underscores with spaces, primarily for when I use variable names as captions.

    Hope you find this useful - and be sure to share any custom functions you can't live without.

     

  • By SamBlenny
    Exploring the Arduino App Lab Binary

    Overview

    To understand what the new Arduino UNO Q is about, I've been looking at the new the Arduino App Lab coding tool. The App Lab download page links to the source code at https://downloads.arduino.cc/app-lab-release/source-app-lab.zip, but the source seems incomplete. Among other differences, there aren't any build instructions or scripts in the source archive. So, I've been poking around trying to understand how the source works and how it differs from the binaries.

    Highlights of what I found: You need a UNO Q board to get past the first screen. The app is written with Wails which uses Go for the backend and a web app for the frontend (similar to Electron, but using a native web view rather than embedding a browser). The binaries include embedded markdown for examples that don't appear in the source. The app may be using Hotjar telemetry for analytics.

    Download Checksums

    When I downloaded the macOS dmg, Linux tar.gz, and source-app-lab.zip files on October 16, 2025, these are the checksums I got:

    Arduino App Lab start screen
  • By hexxorleiva
    Magnetometer - MMC5603 to measure gas usage inside your home with home assistant

    I wanted to measure the gas usage inside my home and push the data into Home Assistant.

    To collect the data, I wanted to use the MMC5603 magnetometer + ESP32 Feather V2. The MMC5603 had a great price point and the ESP32 Feather V2 was able to connect to my Home Assistant server via WiFi.

    Requirements

    • ESPHome 2025.8.3
    • The Adafruit Products above (MMC5603 + ESP32 Feather V2 or similar board that can connect via WiFi)
    • Ethernet Cable (Cat 5 and later is fine)
    • USB Power Adapter (e.g. like this one , 5V 1A)
    • A short USB-A to USB-C connector (e.g. like this one, data isn't important here - so any cable will do)
    • M2.5 screws (lengths and materials will be up to you, I used nylon M2.5 screws to attach the bracket to the gas meter and metal M2.5 screws for the MMC5603 to the bracket).

    If you end up using any earlier version of ESPHome and you'll run into issues like I did where the MMC5603 wasn't being registered properly.

    I connected everything like this diagram:

     

    I ended up having to create a 3D bracket that could hold the MMC5603 onto the US Gas Meter.

    https://www.printables.com/model/1446364-us-gas-meter-brace-for-a-mmc5603-magnetometer

  • Image for user retiredwizard
    By retiredwizard
    Your own TTS engine for the Fruit Jam Spell Jam app

    Overview

    The Spell Jam app on the Fruit Jam is a twist on a classic electronic toy. Check out the Spell Jam learn guide to learn all about it.

    By default, Spell Jam uses the Amazon Polly Text-to-Speech (TTS) service to create audio from the entered text. While Amazon's service can be used for free during a trial period, it does require an AWS account.

    If you'd prefer not to create an Amazon AWS account, you can instead run a local TTS server on your home network using open-source models like KittenTTS, Kani-TTS or eSpeak.

    This playground will walk you through the steps required to run a local TTS server on your network and configure Spell Jam on your Fruit Jam to use that server instead of AWS.

    Installing a Text-to-Speech Model

    There are two TTS AI models that the Spell Jam local backend works with:

    • KittenTTS - lightweight, will run on a Raspberry Pi 4/5
    • Kani-TTS  - newer, but may require an Nvidia GPU. (as of 2/2026 Kani no longer works with server.py)

    Legacy software TTS application:

    • eSpeak - Initially released in 2006, a free and open-source compact speech synthesizer.

    Before install any of the TTS systems, create and/or activate a dedicated Python virtual environment.

    python3 -m venv venv
    source venv/bin/activate
    Humanoid robot whispering to Fruit Jam running Spell Jam
  • Image for user tyeth
    By tyeth
    How to "wire-up" anything (e.g. a featherwing Display) on WipperSnapper

    WipperSnapper is a firmware (Arduino sketch) that connects boards with Components + Sensors in a no-code way. It's meant to be easy for beginners to start making things online quickly and simply, but sometimes we forget how much knowledge is expected.

    It can be a tall order to know how to find the pin names for each board, but doubly so when you want to connect a seemingly easily sandwiched FeatherWing into the mix.

    Today I followed this journey to test a newly supported E-ink FeatherWing display with one of our older boards, the Feather ESP32 v2 (Huzzah32), to replicate the MagTag setup which we know works well.

    As I looked for the documentation link in the New Components dialog, I realised this was a step too far for many people to take confidently alone and I should probably document the journey.

    Breaking it down

    image.png
  • By uandme2
    Judge Doom Hat – Wearable Monster M4sk Eye Tracking with MPU-6050 Head Control


    Hi everyone, just in time for Halloween 👀👁️👁️🎃👻,

    I’ve been working on a project that expands the Adafruit MONSTER M4SK – DIY Electronic Eyes Mask (Product ID: 4343) with an Adafruit MPU-6050 6-DoF Accel and Gyro Sensor – STEMMA QT Qwiic (Product ID: 3886) to add head-tracking eye control. The result is the Judge Doom Hat (inspired by Who Framed Roger Rabbit? 🕶️👀).

    The focus here is on the code integration: reading quaternion data from the MPU-6050, converting to yaw/pitch, normalizing to eyeTargetX / eyeTargetY, and feeding that into the existing M4_Eyes rendering engine for smooth, natural eye motion.

    Features:

    🔹Yaw + Pitch tracking — eyes follow head turns and tilts
    🔹Quaternion math (Simple_MPU6050.h) — fast, smooth, drift-resistant orientation
    🔹Natural movement — eyes decelerate and return to center, no snapping
    🔹Stability mode while walking
    🔹Tilt-based extras — roll can trigger blinks (backlight off) or switch configs
    🔹Configurable ranges — sensitivity, dead zones, and angle limits adjustable
    🔹Random wandering disabled — IMU fully drives eye motion

    Hardware:

    🔹Adafruit MONSTER M4SK – DIY Electronic Eyes Mask
    Product ID: 4343
    🔹Adafruit MPU-6050 6-DoF Accel and Gyro Sensor – STEMMA QT / Qwiic
    Product ID: 3886
    🔹Judge Doom Hat – wearable mount

    Credits & Acknowledgments:

    🔹 (Limor Fried) – Monster M4sk hardware design
    🔹Phil Burgess (PaintYourDragon) – Original M4_Eyes eye animation code

    🔹Adafruit Learning System Team –

    🔹Monster M4sk Augmented Eyes Toon Hat
    — starting point for the hat design and graphics

    🔹Monster M4sk Is Watching You
    — another key starting point we expanded on with the MPU-6050 for natural head-driven eye motion

    ChatGPT (OpenAI) – Code structure, quaternion integration, debugging, and iterative design support

    Code Files:

    This project builds directly on the official M4_Eyes code.
    The only files added for the Judge Doom Hat are shown below:

    Libraries:
    🔹Simple_MPU6050 Library by ZHomeSlice

    DoomHat.h

    user_tracker.cpp

    Drop these 2 files into the existing M4_Eyes project folder, disable other user_*.cpp files, and enable user_tracker.cpp. Then upload to your Monster M4sk.

    You will need to calibrate the MPU6050 unit first. I used the Simple_MPU6050_Calibration example: https://github.com/ZHomeSlice/Simple_MP ... r/Examples

    Future Ideas:

    🔹Haptic feedback (Adafruit DRV2605L) — exploring subtle vibration cues for blinks, config switches, or motion events

    🔹Peripheral awareness — adding slight eye lag/lead for more lifelike motion

    🔹Alternate IMUs (e.g., BNO055, LSM6DSOX) — comparing accuracy and drift over MPU-6050

    👁️👁️✨ That’s it!
    Looking forward to feedback, code suggestions, and ideas from the community.

     

  • Image for user johnsonfarms
    By johnsonfarms
    Herman Entertainment System (Pi Zero2w Python Handheld)

    This is the Herman Entertainment System, a handheld gaming system with a Raspberry Pi Zero 2 W and the Adafruit 1.3″ TFT Bonnet. Why Herman? I don’t know, that is what the kids wanted to call it, apparently Herman needs entertained. As for why we needed this instead of an existing emulator, well this particular setup struggles to run systems like RetroPie due to a display issue, so we created a small setup that uses Python and creates a home screen that lists all the games on the device and you simply choose which to play. It automatically updates with all the games you have added, in our example we have a Snake game, but you can add or make nearly anything. Print files for the enclosure are available here via Printables, and additional details and instructions are available here.

    Step 1: Prepare the SD Card (Headless Setup)

    Flash Pi OS Lite

    1. Download Raspberry Pi Imager from raspberrypi.org
    2. Insert your SD card into your computer
    3. Open Raspberry Pi Imager
    4. Choose “Raspberry Pi OS Lite (32-bit)” – no desktop needed
    5. Click the gear icon (Advanced options) and configure:
      • Enable SSH ✓
      • Set username: herman
      • Set password: (your choice)
      • Configure WiFi: Enter your network name and password
      • Set locale settings: Your country/timezone
    6. Flash the image to SD card

    Step 2: First Boot and SSH Connection

    1. Insert SD card into your Pi Zero 2 W
    2. Power on the Pi (green LED should flash, then stay solid)
    3. Wait 2-3 minutes for first boot to complete
    4. Find the Pi’s IP address:
      • Check your router’s admin page for connected devices
      • Or use an IP scanner like Advanced IP Scanner
      • Look for device named “raspberrypi”

    Connect via SSH

    ssh [email protected]
    # Replace XXX with your Pi's actual IP address
    

    Enter your password when prompted.

    Step 3: System Updates and Basic Setup

    # Update the system
    sudo apt update && sudo apt upgrade -y
    
    # Install essential packages
    sudo apt install python3-pip python3-pil git -y
    
    # Enable SPI (required for TFT display)
    sudo raspi-config
    

    In raspi-config:

    • Go to Interface Options → SPI → Enable
    • Finish and reboot

    Reconnect via SSH after reboot.

    Step 4: Install TFT Display Libraries

    Note: Newer Pi OS versions require --break-system-packages flag

    # Install compatible Adafruit libraries
    sudo pip3 install "adafruit-circuitpython-rgb-display==3.10.0" --break-system-packages
    sudo pip3 install "adafruit-circuitpython-busdevice==5.2.0" --break-system-packages
    sudo pip3 install "adafruit-circuitpython-typing==1.10.1" --break-system-packages
    
    # Install additional required libraries for SPI configuration
    sudo pip3 install click --break-system-packages
    sudo pip3 install adafruit-python-shell --break-system-packages
    
    # Install system packages
    sudo apt install python3-numpy -y

    Step 5: Configure TFT Display Hardware

    # Download and run the SPI configuration script
    wget https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/main/raspi-spi-reassign.py
    sudo python3 raspi-spi-reassign.py
    

    When prompted:

    1. Select 1 Reassign SPI Chip Enable Pins
    2. For CE0 selection, choose 22 Disabled
    3. Reboot when prompted

    Step 6: Create the Gaming System Files

    Reconnect via SSH and create your gaming system:

    # Create directories
    mkdir -p /home/herman/games
    mkdir -p /home/herman/system
    

    Create the main entertainment system:

    nano /home/herman/herman_entertainment_system.py

    Then copy the following code and paste it into the new file.


    #!/usr/bin/env python3
    import time
    import os
    import subprocess
    import digitalio
    import board
    import importlib.util
    import sys
    from adafruit_rgb_display.rgb import color565
    from adafruit_rgb_display import st7789
    import RPi.GPIO as GPIO
    from PIL import Image, ImageDraw, ImageFont

    class HermanEntertainmentSystem:
    def __init__(self):
    # Initialize display and GPIO in the class
    self.setup_hardware()

    # Load fonts
    try:
    self.font_huge = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 36)
    self.font_large = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 24)
    self.font_medium = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 18)
    self.font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 14)
    except:
    self.font_huge = ImageFont.load_default()
    self.font_large = ImageFont.load_default()
    self.font_medium = ImageFont.load_default()
    self.font_small = ImageFont.load_default()

    # Button debouncing
    self.last_button_time = 0
    self.button_debounce = 0.2

    # Game list
    self.games = []
    self.selected_game = 0
    self.games_directory = "/home/herman/games"

    # Create games directory if it doesn't exist
    if not os.path.exists(self.games_directory):
    os.makedirs(self.games_directory)

    self.scan_for_games()

    def setup_hardware(self):
    """Initialize display and GPIO"""
    # TFT Display Setup
    self.cs_pin = digitalio.DigitalInOut(board.CE0)
    self.dc_pin = digitalio.DigitalInOut(board.D25)
    reset_pin = None
    BAUDRATE = 40000000

    self.display = st7789.ST7789(
    board.SPI(),
    cs=self.cs_pin,
    dc=self.dc_pin,
    rst=reset_pin,
    baudrate=BAUDRATE,
    width=240,
    height=240,
    x_offset=0,
    y_offset=80,
    rotation=180, # Fixed rotation for proper orientation
    )

    self.backlight = digitalio.DigitalInOut(board.D26)
    self.backlight.switch_to_output()
    self.backlight.value = True

    # GPIO Setup - Correct pins for Pi Zero 2 W + TFT Bonnet
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP) # UP
    GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_UP) # DOWN
    GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_UP) # LEFT
    GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP) # RIGHT
    GPIO.setup(5, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Button A
    GPIO.setup(6, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Button B
    GPIO.setup(4, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Button C


    def get_text_size(self, draw, text, font):
    """Compatible text size function for older PIL versions"""
    try:
    bbox = draw.textbbox((0, 0), text, font=font)
    return bbox[2] - bbox[0], bbox[3] - bbox[1]
    except AttributeError:
    try:
    return draw.textsize(text, font=font)
    except:
    return len(text) * 8, 16

    def draw_centered_text(self, draw, text, y_pos, font, color):
    """Draw text centered horizontally"""
    text_width, text_height = self.get_text_size(draw, text, font)
    x_pos = (240 - text_width) // 2
    draw.text((x_pos, y_pos), text, font=font, fill=color)
    return text_height

    def read_controls_debounced(self):
    """Read controls with debouncing"""
    current_time = time.time()

    raw_controls = {
    'up': not GPIO.input(17),
    'down': not GPIO.input(22),
    'left': not GPIO.input(27),
    'right': not GPIO.input(23),
    'button_a': not GPIO.input(5),
    'button_b': not GPIO.input(6),
    'button_c': not GPIO.input(4)
    }

    # Apply debouncing
    if current_time - self.last_button_time > self.button_debounce:
    if any(raw_controls.values()):
    self.last_button_time = current_time
    return raw_controls

    return {key: False for key in raw_controls}

    def scan_for_games(self):
    """Scan for available game files"""
    self.games = []

    # Add built-in games
    builtin_games = [
    {"name": "Snake Game", "file": "snake_game_module.py", "type": "builtin"},
    ]

    for game in builtin_games:
    if os.path.exists(f"/home/herman/{game['file']}"):
    self.games.append(game)

    # Scan games directory for additional games
    if os.path.exists(self.games_directory):
    for filename in os.listdir(self.games_directory):
    if filename.endswith('.py'):
    game_name = filename.replace('.py', '').replace('_', ' ').title()
    self.games.append({
    "name": game_name,
    "file": filename,
    "type": "custom"
    })

    # Add system options
    self.games.extend([
    {"name": "System Info", "file": None, "type": "system"},
    {"name": "Shutdown", "file": None, "type": "system"}
    ])

    # Reset selection if out of bounds
    if self.selected_game >= len(self.games):
    self.selected_game = 0

    def show_welcome_screen(self):
    """Display welcome screen"""
    image = Image.new('RGB', (240, 240), (0, 0, 50)) # Dark blue background
    draw = ImageDraw.Draw(image)

    # Title with shadow effect
    self.draw_centered_text(draw, "Herman", 31, self.font_huge, (0, 0, 0)) # Shadow
    self.draw_centered_text(draw, "Herman", 30, self.font_huge, (0, 255, 100)) # Main

    self.draw_centered_text(draw, "Entertainment", 71, self.font_large, (0, 0, 0)) # Shadow
    self.draw_centered_text(draw, "Entertainment", 70, self.font_large, (255, 255, 255)) # Main

    self.draw_centered_text(draw, "System", 101, self.font_large, (0, 0, 0)) # Shadow
    self.draw_centered_text(draw, "System", 100, self.font_large, (255, 255, 255)) # Main

    # Animated dots
    dots = "..." if int(time.time() * 2) % 2 else ""
    self.draw_centered_text(draw, f"Loading{dots}", 140, self.font_medium, (100, 100, 255))

    # Instructions
    self.draw_centered_text(draw, "Press A to Continue", 180, self.font_medium, (255, 255, 0))
    self.draw_centered_text(draw, "Press B to Shutdown", 210, self.font_small, (255, 100, 100))

    self.display.image(image)

    def show_game_list(self):
    """Display scrollable game list"""
    image = Image.new('RGB', (240, 240), (0, 0, 0))
    draw = ImageDraw.Draw(image)

    # Header
    self.draw_centered_text(draw, "Select Game", 10, self.font_large, (0, 255, 0))

    # Draw game list (show 5 games at a time)
    start_index = max(0, self.selected_game - 2)
    end_index = min(len(self.games), start_index + 5)

    y_pos = 50
    for i in range(start_index, end_index):
    game = self.games[i]

    # Highlight selected game
    if i == self.selected_game:
    # Selection background
    draw.rectangle([10, y_pos - 2, 230, y_pos + 25], fill=(0, 100, 0))
    text_color = (255, 255, 255)
    # Selection indicator
    draw.text((15, y_pos + 2), ">", font=self.font_medium, fill=(255, 255, 0))
    else:
    text_color = (200, 200, 200)

    # Game name
    draw.text((35, y_pos + 2), game["name"], font=self.font_medium, fill=text_color)

    # Game type indicator
    if game["type"] == "builtin":
    draw.rectangle([220, y_pos + 5, 235, y_pos + 15], fill=(0, 255, 0))
    elif game["type"] == "custom":
    draw.rectangle([220, y_pos + 5, 235, y_pos + 15], fill=(0, 0, 255))
    elif game["type"] == "system":
    draw.rectangle([220, y_pos + 5, 235, y_pos + 15], fill=(255, 0, 0))

    y_pos += 30

    # Instructions
    self.draw_centered_text(draw, "Up/Down = Navigate", 190, self.font_small, (100, 100, 100))
    self.draw_centered_text(draw, "A = Select B = Back", 210, self.font_small, (100, 100, 100))

    # Scroll indicators
    if start_index > 0:
    draw.text((115, 35), "▲", font=self.font_small, fill=(255, 255, 0))
    if end_index < len(self.games):
    draw.text((115, 175), "▼", font=self.font_small, fill=(255, 255, 0))

    self.display.image(image)

    def show_system_info(self):
    """Display system information"""
    image = Image.new('RGB', (240, 240), (0, 0, 0))
    draw = ImageDraw.Draw(image)

    self.draw_centered_text(draw, "System Info", 20, self.font_large, (0, 255, 0))

    # Get system info
    try:
    with open('/proc/cpuinfo', 'r') as f:
    for line in f:
    if 'Model' in line:
    model = line.split(':')[1].strip()
    break
    else:
    model = "Raspberry Pi"
    except:
    model = "Unknown"

    info_lines = [
    f"Device: {model[:20]}",
    f"Games: {len([g for g in self.games if g['type'] != 'system'])}",
    f"Python: 3.11",
    f"Display: 240x240 ST7789"
    ]

    y_pos = 70
    for line in info_lines:
    self.draw_centered_text(draw, line, y_pos, self.font_small, (255, 255, 255))
    y_pos += 25

    self.draw_centered_text(draw, "Press B to return", 200, self.font_small, (255, 255, 0))

    self.display.image(image)

    def launch_game(self, game):
    """Launch selected game using dynamic import for custom games"""
    if game["type"] == "system":
    if game["name"] == "System Info":
    return "system_info"
    elif game["name"] == "Shutdown":
    return "shutdown"

    try:
    # Show loading screen for both built-in and custom games
    image = Image.new('RGB', (240, 240), (0, 0, 0))
    draw = ImageDraw.Draw(image)
    self.draw_centered_text(draw, "Loading Game...", 120, self.font_medium, (255, 255, 0))
    self.display.image(image)

    if game["type"] == "builtin":
    # Built-in game handling (existing snake game)
    if game['file'] == 'snake_game_module.py':
    from snake_game_module import SnakeGame
    game_instance = SnakeGame(self.display, GPIO, self.backlight)
    game_instance.run()

    elif game["type"] == "custom":
    # Dynamic import for custom games
    game_path = os.path.join(self.games_directory, game["file"])
    module_name = os.path.splitext(game["file"])[0]

    # Import the module
    spec = importlib.util.spec_from_file_location(module_name, game_path)
    game_module = importlib.util.module_from_spec(spec)
    sys.modules[module_name] = game_module
    spec.loader.exec_module(game_module)

    # Convert filename to CamelCase class name
    class_name = ''.join(
    part.capitalize()
    for part in module_name.split('_')
    )

    # Get the game class
    GameClass = getattr(game_module, class_name)

    # Instantiate and run the game
    game_instance = GameClass(self.display, GPIO, self.backlight)
    game_instance.run()

    return "game_list"

    except Exception as e:
    # Unified error handling
    image = Image.new('RGB', (240, 240), (0, 0, 0))
    draw = ImageDraw.Draw(image)
    self.draw_centered_text(draw, "Game Error!", 80, self.font_medium, (255, 0, 0))

    error_msg = str(e)
    if len(error_msg) > 30:
    error_msg = error_msg[:30] + "..."
    self.draw_centered_text(draw, error_msg, 110, self.font_small, (255, 255, 255))

    self.draw_centered_text(draw, "Press A to continue", 160, self.font_small, (255, 255, 255))
    self.display.image(image)

    # Wait for button press
    while True:
    controls = self.read_controls_debounced()
    if controls['button_a']:
    break
    time.sleep(0.1)

    return "game_list"

    def run(self):
    """Main system loop"""
    state = "welcome"

    try:
    while True:
    controls = self.read_controls_debounced()

    if state == "welcome":
    self.show_welcome_screen()

    if controls['button_a']:
    state = "game_list"
    self.scan_for_games() # Refresh game list
    elif controls['button_b']:
    # Shutdown
    image = Image.new('RGB', (240, 240), (0, 0, 0))
    draw = ImageDraw.Draw(image)
    self.draw_centered_text(draw, "Shutting Down...", 120, self.font_medium, (255, 0, 0))
    self.display.image(image)
    time.sleep(2)
    subprocess.run(['sudo', 'shutdown', '-h', 'now'])
    break

    elif state == "game_list":
    self.show_game_list()

    if controls['up']:
    self.selected_game = (self.selected_game - 1) % len(self.games)
    elif controls['down']:
    self.selected_game = (self.selected_game + 1) % len(self.games)
    elif controls['button_a']:
    if self.games:
    result = self.launch_game(self.games[self.selected_game])
    if result == "system_info":
    state = "system_info"
    elif result == "shutdown":
    subprocess.run(['sudo', 'shutdown', '-h', 'now'])
    break
    # Game returned, stay in game_list state
    elif controls['button_b']:
    state = "welcome"

    elif state == "system_info":
    self.show_system_info()

    if controls['button_b']:
    state = "game_list"

    time.sleep(0.1)

    except KeyboardInterrupt:
    pass
    finally:
    try:
    GPIO.cleanup()
    except:
    pass
    try:
    self.backlight.value = False
    except:
    pass

    # Main execution
    if __name__ == "__main__":
    system = HermanEntertainmentSystem()
    system.run()

    Control + x to exit, then Y to save, and Enter to confirm the name. This will be the same process for the Snake game.

    Create the Snake game:

    nano /home/herman/snake_game_module.py
    
    #!/usr/bin/env python3
    import time
    import random
    import RPi.GPIO as GPIO
    from PIL import Image, ImageDraw, ImageFont
    
    class SnakeGame:
        def __init__(self, display_obj, gpio_obj, backlight_obj):
            # Verify objects are not None
            if display_obj is None or gpio_obj is None or backlight_obj is None:
                raise ValueError("Display, GPIO, or backlight object is None")
                
            self.display = display_obj
            self.GPIO = gpio_obj
            self.backlight = backlight_obj
            
            self.grid_size = 12
            self.grid_width = 240 // self.grid_size
            self.grid_height = 240 // self.grid_size
            self.reset_game()
            
            # Load fonts
            try:
                self.font_huge = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 48)
                self.font_large = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 32)
                self.font_medium = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)
                self.font_small = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 16)
            except:
                self.font_huge = ImageFont.load_default()
                self.font_large = ImageFont.load_default()
                self.font_medium = ImageFont.load_default()
                self.font_small = ImageFont.load_default()
            
            # Button debouncing
            self.last_button_time = 0
            self.button_debounce = 0.1
            self.last_direction_time = 0
            self.direction_debounce = 0.1
            
            # Display optimization
            self.last_display_update = 0
            self.display_interval = 0.05
            
        def reset_game(self):
            center_x = self.grid_width // 2
            center_y = self.grid_height // 2
            self.snake = [
                [center_x, center_y],
                [center_x - 1, center_y],
                [center_x - 2, center_y]
            ]
            self.direction = [1, 0]
            self.food = self.generate_food()
            self.score = 0
            self.game_over = False
            self.needs_redraw = True
            
        def generate_food(self):
            while True:
                food_x = random.randint(0, self.grid_width - 1)
                food_y = random.randint(0, self.grid_height - 1)
                if [food_x, food_y] not in self.snake:
                    return [food_x, food_y]
        
        def read_controls_debounced(self):
            current_time = time.time()
            
            raw_controls = {
                'up': not self.GPIO.input(17),
                'down': not self.GPIO.input(22),
                'left': not self.GPIO.input(27),
                'right': not self.GPIO.input(23),
                'button_a': not self.GPIO.input(5),
                'button_b': not self.GPIO.input(6)
            }
            
            debounced_controls = {
                'up': False, 'down': False, 'left': False, 'right': False,
                'button_a': False, 'button_b': False
            }
            
            if current_time - self.last_direction_time > self.direction_debounce:
                if any([raw_controls['up'], raw_controls['down'], 
                       raw_controls['left'], raw_controls['right']]):
                    debounced_controls.update({
                        'up': raw_controls['up'],
                        'down': raw_controls['down'],
                        'left': raw_controls['left'],
                        'right': raw_controls['right']
                    })
                    self.last_direction_time = current_time
            
            if current_time - self.last_button_time > self.button_debounce:
                if raw_controls['button_a'] or raw_controls['button_b']:
                    debounced_controls.update({
                        'button_a': raw_controls['button_a'],
                        'button_b': raw_controls['button_b']
                    })
                    self.last_button_time = current_time
            
            return debounced_controls
        
        def update_direction(self, controls):
            old_direction = self.direction.copy()
            
            if controls['up'] and self.direction[1] != 1:
                self.direction = [0, -1]
            elif controls['down'] and self.direction[1] != -1:
                self.direction = [0, 1]
            elif controls['left'] and self.direction[0] != 1:
                self.direction = [-1, 0]
            elif controls['right'] and self.direction[0] != -1:
                self.direction = [1, 0]
            
            if old_direction != self.direction:
                self.needs_redraw = True
        
        def move_snake(self):
            old_snake = self.snake.copy()
            old_food = self.food.copy()
            old_score = self.score
            
            head = self.snake[0].copy()
            head[0] += self.direction[0]
            head[1] += self.direction[1]
            
            if (head[0] < 0 or head[0] >= self.grid_width or 
                head[1] < 0 or head[1] >= self.grid_height or
                head in self.snake):
                self.game_over = True
                self.needs_redraw = True
                return
            
            self.snake.insert(0, head)
            
            if head == self.food:
                self.score += 1
                self.food = self.generate_food()
            else:
                self.snake.pop()
            
            if (self.snake != old_snake or self.food != old_food or 
                self.score != old_score):
                self.needs_redraw = True
        
        def get_text_size(self, draw, text, font):
            try:
                bbox = draw.textbbox((0, 0), text, font=font)
                return bbox[2] - bbox[0], bbox[3] - bbox[1]
            except AttributeError:
                try:
                    return draw.textsize(text, font=font)
                except:
                    return len(text) * 8, 16
        
        def draw_centered_text(self, draw, text, y_pos, font, color):
            text_width, text_height = self.get_text_size(draw, text, font)
            x_pos = (240 - text_width) // 2
            draw.text((x_pos, y_pos), text, font=font, fill=color)
            return text_height
        
        def draw_game(self):
            current_time = time.time()
            
            if (not self.needs_redraw or 
                current_time - self.last_display_update < self.display_interval):
                return
            
            image = Image.new('RGB', (240, 240), (0, 0, 0))
            draw = ImageDraw.Draw(image)
            
            # Draw game area border
            draw.rectangle([0, 25, 239, 239], outline=(100, 100, 100), width=2)
            
            # Draw score at top
            score_text = f"Score: {self.score}"
            draw.text((5, 2), score_text, font=self.font_medium, fill=(255, 255, 255))
            
            # Draw snake
            for i, segment in enumerate(self.snake):
                x = segment[0] * self.grid_size
                y = 25 + segment[1] * self.grid_size
                
                if i == 0:  # Head
                    draw.rectangle([x, y, x + self.grid_size - 1, y + self.grid_size - 1], 
                                 fill=(0, 255, 0))
                    # Eyes
                    draw.rectangle([x + 2, y + 2, x + 4, y + 4], fill=(255, 255, 255))
                    draw.rectangle([x + 7, y + 2, x + 9, y + 4], fill=(255, 255, 255))
                else:  # Body
                    draw.rectangle([x, y, x + self.grid_size - 1, y + self.grid_size - 1], 
                                 fill=(0, 150, 0))
                    draw.rectangle([x + 2, y + 2, x + self.grid_size - 3, y + self.grid_size - 3], 
                                 fill=(0, 200, 0))
            
            # Draw food
            food_x = self.food[0] * self.grid_size
            food_y = 25 + self.food[1] * self.grid_size
            draw.ellipse([food_x, food_y, food_x + self.grid_size - 1, food_y + self.grid_size - 1], 
                        fill=(255, 0, 0))
            draw.rectangle([food_x + 5, food_y - 2, food_x + 7, food_y + 2], fill=(139, 69, 19))
            
            self.display.image(image)
            self.last_display_update = current_time
            self.needs_redraw = False
        
        def draw_start_screen(self):
            image = Image.new('RGB', (240, 240), (0, 0, 0))
            draw = ImageDraw.Draw(image)
            
            self.draw_centered_text(draw, "SNAKE", 25, self.font_huge, (0, 255, 0))
            
            instructions = [
                ("Joystick = Move", 90, (255, 255, 255)),
                ("Eat Red Apples", 120, (255, 100, 100)),
                ("Don't Hit Walls!", 150, (255, 255, 100)),
                ("Press A = Start", 190, (100, 255, 100)),
                ("Press B = Main Menu", 220, (100, 100, 255))
            ]
            
            for text, y_pos, color in instructions:
                self.draw_centered_text(draw, text, y_pos, self.font_medium, color)
            
            self.display.image(image)
        
        def draw_game_over_screen(self):
            image = Image.new('RGB', (240, 240), (0, 0, 0))
            draw = ImageDraw.Draw(image)
            
            self.draw_centered_text(draw, "GAME", 20, self.font_large, (255, 0, 0))
            self.draw_centered_text(draw, "OVER", 60, self.font_large, (255, 0, 0))
            
            score_text = f"Score: {self.score}"
            self.draw_centered_text(draw, score_text, 110, self.font_large, (255, 255, 0))
            
            if self.score >= 20:
                rating, color = "MASTER!", (255, 215, 0)
            elif self.score >= 10:
                rating, color = "Great!", (192, 192, 192)
            elif self.score >= 5:
                rating, color = "Good!", (205, 127, 50)
            else:
                rating, color = "Try Again!", (255, 255, 255)
            
            self.draw_centered_text(draw, rating, 150, self.font_medium, color)
            
            self.draw_centered_text(draw, "A = Play Again", 190, self.font_medium, (0, 255, 0))
            self.draw_centered_text(draw, "B = Main Menu", 220, self.font_medium, (0, 255, 0))
            
            self.display.image(image)
        
        def run(self):
            game_state = "start"
            last_state = None
            
            while True:
                controls = self.read_controls_debounced()
                
                if game_state != last_state:
                    if game_state == "start":
                        self.draw_start_screen()
                    elif game_state == "game_over":
                        self.draw_game_over_screen()
                    last_state = game_state
                
                if game_state == "start":
                    if controls['button_a']:
                        game_state = "playing"
                        self.reset_game()
                    elif controls['button_b']:
                        return  # Return to main menu
                        
                elif game_state == "playing":
                    self.update_direction(controls)
                    
                    if controls['button_b']:
                        return  # Return to main menu
                    
                    self.move_snake()
                    
                    if self.game_over:
                        game_state = "game_over"
                    
                    self.draw_game()
                    
                    speed = max(0.1, 0.3 - (self.score * 0.01))
                    time.sleep(speed)
                    
                elif game_state == "game_over":
                    if controls['button_a']:
                        game_state = "playing"
                        self.reset_game()
                    elif controls['button_b']:
                        return  # Return to main menu
                
                time.sleep(0.02)
    

    Make files executable:

    chmod +x /home/herman/*.py
    

    Step 7: Test the System Manually

    Before setting up auto-start, test everything works:

    python3 /home/herman/herman_entertainment_system.py
    

    You should see:

    • Herman Entertainment System welcome screen
    • Working joystick controls (GPIO 17=UP, 22=DOWN, 27=LEFT, 23=RIGHT)
    • Display rotation=180 (correct orientation)
    • Snake game launches and returns to menu properly

    Step 8: Set Up Auto-Start Service

    sudo nano /etc/systemd/system/gaming-os.service
    

    Add this content:

    [Unit]
    Description=Herman Entertainment System
    After=multi-user.target
    
    [Service]
    Type=simple
    User=herman
    Group=herman
    WorkingDirectory=/home/herman
    Environment=PATH=/usr/bin:/usr/local/bin
    Environment=PYTHONPATH=/usr/local/lib/python3.11/dist-packages
    ExecStart=/usr/bin/python3 /home/herman/herman_entertainment_system.py
    Restart=always
    RestartSec=10
    
    [Install]
    WantedBy=multi-user.target
    

    Then run:

    # Set correct permissions and enable the service
    sudo chmod 644 /etc/systemd/system/gaming-os.service
    sudo systemctl daemon-reload
    sudo systemctl enable gaming-os.service
    sudo systemctl start gaming-os.service

    # Check if it's working
    sudo systemctl status gaming-os.service

    Step 9: Final Reboot and Testing

    sudo reboot
    

    After reboot, your Pi should automatically:

    1. Boot to the Herman Entertainment System welcome screen
    2. Display “Press A to Continue”
    3. Show the game menu when you press Button A
    4. Launch Snake game and return to menu properly

    Step 10: Adding More Games

    To add new games later:

    1. SSH into your Pi
    2. Copy Python game files to /home/herman/games/
    3. The system will automatically detect and list them

    There is a full write up here to add games to the HES.

    Troubleshooting Tips

    If display doesn’t work:

    • Check TFT Bonnet is firmly seated on all 40 GPIO pins
    • Verify SPI is enabled: lsmod | grep spi

    If controls are wrong:

    • The code includes the correct GPIO mappings: 17=UP, 22=DOWN, 27=LEFT, 23=RIGHT

    If service won’t start:

    • Check logs: sudo journalctl -u gaming-os.service -f

    If WiFi doesn’t connect:

    • Check: sudo nmcli radio wifi (should show “enabled”)
    • Reconfigure: sudo raspi-config → Network Options → WiFi

     

    Hands holding the Herman Entertainment System
    HES Raspi Zero 2w Handheld
  • Image for user danak
    By danak
    Fruit Jam Remote Control & the Power of the Community

    Just got a Fruit Jam and have been learning a lot - so many cool components wrapped into a neat little package. 

    One of the goodies included is an IR receiver that is available as board.IR, so I thought it might be neat to send commands to the Fruit Jam with a remote control. This is a quick look receiving input from a remote control. But this article is also a lesson on the power of the Adafruit CircuitPython community. 

    First, the parts for the project:

    I used a couple of odd remotes I had laying around and in fact I have two remotes coded in. 

    The Fruit Jam Learn Guide doesn't address coding the IR receiver, but there are other guides for getting IR signals. I started with:

  • By SamBlenny
    Pasting in Hard Mode with VNC and SSH

    My main computer is a Mac, but I run third party dev tools on a separate Debian box so they can't mess up my main computer. To control the Debian box from the Mac, I use SSH and VNC over Ethernet. The VNC session runs an Xfce desktop environment.

    Mostly the remote control setup works great, but it's a pain to copy from macOS and paste to apps running in Xfce (macOS Screen Sharing VNC client app doesn't do clipboard sync). For example, suppose I want to copy text from Chrome on the Mac and paste it into VS Code on Debian. Since I can't paste directly into the VNC client, I must go through an SSH shell where pasting works fine.

    In the past, I've used SSH to paste into vim and make a temporary file. But, today I figured out a better way where I can send text directly to the Xfce clipboard from my SSH shell:

    Notes:

    1. If you don't already have xclip installed, you'll need to sudo apt install xclip
    2. The cat <<'EOF' stuff is using bash's heredoc string syntax. Note that in this case the single quotes around EOF are telling bash not to do its normal variable substitution. This is useful if you want to copy and paste code that includes bash variable syntax, perhaps as part of a Makefile. In this case, ${WHATEVER} will get passed through to xclip rather than being evaluated as a variable substitution by bash.
    3. I have TigerVNC server configured to set DISPLAY to :1. Since the instance of bash that's running in my SSH shell is is not part of an X session, it doesn't know about TigerVNC's display. I have to tell xclip which DISPLAY to use, otherwise it would get mad and complain with an error message.
  • By mrklingon
    *Really*, No one needs this calculator...

    Seriously - this calculator was fun to program - but I freely admit it's not a practical tool.

    My first computers were mechanical. There was the  "Think-a-Tron," then "Digicomp I" and finally "Digicomp II." Of those, only the Digicomp I was actually programmable. Think-a-Tron was a sort of punch card reader and Digicomp II was a mechanical binary calculator using marbles.

    The Digicomp II included examples of things the calculator could do - including Newton's method for calculating roots, and that is what this project is for.

    This is a simple calculator that can calculate square and cube roots using Newton's method.

    Set the switch LEFT to calculate ***square*** and RIGHT to calculate ***cube*** roots.

    * Press A to calculate the root of the current target (Default target is "2").
    * Press B to play back the current root (2 or 3), then target value, finally display the current root found.
    * Press A+B to randomly choose (and display) a new target value (2-1000)
    * Touch A1 to display the most recent root found.

      File: Root.js - Javascript version of Circuit Playground code
      Makecode Block version here: https://makecode.com/_b3z9Vi18vcFW

    Numbers are displayed by lighting the Neopixels green digit-by-digit (i.e. 3 pixels = "3", 6 pixels = "6" and so on). All digits purple indicate a decimal point and "0" is all pixels turned yellow.

  • Image for user relic_se
    By relic_se
    Fruit Jam Ssspeed Dating

    Overview

    Tired of all the action and puzzle games on the Adafruit Fruit Jam? Want to try something new? Well, you've got a treat in store for you with the new game, Ssspeed Dating. Dive into a world full of fun snakes looking to spice up their lives by meeting someone new. Chat with 6 unique characters during quick-fire speed dating rounds hosted by the familiar CircuitPython mascot, Blinka, and do your best to charm the snake of your dreams.

    Controls

    Mouse

    You can control all aspects of this application using a USB mouse to control the on-screen cursor. All actions are completed with a left click. Some events require that an object be hovered over and then clicked while others you can click anywhere on the screen to proceed.

    In order to exit while playing the game, click the door icon in the top right corner. This will bring up a confirmation prompt which will return you back to the title screen. At this point, you can reload the device by clicking the "Quit" option on the title screen. If the application is installed within Fruit Jam OS, it will return to the main menu of the operating system.

    Keyboard

    A standard USB keyboard or REPL prompt can be used to control all aspects of this application.

    Key Action
    Arrow keys Navigate menus and dialog options
    Enter or space Select highlighted item or continue to next dialog
    Escape Open exit prompt to return to title screen
    Letter keys (upper/lower) If on-screen keyboard is active, appends character to input
    Backspace If on-screen keyboard is active, remove last character from input

    Buttons

    The 3 buttons on the Fruit Jam device can be used for basic control of this application.

    Button Action
    Button #1 Select highlighted item or continue to next dialog
    Button #2 Navigate down menus and dialog options
    Button #3 Navigate up menus and dialog options

    Gamepad

    USB gamepads are currently not supported but planned to be added in a future release.

    Screen capture of the title screen of the Ssspeed Dating game
To have your work shown off click the Show and Tell toggle when viewing your note.
  • First
  • Previous
  • …
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • …
  • Next
  • Last
345 notes and counting
Search

Getting Started

Adafruit Playground is a wonderful and safe place to share your interests with Adafruit's vibrant community of makers and doers. Have a cool project you are working on? Have a bit of code that you think others will find useful? Want to show off your electronics workbench? You have come to the right place.

The goal of Adafruit Playground is to make it as simple as possible to share your work. On the Adafruit Playground users can create Notes. A note is a single-page space where you can document your topic using Adafruit's easy-to-use editor. Notes are like Guides on the Adafruit Learning System but guides are high-fidelity content curated and maintained by Adafuit. Notes are whatever you want them to be. Have fun and be kind.

Click here to learn more about Adafruit Playground and how to get started.

    • Contact Us
    • Tech Support Forums
    • FAQs
    • Shipping & Returns
    • Freebies
    • Terms of Service
    • Privacy & Legal
    • Website Accessibility
    • LLMs.txt
    • About Us
    • Press
    • Educators
    • Distributors
    • Jobs
    • Gift Cards
    "He who receives an idea from me receives it without lessening me, as he who lights his candle at mine receives light without darkening me"
    Thomas Jefferson
    Adafruit Logo
    A Minority and Woman-owned Business Enterprise (M/WBE)