Software

Install Circuit Python for ItsyBitsy M0

Download the latest version of CircuitPython for the Adafruit ItsyBitsy M0. Click the green button to launch the page and click on the purple download button for the latest stable release.

 

Quick Start

  • Connect the ItsyBitsy board to a computer (PC, mac, Linux) via a known good USB and double press the reset button.
  • Download circuitpython UF2 and upload to the ITSYBOOT flash drive.
  • Open the CIRCUITPY drive and upload the required libraries (listed below) and the code for this project in a file named code.py in the root directory of CIRCUITPY.

Adafruit Circuit Python Libraries

Download the CircuitPython library bundle and unzip the folder. Create a new folder in the CIRCUITPY drive and name it "lib". The following libraries are required to run the code properly. Double check to ensure all of the files and folders are inside the lib folder on the CIRCUITPY drive.

  • adafruit_hid (directory)
  • neopixel.mpy

Upload code.py

You can click Download and download the file. Insure it saves to a file named code.py (if you select Download Zip you'll need to unzip the file to see code.py. Upload the code.py file to the CIRCUITPY drive.

"""
A CircuitPython 'multimedia' dial demo
Uses a ItsyBitsy M0 + Rotary Encoder -> HID keyboard out with neopixel ring
"""

import time
import board
from digitalio import DigitalInOut, Direction, Pull
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 neopixel

DOT_COLOR = 0xFF0000              # set to your favorite webhex color
PRESSED_DOT_COLOR = 0x008080      # set to your second-favorite color
LIT_TIMEOUT = 15                  # after n seconds, turn off ring

# NeoPixel LED ring on pin D1
# Ring code will auto-adjust if not 16 so change to any value!
ring = neopixel.NeoPixel(board.D5, 16, brightness=0.2)
dot_location = 0  # what dot is currently lit

# Encoder button is a digital input with pullup on D9
button = DigitalInOut(board.D9)
button.direction = Direction.INPUT
button.pull = Pull.UP

# Rotary encoder inputs with pullup on D10 & D11
rot_a = DigitalInOut(board.D10)
rot_a.direction = Direction.INPUT
rot_a.pull = Pull.UP
rot_b = DigitalInOut(board.D11)
rot_b.direction = Direction.INPUT
rot_b.pull = Pull.UP

# Used to do HID output, see below
kbd = Keyboard()

# time keeper, so we know when to turn off the LED
timestamp = time.monotonic()

######################### MAIN LOOP ##############################

# the counter counts up and down, it can roll over! 16-bit value
encoder_counter = 0
# direction tells you the last tick which way it went
encoder_direction = 0

# constants to help us track what edge is what
A_POSITION = 0
B_POSITION = 1
UNKNOWN_POSITION = -1  # initial state so we know if something went wrong

rising_edge = falling_edge = UNKNOWN_POSITION

# get initial/prev state and store at beginning
last_button = button.value
rotary_prev_state = [rot_a.value, rot_b.value]

while True:
    # reset encoder and wait for the next turn
    encoder_direction = 0

    # take a 'snapshot' of the rotary encoder state at this time
    rotary_curr_state = [rot_a.value, rot_b.value]

    if rotary_curr_state != rotary_prev_state:
        #print("Changed")
        if rotary_prev_state == [True, True]:
            # we caught the first falling edge!
            if not rotary_curr_state[A_POSITION]:
                #print("Falling A")
                falling_edge = A_POSITION
            elif not rotary_curr_state[B_POSITION]:
                #print("Falling B")
                falling_edge = B_POSITION
            else:
                # uhh something went deeply wrong, lets start over
                continue

        if rotary_curr_state == [True, True]:
            # Ok we hit the final rising edge
            if not rotary_prev_state[B_POSITION]:
                rising_edge = B_POSITION
                # print("Rising B")
            elif not rotary_prev_state[A_POSITION]:
                rising_edge = A_POSITION
                # print("Rising A")
            else:
                # uhh something went deeply wrong, lets start over
                continue

            # check first and last edge
            if (rising_edge == A_POSITION) and (falling_edge == B_POSITION):
                encoder_counter -= 1
                encoder_direction = -1
                print("%d dec" % encoder_counter)
            elif (rising_edge == B_POSITION) and (falling_edge == A_POSITION):
                encoder_counter += 1
                encoder_direction = 1
                print("%d inc" % encoder_counter)
            else:
                # (shrug) something didn't work out, oh well!
                encoder_direction = 0

            # reset our edge tracking
            rising_edge = falling_edge = UNKNOWN_POSITION

    rotary_prev_state = rotary_curr_state

    # Check if rotary encoder went up
    if encoder_direction == 1:
        ConsumerControl().send(ConsumerControlCode.VOLUME_DECREMENT) #Turn Down Volume
    #    kbd.press(Keycode.LEFT_ARROW)
    #    kbd.release_all()
    # Check if rotary encoder went down
    if encoder_direction == -1:
        ConsumerControl().send(ConsumerControlCode.VOLUME_INCREMENT) #Turn Up Volume
    #    kbd.press(Keycode.RIGHT_ARROW)
    #    kbd.release_all()
    # Button was 'just pressed'
    if (not button.value) and last_button:
        print("Button pressed!")
        kbd.press(Keycode.SPACE) #Keycode for spacebar
        kbd.release_all()
        ring[dot_location] = PRESSED_DOT_COLOR # show it was pressed on ring
        timestamp = time.monotonic()        # something happened!
    elif button.value and (not last_button):
        print("Button Released!")
        # kbd.press(Keycode.SHIFT, Keycode.SIX)
        # kbd.release_all()
        ring[dot_location] = DOT_COLOR      # show it was released on ring
        timestamp = time.monotonic()        # something happened!
    last_button = button.value

    if encoder_direction != 0:
        timestamp = time.monotonic()        # something happened!
        # spin neopixel LED around!
        previous_location = dot_location
        dot_location += encoder_direction   # move dot in the direction
        dot_location += len(ring)           # in case we moved negative, wrap around
        dot_location %= len(ring)
        if button.value:
            ring[dot_location] = DOT_COLOR  # turn on new dot
        else:
            ring[dot_location] = PRESSED_DOT_COLOR # turn on new dot
        ring[previous_location] = 0         # turn off previous dot

    if time.monotonic() > timestamp + LIT_TIMEOUT:
        ring[dot_location] = 0   # turn off ring light temporarily

Modify Key Codes

You can customize the key codes to form custom commands, which can be multiple keys, or have it execute just single keyboard characters.

The rotary encoder can execute up to 3 different commands based on features. Pressing the knob and turning the knob left or right. These are commented in the code and can be changed by adjusting the key code value.

List of USB HID Keycodes

The long list of available keyboard characters are listed in the webpage linked below. Most of the characters are for US keyboard only. Function keys and modifiers can be used but some special characters are not currently supported.

Starting with the first command, turning the knob to the right will execute the ctrl+up arrow keys. These are two different keyboard characters that are separated with commas. This will essentially press the two keys simultaneously. The values inside the parentheses kbd.press(keycode.THISKEY) are the ones you want to change. For example, the block of code below is executed when turning the knob to the right.

# Check if rotary encoder went up
if encoder_direction == 1:
kbd.press(Keycode.CONTROL, Keycode.UP_ARROW)
kbd.release_all()

For more information and troubleshooting, please check out the CircuitPython library guide, linked below. 

This guide was first published on Jun 11, 2019. It was last updated on Jun 11, 2019. This page (Software) was last updated on Nov 08, 2019.