ソフトウェアエンジニアがhelixを無線化する際にハマったことと回避策

## 自分のレベル
- 自作キーボードはキットを2,3個組み立てたことがある程度
- cについての知識ゼロ
- 電子工作知識ゼロ
- 普段はjavaとかjsとか書いている・・・

## この記事は
windowsからBLE micro proのファームウェアをビルド・書き込みしようとしたときにハマったこととその回避策を紹介します
ハードウェア的なことはほとんど書きません

## 対象読者
ソフトウェアの人だけど、自作キーボードもはじめてみた人

## 環境
windows 10 home
WSL (Ubuntu 16.04.2 LTS)
msys2 (minitty 2.8.5)

## 基盤を入手する

https://github.com/sekigon-gonnoc/BLE-Micro-Pro

遊舎工房の実店舗で売ってるのを買ってきた。
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する。
すると以下のビルドエラーになった。


```bash
$ 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が見つからないようだ。
実際に対象のパスを見てみると、

```bash
$ 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してみる。


```bash
$ 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みたいなものだろうか。
要はコンパイラのバージョンが古いなどが問題のようだ。
と思って調べてみると・・・

```bash

$ 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すると見つかった。

```bash
$ 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ファイル形式で吐いてくれるらしい。
デバッグ用だろうか？

https://github.com/sekigon-gonnoc/qmk_firmware/blob/nrf52/tmk_core/nrf.mk#L949

なので見てみる。

```bash
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](https://github.com/tan-t/qmk_firmware/commit/c968ef11ab063791d9f7936fb648898585412cd7#diff-75631e1a30ce0479518340a06a1673c1R808)

こんな感じである。
よくわからなかったのでいろいろ足してしまったが、最終的にはこの行だけでよかった。

気を取り直してmakeすると成功した。

成功すると、qmk_firmware以下にhelix_ble_master_キーマップ名.binというバイナリファイルが生成される。


## ファームウェアの書き込み

とはいえ、当該のmakefileを見ればわかるが、WSLからファームウェアを書き込む方法は無いか、あるいはパッとはわからない。

```bash
		echo 'ERROR: nrfutil cannot be automated within the Windows Subsystem for Linux (WSL) currently.'; \
```

https://github.com/sekigon-gonnoc/qmk_firmware/blob/nrf52/tmk_core/nrf.mk#L913

しょうがないのでmsysからやるほかないようだ。

ただmsysからmake経由で書き込もうとすると、書き込み手前のコンパイルでこけてしまう。
なので回避策として、

- WSLでコンパイル済みのバイナリファイルを利用する
- 書き込みは、makefileのnrfutilゴールの記述をbashに書き換えたものをmsysから叩く

という戦略をとった。

具体的には、
https://github.com/sekigon-gonnoc/qmk_firmware/blob/nrf52/tmk_core/nrf.mk#L902-L933
ここの記述だけを実行できるようにする。

その結果できたshellがこちら。

```bash
#!/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側だけ発生したトラブル

```bash
Extended Error 0x0C: The hash of the received firmware image does not match the hash in the init packet.
```

こんな感じの例外が書き込み時に発生したことがあった。
すこしだけ内容を変えつつ何回かビルドし直して書き込みを試みたところ数回目でなぜか成功した。

## 感想
夢が広がるので最高