The code is written so that you can use the keypad as either an HID keyboard or a MIDI controller.
After importing the libraries, you can set either keyboard_mode
or midi_mode
to True
to choose your device type.
import board import displayio import digitalio from adafruit_st7789 import ST7789 import usb_hid from adafruit_hid.keyboard import Keyboard from adafruit_hid.keycode import Keycode import usb_midi import adafruit_midi from adafruit_midi.note_on import NoteOn from adafruit_midi.note_off import NoteOff # if you want to use this as an HID keyboard, set keyboard_mode to True # otherwise, set it to False keyboard_mode = True # if you want to use this as a MIDI keyboard, set midi_mode to True # otherwise, set it to False midi_mode = False
You can also customize what each key does in each mode. The default settings for keyboard_mode
are shortcuts for save, cut, copy and paste. You can modify these in the if
statement, if keyboard_mode:
. The variables assigned to the keycodes for each key are stored in the shortcuts
array.
# change keyboard shortcuts here # defaults are shortcuts for save, cut, copy & paste # comment out ctrl depending on windows or macOS if keyboard_mode: keyboard = Keyboard(usb_hid.devices) # modifier for windows ctrl = Keycode.CONTROL # modifier for macOS # ctrl = Keycode.COMMAND key0 = Keycode.S key1 = Keycode.X key2 = Keycode.C key3 = Keycode.V shortcuts = [key0, key1, key2, key3]
The default MIDI notes for midi_mode
are setup in the if
statement if midi_mode:
and are stored in the midi_notes
array.
# change MIDI note numbers here if midi_mode: midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0) midi_notes = [60, 61, 62, 63]
The keypad uses a TFT screen that uses SPI to communicate with the QT Py RP2040. This requires defining pins for tft_cs
, tft_dc
and reset
in the code.
# spi display setup spi = board.SPI() tft_cs = board.D7 tft_dc = board.D5 display_bus = fourwire.FourWire( spi, command=tft_dc, chip_select=tft_cs, reset=board.D6 ) # display setup display = ST7789(display_bus, width=240, height=240, rowstart=80)
The party parrot bitmap is sized so that each tile is 240x240 to fill the TFT screen. This makes for a larger than normal bitmap file since it has 10 tiles (that's 240x2400!). The code is using OnDiskBitmap()
to load the bitmap so that you don't run into any memory allocation issues.
# CircuitPython 6 & 7 compatible # bitmap setup bitmap = displayio.OnDiskBitmap(open("/parrot-240-sheet.bmp", "rb")) # Create a TileGrid to hold the bitmap parrot0_grid = displayio.TileGrid(bitmap, pixel_shader=getattr(bitmap, 'pixel_shader', displayio.ColorConverter()), tile_height=240, tile_width=240) # # CircuitPython 7+ compatible # bitmap = displayio.OnDiskBitmap("/parrot-240-sheet.bmp") # Create a TileGrid to hold the bitmap # parrot0_grid = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader, # tile_height=240, tile_width=240) # Create a Group to hold the TileGrid group = displayio.Group() # Add the TileGrid to the Group group.append(parrot0_grid) # Add the Group to the Display display.root_group = group
The keys are setup as digital inputs using a for statement that iterates through the key_pins
array. This array has the QT Py RP2040's pins that the keys are connected to.
# digital pins for the buttons key_pins = [board.A0, board.A1, board.A2, board.A3] # array for buttons keys = [] # setup buttons as inputs for key in key_pins: key_pin = digitalio.DigitalInOut(key) key_pin.direction = digitalio.Direction.INPUT key_pin.pull = digitalio.Pull.UP keys.append(key_pin)
p
and a
are variables that are used in the loop to track the index position of the tile grid. This advances the party parrot animation on the screen.
The key_states
array is used to track the states of the four keys for debouncing. When a key is not pressed, its state is False
and when it is pressed, its state is True
.
p = 0 # variable for tilegrid index a = 0 # variable for tile position # states for buttons key0_pressed = False key1_pressed = False key2_pressed = False key3_pressed = False # array for button states key_states = [key0_pressed, key1_pressed, key2_pressed, key3_pressed]
At the beginning of the loop, the tile grid's index is set to p
. This will allow for the tile grid to advance as p
's value changes.
while True: # default tile grid position parrot0_grid[a] = p
If one of the keys is pressed and its corresponding state is False
, then p
increases in value by 1
and its state is updated to True
.
# iterate through 4 buttons for i in range(4): inputs = keys[i] # if button is pressed... if not inputs.value and key_states[i] is False: # tile grid advances by 1 frame p += 1 # update button state key_states[i] = True
If the keypad is setup to be in midi_mode
, then a NoteOn
message is sent for the corresponding MIDI note number from the midi_notes
array.
# if a midi keyboard... if midi_mode: # send NoteOn for corresponding MIDI note midi.send(NoteOn(midi_notes[i], 120))
If the keypad is setup to be in keyboard_mode
, then keyboard.send()
is used to send the keycode(s) that correspond to the pressed key.
# if an HID keyboard... if keyboard_mode: # send keyboard output for corresponding keycode # the default includes a modifier along with the keycode keyboard.send(ctrl, shortcuts[i])
Since the party parrot's tile grid has 10 tiles, it needs to have its index reset to 0
when it goes above 9
.
# if the tile grid's index is at 9... if p > 9: # reset the index to 0 p = 0
When you release a key and the key's state is True
, the state is updated to False
to reset its state.
Additionally, if you're in midi_mode
, a NoteOff
message is sent for the corresponding MIDI note number. An equivalent action is not needed for the keyboard_mode
since keyboard.send()
includes a release message.
# if the button is released... if inputs.value and key_states[i] is True: # update button state key_states[i] = False # if a midi keyboard... if midi_mode: # send NoteOff for corresponding MIDI note midi.send(NoteOff(midi_notes[i], 120))
Page last edited April 18, 2025
Text editor powered by tinymce.