The Memory game will make use of a USB mouse for input from the players. To start, look at the following basic example of initializing the mouse, reading data from it, and plotting a cursor on the screen.
The basic mouse example is embedded below. Click the 'Download Project Bundle' button to download a zip containing this example and the associated image file and required libraries. Unzip and copy them to your CIRCUITPY drive to run the example.
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries
# SPDX-License-Identifier: MIT
"""
This example is made for a basic Microsoft optical mouse with
two buttons and a wheel that can be pressed.
It assumes there is a single mouse connected to USB Host,
and no other devices connected.
"""
import array
from displayio import Group, OnDiskBitmap, TileGrid
from adafruit_display_text.bitmap_label import Label
import supervisor
import terminalio
import usb.core
# pylint: disable=ungrouped-imports
if hasattr(supervisor.runtime, "display") and supervisor.runtime.display is not None:
# use the built-in HSTX display for Metro RP2350
display = supervisor.runtime.display
else:
# pylint: disable=ungrouped-imports
from displayio import release_displays
import picodvi
import board
import framebufferio
# initialize display
release_displays()
fb = picodvi.Framebuffer(
320,
240,
clk_dp=board.CKP,
clk_dn=board.CKN,
red_dp=board.D0P,
red_dn=board.D0N,
green_dp=board.D1P,
green_dn=board.D1N,
blue_dp=board.D2P,
blue_dn=board.D2N,
color_depth=16,
)
display = framebufferio.FramebufferDisplay(fb)
# group to hold visual elements
main_group = Group()
# make the group visible on the display
display.root_group = main_group
# load the mouse cursor bitmap
mouse_bmp = OnDiskBitmap("mouse_cursor.bmp")
# make the background pink pixels transparent
mouse_bmp.pixel_shader.make_transparent(0)
# create a TileGrid for the mouse, using its bitmap and pixel_shader
mouse_tg = TileGrid(mouse_bmp, pixel_shader=mouse_bmp.pixel_shader)
# move it to the center of the display
mouse_tg.x = display.width // 2
mouse_tg.y = display.height // 2
# text label to show the x, y coordinates on the screen
output_lbl = Label(
terminalio.FONT, text=f"{mouse_tg.x},{mouse_tg.y}", color=0xFFFFFF, scale=1
)
# move it to the upper left corner
output_lbl.anchor_point = (0, 0)
output_lbl.anchored_position = (1, 1)
# add it to the main group
main_group.append(output_lbl)
# add the mouse tile grid to the main group
main_group.append(mouse_tg)
# button names
# This is ordered by bit position.
BUTTONS = ["left", "right", "middle"]
# scan for connected USB device and loop over any found
for device in usb.core.find(find_all=True):
# print device info
print(f"{device.idVendor:04x}:{device.idProduct:04x}")
print(device.manufacturer, device.product)
print(device.serial_number)
# assume the device is the mouse
mouse = device
# detach the kernel driver if needed
if mouse.is_kernel_driver_active(0):
mouse.detach_kernel_driver(0)
# set configuration on the mouse so we can use it
mouse.set_configuration()
# buffer to hold mouse data
# Boot mice have 4 byte reports
buf = array.array("b", [0] * 4)
# main loop
while True:
try:
# attempt to read data from the mouse
# 10ms timeout, so we don't block long if there
# is no data
count = mouse.read(0x81, buf, timeout=10)
except usb.core.USBTimeoutError:
# skip the rest of the loop if there is no data
continue
# update the mouse tilegrid x and y coordinates
# based on the delta values read from the mouse
mouse_tg.x = max(0, min(display.width - 1, mouse_tg.x + buf[1]))
mouse_tg.y = max(0, min(display.height - 1, mouse_tg.y + buf[2]))
# string with updated coordinates for the text label
out_str = f"{mouse_tg.x},{mouse_tg.y}"
# loop over the button names
for i, button in enumerate(BUTTONS):
# check if each button is pressed using bitwise AND shifted
# to the appropriate index for this button
if buf[0] & (1 << i) != 0:
# append the button name to the string to show if
# it is being clicked.
out_str += f" {button}"
# update the text label with the new coordinates
# and buttons being pressed
output_lbl.text = out_str
The code contains comments describing the purpose of each section. An overview of the code functionality can be found below. Reading both will give you a good understanding of one of the core principals that will get used by the Memory game.
Display Setup
First the code checks whether there is a built-in display. For the Metro RP2350 starting with CircuitPython 9.2.5 the HSTX connector counts as the built-in display, so it will get used by this script by default. If a display is not found, then it will attempt to initialize one manually using the default HSTX pins.
Visual Elements Setup
Next the code creates a main_group to put all visual elements into and sets it as the root_group on the display so it is shown on the screen. Then it creates an OnDiskBitmap to load the mouse_cursor.bmp file.
This files contains a pink color in the palette index 0 which is treated as transparency by calling mouse_bmp.pixel_shader.make_transparent(0). A TileGrid is created and stored in the variable mouse_tg. Later on when reading mouse data, the program will use it to move mouse_tg around the screen. The mouse cursor is put into the center of the screen to start with.
A Label named output_lbl is created and added to the main_group after being placed in the top left corner of the screen. This will be used to show the current mouse coordinates and any buttons that are pressed.
The mouse_tg is added to main_group last so that it will be visually in front of everything else.
USB Mouse Device Setup
The list BUTTONS is created with values "left", "right", and "middle". The order and indexes of these values align with the mouse protocol which will use bits in the same positions in order to denote whether each button is being pressed or not.
Next the code scans for connected USB devices and sets the first one found to the mouse variable. Once found, the connected mouse is detached from the kernel driver, if needed. Next mouse.set_configuration() is called in order to get the mouse ready to send data.
An array of bytes that can hold 4 elements is created to store the data that is read from the mouse device.
Main Loop
Inside the main loop, mouse.read(0x81, buf, timeout=10) is called to read data from the mouse. 0x81 is the default endpoint address for basic HID mice, buf is the 4 byte buffer array that will get filled with the data that is read. timeout is how many milliseconds to wait before raising a USBTimeoutError if there is no data to read. This code uses a low value of 10 milliseconds to illustrate how the main loop can do other things if the timeout is kept low. Higher timeout values result in the read() call blocking other code execution for longer times.
If there is no data, the USBTimeoutError is raised and the code skips to the next iteration with continue.
If there is data, the code reads the delta x and y values from the buffer indexes 1 and 2. These delta values represent how far the mouse has moved in each direction. It will have negative values for up/left, and positive values for right/down.
The delta values are used to move the mouse_tg to a new location. min() and max() are used to clamp the mouse cursor to the bounds of the display. The mouse itself is not aware of these bounds, the code enforces staying on the display after reading raw data from the mouse.
The out_str is updated with the current x and y coordinates of the mouse_tg.
Next the code checks for button presses by looping over the BUTTONS list and checking the bits in the respective positions within the byte at index 0 of the buffer. Any buttons that are pressed have their name added to the out_str.
Finally out_str is set as the text on the output_lbl in order to show the current values on the screen.
Page last edited April 03, 2025
Text editor powered by tinymce.