ユーザ用ツール

文書の過去の版を表示しています。


PocketTerm35のキーマップを変更する

概要

WaveShareが提供するPocketTerm35のキーボードコントローラはRP2040である

ファームウェアをカスタマイズすることで任意のキーマップを実装できる

そこで、サポートに、ファームウェアのソースを公開してほしいと連絡すると、Circuit Pythonだから、code.pyを変更すればキーマップをカスタマイズできるよ、といわれた

なるほど、それならばそれでやってみよう

なお、Raspberry Pi Picoのファームウェアを変更できるだけの知識がない場合は、手を出さない方がいいと思う

また、ここに書かれていることに基づいてキーマップの変更などを試みて、PocketTerm35が回復不能なダメージを受けたとしても、当方は一切関知しない

この種の改変は、間違えば回復不能なダメージをPocketTerm35に与える可能性があることを念頭に、自己責任で行っていただきたい

CIRCUITPYをマウントする

Circuit Pythonのデバイスは、CIRCUITPYというUSBマスストレージとターミナルにつながるUSBシリアルデバイスを提供する

なので、ホスト端末からは CIRCUITPYが見えるはずだが、PocketTerm35には存在していない

サポート曰く「(RP2040の)リセットボタンを押せばみえるよ」とのことだが、リセットを押したとて出てこない

のちにわかるのだが、boot.pyのなかでCIRCUITPYを無効化しているので、基本的に出てこないのだ

以下が2026年6月到着のデバイスに入っていたデフォルトのboot.pyの内容である

boot.py
import storage
sotrage.disable_usb_drive()

ではどうするのか

いい感じにRESETボタンを連打する

これしかないので、いい感じにRESETボタンを連打する

いいタイミングでRESETが入ると、boot.pyがキャンセルされるようで、CIRCUITPYがマウントされる

このとき、RP PI2がみえることがあるが、これはRP2040自体のファームウェアを変更するためのもので、このデバイスは操作しないこと

おそらくは、boot.pyではなく Circuit Python自体の起動がキャンセルされてしまうとこれが出てくるのだろうと推測しているが、確かなことはわからない

いえることはとにかくそれには触るな、である

なお、CIRCUITPYは、PocketTerm35本体からログインしていないと出現しない

リモートからsshでログインしているだけでは永遠に出てこないので、この部分だけは必ずローカルからログインした状態で操作すること

なお、このいい感じにしかCIRCUITPYが出てこないのはあまりに不便なので、この問題についても、改造の過程で解消していきたいと思う

準備

Circuit Pythonの改造は比較的難易度は低いが、不完全なコードをデプロイすると PocketTerm35のキーボードが一切効かないなどということになりかねないリスクがある

なので、必ず以下の準備をする

  • CIRCUITPYのバックアップ
  • USBキーボード

できれば以下のものもあるとよい

  • PocketTerm35にsshでアクセスできる環境
  • ファームウェアの回復用のuf2ファイルセット1)
  • 実験用のRaspbery Pi Pico (RP2040)

テスト中は頻繁にキーボードが使えなくなるのでsshでリモートからいじる方が楽である

boot.pyをいじる場合は、致命的な間違いを犯していないか確認する意味でも実験用のRaspberry Pi Picoがあると安心である

実験用のRaspberry Pi Picoがあると、キーを実装していなくても、シリアルコンソールで最低限の動作確認は行えるし、万一致命傷を与えても、Adafruitが配布している誰でも入手できる uf2ファイルで簡単に復旧できるので、Waveshareから回復用のファイルをもらえなかった場合にも安心である

実験機の上で最低限、致命的な問題が起きないことを確認してから、PocketTerm35に持ち込めば、いきなり文鎮化するような悲劇は回避できるだろう

キーマップの設計

不便であると感じているのは以下の点

  • 左シフトキーがない
  • ゲームパッド風のキーがあるのにただ英字が割り当てられているだけでゲームパッドとして機能しない
  • マウスがないと困る場面でマウスとして使える機能が欲しい
  • CIRCUITPYの存在が不安定

の なので、以下のようにキーマップを変更することにする

  • TABキーは左シフトにして Fn+TABでTABキーとする
  • 右シフトキーには右シフトを割り当てる(デフォルトでは左シフトになっている)
  • Fn+上, Fn+下, Fn+左, Fn+右, Fn+L, Fn+Rはマウスカーソルの移動および左クリック、右クリックとする
  • Select, Start は常時ゲームパッドの Select/Start にする
  • 刻印通り Fn+SelectをPrint Screen、Fn+Start を Pauseとする
  • Fn+BでカーソルキーとABXYLRのアクションキーをゲームパッドとしてふるまうようにする
  • Fn+AでカーソルキーとABXYLRのアクションキーをデフォルトのカーソルキーとABXYLRの英字に戻す
  • 左Fnキーを押したままRP2040をRESETすると CIRCUITPYが有効化される(デフォルトでは無効)

依存するライブラリ

デフォルトのファームウェアとして入っている Circuit Pythonは Adafruit hidを利用しているが、なぜかhidという名前になっている

互換性を鑑みて adafruit_hid と入れ替えたい

Circuit Python LibirariesからVersion 10.x用のライブラリを取得し、中にある adafruit_hid のフォルダを丸ごと CIRCUITPY/libの下にコピーする

hidは消してしまっても構わない

hid_gamepad.pyはAdafruit CircuitPython_HIDのexamplesの中にある

これを同じく CIRCUITPY/libにコピーしておく

コード

Circuit Pythonの制御は boot.pyと code.pyの二つで行う

boot.pyはデバイスの初期化に関する処理を、code.pyは動作中の処理を記述するファイルである

ここでは、

  • CIRCUITPYを見せる・隠すの制御
  • Gamepad をHIDエンドポイントとして追加

を boot.pyで、キーの監視とキーコードの送信やインジケータ、スピーカ、LCDのバックライトなどの制御を code.py行う

boot.pyをいじるのはおっかないので、code.pyで全部やればいいだろうとは思うのだが、上の二点はどうしても boot.pyでやらないとダメなので、覚悟を決めてコードの変更を行う

code.pyの変更は比較的安全だが、boot.pyの方は中で例外などが発生して、それを適切に処理しないとファームウェアが再起不能になる危険性があるので、慎重に行う

万一、再起不能になった場合は、復旧用の uf2ファイルで修復しないと回復しない

なお、code.pyの変更は自動的に検知されてリロードされるし、シリアルコンソールで Ctrl+Dをたたくことで強制的にリロードできるが、boot.pyを変更した場合は RP2040を本体背面のリセットボタンでリセットする必要がある

boot.py

左 Fnキーが押されていたら storage.enable_usb_drive()でCIRCUITPYを有効化する処理と、GamepadをUSB HIDのエンドポイントとして追加する処理を記述する

コメントにあるように、Gamepadのエントリはサンプルから持ってきた

ただし、ボタン数は15にしてある

サンプルは16だったが、evtestで確認したところ、16個目のボタンにコードが割り当てられていなかったので削減した

キー処理の部分は try~except2)で例外処理を行っている

上に書いたように万一未処理の例外が発生するとファームウェアが修復不能になるためである

なお、boot.pyの中にある print()などの出力は boot_out.txtというファイルに出力される

このファイルは、boot.pyから何も出力されなければ昔のファイルがいつまでも残っているので、boot.pyをテストする前には boot_out.txtを消してからやった方が問題の有無がわかりやすい

なお、boot.pyの中で未処理の例外が発生するなどすると、CIRCUITPYドライブ自体にアクセスできなくなるので、boot_out.txtに何が書かれたかを知る術はない

boot.py
import usb_hid
import storage
import board
import digitalio
import time
 
# left fn key is board.GP0 x board.GP15
# key check -- if left fn key is pressed, do not disable_usb_drive()
# initialize pins
try:
    storage.disable_usb_drive()
    col_pin = digitalio.DigitalInOut(board.GP0)
    row_pin = digitalio.DigitalInOut(board.GP15)
    row_pin.direction = digitalio.Direction.INPUT
    row_pin.pull = digitalio.Pull.UP
    col_pin.direction = digitalio.Direction.OUTPUT
    col_pin.value = False
    time.sleep(0.05)
    # scan left fn key
    col_pin.direction = digitalio.Direction.OUTPUT
    col_pin.value = True
    row_pin.direction = digitalio.Direction.INPUT
    row_pin.pull = digitalio.Pull.DOWN
    # check if left fn is not pressed
    if row_pin.value:
        time.sleep(0.05)
        if row_pin.value:
            storage.enable_usb_drive()
except Exception as e:
    print(f"An error occurred: {e}")
 
# This is only one example of a gamepad report descriptor,
# and may not suit your needs.
GAMEPAD_REPORT_DESCRIPTOR = bytes((
    0x05, 0x01,  # Usage Page (Generic Desktop Ctrls)
    0x09, 0x05,  # Usage (Game Pad)
    0xA1, 0x01,  # Collection (Application)
    0x85, 0x04,  #   Report ID (4)
    0x05, 0x09,  #   Usage Page (Button)
    0x19, 0x01,  #   Usage Minimum (Button 1)
    0x29, 0x0F,  #   Usage Maximum (Button 15)
    0x15, 0x00,  #   Logical Minimum (0)
    0x25, 0x01,  #   Logical Maximum (1)
    0x75, 0x01,  #   Report Size (1)
    0x95, 0x10,  #   Report Count (16)
    0x81, 0x02,  #   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    0x05, 0x01,  #   Usage Page (Generic Desktop Ctrls)
    0x15, 0x81,  #   Logical Minimum (-127)
    0x25, 0x7F,  #   Logical Maximum (127)
    0x09, 0x30,  #   Usage (X)
    0x09, 0x31,  #   Usage (Y)
    0x09, 0x32,  #   Usage (Z)
    0x09, 0x35,  #   Usage (Rz)
    0x75, 0x08,  #   Report Size (8)
    0x95, 0x04,  #   Report Count (4)
    0x81, 0x02,  #   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    0xC0,        # End Collection
))
 
gamepad = usb_hid.Device(
    report_descriptor=GAMEPAD_REPORT_DESCRIPTOR,
    usage_page=0x01,           # Generic Desktop Control
    usage=0x05,                # Gamepad
    report_ids=(4,),           # Descriptor uses report ID 4.
    in_report_lengths=(6,),    # This gamepad sends 6 bytes in its report.
    out_report_lengths=(0,),   # It does not receive any reports.
)
 
usb_hid.enable(
    (usb_hid.Device.KEYBOARD,
     usb_hid.Device.MOUSE,
     usb_hid.Device.CONSUMER_CONTROL,
     gamepad)
)

code.py

もちろん、果敢に、すべてをスクラッチから作成することも可能ではあるが、基本的にはオリジナルのcode.pyに手を入れる形で行う方がいいだろう

ここでベースにしているのは製品に同梱されていたものではなく「ファームウェア頂戴」って依頼したときに、回復用の uf2ファイルと一緒に「Fn+L/Fn+Rでマウスクリックできるようにしたサンプルもつけとく」っていって同梱してきてくれたファイルなのでもしかすると微妙に違うかもしれないが、大体同じはずである

アクションキーは row0 に大体まとまっているので、Fn+A/Fn+Bでの動作では KEY_MAP[0]をごっそり入れ替えることで実現している

標準のカスタムキー処理は押したときにしか発動しないが、キーリピートなどをキーパッドにもさせたかったので、カスタムキー処理の関数登録を(押す,離す)のタプルで登録してあった場合に、キーリピート処理ができるように拡張した

なお、code.pyの中の print()などの出力はシリアルコンソールで見ることができる

code.py
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
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.mouse import Mouse
from hid_gamepad import Gamepad
import board
import microcontroller
import digitalio
import time
import pwmio  # PWM module
 
 
bl_pwm_value = 32767            # Middle value
ad_pwm_value = 32767            # Middle value
 
try:
    # Set up a keyboard device.
    kbd = Keyboard(usb_hid.devices)
    layout = KeyboardLayoutUS(kbd)
    consumer_control = ConsumerControl(usb_hid.devices)
    gp = Gamepad(usb_hid.devices)
    mouse = Mouse(usb_hid.devices)
 
except Exception as e:
    # Log the error message if initialization fails
    print(f"Initialization failed: {e}")
    # Reset the RP2040
    microcontroller.reset()
 
# Initialize the GP22 pin
gp22 = digitalio.DigitalInOut(board.GP22)
gp22.direction = digitalio.Direction.OUTPUT
# Set the GP22 pin high
gp22.value = False
 
# Initialize the GP19 pin
gp19 = digitalio.DigitalInOut(board.GP19)
gp19.direction = digitalio.Direction.OUTPUT
# Set the GP19 pin high
gp19.value = False
 
gp21 = digitalio.DigitalInOut(board.GP21)
gp21.direction = digitalio.Direction.OUTPUT
# Set the GP21 pin high
gp21.value = False
 
# Initialize PWM output pin GP22 for the breathing light effect
# gp22_pwm = pwmio.PWMOut(board.GP22, frequency=5000, duty_cycle=0)
 
# Initialize BL PWM
BL_PWM_RP = pwmio.PWMOut(board.GP20, frequency=5000, duty_cycle=5000)
 
# Initialize AD PWM
AD_PWM_RP = pwmio.PWMOut(board.GP18, frequency=5000, duty_cycle=32700)
 
def toggle_gp22():
    gp22.value = not gp22.value
    print(f"GP22 state toggled to: {gp22.value}")
    kbd.press(Keycode.CAPS_LOCK)
    kbd.release(Keycode.CAPS_LOCK)
 
def start_breathing_light():
    tmp_time = 0
    stop_loop = True  # Flag used to break out of the while loop
    kbd = None  # Make sure kbd is initialized
    gp22_temp = gp22.value # Record the current Caps Lock state
 
    for i in range(1,10+1):
        gp22.value = not gp22.value
        # print("GP22 TEST:",gp22.value)
        time.sleep(0.3)
 
    try:
        while stop_loop:
            tmp_time +=1
 
            if tmp_time >= 255:
                gp22.value = not gp22.value
                # print("GP22 IS:",gp22.value)
                tmp_time = 0
 
            current_keys = set(scan_keyboard())
 
            # Handle keys that are pressed
            for key in current_keys:
                if key is not None:
                    time.sleep(0.2)
                    if key is not None:
                        if kbd:
                            kbd.release_all()
 
                        gp22.value = gp22_temp
                        gp21.value = not gp21.value
                        stop_loop = False  # Set the flag
                        break
    finally:
        if kbd:
            kbd.release_all()
            gp22.value = gp22_temp
 
 
def toggle_gp19():
    gp19.value = not gp19.value
    # print(f"GP19 state toggled to: {gp19.value}")
 
def toggle_gp21():
    gp21.value = not gp21.value
    # print(f"GP21 state toggled to: {gp21.value}")
    start_breathing_light()
 
def ad_pwm_down():
    global ad_pwm_value
    if ad_pwm_value <= 0:
        ad_pwm_value = 0
    else:
        ad_pwm_value -= 6553
    ad_pwm_value = min(max(ad_pwm_value, 0), 65535)
    # Update the duty cycle
    AD_PWM_RP.duty_cycle = ad_pwm_value
    print("pwm_value == ", ad_pwm_value)
 
def ad_pwm_up():
    global ad_pwm_value
    if ad_pwm_value >= 65535:
        ad_pwm_value = 65535
    else:
        ad_pwm_value += 6553
    ad_pwm_value = min(max(ad_pwm_value, 0), 65535)
    # Update the duty cycle
    AD_PWM_RP.duty_cycle = ad_pwm_value
    print("pwm_value == ", ad_pwm_value)
 
# Because the backlight is controlled by an NPN transistor pull-down, a higher PWM duty cycle makes the backlight dimmer. So the pwm_up functions below actually decrease the duty cycle, and pwm_down increases it.
def bl_pwm_up():
    global bl_pwm_value
    if bl_pwm_value <= 0:
        bl_pwm_value = 0
    else:
        bl_pwm_value -= 6553
    bl_pwm_value = min(max(bl_pwm_value, 0), 65535)
    # Update the duty cycle
    BL_PWM_RP.duty_cycle = bl_pwm_value
    print("pwm_value == ",bl_pwm_value)
 
def bl_pwm_down():
    global bl_pwm_value
    if bl_pwm_value >= 65535:
        bl_pwm_value = 65535
    else:
        bl_pwm_value += 6553
    bl_pwm_value = min(max(bl_pwm_value, 0), 65535)
    # Update the duty cycle
    BL_PWM_RP.duty_cycle = bl_pwm_value
    print("pwm_value == ",bl_pwm_value)
 
def shift_left_bracket():
    kbd.press(Keycode.SHIFT, Keycode.LEFT_BRACKET)
    # kbd.release(Keycode.SHIFT)
    kbd.release_all()
 
def shift_right_bracket():
    kbd.press(Keycode.SHIFT, Keycode.RIGHT_BRACKET)
    # kbd.release(Keycode.SHIFT)
    kbd.release_all()
 
def shift_backslash():
    kbd.press(Keycode.SHIFT, Keycode.BACKSLASH)
    # kbd.release(Keycode.SHIFT)
    kbd.release_all()
 
def shift_grave_accent():
    kbd.press(Keycode.SHIFT, Keycode.GRAVE_ACCENT)
    # kbd.release(Keycode.SHIFT)
    kbd.release_all()
 
def lock_screen():
# Lock the screen (suspend on Linux)
    kbd.press(Keycode.WINDOWS, Keycode.L)
    kbd.release_all()
 
def scan_previous_track():
    consumer_control.press(ConsumerControlCode.SCAN_PREVIOUS_TRACK)
    consumer_control.release()
 
def play_pause():
    consumer_control.press(ConsumerControlCode.PLAY_PAUSE)
    consumer_control.release()
 
def scan_next_track():
    consumer_control.press(ConsumerControlCode.SCAN_NEXT_TRACK)
    consumer_control.release()
 
def mouse_left():
    mouse.press(Mouse.LEFT_BUTTON)
    time.sleep(0.02)
    mouse.release(Mouse.LEFT_BUTTON)
    time.sleep(0.25)
 
def mouse_right():
    mouse.press(Mouse.RIGHT_BUTTON)
    time.sleep(0.02)
    mouse.release(Mouse.RIGHT_BUTTON)
    time.sleep(0.25)
 
def mouse_press_up():
    mouse.move(y=-8)
 
def mouse_release_up():
    mouse.move(y=0)
 
def mouse_press_down():
    mouse.move(y=8)
 
def mouse_release_down():
    mouse.move(y=0)
 
def mouse_press_left():
    mouse.move(x=-8)
 
def mouse_release_left():
    mouse.move(x=0)
 
def mouse_press_right():
    mouse.move(x=8)
 
def mouse_release_right():
    mouse.move(x=0)
 
def gamepad_press_north():
    # X button
    gp.press_buttons(4)
 
def gamepad_release_north():
    gp.release_buttons(4)
 
def gamepad_press_south():
    # B button
    gp.press_buttons(1)
 
def gamepad_release_south():
    gp.release_buttons(1)
 
def gamepad_press_west():
    # Y button
    gp.press_buttons(5)
 
def gamepad_release_west():
    gp.release_buttons(5)
 
def gamepad_press_east():
    # A button
    gp.press_buttons(2)
 
def gamepad_release_east():
    gp.release_buttons(2)
 
def gamepad_press_tl():
    # L button
    gp.press_buttons(7)
 
def gamepad_release_tl():
    gp.release_buttons(7)
 
def gamepad_press_tr():
    # R button
    gp.press_buttons(8)
 
def gamepad_release_tr():
    gp.release_buttons(8)
 
def gamepad_btn_select():
    # Select button
    gp.click_buttons(11)
 
def gamepad_btn_start():
    # Start button
    gp.click_buttons(12)
 
def gamepad_press_up():
    gp.move_joysticks(x=0, y=-127)
 
def gamepad_release_up():
    gp.move_joysticks(x=0,y=0)
 
def gamepad_press_down():
    gp.move_joysticks(x=0, y=127)
 
def gamepad_release_down():
    gp.move_joysticks(x=0,y=0)
 
def gamepad_press_left():
    # Send both Gamepad / Keyboard
    gp.move_joysticks(x=-127, y=0)
 
def gamepad_release_left():
    gp.move_joysticks(x=0,y=0)
 
def gamepad_press_right():
    gp.move_joysticks(x=127, y=0)
 
def gamepad_release_right():
    gp.move_joysticks(x=0,y=0)
 
def custom_btn_a():
    KEY_MAP[0] = [Keycode.UP_ARROW, Keycode.LEFT_ARROW, Keycode.DOWN_ARROW, Keycode.RIGHT_ARROW, Keycode.L, Keycode.R, Keycode.X, Keycode.Y, Keycode.B, Keycode.A]
 
def custom_btn_b():
    KEY_MAP[0] = [CUSTOM_KEYS["GAMEPAD_UP"], CUSTOM_KEYS["GAMEPAD_LEFT"], CUSTOM_KEYS["GAMEPAD_DOWN"], CUSTOM_KEYS["GAMEPAD_RIGHT"], CUSTOM_KEYS["GAMEPAD_TL"], CUSTOM_KEYS["GAMEPAD_TR"], CUSTOM_KEYS["GAMEPAD_NORTH"], CUSTOM_KEYS["GAMEPAD_WEST"], CUSTOM_KEYS["GAMEPAD_SOUTH"],CUSTOM_KEYS["GAMEPAD_EAST"]]
 
# Define custom function keys using a dictionary
CUSTOM_KEYS = {
    # Add more custom keys as needed
    "FN_KEY"                : -100,
    "FN_MUTE"               : -101,
    "FN_VOLUME_DOWN"        : -102,
    "FN_VOLUME_UP"          : -103,
    "FN_LOCK_SCREEN"        : -104,
    "FN_BL_CONTROL_SCREEN"  : -105,
    "FN_BL_PWM_DOWN"        : -106,
    "FN_BL_PWM_UP"          : -107,
    "SHIFT_GRAVE_ACCENT"    : -108,
    "SHIFT_BACKSLASH"       : -109,
    "SHIFT_LEFT_BRACKET"    : -110,
    "SHIFT_RIGHT_BRACKET"   : -111,
    "FN_MOUSE_LEFT"         : -112,
    "FN_MOUSE_RIGHT"        : -113,
    "FN_MOUSE_MOVE_UP"      : -114,
    "FN_MOUSE_MOVE_DOWN"    : -115,
    "FN_MOUSE_MOVE_LEFT"    : -116,
    "FN_MOUSE_MOVE_RIGHT"   : -117,
    "FN_BTN_A"              : -118,
    "FN_BTN_B"              : -119,
    "FN_BTN_X"              : -120,
    "FN_BTN_Y"              : -121,
    "GAMEPAD_NORTH"         : -122,
    "GAMEPAD_SOUTH"         : -123,
    "GAMEPAD_WEST"          : -124,
    "GAMEPAD_EAST"          : -125,
    "GAMEPAD_TL"            : -126,
    "GAMEPAD_TR"            : -127,
    "GAMEPAD_SELECT"        : -128,
    "GAMEPAD_START"         : -129,
    "GAMEPAD_UP"            : -130,
    "GAMEPAD_DOWN"          : -131,
    "GAMEPAD_LEFT"          : -132,
    "GAMEPAD_RIGHT"         : -133
}
 
SPECIAL_KEY_FUNCTIONS = {
    CUSTOM_KEYS["FN_MUTE"]                  : toggle_gp19,
    CUSTOM_KEYS["FN_VOLUME_DOWN"]           : ad_pwm_down,
    CUSTOM_KEYS["FN_VOLUME_UP"]             : ad_pwm_up,
    CUSTOM_KEYS["FN_LOCK_SCREEN"]           : lock_screen,
    CUSTOM_KEYS["FN_BL_CONTROL_SCREEN"]     : toggle_gp21,
    CUSTOM_KEYS["FN_BL_PWM_DOWN"]           : bl_pwm_down,
    CUSTOM_KEYS["FN_BL_PWM_UP"]             : bl_pwm_up,
    CUSTOM_KEYS["SHIFT_GRAVE_ACCENT"]       : shift_grave_accent,
    CUSTOM_KEYS["SHIFT_BACKSLASH"]          : shift_backslash,
    CUSTOM_KEYS["SHIFT_LEFT_BRACKET"]       : shift_left_bracket,
    CUSTOM_KEYS["SHIFT_RIGHT_BRACKET"]      : shift_right_bracket,
    Keycode.CAPS_LOCK                       : toggle_gp22,
    ConsumerControlCode.SCAN_PREVIOUS_TRACK : scan_previous_track,
    ConsumerControlCode.PLAY_PAUSE          : play_pause,
    ConsumerControlCode.SCAN_NEXT_TRACK     : scan_next_track,
    CUSTOM_KEYS["FN_MOUSE_LEFT"]            : mouse_left,
    CUSTOM_KEYS["FN_MOUSE_RIGHT"]           : mouse_right,
    CUSTOM_KEYS["FN_MOUSE_MOVE_UP"]         : (mouse_press_up, mouse_release_up),
    CUSTOM_KEYS["FN_MOUSE_MOVE_DOWN"]       : (mouse_press_down, mouse_release_down),
    CUSTOM_KEYS["FN_MOUSE_MOVE_LEFT"]       : (mouse_press_left, mouse_release_left),
    CUSTOM_KEYS["FN_MOUSE_MOVE_RIGHT"]      : (mouse_press_right, mouse_release_right),
    CUSTOM_KEYS["FN_BTN_A"]                 : custom_btn_a,
    CUSTOM_KEYS["FN_BTN_B"]                 : custom_btn_b,
    CUSTOM_KEYS["GAMEPAD_NORTH"]            : (gamepad_press_north, gamepad_release_north),
    CUSTOM_KEYS["GAMEPAD_SOUTH"]            : (gamepad_press_south, gamepad_release_south),
    CUSTOM_KEYS["GAMEPAD_WEST"]             : (gamepad_press_west, gamepad_release_west),
    CUSTOM_KEYS["GAMEPAD_EAST"]             : (gamepad_press_east, gamepad_release_east),
    CUSTOM_KEYS["GAMEPAD_TL"]               : (gamepad_press_tl, gamepad_release_tl),
    CUSTOM_KEYS["GAMEPAD_TR"]               : (gamepad_press_tr, gamepad_release_tr),
    CUSTOM_KEYS["GAMEPAD_SELECT"]           : gamepad_btn_select,
    CUSTOM_KEYS["GAMEPAD_START"]            : gamepad_btn_start,
    CUSTOM_KEYS["GAMEPAD_UP"]               : (gamepad_press_up, gamepad_release_up),
    CUSTOM_KEYS["GAMEPAD_DOWN"]             : (gamepad_press_down, gamepad_release_down),
    CUSTOM_KEYS["GAMEPAD_LEFT"]             : (gamepad_press_left, gamepad_release_left),
    CUSTOM_KEYS["GAMEPAD_RIGHT"]            : (gamepad_press_right, gamepad_release_right),
}
 
# Define the number of rows and columns
NUM_ROWS = 7
NUM_COLS = 10
 
# Define the key map for the keyboard matrix
KEY_MAP = [
    #col0                            col1                                col2                                    col3                                col4                                        col5                                    col6                                    col7                                    col8                        col9
    [Keycode.UP_ARROW,               Keycode.LEFT_ARROW,                 Keycode.DOWN_ARROW,                     Keycode.RIGHT_ARROW,                Keycode.L,                                  Keycode.R,                              Keycode.X,                              Keycode.Y,                              Keycode.B,                    Keycode.A],             # row 0
    [Keycode.ONE,                    Keycode.TWO,                        Keycode.THREE,                          Keycode.FOUR,                       Keycode.FIVE,                               Keycode.SIX,                            Keycode.SEVEN,                          Keycode.EIGHT,                          Keycode.NINE,               Keycode.ZERO],            # row 1
    [Keycode.Q,                      Keycode.W,                          Keycode.E,                              Keycode.R,                          Keycode.T,                                  Keycode.Y,                              Keycode.U,                              Keycode.I,                              Keycode.O,                  Keycode.P],               # row 2
    [Keycode.A,                      Keycode.S,                          Keycode.D,                              Keycode.F,                          Keycode.G,                                  Keycode.H,                              Keycode.J,                              Keycode.K,                              Keycode.L,                  Keycode.BACKSPACE],       # row 3
    [Keycode.Z,                      Keycode.X,                          Keycode.C,                              Keycode.V,                          Keycode.B,                                  Keycode.N,                              Keycode.M,                              Keycode.FORWARD_SLASH,                  Keycode.ENTER,              None],                    # row 4
    [Keycode.SHIFT,                  Keycode.CAPS_LOCK,                  Keycode.MINUS,                          Keycode.EQUALS,                     Keycode.SEMICOLON,                          Keycode.QUOTE,                          Keycode.COMMA,                          Keycode.PERIOD,                         Keycode.RIGHT_SHIFT,        None],                    # row 5
    [CUSTOM_KEYS["FN_KEY"],          Keycode.CONTROL,                    Keycode.LEFT_ALT,                       CUSTOM_KEYS["GAMEPAD_SELECT"],      Keycode.SPACE,                              CUSTOM_KEYS["GAMEPAD_START"],           Keycode.RIGHT_ALT,                      Keycode.WINDOWS,                        CUSTOM_KEYS["FN_KEY"],      None]                     # row 6
]
 
# Define the FN key map for the keyboard matrix
FN_MAP = [
    #col0                            col1                                col2                                    col3                                col4                                        col5                                    col6                                    col7                                    col8                        col9
    [CUSTOM_KEYS["FN_MOUSE_MOVE_UP"],CUSTOM_KEYS["FN_MOUSE_MOVE_LEFT"],  CUSTOM_KEYS["FN_MOUSE_MOVE_DOWN"],      CUSTOM_KEYS["FN_MOUSE_MOVE_RIGHT"], CUSTOM_KEYS["FN_MOUSE_LEFT"],               CUSTOM_KEYS["FN_MOUSE_RIGHT"],          Keycode.X,                              Keycode.Y,                              CUSTOM_KEYS["FN_BTN_B"],    CUSTOM_KEYS["FN_BTN_A"]], # row 0
    [Keycode.F1,                     Keycode.F2,                         Keycode.F3,                             Keycode.F4,                         Keycode.F5,                                 Keycode.F6,                             Keycode.F7,                             Keycode.F8,                             Keycode.F9,                 Keycode.F10],             # row 1
    [Keycode.ESCAPE,                 CUSTOM_KEYS["FN_MUTE"],             CUSTOM_KEYS["FN_VOLUME_DOWN"],          CUSTOM_KEYS["FN_VOLUME_UP"],        ConsumerControlCode.SCAN_PREVIOUS_TRACK,    ConsumerControlCode.PLAY_PAUSE,         ConsumerControlCode.SCAN_NEXT_TRACK,    CUSTOM_KEYS["FN_LOCK_SCREEN"],          Keycode.F11,                Keycode.F12],             # row 2
    [Keycode.GRAVE_ACCENT,           CUSTOM_KEYS["SHIFT_GRAVE_ACCENT"],  Keycode.BACKSLASH,                      CUSTOM_KEYS["SHIFT_BACKSLASH"],     CUSTOM_KEYS["SHIFT_LEFT_BRACKET"],          CUSTOM_KEYS["SHIFT_RIGHT_BRACKET"],     Keycode.LEFT_BRACKET,                   Keycode.RIGHT_BRACKET,                  Keycode.L,                  Keycode.DELETE],          # row 3
    [Keycode.INSERT,                 Keycode.HOME,                       CUSTOM_KEYS["FN_BL_CONTROL_SCREEN"],    Keycode.END,                        Keycode.PAGE_UP,                            Keycode.PAGE_DOWN,                      Keycode.SCROLL_LOCK,                    Keycode.FORWARD_SLASH,                  Keycode.ENTER,              None],                    # row 4
    [Keycode.TAB,                    Keycode.CAPS_LOCK,                  CUSTOM_KEYS["FN_BL_PWM_DOWN"],          CUSTOM_KEYS["FN_BL_PWM_UP"],        Keycode.SEMICOLON,                          Keycode.QUOTE,                          Keycode.COMMA,                          Keycode.PERIOD,                         Keycode.SHIFT,              None],                    # row 5
    [CUSTOM_KEYS["FN_KEY"],          Keycode.CONTROL,                    Keycode.LEFT_ALT,                       Keycode.PRINT_SCREEN,               Keycode.SPACE,                              Keycode.PAUSE,                          Keycode.RIGHT_ALT,                      Keycode.WINDOWS,                        CUSTOM_KEYS["FN_KEY"],      None]                     # row 6
]
 
# Define the row and column pins
row_pins = [board.GP16, board.GP10, board.GP11, board.GP12, board.GP13, board.GP14, board.GP15]
col_pins = [board.GP0,  board.GP1,  board.GP2,  board.GP3,  board.GP4,  board.GP5,  board.GP6,  board.GP7,  board.GP8,  board.GP9]
 
# Initialize row and column pins
row_gpio = [digitalio.DigitalInOut(pin) for pin in row_pins]
col_gpio = [digitalio.DigitalInOut(pin) for pin in col_pins]
 
# Set rows as inputs with pull-up and columns as outputs set to low
for row in row_gpio:
    row.direction = digitalio.Direction.INPUT
    row.pull = digitalio.Pull.UP
 
 
for col in col_gpio:
    col.direction = digitalio.Direction.OUTPUT
    col.value = False
 
# Function to scan the keyboard matrix
def scan_keyboard():
    keys_pressed = []
    fn_active = False
 
    for col_index, col_pin in enumerate(col_gpio):
        col_pin.direction = digitalio.Direction.OUTPUT
        col_pin.value = True
 
        for row_index, row_pin in enumerate(row_gpio):
            row_pin.direction = digitalio.Direction.INPUT
            row_pin.pull = digitalio.Pull.DOWN
 
            if row_pin.value:  # Key is pressed
                time.sleep(0.05)  # Debounce delay
                if row_pin.value:
                    key = KEY_MAP[row_index][col_index]
                    if key == CUSTOM_KEYS["FN_KEY"]:
                        fn_active = True
                    keys_pressed.append((row_index, col_index))  # Record the key position
 
        col_pin.value = False  # Set the column back to low
 
    active_map = FN_MAP if fn_active else KEY_MAP
 
    translated_keys = []
    for row_index, col_index in keys_pressed:
        translated_key = active_map[row_index][col_index]
        translated_keys.append(translated_key)
        print("ROW", row_index, "COL", col_index)
 
    return translated_keys
 
previous_keys = set()
 
# Main loop
try:
    while True:
        current_keys = set(scan_keyboard())
 
        # Handle keys that were released
        for key in previous_keys - current_keys:
            if key is not None and key >= 0:
                kbd.release(key)
            elif key is not None and key in SPECIAL_KEY_FUNCTIONS:
                if isinstance(SPECIAL_KEY_FUNCTIONS[key], tuple):
                    (fun_make, fun_break) = SPECIAL_KEY_FUNCTIONS[key]
                    fun_break()
 
        # Handle keys that are pressed
        for key in current_keys - previous_keys:
            if key is not None:
                if key in SPECIAL_KEY_FUNCTIONS:
                    if isinstance(SPECIAL_KEY_FUNCTIONS[key], tuple):
                        (fun_make, fun_break) = SPECIAL_KEY_FUNCTIONS[key]
                        fun_make()
                    else:
                        SPECIAL_KEY_FUNCTIONS[key]()
                elif key >= 0:  # Filter out negative key codes
                    kbd.press(key)
 
        previous_keys = current_keys
 
        time.sleep(0.01)  # Small delay to avoid too fast key presses
 
except Exception as e:
    print(f"An error occurred: {e}")
    # Perform necessary cleanup, such as releasing all keys
    try:
        kbd.release_all()
    except Exception as cleanup_error:
        print(f"Error during cleanup: {cleanup_error}")
    # Reset the RP2040
    microcontroller.reset()

デバッグ

シリアルコンソール(/dev/ttyACM0)を使って code.pyのデバッグを行うことができる

基本的に接続すると問題があれば大体エラーを出している

エラーメッセージが流れてしまった後でも Ctrl+Dを押せば code.pyをリロードするので、エラーがあればそこで表示される

code.pyを更新したときも、Ctrl+Dで強制的にリロードさせることができる3)

$ cu -l /dev/ttyACM0
Connected.
Traceback (most recent call last):
  File "code.py", line 414, in <module>
KeyboardInterrupt:
 
Code done running.
Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.
 
Press any key to enter the REPL. Use CTRL-D to reload.
soft reboot
 
Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.
code.py output:
Traceback (most recent call last):
  File "code.py", line 414, in <module>
KeyboardInterrupt:
 
Code done running.
 
Press any key to enter the REPL. Use CTRL-D to reload.
 
Adafruit CircuitPython 10.0.0-beta.0-dirty on 2025-07-23; Raspberry Pi Pico with rp2040
>>> import inspect
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: no module named 'inspect'
>>>
soft reboot
 
Auto-reload is on. Simply save files over USB to run them or enter REPL to disable.
code.py output:
 
Code stopped by auto-reload. Reloading soon.
soft reboot
1)
Waveshareのサポートに頼むともらえるかもしれない。わたしはもらえた
2)
個人的にはPythonは好きではない。インデント依存なのがFORTRANみたいで嫌い。そしてこの例外処理も、except Exception as e: とかで書くのだが「except ExceptionだったらそれはException以外だろうがぁぁぁあ!」と感じるのも嫌い
3)
ほっておいても自動的に更新を検知してリロードするが

This website uses cookies. By using the website, you agree with storing cookies on your computer. Also, you acknowledge that you have read and understand our Privacy Policy. If you do not agree, please leave the website.

More information