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.