ISP1807 Micro Boardにキーパッドを接続してHID/BLEキーボードに

概要

3年ほど前に、Arduino ProMicroキーパッドをつなげてPCの入力装置に というタイトルで簡単な電子工作とプログラミングの話を書いた。ProMicroに載ってるATmega32u4のUSBを使って、小さなキーパッドをHIDキーボード化する話だった。

ISP1807 + 秋月キーパッド

この夏、ProMicroピン互換BLEモジュールの ISP1807 Microボード というスイッチサイエンス社オリジナルの製品を買ったので、以前作ったキーパッドを使って同じようなことをやってみた。

  • ISP1807 Microボードについて
  • 今回の回路について
  • USB接続のHIDキーボード化
  • BLE接続のHIDキーボード化

ISP1807Microボードについて

ISP1807 Microボードは、フランスに本社があるInsightSiP社のISP1807 BLEモジュールを SparkFun ProMicroとピン互換の基板に載せたスイッチサイエンス社独自の製品で、Arduino IDEを使ったBLEアプリケーションの開発が可能。

ISP1807 Micro. USBはtypeC

ISP1807自体は、NORDIC社のnRF52840 (ARM Cortex-M4F CPU、1MB Flash、256KB RAMの低消費電力BLEモジュール)とアンテナを組み合わせた製品で、現在のところ2000個買ったら@900円程度の価格らしい。技術基準適合認定済で基板の裏にシールが貼ってある。

ISP1807 Micro 裏面

BluetoothやWiFiを扱う場合、趣味の電子工作であっても技適の有無は気になる。2年ほど前は、やはりnRF52840を使った Arduino nano 33 BLE という基板に期待していたのだけど、技適がどうなっているのかはっきりしないし、秋月やスイッチサイエンスでも売らないし、で躊躇しているうちに時間が経ってしまった。nano 33 BLEには、u-blox NINA B306 というBLEモジュールが載っているようで、このモジュール自体は認証取得済とのこと。ISP1807 Microのように、実装済のボードにテレックマークと認証番号を印刷したシールを貼ってくれると分かりやすくていいんですが。

類似の製品としては、SparkFun社のPro nRF52840 Mini というのもあり、こちらには認証取得済のRaytac MDBT50Q-P1M (やはり、nRF52840とアンテナなどを組み合わせたBLEモジュール)が載っていて、LiPO充電用IC (MCP73831) やコネクタなども実装されている。ただちょっと高いし、ProMicroとピン互換ではない。

ISP1807 Microボードは送料込みで 3300円だった。ばんばん売れて2000円台になるといいですね。

Arduino IDEでの開発

今回もArduino IDEの1.8.15版を使った。このボード用のスケッチを書くときには、環境設定の追加のボードマネージャのURL に Adafruit とスイッチサイエンスのURLを追加する。

https://www.adafruit.com/package_adafruit_index.json
https://raw.githubusercontent.com/SWITCHSCIENCE/Arduino_Board_Package/master/package_Switch_Science_index.json

(注:このURLは変更になっている。下記参照)。

そして、ボードマネージャAdafruit nRF52Switch Science nRF52 Boards の2つのボードサポートパッケージ (BSP) をインストールする。実施した時点では、おのおの0.24版と0.18版が最新だったので、これらを選択した。

追記 ボードマネージャのURL修正(2022/3/21)

Adafruit側のURLがしばらく前に変更になっており、現時点では以下の内容を追加する。

https://adafruit.github.io/arduino-board-index/package_adafruit_index.json
https://raw.githubusercontent.com/SWITCHSCIENCE/Arduino_Board_Package/master/package_Switch_Science_index.json

修正情報を書いた時点で、両者のBSPのバージョンは、以下のようになっている。

  • Switch Science nRF52 Boards : 0.1.9
  • Adafruit nRF52 : 1.3.0

(追記ここまで)

Adafruit nRF52のインストールによって、コンパイラや各種開発ツール ( GNU Arm Embedded Toolchain 9-2019-q4) やnRF52関連のライブラリが、c:/users/ユーザー名/AppData/Arduino15/packages/adafruit 以下に 860MBytesほど導入された。

Switch Science nRF52 Boards のインストールでは、 …/packages/switchscience 以下にnRF52を使ったボードをArduino IDEで開発するためのboards.txtやvariant.cpp / variant.h などが入った。

そして、ツール/ボードにて、Switch Science ISP1807 Micro Board をターゲットとして選択する。スイッチサイエンスの商品ページを参照

ボードの識別子について (BSP 0.1.8版のはなし)

Arduino IDEでビルドするときは、このボードの名前などの識別情報なんかがビルドオプションとして #define されるが、もとになる情報は、…\Arduino15\packages\switchscience\hardware\nrf52\0.1.8\boards.txt に書かれている。このファイルを見てみると、

isp1807breakout.build.board=NRF52840_PCA10056
...
isp1807microboard.build.board=NRF52840_PCA10056
...

のように、別製品のISP1807ピッチ変換基板と同じボード名になっていた。NRF52840_PCA10056 という名前は、Nordic社の開発キットである NRF52840 DK のボードと同じで、なおかつDK由来の製品にもいくつか使われているようだった。Adafruitのboards.txtにも、…board=NRF52840_PCA10056 という記述がある。

今回作るようなスケッチでは、#ifdef NRF52840_PCA10056  とかする必要もないのでいいのだが、いろいろなMCUやボードに対応したライブラリ( USB Host Shield Library 2.0とか ) にISP1807 MB対応を追加したいときなど、ちょっとまずいかもしれない。

追記 ボードの識別子は変更された

スイッチサイエンス社のnRF52 Arduino ボードサポートパッケージ(BSP)は、2021年9月はじめに0.1.9版が公開された。この版では以下のようにおのおのユニークなボード名称に変更されている。

isp1807breakout.build.board=SSCI_ISP1507_BREAKOUT
...
isp1807microboard.build.board=SSCI_ISP1807_MICRO_BOARD
...

これからターゲットボードの切り分けのために #ifdef …. なんて書くときには SSCI_ISP1807_MICRO_BOARD を使えば良くなったわけで、先に書いた危惧は解消されました。

ついでに書いておくと、Adafruit のnRF52 Arduino BSPも1.0.0版が公開されている。まだまだ新しいライブラリなので、今後も更新が続くんでしょう。

メモリいっぱいで嬉しいです

とりあえず、以下のようなスケッチを書いてみた。

#include <bluefruit.h> 
void setup() {
  Serial.begin(115200);
}
void loop() {
  Serial.print("hello ISP1807\n");
  delay(1000);
}

ビルドしてみると、フラッシュとSRAMの使用量は以下のようになった。avr-size みたいなことを、arm-none-eabi-size というコマンドが実行している模様。GNU sizeのarm版なのだろう。

最大815104バイトのフラッシュメモリのうち、スケッチが42348バイト(5%)を使っています。
最大237568バイトのRAMのうち、グローバル変数が13652バイト(5%)を使っていて、ローカル変数で223916バイト使うことができます。

8ビットAVRと違ってメモリがふんだんにあって嬉しくなる。書き込んで実行すると、シリアルモニタには無事に Hello… と表示された。

書き込みがうまくいかないことがある

おおむねAVR Arduinoをターゲットにしているときと同じように、スケッチを書いて、ビルドして、ボードに書き込んで、シリアルモニタを見て…という流れを繰り返すのだけど、BLEを使ったスケッチ例を書き込んで実行しているとき(つまり、BLEが接続中のとき) など、マイコンボードに書き込む… が失敗することが多い。

Arduino IDEのメッセージ領域は、以下のような感じで表示が繰り返される。

...
"C:\\Users\\arduino\\AppData\\Local\\Arduino15\\packages\\adafruit\\tools\\arm-none-eabi-gcc\\9-2019q4/bin/arm-none-eabi-size" -A "C:\\Users\\arduino\\AppData\\Local\\Temp\\arduino_build_327994/ISP1807_MB_4x3_keypad_BLE.ino.elf"
最大815104バイトのフラッシュメモリのうち、スケッチが144112バイト(17%)を使っています。
最大237568バイトのRAMのうち、グローバル変数が14068バイト(5%)を使っていて、ローカル変数で223500バイト使うことができます。
シリアルポート「COM12」を1200bpsで開いて閉じる事によって、リセットを行っています。
PORTS {COM10, COM12, } / {COM10, COM12, } => {}
PORTS {COM10, COM12, } / {COM10, COM12, } => {}
PORTS {COM10, COM12, } / {COM10, COM12, } => {}
PORTS {COM10, COM12, } / {COM10, COM12, } => {}
....

つまり、USBを制御しているはずのCDCからブートローダーが起動できていないか、ブートローダーが転送用のシリアルポートを作ってくれないことがあるみたいだった。書き込み操作を繰り返すとうまくいくこともあるが、途中でタイムアウトして失敗することが多かった。

解決策としては、ISP1807 Microボードのリセットボタンを素早く二度押ししてブートローダーモードに入る。するとブートローダーが新しくシリアルポートを作ってくれるので、Arduino IDEのツール/シリアルポートでそれを選択してから マイコンボードに書き込む を実行するとうまくいくようだった。

それでも新しいシリアルポートができない(PCから見えない)ときは、あきらめてUSBケーブルを抜き、もう一度差してからやり直す。

UF2ブートローダーがブートローダーモードに入ると、ボード上の赤いLEDがPWMっぽく暗くなったり明るくなったりする。そして、USBメモリをPCにマウントしたときのように、Explorerに新しいドライブが開くから、ここにビルドしたファイルをドラッグしてもいいのかもしれない。今のところ、UF2フォーマットってなに? という知識レベルで、Arduinoのバイナリイメージを書けるのかどうか分からないし、なんか壊れると怖いので試していません

今回の回路

以前と同じキーパッドを使って、Adafruitのライブラリに依存したHIDキーボードとBLEキーボードを作ってみる。キーボードといっても12キーです。

ISP1807_MB_4x3_KEY

Pro Microと組み合わせたときとはスイッチマトリックスとマイコンの接続を変更し、LEDを1個追加した。以前はD2~D4で列を選択しD5~D8で行を読むようにしていたが、今回はD7~D9で列選択し、そのときの行の状態をD3~D6で読む。追加したLEDはD10ピンに接続した(HIGHで消灯、LOWで点灯)。

A_PRO_MICRO_4x3_KEY_1
Pro Micro + KeyPad.

これはProMicro版で、ISP1807 Micro版は下のようになった。

ISP1807 Micro + KeyPad

ボードのUSB端子がtype-Cになっただけでも、ずいぶん安心感がある。

ISP1807 MicroボードにはISP1807のポートに接続されたLEDが1つだけ載っている。オンボードLEDは、variant.hPIN_LED1として定義されており、LED_REDや(赤いのに)LED_BLUEといった別名でも使える。AdafruitのBLEライブラリを使うと、ペアリング待ちのときやホスト側との接続時にオンボードのLEDが点滅したり点灯したりする。できれば青いLEDも載せて欲しかった (50円高くてもいいので…)。

USB接続のHIDキーパッド化

以前はArduinoのKeyboardクラスを使ったが、今回はAdafruit_TinyUSB_Arduinoライブラリを利用した。以前の投稿と重複する部分もあるが、ハードウェアについての説明も兼ねて有線のUSB接続キーパッド用のスケッチを用意した。

//ISP1807_MB_4x3_keypad.ino
#include "Adafruit_TinyUSB.h"

Adafruit_USBD_HID hid;
#define REPORT_ID_KEYBOARD 0
uint8_t const report_descriptor[] = {  TUD_HID_REPORT_DESC_KEYBOARD(), };
static const uint8_t LED_D10 = 10;  // 外付けLED
// マトリックス構成
const  uint8_t scan_port[] = { 9, 8, 7 };
const  uint8_t data_port[] = { 3, 4, 5, 6 };
const uint8_t hid_codes[] = { // 発生コード(HID Usage ID)
  HID_KEY_1, HID_KEY_4, HID_KEY_7, HID_KEY_BACKSPACE, 
  HID_KEY_2, HID_KEY_5, HID_KEY_8, HID_KEY_0, 
  HID_KEY_3, HID_KEY_6, HID_KEY_9, HID_KEY_ENTER
};
// HID keyboard report data.
uint8_t report_keys[6] = {0};
uint8_t report_modifiers = 0;

// 全キースキャンして、押下されているキーのビットを1にした以下のようなビットマップを返す。
// (bit0)SW1 SW4 SW7 SW10 SW2 SW5 SW8 SW11 SW3 SW6 SW9 SW12(bit11)
uint16_t key_scan() {
  uint16_t key_pressed = 0;
  for(uint8_t col = 0; col < sizeof(scan_port); col++) {
    digitalWrite(scan_port[col], LOW);
    uint8_t row_data = 0;
    for(uint8_t row = 0; row < sizeof(data_port); row++)
      row_data |= (digitalRead(data_port[row]) ? 0 : 1 << row);
    digitalWrite(scan_port[col], HIGH);
    key_pressed |= (row_data << (sizeof(data_port) * col));
  }
  return key_pressed;    
}

void hid_led_notify(uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) {
  if (report_type == HID_REPORT_TYPE_OUTPUT && bufsize > 1) {
    uint8_t led_state = buffer[1];
    digitalWrite(LED_D10, (led_state & KEYBOARD_LED_CAPSLOCK) != KEYBOARD_LED_CAPSLOCK);
  }
}
void setup() {
  for(uint8_t i = 0; i < sizeof(scan_port); i++) {
    pinMode(scan_port[i], OUTPUT);
    digitalWrite(scan_port[i], HIGH);
  }
  for(uint8_t i = 0; i < sizeof(data_port); i++)
    pinMode(data_port[i], INPUT_PULLUP);
  pinMode(LED_D10, OUTPUT);
  digitalWrite(LED_D10, HIGH);
  hid.setPollInterval(5);
  hid.setReportDescriptor(report_descriptor, sizeof(report_descriptor));
  hid.setReportCallback(NULL, hid_led_notify);
  hid.setStringDescriptor("ISP1807_4x3_keypad");
  hid.begin();
  while(!USBDevice.mounted()) {
    digitalToggle(LED_D10);
    delay(200);
  }
}
// 入力レポート配列に新規キーを追加または削除して送信する
void sendReport(uint8_t key, bool pressed) {
  if (!pressed) {
    for(uint8_t i = 0; i < sizeof(report_keys); i++)
      if (report_keys[i] == key) {
        report_keys[i] = 0;
        break;
      }
  } else {
    uint8_t empty_slot = 0xff;
    for(uint8_t i = 0; i < sizeof(report_keys); i++) {
      if (report_keys[i] == key)
        return;
      if (report_keys[i] == 0 && empty_slot == 0xff) {
        empty_slot = i;
        continue;
      }
    }
    if (empty_slot == 0xff)
      return; // 既に6キー押されていたら何も送信しない
    report_keys[empty_slot] = key;
  }
  hid.keyboardReport(REPORT_ID_KEYBOARD, report_modifiers, report_keys);
}

void loop() {
  static uint16_t last_key_pressed = 0;
  uint16_t key_pressed = key_scan();
  uint16_t change = key_pressed ^ last_key_pressed;
  if (change) {
    uint16_t mask = 1;
    for(uint8_t i = 0; i < sizeof(hid_codes); i++, mask <<= 1)
      if (change & mask) sendReport(hid_codes[i], key_pressed & mask);
    last_key_pressed = key_pressed;
  }
  delay(5);
}

Adafruit TinyUSBライブラリを使う際には、#include “Adafruit_TinyUSB.h” をまず書いておく。そしてAdafruit_USBD_HID hid; により、Adafruit_USBD_HIDクラスのインスタンスを作成する。

キーボードの性質というか特性をあらわすディスクリプタもライブラリ内に用意されており、今回はキーボード用のTUD_HID_REPORT_DESC_KEYBOARD を利用した。このディスクリプタは、従来はArduinoライブラリを書き換えて実現していた、日本語キーボード用に拡張されたキーのHID Usage IDも入力レポートのコード範囲に含んでいるし、ホスト(PCなど)からのLED通知を処理するための定義も含んでいるから、手を加えずそのまま使える。

また、ライブラリ内にはコンシューマーデバイス用やシステムコントロール用のレポートディスクリプタ (TUD_HID_REPORT_DESC_CONSUMER および  TUD_HID_REPORT_DESC_SYSTEM_CONTROL ) も用意されているので、メディア制御(音量、Play/Pauseなど) やシステム制御(スリープ、シャットダウンなど) を行うキーパッドなどの実装も容易だろう。

uint16_t key_scan()

マトリックススキャンのために、選択側(scan_port[])と読み取り側(data_port[])のポートを、Arduinoのピン番号で定義している。

選択する列をLOWにしているときに行のポートピンを読み取ると、スイッチがオンになっているポートのみが0になる。0が得られたポートについてrow_dataのLSBから順に1を書いている。3列4行分のスキャンが完了すると、12ビットのスキャン結果 (key_pressed)ができあがるのでそれを返す。key_pressedのビット構成は以下のようになっている。

(bit0)SW1 SW4 SW7 SW10 SW2 SW5 SW8 SW11 SW3 SW6 SW9 SW12(bit11)

書き方逆かな。

loop() と sendReport()

void loop() ではkey_scan() を繰り返して実行し、前回のスキャン結果 (last_key_pressed) と比べてオンまたはオフになったビットがあれば、それに対応するキーコード(HID Usage ID)を hid_codes[] から拾って sendReport() に渡す。

void sendReport() では、キーボード入力レポート用のデータ ( report_keys[] ) にオンになったキーを追加し、オフになったキーを取り除くという処理を行って、最終的に Adafruit_USBD_HID クラスの keyboardReport() を呼び出してホスト側に入力レポートを送信している。report_keys[] の大きさは6バイトで、この実装では、7キー同時に押されたときの7キー目は捨てている。

今回は、修飾キー(Shiftキー、Ctrlキーなど) はないのでreport_modifiers は常に0 だが、常にShiftオンにするならば、report_modifiers = KEYBOARD_MODIFIER_LEFTSHIFT; などと書いておく。このあたりのマクロは、ライブラリの奥深くにある hid.h に定義されていた。

void setup()

ポートの入出力方向の設定と初期値の設定のあとで、HIDキーボードに必要な操作を行っている。そしてAdafruit_USBD_HID の初期化を行って、hid.begin() でこのHIDデバイスを追加して有効化し、ホストに登録されるのを待つ。このあたりは、Adafruitのhid_keyboard.inoサンプルを参考にしています。このライブラリのスケッチ例は、…/Adafruit_TinyUSB_Arduino/examples/HID 内にいくつか格納されている。

hid.setStringDescriptor(“ISP1807_4x3_keypad”); によってデバイスの説明文を登録してみたが、ここで設定した内容は、Windowsのデバイスマネージャーのヒューマンインタフェースデバイス / USB入力デバイスのプロパティの中の、バスによって報告されるデバイスの説明 にひっそりと表示されていた。

setStringDescriptor()

追記 LED通知について

最初に載せたコード(BSP 0.2.4依存)では、PCからのLED通知を以下のように受けていた。

void hid_led_notify(uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) { 
  if (report_type == HID_REPORT_TYPE_OUTPUT && bufsize > 1) { 
    uint8_t led_state = buffer[1];
....

BSP1.0.0からは、

if (report_type == HID_REPORT_TYPE_OUTPUT && bufsize > 0) { 
  uint8_t led_state = buffer[0];
...

とする必要がある。たぶん、こちらがライブラリ作者がもともと意図した動作なんでしょう。

BLE接続のHIDキーパッド化

先のUSB版に若干の修正を加えるだけでBLEキーボードとして動作した。やはりAdafruitのhid_keyboard.inoサンプル(Bluefruit52Lib内)を参考にした。

//ISP1807_MB_4x3_BLE.ino
#include <bluefruit.h>
BLEDis bledis;
BLEHidAdafruit blehid;

static const uint8_t LED_D10 = 10;  // 外付けLED
// マトリックス構成
const  uint8_t scan_port[] = { 9, 8, 7 };
const  uint8_t data_port[] = { 3, 4, 5, 6 };
const uint8_t hid_codes[] = { // 発生コード(HID Usage ID)
  HID_KEY_1, HID_KEY_4, HID_KEY_7, HID_KEY_BACKSPACE, 
  HID_KEY_2, HID_KEY_5, HID_KEY_8, HID_KEY_0, 
  HID_KEY_3, HID_KEY_6, HID_KEY_9, HID_KEY_ENTER
};
// HID keyboard report data.
uint8_t report_keys[6] = {0};
uint8_t report_modifiers = 0;

// 全キースキャンして、押下されているキーのビットを1にした以下のようなビットマップを返す。
// (bit0)SW1 SW4 SW7 SW10 SW2 SW5 SW8 SW11 SW3 SW6 SW9 SW12(bit11)
uint16_t key_scan() {
  uint16_t key_pressed = 0;
  for(uint8_t col = 0; col < sizeof(scan_port); col++) {
    digitalWrite(scan_port[col], LOW);
    uint8_t row_data = 0;
    for(uint8_t row = 0; row < sizeof(data_port); row++)
      row_data |= (digitalRead(data_port[row]) ? 0 : 1 << row);
    digitalWrite(scan_port[col], HIGH);
    key_pressed |= (row_data << (sizeof(data_port) * col));
  }
  return key_pressed;    
}
void led_notify(uint16_t handle, uint8_t _led_state) {
  digitalWrite(LED_D10, (_led_state & KEYBOARD_LED_CAPSLOCK) != KEYBOARD_LED_CAPSLOCK);
}
// 入力レポートに新規キーを追加または削除して送信する
void sendReport(uint8_t key, bool pressed) {
  if (!pressed) {
    for(uint8_t i = 0; i < sizeof(report_keys); i++)
      if (report_keys[i] == key) {
        report_keys[i] = 0;
        break;
      }
  } else {
    uint8_t empty_slot = 0xff;
    for(uint8_t i = 0; i < sizeof(report_keys); i++) {
      if (report_keys[i] == key)
        return;
      if (report_keys[i] == 0 && empty_slot == 0xff) {
        empty_slot = i;
        continue; // わざわざ6回まわすこともないか。
      }
    }
    if (empty_slot == 0xff)
      return; // 既に6キー押されていたら何も送信しない
    report_keys[empty_slot] = key;
  }
  blehid.keyboardReport(report_modifiers, report_keys);
}

void start_advertising() {
  // Advertising packet
  Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
  Bluefruit.Advertising.addTxPower();
  Bluefruit.Advertising.addAppearance(BLE_APPEARANCE_HID_KEYBOARD);
  
  Bluefruit.Advertising.addService(blehid);
  Bluefruit.Advertising.addName();
  Bluefruit.Advertising.restartOnDisconnect(true);
  Bluefruit.Advertising.setInterval(32, 244);    // in unit of 0.625 ms
  Bluefruit.Advertising.setFastTimeout(30);      // number of seconds in fast mode
  Bluefruit.Advertising.start(0);                // 0 = Don't stop advertising after n seconds
}

void setup() {
  for(uint8_t i = 0; i < sizeof(scan_port); i++) {
    pinMode(scan_port[i], OUTPUT);
    digitalWrite(scan_port[i], HIGH);
  }
  for(uint8_t i = 0; i < sizeof(data_port); i++)
    pinMode(data_port[i], INPUT_PULLUP);

  pinMode(LED_D10, OUTPUT);
  digitalWrite(LED_D10, HIGH);
  Bluefruit.begin();
  Bluefruit.Periph.setConnInterval(9, 16);
  Bluefruit.setTxPower(4);    // Check bluefruit.h for supported values
  Bluefruit.setName("ISP1807_MB_4x3_BLE");
  // Configure and Start Device Information Service
  bledis.setManufacturer("unknown");
  bledis.setModel("ISP1807_MB_4x3_BLE");
  bledis.begin();
  blehid.begin();
  blehid.setKeyboardLedCallback(led_notify);
  start_advertising();
}

void loop() {
  static uint16_t last_key_pressed = 0;
  uint16_t key_pressed = key_scan();
  uint16_t change = key_pressed ^ last_key_pressed;
  if (change) {
    uint16_t mask = 1;
    for(uint8_t i = 0; i < sizeof(hid_codes); i++, mask <<= 1)
      if (change & mask) sendReport(hid_codes[i], key_pressed & mask);
    last_key_pressed = key_pressed;
  }
  delay(5);
}

USB版と比べると、キーパッドとの接続やキーの取得方法などはまったく同じで、キー入力レポートの送信部分もUSB版とほとんど変わらない。BLE化のためにしていることは以下のような感じ。

Bluefruit インスタンス

AdafruitのnRF52用APIの核となっているAdafruitBluefruit クラスののインスタンス Bluefruit を作成する。このクラスのAPIとして用意されている begin() の呼び出し以降、インスタンス内部の初期化やプロトコル・スタックにあたるSoftDeviceの構成を行っている。

start_advertising() では、Bluefruit がもつアドバタイジングインスタンス (BLEAdvertising Advertising)   を使って、BLEのHIDキーボードですよ、接続させてくださいよ、お願いしますよ、と電波を飛ばす。セントラル(PCやスマホ)が接続を受け入れてくれるとアドバタイジングは停止し、接続が切れると再開するようだった。

BLEDis bledis;

Device Information Service (GATT 0x180A) のインスタンス。bledisにプロパティをセットすることで、デバイスの名前や製造元などが公開される。

BLEHidAdafruit blehid;

HIDプロファイルの実装になっていて、blehid .begin() とやるだけで、ライブラリが用意してくれているレポートディスクリプタ(キーボード、コンシューマー、マウス)の追加や初期化などをやってくれる。blehid.setKeyboardLedCallback(led_notify);  によりホスト側からの出力レポート(LED状態通知)が led_notify() に届くようになるので、適宜処理している。

キーのオン/オフを検出したら、USB版と同じように sendReport() 内でblehid.keyboardReport() を呼び出している。このメソッドはUSB版と同じ名前だが、USB版とは引数が違っていて、レポートIDを指定する必要がない。これは、APIの内部でレポートIDを補っていることと、ライブラリが複数のコネクションをサポートしているためだろう。

USBとBLEの合体?

スケッチを書いてて思ったのは、BLE接続機能とUSB接続機能を合体させたクラス、つまり、Adafruit_USBD_HID BLEHidAdafruit の、おのおのインスタンスを内部にもつラッパークラスを作ってやれば、一本でどちらもに対応できるんじゃなかろうか、ということ。今回は見送るが、次の機会にでも試してみます。

Windows10との接続

Windows10のPC(BTドングル付き)に接続してデバイスマネージャを見ると、以下のように名前も表示されていた。

BLE接続済

接続の様子は動画にしてみた。

Windows10の設定/デバイスには、すでに “Switch Science ISP1807 Micro Boa” というデバイスが見えているが、これは初回にUSB接続した時点でUSBコンポジットデバイスとしてシステムに組み込まれたもので、シリアルデバイス(CDC)の親になっている。ISP1807MBをUSBキーボードとした場合もこのデバイスの子になるので、スケッチに書いた名前は設定/デバイスには表示されなかった。

きょうのまとめ

Adafruitのライブラリのおかげで、開封して数日で出来上がるほど、簡単にBLEのキーパッドが実現できた。今までPro Microを使ってきた電子工作基板にISP1807 MBを差せば、あんがいと簡単にBLE化できるかもしれない。

留意すべき点としては、8ビットのAVRマイコンに特化したプログラムコード(特にポートピンへの直接のアクセスとか、内部レジスタの操作とか)は当然使えないので、適宜コードを修正していく必要がある。

BLE接続済の状態で消費電流を見てみると、テスターの都合もあって、数mA~10mA程度と満足のいく値だった。ライブラリで使っているパラメータを変えてみたり、キーボードのスキャンの頻度を下げたり暇なときはスリープしたりと、省電力のためにやれることはいろいろあるだろう。

個人的には、hoboNicolaアダプタのBLE化に使いたいので、UHSライブラリ2.0のISP1807対応とかTinyUSB対応とかすすめてみます。