Libraries

Once your ItsyBitsy is set up with CircuitPython 5.3.0 or greater, you'll also need to add some libraries. Follow this page for information on how to download and add libraries to your ItsyBitsy.

From the library bundle you downloaded in that guide page, transfer the following libraries onto the ItsyBity /lib directory on the CIRCUITPY drive:

  • adafruit_bus_device
  • adafruit_dotstar
  • adafruit_hid
  • adafruit_led_animation
  • neopixel
  • simpleio

Text Editor

Adafruit recommends using the Mu editor for using your CircuitPython code with the ItsyBitsy boards. You can get more info in this guide.

Alternatively, you can use any text editor that saves files.

Code.py

Copy the code below and paste it into Mu. Then, save it to your ItsyBitsy as code.py.

# ItsyBitsy Keypad
# Uses ItsyBitsy M4/M0 plus Pimoroni Keybow
# To build a customizable USB keypad

import time
import board
from digitalio import DigitalInOut, Direction, Pull
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_hid.consumer_control_code import ConsumerControlCode
import adafruit_dotstar as dotstar

dots = dotstar.DotStar(board.SCK, board.MOSI, 12, brightness=0.4)

RED = 0xFF0000
AMBER = 0xAA9900
BLUE = 0x0066FF
MAGENTA = 0xFF00FF
PURPLE = 0x3B0F85
BLACK = 0x000000

kbd = Keyboard(usb_hid.devices)
cc = ConsumerControl(usb_hid.devices)

orientation = 1  # 0 = portrait/vertical, 1 = landscape/horizontal
if orientation == 0:
    key_dots = [0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11]
    # 0  #4  #8
    # 1  #5  #9
    # 2  #6  #10
    # 3  #7  #11
if orientation == 1:
    key_dots = [3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8]
    # 3  #2  #1  #0
    # 7  #6  #5  #4
    # 11 #10 #9  #8


def dot_on(dot, color):
    dots[dot] = color


def dot_off(dot):
    dots[dot] = BLACK


# Pin definitions
if orientation == 0:  # 0 = portrait/vertical
    pins = [
        board.D11,
        board.D12,
        board.D2,
        board.D10,
        board.D9,
        board.D7,
        board.A5,
        board.A4,
        board.A3,
        board.A2,
        board.A1,
        board.A0,
    ]
if orientation == 1:  # 1 = landscape/horizontal
    pins = [
        board.A2,
        board.A5,
        board.D10,
        board.D11,
        board.A1,
        board.A4,
        board.D9,
        board.D12,
        board.A0,
        board.A3,
        board.D7,
        board.D2,
    ]
# the two command types -- MEDIA for ConsumerControlCodes, KEY for Keycodes
# this allows button press to send the correct HID command for the type specified
MEDIA = 1
KEY = 2
keymap = {
    (0): (AMBER, MEDIA, ConsumerControlCode.PLAY_PAUSE),
    (1): (AMBER, MEDIA, ConsumerControlCode.MUTE),
    (2): (AMBER, MEDIA, ConsumerControlCode.VOLUME_DECREMENT),
    (3): (AMBER, MEDIA, ConsumerControlCode.VOLUME_INCREMENT),
    (4): (BLUE, KEY, (Keycode.GUI, Keycode.C)),
    (5): (BLUE, KEY, (Keycode.GUI, Keycode.V)),
    (6): (MAGENTA, KEY, [Keycode.UP_ARROW]),
    (7): (PURPLE, KEY, [Keycode.BACKSPACE]),
    (8): (BLUE, KEY, [Keycode.SPACE]),
    (9): (MAGENTA, KEY, [Keycode.LEFT_ARROW]),
    (10): (MAGENTA, KEY, [Keycode.DOWN_ARROW]),
    (11): (MAGENTA, KEY, [Keycode.RIGHT_ARROW]),
}

switches = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
for i in range(12):
    switches[i] = DigitalInOut(pins[i])
    switches[i].direction = Direction.INPUT
    switches[i].pull = Pull.UP

switch_state = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

print("ItsyBitsy Keybow")

# Starup lights
for k in range(12):
    dot_on(key_dots[k], RED)
    time.sleep(0.05)
    dot_on(key_dots[k], keymap[k][0])  # use individual key colors from set
    time.sleep(0.05)

while True:
    for button in range(12):
        if switch_state[button] == 0:
            if not switches[button].value:
                try:
                    if keymap[button][1] == KEY:
                        kbd.press(*keymap[button][2])
                    else:
                        cc.send(keymap[button][2])
                    dot_on(key_dots[button], RED)
                except ValueError:  # deals w six key limit
                    pass
                print("pressed key{}".format(button))
                switch_state[button] = 1

        if switch_state[button] == 1:
            if switches[button].value:
                try:
                    if keymap[button][1] == KEY:
                        kbd.release(*keymap[button][2])
                    dot_on(key_dots[button], keymap[button][0])
                except ValueError:
                    pass
                print("released key{}".format(button))
                switch_state[button] = 0

    time.sleep(0.01)  # debounce

Testing

With the code.py loaded onto your ItsyBitsy M4/M0, you'll see the Keybow DotStar LEDs light up with a startup animation, and then they'll settle into their color coded default stated.

Try pressing the four amber top row keys -- they control media play/pause, mute, and volume up/down.

To test the dark blue copy/paste keys, first select some text and hit the left copy key. Then, place your cursor in a text field and press the right paste key.

The magenta keys are up/down/left/right arrows for navigation.

Be careful with the purple key, it's the delete key!

And the cyan key is the spacebar.

Customizing the Code

There are nearly limitless customization you can do to make your ItsyBitsy Keybow work the way you want! Here are a few to try:

  • color
  • key assignments
  • orientation
  • animation

Keybow Orientation

We can use the Keybow in a vertical or horizontal orientation by re-assigning the relationship in code between the physical pins and their logical representations. This means that we can rotate the board by only changing a single variable and the key colors and assignments will update automatically.

So, for example, if we use this assignment in vertical orientation:

a b c

d e f

g h 1

2 3 4

It will become this when set to horizontal orientation:

a b c d

e f g h

1 2 3 4

This is accomplished by setting the orientation variable to 0 or 1, which then chooses which pins list to use. These set the logical order from 0-11 to use the physical pins on the ItsyBitsy that are wired via the ProtoBonnet to the Keybow.

Download: file
orientation = 0  # 0 = portrait/vertical, 1 = landscape/horizontal

# Pin definitions
if orientation == 0:  # 0 = portrait/vertical
    pins = [
        board.D11,
        board.D12,
        board.D2,
        board.D10,
        board.D9,
        board.D7,
        board.A5,
        board.A4,
        board.A3,
        board.A2,
        board.A1,
        board.A0,
    ]
if orientation == 1:  # 1 = landscape/horizontal
    pins = [
        board.A2,
        board.A5,
        board.D10,
        board.D11,
        board.A1,
        board.A4,
        board.D9,
        board.D12,
        board.A0,
        board.A3,
        board.D7,
        board.D2,
    ]

Here's an example of the vertical orientation and some alternate keycaps.

The orientation variable also is used to remap the DotStar physical order to the appropriate logical order.

The physical order of the DotStar LEDs can be seen in the diagram to the left.

So, to use these with our keymap ordering, the code below is used, with the if orientation == 1: list:

Download: file
if orientation == 0:
    key_dots = [0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11]
    # 0  #4  #8
    # 1  #5  #9
    # 2  #6  #10
    # 3  #7  #11
if orientation == 1:
    key_dots = [3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8]
    # 3  #2  #1  #0
    # 7  #6  #5  #4
    # 11 #10 #9  #8
USB keyboards and mice show up on your computer as 'HID' devices, which stands for 'Human Interface Device'

HID Keyboard Basics

This guide page has a great intro to CircuitPython HID Keyboard.

For even more details, check out the documentation at https://circuitpython.readthedocs.io/projects/hid/en/latest/ which includes all of the keycodes and media codes you can use.

By importing the adafruit_hid library into our program we can make calls to send keyboard keys and media keys.

Keyboard Press/Release

Using the HID library in CircuitPtyhon, we can send this command to "type" the letter 'a':

kbd.press(Keycode.A)

kbd.release(Keycode.A)

This would send a lowercase 'a' to the computer just as if you had typed it yourself. To send a capital 'A', we'd add the shift key to the command like this:

kbd.press(Keycode.SHIFT, Keycode.A)

kbd.release(Keycode.SHIFT, Keycode.A)

This is pretty cool, since it means we can layer on lots of keys all at the same time, just like you do on your physical keyboard when using keyboard shortcuts!

So, if there's some keyboard shortcut you want to use (or create for yourself in something like Quicksilver or AutoKeys) that is command+option+ctrl+a the CircuitPython code would look like this:

kbd.press(Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.A)

kbd.release(Keycode.GUI, Keycode.ALT, Keycode.CONTROL, Keycode.A)

The adafruit_hid library allows for operating system specific names such as 'Keycode.COMMAND' on macOS which is 'Keycode.WINDOWS' on Windows. Or, you can use the generic 'Keycode.GUI' on any operating system. Same goes for 'ALT/OPTION'

Media Control

There is a second command we'll use when we want to adjust volume, play/pause, skip tracks, and so on with media such as songs and videos. These are often represented on a physical keyboard as icons silkscreened onto the rightmost function keys.

In USB HID speak, these are known as "Consumer Control codes". To play or pause a track we'll use this command:

cc.send(ConsumerControlCode.PLAY_PAUSE)

Key Assignements

With that in mind, you can now fully customize the function of each key by editing this section of the code:

Download: file
keymap = {
    (0): (AMBER, MEDIA, ConsumerControlCode.PLAY_PAUSE),
    (1): (AMBER, MEDIA, ConsumerControlCode.MUTE),
    (2): (AMBER, MEDIA, ConsumerControlCode.VOLUME_DECREMENT),
    (3): (AMBER, MEDIA, ConsumerControlCode.VOLUME_INCREMENT),
    (4): (BLUE, KEY, (Keycode.GUI, Keycode.C)),
    (5): (BLUE, KEY, (Keycode.GUI, Keycode.V)),
    (6): (MAGENTA, KEY, [Keycode.UP_ARROW]),
    (7): (PURPLE, KEY, [Keycode.BACKSPACE]),
    (8): (BLUE, KEY, [Keycode.SPACE]),
    (9): (MAGENTA, KEY, [Keycode.LEFT_ARROW]),
    (10): (MAGENTA, KEY, [Keycode.DOWN_ARROW]),
    (11): (MAGENTA, KEY, [Keycode.RIGHT_ARROW]),
}

Note, in order to use a single stroke keycode, you'll surround it in [brackets], while a multi-stroke keycode will have its own (parentheses) as shown here:

Download: file
(4): (BLUE, KEY, [Keycode.C]),
(5): (BLUE, KEY, (Keycode.SHIFT, Keycode.C)),

Since we can use the orientation variable as shown above, the keymap values pertain to the keys starting at the origin key in the upper left corner of the board and moving left to right, top to bottom toward the last key in the lower right corner in either case.

Colors

On easy way to customize colors is to simply change the keymap color assignments, or, you can create your own colors to augment this list:

Download: file
RED = 0xFF0000
AMBER = 0xAA9900
BLUE = 0x0066FF
MAGENTA = 0xFF00FF
PURPLE = 0x3B0F85
BLACK = 0x000000

Animation

In this code we're doing a simple animation at startup by calling the dot_on() function with different color settings.

However, if you want to get HIGHLY FANCY, -- and who doesn't?! -- you can use the adafruit_led_animation library to create beautiful effects. Here's a great guide for getting started with CircuitPython LED Animation

Here's an example of some adafruit_led_animation library sequences running on the ItstyBitsy Keybow's DotStars. (Note, this is an animation example only and doesn't have key functions.)

Download: file
import board
from adafruit_led_animation.sequence import AnimationSequence
from adafruit_led_animation.animation.comet import Comet
from adafruit_led_animation.animation.chase import Chase
from adafruit_led_animation.animation.blink import Blink
from adafruit_led_animation.helper import PixelMap
from adafruit_led_animation.color import RED, BLUE, GREEN
import adafruit_dotstar as dotstar

dots = dotstar.DotStar(board.SCK, board.MOSI, 12, brightness=0.85, auto_write=False)

pixel_grid = PixelMap(dots, [
    [3],  [2],  [1],  [0],
    [7],  [6],  [5],  [4],
    [11], [10], [9],  [8],
], individual_pixels=True)

blink = Blink(pixel_grid, speed=0.5, color=BLUE)
comet = Comet(pixel_grid, speed=0.03, color=RED, tail_length=12)
chase = Chase(pixel_grid, speed=0.1, color=GREEN, size=1, spacing=3)
animations = AnimationSequence(chase, comet, blink, advance_interval=2, auto_clear=True)

while True:
    animations.animate()
This guide was first published on Jun 10, 2020. It was last updated on Jun 10, 2020.
This page (Code and Use the ItsyBitsy Keyboard) was last updated on Oct 15, 2020.