文書の過去の版を表示しています。
PocketTerm35のキーマップを変更する
概要
WaveShareが提供するPocketTerm35のキーボードコントローラはRP2040である
ファームウェアをカスタマイズすることで任意のキーマップを実装できる
そこで、サポートに、ファームウェアのソースを公開してほしいと連絡すると、Circuit Pythonだから、code.pyを変更すればキーマップをカスタマイズできるよ、といわれた
なるほど、それならばそれでやってみよう
CIRCUITPYをマウントする
Circuit Pythonのデバイスは、CIRCUITPYというUSBマスストレージとターミナルにつながるUSBシリアルデバイスを提供する
なので、ホスト端末からは CIRCUITPYが見えるはずだが、PocketTerm35には存在していない
サポート曰く「(RP2040の)リセットボタンを押せばみえるよ」とのことだが、リセットを押したとて出てこない
のちにわかるのだが、boot.pyのなかでCIRCUITPYを無効化しているので、基本的に出てこないのだ
- boot.py
import storage sotrage.disable_usb_drive()
ではどうするのか
いい感じにRESETボタンを連打する
これしかないので、いい感じにRESETボタンを連打してください
この問題についても、改造の過程で解消していきたいと思う
準備
Circuit Pythonの改造は比較的難易度は低いが、不完全なコードをデプロイすると PocketTerm35のキーボードが一切効かないなどということになりかねないリスクがある
なので、必ず以下の準備をする
- CIRCUITPYのバックアップ
- USBキーボード
できれば以下のものもあるとよい
- PocketTerm35にsshでアクセスできる環境
- ファームウェアの回復用のuf2ファイルセット1)
- 実験用のRaspbery Pi Pico (RP2040)
テスト中は頻繁にキーボードが使えなくなるのでsshでリモートからいじる方が楽である
boot.pyをいじる場合は、致命的な間違いを犯していないか確認する意味でも実験用のRaspberry Pi Picoがあると安心である
キーを実装していなくても、シリアルコンソールで最低限の動作確認は行えるし、万一致命傷を与えても、Adafruitが配布している誰でも入手できる uf2ファイルで簡単に復旧できるので、Waveshareから回復用のファイルをもらえなかった場合にも安心である
キーマップの設計
不便であると感じているのは以下の点
- 左シフトキーがない
- ゲームパッド風のキーがあるのにただ英字が割り当てられているだけでゲームパッドとして機能しない
- マウスがないと困る場面でマウスとして使える機能が欲しい
- 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が有効化される(デフォルトでは無効)
依存するライブラリ
- adafruit_hid
デフォルトのファームウェアとして入っている 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の方は中で例外などが発生して、それを適切に処理しないとファームウェアが再起不能になる危険性があるので、慎重に行う
万一、再起不能になった場合は、復旧用の uf2ファイルで修復しないと回復しない
boot.py
左 Fnキーが押されていたら storate.enable_usb_drive()でCIRCUITPYを有効化する処理と、GamepadをUSB HIDのエンドポイントとして追加する処理を記述する
コメントにあるように、Gamepadのエントリはサンプルから持ってきた
ただし、ボタン数は15にしてある
サンプルは16だったが、evtestで確認したところ、16個目のボタンにコードが割り当てられていなかったので削減した
キー処理の部分は try~exceptで例外処理を行っている
上に書いたように万一未処理の例外が発生するとファームウェアが修復不能になるためである
- 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
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で強制的にリロードさせることができる2)
$ 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
