资源准备
这板子是adafruit设计的,按照惯例,直接去adafruit官网上就能找到这块板卡的资料。
Overview | Adafruit MacroPad RP2040 | Adafruit Learning System
固件下载
在官网上找到的资料显示,这块板卡可以使用arduino和CircuitPython两种方式使用,个人不太喜欢arduino随便改一个东西就要全局编译的操作,且更喜欢解释型脚本(变更需求只需要打开板卡中的脚本,改一下脚本就行,不需要去托管平台找源码再去编译),因此选择CircuitPython平台。具体固件链接如下:
固件烧录
板卡通过typec线接到电脑,之后按住旋钮,并短按一下reset键进入2040的固件升级模式,并将下载到的uf2文件拷贝至新出现的U盘的根目录即可。
下载marcopad相关库
下载链接如下:
下载好后,将文件解压,解压完后,将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上很常见,但是目前还是依赖鼠标切换过去
来点小小的吐槽
确切的说不能算吐槽,应该是说改进建议,如果有改版的话,个人建议显示屏单独做一个小板,通过定位柱与主板连接。原因是目前显示屏位置在按键上方,但是高度又与按键不齐平。当当作快捷键使用时,因为个人习惯是键盘放在手伸直才能够得着的位置,导致无法看到屏幕显示内容。若能够做到显示屏与键盘同高度,且选用的屏视场角够大,则刚好可以规避该问题。
