Flashing the ClawTouch HID Firmware

June 1, 2026 · View on GitHub

English | 简体中文

Flashing the ClawTouch HID Firmware

Last verified: 2026-03-14 — flash procedure on CircuitPython 10.1.4

  • a Raspberry Pi Pico 2. The same procedure flashes every firmware in this repo through v1.1.2: the v1.1.0 drag opcodes, the v1.1.1 keyboard byte-order unification, and the v1.1.2 panic-stop fix are all code.py-only changes — no boot.py or bootloader change — so the flash path is identical.

What you need

  • A Raspberry Pi Pico 2 (RP2350 board)
  • A USB-C data cable (not a charge-only cable)
  • A PC running Windows / macOS / Linux

Step 1: Install CircuitPython

  1. Download the latest CircuitPython 10.x UF2 for the Pico 2 from the official site: https://circuitpython.org/board/raspberry_pi_pico2/ (last verified version: 10.1.4)
  2. Hold down the BOOTSEL button on the Pico 2 (the only push-button on the board; on a ClawTouch cased unit it's the button on the left when the USB-C port faces up), then plug in the USB cable while still holding it.
  3. A USB drive named RPI-RP2 will appear (it contains INFO_UF2.TXT).
  4. Drag the downloaded .uf2 file onto that drive.
  5. The Pico 2 reboots automatically; the drive renames itself to CIRCUITPY.

Step 2: Install the adafruit_hid library

The firmware imports the Adafruit CircuitPython HID library for the keyboard / mouse classes. You have two options:

Option A — use the bundled copy (recommended):

This repository ships a known-good copy under firmware/lib/adafruit_hid/. Copy it to the Pico's lib/ folder.

The bundled .mpy files are CircuitPython mpy format v6 (ABI 6), which CircuitPython 10.x loads as-is — the .mpy format did not change across the 10.x series. If you ever see an incompatible .mpy file error at boot, use Option B to fetch the matching 10.x bundle.

Option B — fetch a fresh copy from Adafruit:

Download the 10.x bundle from https://circuitpython.org/libraries, then copy the adafruit_hid folder from lib/ inside the bundle to CIRCUITPY/lib/adafruit_hid/.

Step 3: Copy the firmware

The deployable files live in this repository's firmware/ folder. On Windows PowerShell (substitute your actual CIRCUITPY drive letter):

$pico = "G:"

Copy-Item "firmware\boot.py"          "$pico\boot.py"          -Force
Copy-Item "firmware\code.py"          "$pico\code.py"          -Force
Copy-Item "firmware\lib\adafruit_hid" "$pico\lib\adafruit_hid" -Recurse -Force

On macOS / Linux (assuming the drive mounts at /Volumes/CIRCUITPY):

PICO=/Volumes/CIRCUITPY        # or /media/$USER/CIRCUITPY on Linux

cp firmware/boot.py          "$PICO/boot.py"
cp firmware/code.py          "$PICO/code.py"
cp -R firmware/lib/adafruit_hid "$PICO/lib/adafruit_hid"

boot.py only runs once at power-on. After copying boot.py you must unplug and re-plug the Pico (or pop CIRCUITPY's eject menu and reconnect). Changes to code.py alone auto-reload — no replug needed.

Expected layout on CIRCUITPY

CIRCUITPY/
├── boot.py                          ← USB descriptor setup
├── code.py                          ← Firmware main loop
├── boot_out.txt                     ← Auto-generated by CircuitPython
├── settings.toml                    ← CircuitPython config
└── lib/
    └── adafruit_hid/                ← HID driver library (.mpy files)
        ├── __init__.mpy
        ├── keyboard.mpy
        ├── keycode.mpy
        ├── keyboard_layout_base.mpy
        ├── keyboard_layout_us.mpy
        ├── mouse.mpy
        ├── consumer_control.mpy
        └── consumer_control_code.mpy

Step 4: Smoke test

After unplugging and replugging:

  1. LED blink: the onboard LED should blink once per second. Solid-on or off both mean the firmware did not start.
  2. COM ports: two new serial ports appear (one console + one data). On Windows check Device Manager; on macOS / Linux they appear as /dev/tty.usbmodem* or /dev/ttyACM*.
  3. PING: run examples/ping_test.py pointed at the data port. You should see PONG received.
  4. CIRCUITPY drive: with this (development) firmware the CIRCUITPY USB mass-storage drive stays mounted on the host — intentional, so the firmware stays editable in place. On a locked-down / enterprise host where a removable drive is unwanted (USB-storage policies, scans, audit entries), flash the production firmware instead — see Production (locked-down) firmware below.

Production (locked-down) firmware

The default boot.py is the development variant: it keeps the CircuitPython REPL console and the CIRCUITPY USB mass-storage drive exposed to the host so the board stays debuggable and the firmware editable in place. On a deployed device you usually want the controlled host to see only the HID keyboard/mouse and the CDC data channel — no removable drive, no REPL.

To harden it, copy firmware/boot_production.py onto the CIRCUITPY drive renamed to boot.py (overwriting the dev boot.py), then eject and replug:

# from the CIRCUITPY drive root
cp boot_production.py boot.py   # or copy + rename in your file manager

CircuitPython only runs the file literally named boot.py, so boot_production.py is inert until you rename it. After this the device exposes only HID + CDC data — the CIRCUITPY drive and the REPL console disappear.

Caveat: with the drive hidden you can no longer edit files over USB. To make further changes, re-enter BOOTSEL and re-flash CircuitPython (Step 1), which restores the editable CIRCUITPY drive, then re-copy the firmware.

Upgrading the firmware

Once CircuitPython is on the board, upgrading the firmware does not require BOOTSEL — just overwrite code.py:

cp firmware/code.py "$PICO/code.py"

CircuitPython auto-reloads on file change. To upgrade CircuitPython itself, hold BOOTSEL again and drop a new .uf2.

Troubleshooting

SymptomFix
No CIRCUITPY drive after first bootHold BOOTSEL, replug, re-flash the UF2
boot.py error keeps board from startingPress the reset button twice quickly to enter safe mode, then fix boot.py
USB HID device not recognised by OSCheck boot.py enabled the HID descriptors; replug
No data on the serial portConfirm usb_cdc.enable(data=True) is set; verify you are using the data port, not the console
LED never blinkscode.py has a syntax error or crashed — connect to the console port with Thonny / screen / minicom to see the traceback
Only one COM port appearsboot.py did not run — replug the board