はじめに
前回まででキーボードと言えるものを作成することができた。
今までは仮組のファームウェアで動作確認を行っていたが今回でファームウェアを完成させていく。
キーボードの名前
その前にキーボードの名前を決めていく。
今回のキーボードのコンセプトは
- 無線接続(Seeed XIAO BLE nRF52840を使用)
- 狭ピッチ(17mm)
- トラックボール
- ガスケットマウント
実際にタイピングをしてみると打ち心地が柔らかいキーボードなので、この情報元にGeminiに相談してみる。
1. 柔らかい打ち心地(ガスケットマウント)を表現する
「柔らかさ」「しなやかさ」「浮遊感」といったイメージを持つフランス語です。
Souple (スープル): 「しなやかな、柔軟な」という意味。ガスケットマウントの柔軟性を直接的に表現できます。
Velours (ヴルール): 「ベルベット」という意味。ベルベットのような滑らかで柔らかい触感を連想させ、打ち心地の良さを表します。
Flottant (フロッタン): 「浮遊する、漂う」という意味。キーが浮いているかのような、軽やかな打鍵感をイメージさせます。
Cocon (ココン): 「繭(まゆ)」という意味。優しく包み込まれるような、快適な打ち心地を表現できます。
・・・
Cocon (ココン)いいなと思ったのでこれで決定。
ZMK環境構築
公式ドキュメントを確認しながら作成していく。
Windowsの場合下記のコマンドを実行することで必要なファイルを作成してくれる。
Keyboard Selection:は一旦適当なものを選択。
MCU Board Selection:はSeeed Studio XIAO nRF52840を選択。
powershell -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://zmk.dev/setup.ps1'))"
コマンドを実行すると下記の様にファイルが生成される。
C:.
| build.yaml
|
+---.github
| \---workflows
| build.yml
|
+---boards
| \---shields
| .gitkeep
|
+---config
| corne.conf
| corne.keymap
| west.yml
|
\---zephyr
module.yml
ここから必要なファイルの追加と生成されたファイルを編集していく。
build.yaml
include:
- board: seeeduino_xiao_ble
shield: Cocon_R rgbled_adapter
snippet: studio-rpc-usb-uart
- board: seeeduino_xiao_ble
shield: Cocon_L rgbled_adapter
- board: seeeduino_xiao_ble
shield: settings_reset
west.yamlにはPMW3610のドライバとrgbled-widgetを追加する
west.yaml
remotes:
- name: zmkfirmware
url-base: https://github.com/zmkfirmware
- name: badjeff
url-base: https://github.com/badjeff
- name: caksoylar
url-base: https://github.com/caksoylar
projects:
- name: zmk
remote: zmkfirmware
revision: main
import: app/west.yml
- name: zmk-pmw3610-driver
remote: badjeff
revision: main
- name: zmk-rgbled-widget
remote: caksoylar
revision: main
self:
path: config
conf
confには有効化無効化する機能を書き込んでいく。
CONFIG_EC11=y
CONFIG_EC11_TRIGGER_GLOBAL_THREAD=y
CONFIG_ZMK_BATTERY_REPORTING=y
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_PROXY=y
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING=y
CONFIG_RGBLED_WIDGET=y
CONFIG_RGBLED_WIDGET_BATTERY_SHOW_SELF=y
右側はCentralかつトラックボールがあるので左より多め
CONFIG_ZMK_KEYBOARD_NAME="Cocon"
CONFIG_ZMK_MOUSE=y
CONFIG_NFCT_PINS_AS_GPIOS=y
CONFIG_ZMK_BLE=y
CONFIG_SPI=y
CONFIG_INPUT=y
CONFIG_ZMK_POINTING=y
CONFIG_PMW3610=y
CONFIG_PMW3610_INVERT_X=y
CONFIG_EC11=y
CONFIG_EC11_TRIGGER_GLOBAL_THREAD=y
CONFIG_ZMK_BATTERY_REPORTING=y
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_PROXY=y
CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING=y
CONFIG_ZMK_STUDIO=y
CONFIG_ZMK_STUDIO_LOCKING=n
CONFIG_RGBLED_WIDGET=y
CONFIG_RGBLED_WIDGET_BATTERY_SHOW_SELF=y
CONFIG_RGBLED_WIDGET_CONN_SHOW_USB=y
overlay
overlayではdtsiの内容と左右で異なる内容の設定を書き込んでいく。
特にxiao bleで使用するpinの設定とトラックボールの設定を行う。
回路の1行目がxiao bleのD0に接続されているので合わせていく。
#include "Cocon.dtsi"
&kscan0 {
col-gpios
= <&xiao_d 0 GPIO_ACTIVE_HIGH>
, <&xiao_d 1 GPIO_ACTIVE_HIGH>
, <&xiao_d 2 GPIO_ACTIVE_HIGH>
, <&xiao_d 3 GPIO_ACTIVE_HIGH>
, <&xiao_d 4 GPIO_ACTIVE_HIGH>
, <&xiao_d 5 GPIO_ACTIVE_HIGH>
;
};
右側はトラックボールの設定をする。
NFCの部分は画像の右からGPIO0 10・GPIO0 9と振られているのでkscanにはGPIO0 10を割り当てる。
#include "Cocon.dtsi"
#include <input/processors.dtsi>
#include <dt-bindings/zmk/input_transform.h>
&default_transform {
col-offset = <6>;
};
&kscan0 {
col-gpios
= <&xiao_d 1 GPIO_ACTIVE_HIGH>
, <&xiao_d 2 GPIO_ACTIVE_HIGH>
, <&xiao_d 3 GPIO_ACTIVE_HIGH>
, <&xiao_d 6 GPIO_ACTIVE_HIGH>
, <&gpio0 10 GPIO_ACTIVE_HIGH>
;
};
/ {
trackball_listener {
compatible = "zmk,input-listener";
device = <&trackball>;
};
};
&pinctrl {
spi0_default: spi0_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 0, 5)>,
<NRF_PSEL(SPIM_MOSI, 0, 4)>,
<NRF_PSEL(SPIM_MISO, 0, 4)>;
};
};
spi0_sleep: spi0_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 0, 5)>,
<NRF_PSEL(SPIM_MOSI, 0, 4)>,
<NRF_PSEL(SPIM_MISO, 0, 4)>;
low-power-enable;
};
};
};
#include <zephyr/dt-bindings/input/input-event-codes.h>
&spi0 {
status = "okay";
compatible = "nordic,nrf-spim";
pinctrl-0 = <&spi0_default>;
pinctrl-1 = <&spi0_sleep>;
pinctrl-names = "default", "sleep";
cs-gpios = <&gpio0 9 GPIO_ACTIVE_LOW>;
trackball: trackball@0 {
status = "okay";
compatible = "pixart,pmw3610";
reg = <0>;
spi-max-frequency = <2000000>;
irq-gpios = <&gpio0 2 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
cpi = <800>;
evt-type = <INPUT_EV_REL>;
x-input-code = <INPUT_REL_X>;
y-input-code = <INPUT_REL_Y>;
force-awake;
/* keep the sensor awake while ZMK activity state is ACTIVE,
fallback to normal downshift mode after ZMK goes into IDLE / SLEEP mode.
thus, the sensor would be a `wakeup-source` */
};
};
/ {
/omit-if-no-ref/ zip_temp_layer: zip_temp_layer {
compatible = "zmk,input-processor-temp-layer";
#input-processor-cells = <2>;
require-prior-idle-ms = <1000>;
excluded-positions = <
16 // J
17 // K
>;
};
trackball_listener {
compatible = "zmk,input-listener";
device = <&trackball>;
input-processors = <&zip_temp_layer 3 500>;
scroller {
layers = <4>;
input-processors = <&zip_xy_transform (INPUT_TRANSFORM_Y_INVERT)>, <&zip_xy_scaler 1 10>, <&zip_xy_to_scroll_mapper>;
process-next;
};
};
};
クリックは「J・K」を使用しているので、input-processor-temp-layer
押している間はLayer2にとどまるよう設定する。
またスクロールLayerをLayer4に変更、またスクロール方向の変更と下スクロールの速度を10分の1にしておく。
dtsi
dtsiではキーマトリクスの設定とkscanのrow側を設定する。
physical_layoutは下記のサイトを使用して作成。
#include <dt-bindings/zmk/matrix_transform.h>
#include <physical_layouts.dtsi>
/ {
chosen {
zmk,physical-layout = &Cocon_physical_layout;
};
Cocon_physical_layout: Cocon_physical_layout {
compatible = "zmk,physical-layout";
display-name = "Default";
transform = <&default_transform>;
kscan = <&kscan0>;
keys // w h x y rot rx ry
= <&key_physical_attrs 100 100 0 38 0 0 0>
, <&key_physical_attrs 100 100 100 12 0 0 0>
, <&key_physical_attrs 100 100 200 0 0 0 0>
, <&key_physical_attrs 100 100 300 12 0 0 0>
, <&key_physical_attrs 100 100 400 25 0 0 0>
, <&key_physical_attrs 100 100 800 25 0 0 0>
, <&key_physical_attrs 100 100 900 12 0 0 0>
, <&key_physical_attrs 100 100 1000 0 0 0 0>
, <&key_physical_attrs 100 100 1100 12 0 0 0>
, <&key_physical_attrs 100 100 1200 38 0 0 0>
, <&key_physical_attrs 100 100 0 138 0 0 0>
, <&key_physical_attrs 100 100 100 112 0 0 0>
, <&key_physical_attrs 100 100 200 100 0 0 0>
, <&key_physical_attrs 100 100 300 112 0 0 0>
, <&key_physical_attrs 100 100 400 125 0 0 0>
, <&key_physical_attrs 100 100 800 125 0 0 0>
, <&key_physical_attrs 100 100 900 112 0 0 0>
, <&key_physical_attrs 100 100 1000 100 0 0 0>
, <&key_physical_attrs 100 100 1100 112 0 0 0>
, <&key_physical_attrs 100 100 1200 138 0 0 0>
, <&key_physical_attrs 100 100 0 238 0 0 0>
, <&key_physical_attrs 100 100 100 212 0 0 0>
, <&key_physical_attrs 100 100 200 200 0 0 0>
, <&key_physical_attrs 100 100 300 212 0 0 0>
, <&key_physical_attrs 100 100 400 225 0 0 0>
, <&key_physical_attrs 100 100 500 240 0 0 0>
, <&key_physical_attrs 100 100 800 225 0 0 0>
, <&key_physical_attrs 100 100 900 212 0 0 0>
, <&key_physical_attrs 100 100 1000 200 0 0 0>
, <&key_physical_attrs 100 100 1100 212 0 0 0>
, <&key_physical_attrs 100 100 1200 238 0 0 0>
, <&key_physical_attrs 100 100 100 312 0 0 0>
, <&key_physical_attrs 100 100 200 300 0 0 0>
, <&key_physical_attrs 100 100 348 350 0 0 0>
, <&key_physical_attrs 100 100 348 300 1500 200 800>
, <&key_physical_attrs 100 100 348 300 3000 300 800>
, <&key_physical_attrs 100 100 952 312 (-3000) 1080 790>
, <&key_physical_attrs 100 100 952 330 (-1500) 1100 700>
, <&key_physical_attrs 100 100 995 350 0 0 0>
, <&key_physical_attrs 100 100 1100 312 0 0 0>
, <&key_physical_attrs 100 100 1200 338 0 0 0>
;
};
default_transform: keymap_transform_0 {
compatible = "zmk,matrix-transform";
columns = <11>;
rows = <4>;
map = <
RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,6) RC(0,7) RC(0,8) RC(0,9) RC(0,10)
RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,6) RC(1,7) RC(1,8) RC(1,9) RC(1,10)
RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(2,6) RC(2,7) RC(2,8) RC(2,9) RC(2,10)
RC(3,0) RC(3,1) RC(3,2) RC(3,3) RC(3,4) RC(3,6) RC(3,7) RC(3,8) RC(3,9) RC(3,10)
>;
};
kscan0: kscan {
compatible = "zmk,kscan-gpio-matrix";
label = "KSCAN";
diode-direction = "col2row";
row-gpios
= <&xiao_d 10 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
, <&xiao_d 9 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
, <&xiao_d 8 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
, <&xiao_d 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>
;
};
sensors: sensors {};
};
Cocon.yml
shields内のymlにはキーボードの名前とGitHubのURLを記載。
file_format: "1"
id: Cocon
name: Cocon
type: shield
url: https://github.com/waressyoi/Cocon-zmk-config
requires: [seeeduino_xiao_ble]
features:
- keys
Kconfig.defconfig
残りのconfig系は下記のように設定した。
if SHIELD_COCON_R
config ZMK_KEYBOARD_NAME
default "Cocon_R"
config ZMK_SPLIT
default y
config ZMK_SPLIT_ROLE_CENTRAL
default y
endif
if SHIELD_COCON_L
config ZMK_KEYBOARD_NAME
default "Cocon_L"
config ZMK_SPLIT
default y
endif
Kconfig.shield
config SHIELD_COCON_R
def_bool $(shields_list_contains,Cocon_R)
config SHIELD_COCON_L
def_bool $(shields_list_contains,Cocon_L)
ディレクトリ構成
最終的な構成はこうなった。
C:.
| build.yaml
|
+---.github
| \---workflows
| build.yml
|
+---boards
| \---shields
| | .gitkeep
| |
| \---Test
| Cocon.dtsi
| Cocon.yml
| Cocon_L.conf
| Cocon_L.overlay
| Cocon_R.conf
| Cocon_R.overlay
| Kconfig.defconfig
| Kconfig.shield
|
+---config
| Cocon.json
| Cocon.keymap
| west.yml
|
\---zephyr
module.yml
GitHub Actions
これをコミットしてリモートと同期するとGitHub Actionsでビルドが始まる。
ビルドが完了するとArtifactsからfirmwareをダウンロードできる。
完成
実際にファームウェアを導入して完成。
時間があったのでキーキャップも作成してみた。
最後に
実際に作成し始めてから半年以上かかった。
回路の知識やZMKの設定等でかなり苦戦したが最後まで作り切ることができた。
ここから普段の生活で使用してみて足りない箇所や追加したい機能が出てくると思うので今後も何かあれば更新していこうと思う。