【DigiKey好物畅享】+ Adafruit + RP2040 MarcoPad + VSCode快捷键

资源准备

这板子是adafruit设计的,按照惯例,直接去adafruit官网上就能找到这块板卡的资料。

Overview | Adafruit MacroPad RP2040 | Adafruit Learning System

固件下载

在官网上找到的资料显示,这块板卡可以使用arduino和CircuitPython两种方式使用,个人不太喜欢arduino随便改一个东西就要全局编译的操作,且更喜欢解释型脚本(变更需求只需要打开板卡中的脚本,改一下脚本就行,不需要去托管平台找源码再去编译),因此选择CircuitPython平台。具体固件链接如下:

MacroPad RP2040 Download

固件烧录

板卡通过typec线接到电脑,之后按住旋钮,并短按一下reset键进入2040的固件升级模式,并将下载到的uf2文件拷贝至新出现的U盘的根目录即可。

下载marcopad相关库

下载链接如下:

Download File

下载好后,将文件解压,解压完后,将libs和marcos文件夹拷贝至板卡U盘的根目录下。

修改code.py

用记事本打开U盘根目录下的code.py程序,编写如下软件:

import os
import time
import displayio
import terminalio
from adafruit_display_shapes.rect import Rect
from adafruit_display_text import label
from adafruit_macropad import MacroPad


# CONFIGURABLES ------------------------

MACRO_FOLDER = '/macros'


# CLASSES AND FUNCTIONS ----------------

class App:
    """ Class representing a host-side application, for which we have a set
        of macro sequences. Project code was originally more complex and
        this was helpful, but maybe it's excessive now?"""
    def __init__(self, appdata):
        self.name = appdata['name']
        self.macros = appdata['macros']

    def switch(self):
        """ Activate application settings; update OLED labels and LED
            colors. """
        group[13].text = self.name   # Application name
        if self.name:
            rect.fill = 0xFFFFFF
        else: # empty app name indicates blank screen for which we dimm header
            rect.fill = 0x000000
        for i in range(12):
            if i < len(self.macros): # Key in use, set label + LED color
                macropad.pixels[i] = self.macros[i][0]
                group[i].text = self.macros[i][1]
            else:  # Key not in use, no label or LED
                macropad.pixels[i] = 0
                group[i].text = ''
        macropad.keyboard.release_all()
        macropad.consumer_control.release()
        macropad.mouse.release_all()
        macropad.stop_tone()
        macropad.pixels.show()
        macropad.display.refresh()


# INITIALIZATION -----------------------

macropad = MacroPad()
macropad.display.auto_refresh = False
macropad.pixels.auto_write = False

# Set up displayio group with all the labels
group = displayio.Group()
for key_index in range(12):
    x = key_index % 3
    y = key_index // 3
    group.append(label.Label(terminalio.FONT, text='', color=0xFFFFFF,
                             anchored_position=((macropad.display.width - 1) * x / 2,
                                                macropad.display.height - 1 -
                                                (3 - y) * 12),
                             anchor_point=(x / 2, 1.0)))
rect = Rect(0, 0, macropad.display.width, 13, fill=0xFFFFFF)
group.append(rect)
group.append(label.Label(terminalio.FONT, text='', color=0x000000,
                         anchored_position=(macropad.display.width//2, 0),
                         anchor_point=(0.5, 0.0)))
macropad.display.root_group = group

# Load all the macro key setups from .py files in MACRO_FOLDER
apps = []
files = os.listdir(MACRO_FOLDER)
files.sort()
for filename in files:
    if filename.endswith('.py') and not filename.startswith('._'):
        try:
            module = __import__(MACRO_FOLDER + '/' + filename[:-3])
            apps.append(App(module.app))
        except (SyntaxError, ImportError, AttributeError, KeyError, NameError,
                IndexError, TypeError) as err:
            print("ERROR in", filename)
            import traceback
            traceback.print_exception(err, err, err.__traceback__)

if not apps:
    group[13].text = 'NO MACRO FILES FOUND'
    macropad.display.refresh()
    while True:
        pass

last_position = None
last_encoder_switch = macropad.encoder_switch_debounced.pressed
app_index = 0
apps[app_index].switch()


# MAIN LOOP ----------------------------

while True:
    # Read encoder position. If it's changed, switch apps.
    position = macropad.encoder
    if position != last_position:
        app_index = position % len(apps)
        apps[app_index].switch()
        last_position = position

    # Handle encoder button. If state has changed, and if there's a
    # corresponding macro, set up variables to act on this just like
    # the keypad keys, as if it were a 13th key/macro.
    macropad.encoder_switch_debounced.update()
    encoder_switch = macropad.encoder_switch_debounced.pressed
    if encoder_switch != last_encoder_switch:
        last_encoder_switch = encoder_switch
        if len(apps[app_index].macros) < 13:
            continue    # No 13th macro, just resume main loop
        key_number = 12 # else process below as 13th macro
        pressed = encoder_switch
    else:
        event = macropad.keys.events.get()
        if not event or event.key_number >= len(apps[app_index].macros):
            continue # No key events, or no corresponding macro, resume loop
        key_number = event.key_number
        pressed = event.pressed

    # If code reaches here, a key or the encoder button WAS pressed/released
    # and there IS a corresponding macro available for it...other situations
    # are avoided by 'continue' statements above which resume the loop.

    sequence = apps[app_index].macros[key_number][2]
    if pressed:
        # 'sequence' is an arbitrary-length list, each item is one of:
        # Positive integer (e.g. Keycode.KEYPAD_MINUS): key pressed
        # Negative integer: (absolute value) key released
        # Float (e.g. 0.25): delay in seconds
        # String (e.g. "Foo"): corresponding keys pressed & released
        # List []: one or more Consumer Control codes (can also do float delay)
        # Dict {}: mouse buttons/motion (might extend in future)
        if key_number < 12: # No pixel for encoder button
            macropad.pixels[key_number] = 0xFFFFFF
            macropad.pixels.show()
        for item in sequence:
            if isinstance(item, int):
                if item >= 0:
                    macropad.keyboard.press(item)
                else:
                    macropad.keyboard.release(-item)
            elif isinstance(item, float):
                time.sleep(item)
            elif isinstance(item, str):
                macropad.keyboard_layout.write(item)
            elif isinstance(item, list):
                for code in item:
                    if isinstance(code, int):
                        macropad.consumer_control.release()
                        macropad.consumer_control.press(code)
                    if isinstance(code, float):
                        time.sleep(code)
            elif isinstance(item, dict):
                if 'buttons' in item:
                    if item['buttons'] >= 0:
                        macropad.mouse.press(item['buttons'])
                    else:
                        macropad.mouse.release(-item['buttons'])
                macropad.mouse.move(item['x'] if 'x' in item else 0,
                                    item['y'] if 'y' in item else 0,
                                    item['wheel'] if 'wheel' in item else 0)
                if 'tone' in item:
                    if item['tone'] > 0:
                        macropad.stop_tone()
                        macropad.start_tone(item['tone'])
                    else:
                        macropad.stop_tone()
                elif 'play' in item:
                    macropad.play_file(item['play'])
    else:
        # Release any still-pressed keys, consumer codes, mouse buttons
        # Keys and mouse buttons are individually released this way (rather
        # than release_all()) because pad supports multi-key rollover, e.g.
        # could have a meta key or right-mouse held down by one macro and
        # press/release keys/buttons with others. Navigate popups, etc.
        for item in sequence:
            if isinstance(item, int):
                if item >= 0:
                    macropad.keyboard.release(item)
            elif isinstance(item, dict):
                if 'buttons' in item:
                    if item['buttons'] >= 0:
                        macropad.mouse.release(item['buttons'])
                elif 'tone' in item:
                    macropad.stop_tone()
        macropad.consumer_control.release()
        if key_number < 12: # No pixel for encoder button
            macropad.pixels[key_number] = apps[app_index].macros[key_number][0]
            macropad.pixels.show()

这软件也是adafruit上提供的例程,实现了一个按键框架,让用户可以自定义配置按键功能组和按键上报地信息,保存后,程序重新运行。

新增按键

在体验运行效果后,我发现例程已经大致实现了我最初的想法,即我所使用的笔记本阉割掉了小键盘,我想增加一个小键盘类似的实现,例程已经实现了这块的功能,虽然由于按键数量问题导致无法实现所有按键。因此就更换了实现思路,新增一个用于提高编码效率地配置—vscode快捷键盘。具体修改如下:
在marcos目录下新增文件vscode.py,并输入以下内容后保存:

# SPDX-FileCopyrightText: 2021 Victor Toni - GitHub @vitoni
#
# SPDX-License-Identifier: MIT

# MACROPAD Hotkeys example: blank screen for idle

from adafruit_hid.keycode import Keycode # REQUIRED if using Keycode.* values

app = {                      # REQUIRED dict, must be named 'app'
    'name' : 'VsCode',             # Application name
    'macros' : [             # List of button macros...
        # COLOR    LABEL    KEY SEQUENCE
        # 1st row ----------
        (0x002000, 'Copy', [Keycode.CONTROL, 'c']),  # Copy
        (0x002000, 'Past', [Keycode.CONTROL, 'v']),  # Past
        (0x002000, 'Undo', [Keycode.CONTROL, 'z']),  # Undo
        # 2nd row ----------
        (0x200000, 'JumpIn', [Keycode.CONTROL, '=']), # Go to selected function
        (0x200000, 'GoBack', [Keycode.ALT, ',']),     # Return to upper layer
        (0x000020, 'FScan', [Keycode.CONTROL,'f']),   # get scan info
        # 3rd row ----------
        (0x000000, '',          []),
        (0x000000, '',          []),
        (0x000000, '',          []),
        # 4th row ----------
        (0x000000, '',          []),
        (0x000000, '',          []),
        (0x000000, '',          []),
        # Encoder button ---
        (0x000000, '',          [])
    ]
}

效果验证

机器重启后,旋转旋钮至vscode界面

在此界面下:

  • 按Copy键可以快速复制想要复制地代码
  • 按Past可以在鼠标选中位置粘贴复制的内容
  • 按Undo键可以撤销修改
  • 按JumpIn键可以跳转至函数实现
  • 按GoBack键可以返回上一级调用位置
  • 按FScan键可以弹出文件搜索框

后续改进计划

  • 屏是oled的,不太适合一直亮着(之前经验,一直亮着会烧屏),因此增加一个一段时间不用息屏功能,随意按键唤醒的功能。
  • VSCode快捷键继续补充快速跳转至工程搜索功能,这个功能在vscode上很常见,但是目前还是依赖鼠标切换过去

来点小小的吐槽

确切的说不能算吐槽,应该是说改进建议,如果有改版的话,个人建议显示屏单独做一个小板,通过定位柱与主板连接。原因是目前显示屏位置在按键上方,但是高度又与按键不齐平。当当作快捷键使用时,因为个人习惯是键盘放在手伸直才能够得着的位置,导致无法看到屏幕显示内容。若能够做到显示屏与键盘同高度,且选用的屏视场角够大,则刚好可以规避该问题。