自分のレベル
- 自作キーボードはキットを2,3個組み立てたことがある程度
- cについての知識ゼロ
- 電子工作知識ゼロ
- 普段はjavaとかjsとか書いている・・・
この記事は
windowsからBLE micro proのファームウェアをビルド・書き込みしようとしたときにハマったこととその回避策を紹介します
ハードウェア的なことはほとんど書きません
対象読者
ソフトウェアの人だけど、自作キーボードもはじめてみた人
環境
windows 10 home
WSL (Ubuntu 16.04.2 LTS)
msys2 (minitty 2.8.5)
基盤を入手する
遊舎工房の実店舗で売ってるのを買ってきた。
4000円ぐらい。
ピンヘッダを入手する
helixに付属しているスプリングピンヘッダを使うとhelix <-> BLE micro pro間ではんだ付けが不要になる。
これはBLE micro proには同梱されていないため、遊舎工房で購入した。
「helixについてるスプリングピンヘッダが欲しいんですけど」って言えば伝わると思われる。
普通に実装する
BLE micro pro <-> ピンヘッダ間ははんだ付けする。
ピンヘッダ <-> helix間は挿すだけ。
BLE micro proは通常のpromicroよりピンが2つ多い。
先端の「GND」「BAT」ピンは電池基盤との接続用なので、それ以外のピンを実装する。
ファームウェアのビルド
msys編
公式ドキュメントによれば、nrfsdkを落としてくれば普通にmsys2でmakeできるというように書いてあるが、自分はハマったのでそこを記しておく。
まずnrfsdkを落としてくる。この際、15.00.00を落とす必要がある。
15.2ぐらいのものを落としてくるとmakeでコケるので注意。
次にNRFSDK15_ROOTをnrfsdkのパスにし、普通にmakeする。
すると以下のビルドエラーになった。
$ make helix_ble/master:default
QMK Firmware 0.6.96
Making helix_ble/master with keymap default
"PLATFORM NRF5"
# 略
Compiling: tmk_core/protocol/nrf/sdk15/usbd.c In file included from ./lib/lufa/LUFA/Drivers/USB/Class/Common/HIDClassCommon.h:54:0,
from tmk_core/protocol/chibios/lufa_utils/LUFA/Drivers/USB/USB.h:37,
from tmk_core/protocol/usb_descriptor.h:47,
from tmk_core/protocol/nrf/sdk15/usbd.c:47:
./lib/lufa/LUFA/Drivers/USB/Class/Common/../../Core/StdDescriptors.h:53:38: fatal error: ../../../Common/Common.h: No such file or directory
#include "../../../Common/Common.h"
^
compilation terminated.
要は../../../Common/Common.hなんてねーよと言われている。
このincludeを行っているファイル(./lib/lufa/LUFA/Drivers/USB/Class/Common/../../Core/StdDescriptors.h)からCommon.hが見つからないようだ。
実際に対象のパスを見てみると、
$ ls ./lib/lufa/LUFA/Drivers/USB/Class/Common/../../Core/../../../Common/Common.h
./lib/lufa/LUFA/Drivers/USB/Class/Common/../../Core/../../../Common/Common.h
普通に存在している。
パスの解決方法が何か違うとかあるのだろうか?
困った。
WSL編
WSLではファームウェアの書き込みがサポートされていないようなのだが、とりあえずビルドはできるかもしれない。
ということでとりあえずmakeしてみる。
$ make helix_ble/master:default
QMK Firmware 0.6.96
Making helix_ble/master with keymap default
"PLATFORM NRF5"
# 略
Compiling: /mnt/c/Users/tan_t/Desktop/nRF5_SDK_15.0.0_a53641A/components/libraries/crc16/crc16.c /mnt/c/Users/tan_t/Desktop/nRF5_SDK_15.0.0_a53641A/components/libraries/crc16/crc16.c: In function 'crc16_compute':
/mnt/c/Users/tan_t/Desktop/nRF5_SDK_15.0.0_a53641A/components/libraries/crc16/crc16.c:50:5: error: 'for' loop initial declarations are only allowed in C99 or C11 mode
for (uint32_t i = 0; i < size; i++)
^
/mnt/c/Users/tan_t/Desktop/nRF5_SDK_15.0.0_a53641A/components/libraries/crc16/crc16.c:50:5: note: use option -std=c99, -std=gnu99, -std=c11 or -std=gnu11 to compile your code
グ、グェェェーーーーッ!!!!!!!!
1999年以降の仕様なので「最近モード」的なフラグ(stdオプション)をコンパイラに渡せとのこと。
jsでいうharmonyみたいなものだろうか。
要はコンパイラのバージョンが古いなどが問題のようだ。
と思って調べてみると・・・
$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
なんか新しそう。。。
gccをapt-get updateしてから入れなおすなどしたが進展なし。
とりあえずコンパイルエラーの指示の通り、stdオプションを渡してやる方法を探す。
私が叩いているのはmakeコマンドだが、実際にはmakeはタスクランナーのようなものであり、実際のビルド手順はmakefileに書いてあるらしい。
したがって実際にコンパイラを叩いているのはその中となる。
問題は、makefileは複数存在するということである。なので当該ファイルをビルドしているmakefileを探す必要がある。
素直にエラーになった箇所のファイル名でgrepすると見つかった。
$ grep crc **/*.mk
tmk_core/nrf.mk: $(NRFSDK_ROOT)/components/libraries/crc16/crc16.c \
tmk_core/nrf.mk: $(NRFSDK_ROOT)/components/libraries/crc16 \
tmk_core/nrf.mk: $(NRFSDK_ROOT)/components/libraries/crc32 \
tmk_core/nrf.mk: $(NRFSDK_ROOT)/components/libraries/crc16/crc16.c \
tmk_core/nrf.mk: $(NRFSDK_ROOT)/components/libraries/crc16 \
tmk_core/nrf.mk: $(NRFSDK_ROOT)/components/libraries/crc32 \
ソースをなんとなく追うと、どうやらコンパイラを叩く際のオプションを.buildフォルダにtxtファイル形式で吐いてくれるらしい。
デバッグ用だろうか?
なので見てみる。
cat .build/libnrf.sdk15.NRF52840.a/cflags.txt
-DNRF_SDK_MAJOR_VER=15 -DNRF_LOG_ENABLED=1 -DNRF_LOG_BACKEND_UART_ENABLED=0 -DNRF_LOG_DEFAULT_LEVEL=3 -DAPP_USBD_VID=VENDOR_ID -DAPP_USBD_PID=PRODUCT_ID -DBOARD_CUSTOM -DCONFIG_NFCT_PINS_AS_GPIOS -DFLOAT_ABI_HARD -DSOFTDEVICE_PRESENT -DSWI_DISABLE0 -mcpu=cortex-m4 -mthumb -mabi=aapcs -mfloat-abi=hard -mfpu=fpv4-sp-d16 -ffunction-sections -fdata-sections -fno-strict-aliasing -fno-builtin -fshort-enums -Os -DNRF52840_XXAA -DNRF_SD_BLE_API_VERSION=6 -DS140 -D__HEAP_SIZE=0 -D__STACK_SIZE=16384
-std=ほげほげ、というオプションが渡っていないことがわかる。
なのでmakefileにその旨を追加してやればよい。
avoid for loop variable decralation compilation error · tan-t/qmk_firmware@c968ef1 · GitHub
こんな感じである。
よくわからなかったのでいろいろ足してしまったが、最終的にはこの行だけでよかった。
気を取り直してmakeすると成功した。
成功すると、qmk_firmware以下にhelix_ble_master_キーマップ名.binというバイナリファイルが生成される。
ファームウェアの書き込み
とはいえ、当該のmakefileを見ればわかるが、WSLからファームウェアを書き込む方法は無いか、あるいはパッとはわからない。
echo 'ERROR: nrfutil cannot be automated within the Windows Subsystem for Linux (WSL) currently.'; \
しょうがないのでmsysからやるほかないようだ。
ただmsysからmake経由で書き込もうとすると、書き込み手前のコンパイルでこけてしまう。
なので回避策として、
- WSLでコンパイル済みのバイナリファイルを利用する
- 書き込みは、makefileのnrfutilゴールの記述をbashに書き換えたものをmsysから叩く
という戦略をとった。
具体的には、
https://github.com/sekigon-gonnoc/qmk_firmware/blob/nrf52/tmk_core/nrf.mk#L902-L933
ここの記述だけを実行できるようにする。
その結果できたshellがこちら。
#!/bin/bash
NRFUTIL="nrfutil"
GREP="grep"
TARGET="$1"
echo "NRFUTIL=${NRFUTIL}"
echo "TARGET=${TARGET}"
echo "GREP=${GREP}"
# ここからコピペ。
if ! type "nrfutil" > /dev/null 2>&1; then \
echo 'ERROR: nrfutil is not found'; exit 1;\
fi
${NRFUTIL} pkg generate --debug-mode --hw-version 52 --sd-req 0xA9 --application ${TARGET}.bin ${TARGET}.zip
if ${GREP} -q -s Microsoft /proc/version; then \
echo 'ERROR: nrfutil cannot be automated within the Windows Subsystem for Linux (WSL) currently.'; \
else \
printf "Detecting USB port, put your controller into dfu-mode now."; \
ls /dev/tty* > /tmp/1; \
while [ -z $USB ]; do \
sleep 0.5; \
printf "."; \
ls /dev/tty* > /tmp/2; \
USB=`comm -13 /tmp/1 /tmp/2 | ${GREP} -o '/dev/tty.*'`; \
mv /tmp/2 /tmp/1; \
done; \
echo ""; \
echo "Detected controller on USB port at $USB"; \
if ${GREP} -q -s 'MINGW\|MSYS' /proc/version; then \
USB=`echo "$USB" | perl -pne 's/\/dev\/ttyS(\d+)/COM.($1+1)/e'`; \
echo "Remapped MSYS2 USB port to $USB"; \
fi; \
sleep 1; \
echo "Programming Started"; \
${NRFUTIL} dfu usb-serial -pkg ${TARGET}.zip -p $USB; \
fi
まあ、makefileから普通にコピーしてきただけなのだが、一点だけ
「$$USB
」の部分は「$USB
」に書き換える必要がある。($$
は単体でPIDを表すため)
このshellをqmk_firmware直下に配置し、第一引数に生成されたバイナリファイルの拡張子以前の部分を渡せば書き込みできた。
その後master/slaveのペアリング、端末とのペアリングも問題なくできた。
slave側だけ発生したトラブル
Extended Error 0x0C: The hash of the received firmware image does not match the hash in the init packet.
こんな感じの例外が書き込み時に発生したことがあった。
すこしだけ内容を変えつつ何回かビルドし直して書き込みを試みたところ数回目でなぜか成功した。
感想
夢が広がるので最高