ユーザ用ツール

サイト用ツール


ハイハイスクールアドベンチャー_m5stack_m5cardputer版

差分

このページの2つのバージョン間の差分を表示します。

この比較画面へのリンク

両方とも前のリビジョン前のリビジョン
次のリビジョン
前のリビジョン
ハイハイスクールアドベンチャー_m5stack_m5cardputer版 [2024/02/07 00:20] – [ビルド] arakiハイハイスクールアドベンチャー_m5stack_m5cardputer版 [2025/04/25 01:56] (現在) – [BTキーボードをつなごう] araki
行 34: 行 34:
 作っている最中に M5Cardputerもリリースされたため、これにも対応を行った。 作っている最中に M5Cardputerもリリースされたため、これにも対応を行った。
  
-M5Stack Core (BASIC, Grey, Fire)および Core2、また M5Cardputerに対応しているが、M5Stack Core/Core2については FACESのキーボードモジュールが存在していることが前提である。 +M5Stack Core (BASIC, Grey, Fire)および Core2、また M5Cardputerに対応しているが、M5Stack Core/Core2については FACESのキーボードモジュールが存在している<del>ことが前提である</del>か、あるいはBLEキーボードが必要である。 
-入手困難なようなので、見かけたらゲットされたい。+FACESに関しては入手困難なようなので、見かけたらゲットされたい。
  
 また、データをmicroSDカードにストアして利用するためmicroSDカードも必須である。 また、データをmicroSDカードにストアして利用するためmicroSDカードも必須である。
行 47: 行 47:
 ソースは、GitHubから取得して、Platform IOのプロジェクトとしてビルドする。 ソースは、GitHubから取得して、Platform IOのプロジェクトとしてビルドする。
  
-<code>+<code bash>
 $ git clone https://github.com/wildtree/hhsadv.git $ git clone https://github.com/wildtree/hhsadv.git
 </code> </code>
行 57: 行 57:
 M5Stack Core2であっても M5Stack-grey でビルドしたバイナリを使用可能である。 M5Stack Core2であっても M5Stack-grey でビルドしたバイナリを使用可能である。
 M5 Cardputer用は M5Cardputerをターゲットとしてビルドしたものを使用する。 M5 Cardputer用は M5Cardputerをターゲットとしてビルドしたものを使用する。
 +Ver 1.6より m5atomExtDisplay というターゲットが追加されており、M5Atom + 240x240 SPI液晶モジュール + BLEキーボードでのプレイが可能になっている。M5Atom + SPI液晶に関してはしかるのちさん (@shikarunoci)からコードの提供をいただいた。
  
 {{::hhsadv:platformio_targets.png?400|PlatformIOターゲット}} {{::hhsadv:platformio_targets.png?400|PlatformIOターゲット}}
行 77: 行 78:
 幸い、このあたりも M5Cardputerのライブラリがまとめて面倒を見てくれているので、アプリを書くにあたっては困らないが、コードに差異が生じるので理解しておかないといけない。 幸い、このあたりも M5Cardputerのライブラリがまとめて面倒を見てくれているので、アプリを書くにあたっては困らないが、コードに差異が生じるので理解しておかないといけない。
  
-お、M5シリーズは、ぽいぽいピンアサインが変わるので、このあたりも留意していなければならない。+==== ピンアサインど ==== 
 + 
 +M5シリーズは、ぽいぽいピンアサインが変わるので、このあたりも留意していなければならない。
 SDカードやキーボードの割り当てはちゃんと機種を見て動作を変えないといけない部分だ。 SDカードやキーボードの割り当てはちゃんと機種を見て動作を変えないといけない部分だ。
 +
 +例えば、SDカードのマウントは以下のようにしている。
 +Core/Core2なら 用意されているSPIを使えば動くが、Cardputerはそこもまとめて面倒をみないといけない。
 +
 +<code cpp>
 +    cfg.clear_display = true;
 +    M5.begin(cfg);
 +    uint8_t ssPin = M5.getPin(m5::pin_name_t::sd_spi_ss);
 +    if (M5.getBoard() == m5::board_t::board_M5Cardputer)
 +    {
 +      M5Cardputer.begin(cfg);
 +      spi.begin(
 +        M5.getPin(m5::pin_name_t::sd_spi_sclk),
 +        M5.getPin(m5::pin_name_t::sd_spi_miso),
 +        M5.getPin(m5::pin_name_t::sd_spi_mosi),
 +        M5.getPin(m5::pin_name_t::sd_spi_ss)
 +      );
 +    }
 +    else
 +    {
 +      spi = SPI;
 +    }
 +    M5.Display.setRotation(1);
 +    Serial.printf("Free heap size: %6d\r\n", esp_get_free_heap_size());
 +    // mount SD (need for M5Unified library)
 +    while (false == SD.begin(ssPin /*GPIO_NUM_4*/, spi, 25000000))
 +    {
 +      M5.Display.println("SD Wait ...");
 +      delay(500);
 +    }
 +</code>
 +
 +キーボードも Cardputerはさておき、CoreとCore2とではピンアサインが違うため、コードを変えないといけない。
 +こっちは、KeyBoardという仮想クラスを作っておいてボードによってインスタンスを変えることで対応している。
 +
 +CoreはWireでINTR=5だが、Core2はWire1でINTR=33だ。
 +
 +<code cpp>
 +class M5StackKeyBoard : public KeyBoard
 +{
 +protected:
 +public:
 +    M5StackKeyBoard() : KeyBoard(m5::board_t::board_M5Stack)
 +    {
 +        Wire.begin();
 +        pinMode(INTR, INPUT);
 +        digitalWrite(INTR, HIGH);
 +    }
 +    virtual ~M5StackKeyBoard() { Wire.end(); }
 +    virtual bool wait_any_key() override;
 +    virtual bool fetch_key(uint8_t &c) override;
 +
 +    static const int INTR = 5;
 +};
 +
 +class M5Core2KeyBoard : public KeyBoard
 +{
 +protected:
 +public:
 +    M5Core2KeyBoard() : KeyBoard(m5::board_t::board_M5StackCore2)
 +    { 
 +        Wire1.begin();
 +        pinMode(INTR, INPUT);
 +        digitalWrite(INTR, HIGH);
 +    }
 +    virtual ~M5Core2KeyBoard() { Wire1.end(); }
 +    virtual bool wait_any_key() override;
 +    virtual bool fetch_key(uint8_t &c) override;
 +
 +    static const int INTR = 33;
 +};
 +</code>
 +
 +==== BTキーボードをつなごう ====
 +
 +ESP32を使っているのだから、BTキーボードをつなげられたら、FACESがなくてもハイハイスクールアドベンチャーを遊べる。
 +BTキーボードをM5 Stackに繋いだ話は[[https://shikarunochi.matrix.jp/?p=4529|しかるのちさんのところ]]にあったので、多分つながる。
 +
 +問題は、上記の話以外はESP32とBTキーボードで探すと、USBキーボードをESP32を使ってBTキーボードに仕立てる話ばかりで、肝心のBTキーボードをESP32に接続する話は見当たらない。
 +需要がないのか?
 +
 +しかるのちさんによれば、esp_8_bit ではBLE((Bluetooth Low Energy))キーボードがつながらないらしいが、今更作るなら BLEキーボードがつながればいい。
 +大体が、PlatformIOで引っかかってくるライブラリは NimBLEなので、BLEで行く方がいい。
 +他人のプロジェクトをぺろっと自分のプロジェクトに張り付けるのはなんともいえない座りの悪さもあるし、あと、esp_8_bitのコードはちょっと気になる部分もあったので、使いたくなかったのだ。
 +
 +=== BT(BLE)のお約束 ===
 +
 +BTの約束事として、接続するものの片方をサーバ、残りをクライアントと呼ぶ。
 +さて、M5 StackとBLEキーボード、どっちがサーバでどっちがクライアントでしょう?
 +
 +答えはM5 Stackがクライアントで、BLEキーボード側がサーバになります。
 +最初、サーバ側のサンプル読んで始めようとしていたので、全然間違ってたわけです。
 +esp_8_bitで気になるところというのは、hid_serverとかhci_serverとか、サーバ側であるかのような名前を使っているところが気になったのです。
 +
 +クライアント側はサーバー側の Advertise を拾って、クライアントとして接続をかけに行きます。
 +
 +接続が成立したら、サーバ側から、Characteristic の一覧をもらって、キーボードの場合なら、キーボード側からの通知を拾ってキーを受け取るという動作になります。
 +
 +HID_DEVICEのUUIDは 0x1812と決まっています。
 +これを見つけて接続していきます。
 +
 +バッテリーの残量やらなんやらを渡してくる Characteristic なんかもあるんですが、それはここでは関係ないので割愛します。
 +簡単。
 +
 +キーボードからのデータは HID_REPORT_DATA == 0x2A4D で渡されます。
 +
 +=== キーボードの挙動 ===
 +
 +キーボードが持っている Characteristic については、結構たくさんあるのですが、要は、キーを拾えればいいのです。
 +調べたところ、手元のキーボードではHandle == 41 で通常のキーが、 Handle == 51でメディアキー((音量をいじったり、液晶の明るさをいじったり))を受け取るようになっています。
 +
 +キーボードが返してくる通常キーについては11バイトのデータ構成で、先頭が modifiers ((1:CTRL, 2:SHIFT, 4:Alt, 8:Cmd))で、残り10バイトがキーコードになります。全部使えば10キーロールオーバーになるんでしょうが、多分その辺はキーボードによって違うようです。
 +
 +キーが押されたり離されたりすると、そのたびに通知がやってきます。
 +
 +キーが押されたのか離されたのかをどっかのフラグに持っていてくれるとよかったのですがそうではないようです。
 +
 +キーが modifiers 以外には一文字ずつしか押されないと仮定すると、キーが押されたときにはキーコードが渡され、キーが離されると0 がデータとしてわたってきます。
 +
 +Nキーが同時に押されているなら、押されている全部のキーのキーコードが入ってくるのですが、離されると離されたキーの値が0になって残りのキーコードは入ったままになってきます。
 +この時、0でないキーコードは先頭側に詰められてきます。
 +
 +真面目にドライバーを書くなら、いちいちキーコードがどう変化したのかを記録してやらないとダメそうですが、基本的に複数キーが押されることはないだろうハイハイスクールアドベンチャーなら、あんまり真剣に考えないでも良さそうです。
 +
 +0じゃない文字数を数えて、前回より増えていたら新しくキーが押されたとして扱います。
 +本当はこれだと色々具合が悪いのですが、今回はよしとします。
 +まあキーリピートもききませんが、今回はまあいいでしょう。
 +
 +新たにキーが押されたと判定したら、渡されてきたバッファのキーコードを modifiersを考慮して、対応するASCIIコードに変換してキューに格納し、ゲーム側から求められたら先頭から順に渡していきます。
 +
 +この辺も手抜きで、modifiersについてはCTRLが押されていたらそれを優先してあとはSHIFTしか考慮していません。AltやCmdは無視しています。
 +
 +とりあえずこれでBTキーボードが使えるようになりました。
 +ゲームには支障はない感じですが、アクションげーんとかを考えてる人はもう少し真剣に考えないといけないと思います。
 +
 +=== コード片 ===
 +
 +ハイハイスクールアドベンチャーはキーボード周りを KeyBoardクラスの派生クラスとして扱っている。
 +M5 Stackグレー, M5 Stack Core2, Cardputer 用のキーボードクラスがそれぞれ定義されているが、ここにBTキーボードクラスも追加する。
 +
 +但し、NimBLEのAPIは通知を受け取る notify callback 関数を渡さないといけないので、完全にこのクラス内に閉じることができない。
 +
 +なので、keyboard.cpp 内に閉じた関数や変数にそこそこ依存している。
 +
 +== 変換テーブル ==
 +
 +キーコードの変換テーブルは英字配列を想定している。
 +0~95の96種類のキーコードを扱えるが、抜けているところは手元のキーボードにはないキーである。
 +三組定義されていて、modifiersなし、CTRL、SHIFTの組み合わせになっている。
 +JIS配列に対応させたい場合はこれを書き換えることになる。
 +
 +<code cpp>
 +const uint8_t BTKeyBoard::_keymap[][96] = {
 +    {    0,   0,   0,   0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
 +       'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2',
 +       '3', '4', '5', '6', '7', '8', '9', '0',  13,  27,   8,   9, ' ', '-', '=', '[',
 +       ']','\\',   0, ';','\'', '`', ',', '.', '/',   0,   0,   0,   0,   0,   0,   0,
 +         0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 127,   0,   0,   0,
 +         0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
 +    },
 +    {
 +         0,   0,   0,   0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,
 +        13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,   0,   0,
 +         0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
 +         0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
 +         0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
 +         0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
 +    },
 +    {
 +         0,   0,   0,   0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
 +       'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '@',
 +       '#', '$', '%', '^', '&', '*', '(', ')',  13,  27,   8,   9, ' ', '_', '+', '{',
 +       '}', '|',   0, ':', '"', '~', '<', '>', '?',   0,   0,   0,   0,   0,   0,   0,
 +         0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0, 127,   0,   0,   0,
 +         0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
 +    },
 +};
 +</code>
 +
 +
 +== 通知処理 ==
 +
 +手抜きの極みのキー処理である。
 +キーボードからの通知が来るたびに、これが呼び出される。
 +Handle == 41は modifiers + 10文字分のキーバッファだが Handle == 29は modifiers + res + 6文字分のキーバッファとなっている。
 +<del>そもそも Handle で見分けるのが正しいのかさえよくわかっていないがとにかく試して動いたキーボードはこのどちらかだった。</del>
 +実はキーボードからのキー入力の通知についてはハンドルで見分けることができる。
 +
 +btstack でBLEキーボードをつないだ時に知ったのだけれど、Notification で渡されるパケットには実は二種類のハンドルが入っている。
 +一つが attribute handle で、もう一つが value handleだ。
 +
 +私が調べたところ、attribute handle == 0x0040 のものがキーボードからのキー入力の通知になる。
 +なので、btstack ではそのようにしてふるっているし、NimBLEでもそうするべきだろうと思ったのだが、NimBLERemoteCharacteristic クラスは、value_handleしか取得できない。
 +
 +value_handleの方はキーボードによってまちまちの値を返すので、使い物にならない。
 + 
 +どうにかして attribute handle を取り出せないものかと NimBLEのコードを掘っていったら、あろうことか、attribute_handleの方は中で捨ててた。
 +
 +よって、NimBLEでは Notification Event Handlerの中でハンドルで処理を分けるやり方はできない、というのが結論である。
 +
 +動作は単純で、前回渡されたデータと、今回渡ってきたデータとでキーバッファ部分を比べて、新しいのがあったらそれが今回入力されたキーだとみなして、keybufに積んでいく。
 +これは、例えば、A, B, Cのキーを押しっぱなしにして、新しく D を押したときに、バッファには A, B, C, Dの四文字が入ってくるからである。古いバッファは A, B, Cのみだったので Dが追加で押されたことがわかる。
 +
 +データは keybufに格納され、あとで、BTKeyBoard::update()が呼び出されるときに処理される。
 +
 +
 +<code cpp>
 +typedef union {
 +    struct __attribute__((__packed__))
 +    {
 +        uint8_t modifiers;
 +        uint8_t keys[10];
 +    } k1;
 +    struct __attribute__((__packed__))
 +    {
 +        uint8_t modifiers;
 +        uint8_t reserved;
 +        uint8_t keys[6];
 +        uint8_t padding[3];
 +    } k2;
 +    uint8_t raw[11];
 +} keyboard_t;
 +
 +
 +static keyboard_t keyboardReport;
 +static std::queue<uint16_t> keybuf;
 +
 +static void
 +notifyCallback(NimBLERemoteCharacteristic *pRemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify)
 +{
 +    // handle: 41 -- key / 51 -- media key
 +    switch (pRemoteCharacteristic->getHandle())
 +    {
 +        case 22:
 +        case 29:
 +        case 41:
 +            if (length != 8 && length != 11) return; // not key data interface (maybe)
 +            keyboard_t *newKeyReport = (keyboard_t*)pData;
 +            int buflen = 6;
 +            uint8_t *buf = keyboardReport.k2.keys;
 +            uint8_t *input = newKeyReport->k2.keys;
 +            uint8_t mod = newKeyReport->k2.modifiers;
 +            if (length == 11)
 +            {
 +                buflen = 10;
 +                buf = keyboardReport.k1.keys;
 +                input = newKeyReport->k1.keys;
 +                mod = newKeyReport->k1.modifiers;     
 +            }
 +            for (int i = 0 ; i < buflen ; i++)
 +            {
 +                uint8_t c = input[i];
 +                if (c == 0) continue;
 +                if (memchr(buf, c, buflen) == NULL) keybuf.push(((uint16_t)mod << 8)|c);
 +            }
 +            memcpy(&keyboardReport, pData, length);
 +            break;
 +    }
 +}
 +</code>
 +
 +== つながるキーボードつながらないキーボード ==
 +
 +BLEキーボードっていっているのにつながるやつとつながらないやつがありまして、なんでだろうと思っていたのです。
 +思えば、最初にテストに使った、バッファローのBSKBB335がつながる方のやつだったので、深く考えないで、かつさっさと実装を終えられたのですが。
 +
 +それでもつながらないのがあるというのが気になります。
 +BLEの実装に関しては、ペリフェラル側((サーバー側))に多く、セントラル側((クライアント側))については、ほぼ、NimBLEのサンプルとその亜種しかないのです。
 +
 +ハイハイスクールアドベンチャーについても、もちろん、亜種の一つです。
 +
 +通知可能のCharacteristicは端から notfy()をかけてみても、まったく一つも callbackを返してきません。
 +悶々とします。
 +
 +で、gatttool を使ってもう少し掘ってみることにしました。
 +
 +<code bash>
 +$ gatttool -I -b xx:xx:xx:xx:xx:xx
 +...
 +[xx:xx:xx:xx:xx:xx][LE]> char-read-uuid 2a4d
 +Error: Read characteristics by UUID failed: Encryption required before read/write
 +[xx:xx:xx:xx:xx:xx][LE]>
 +</code>
 +
 +え、これって、セキュアな接続をはらないとダメってことですか?
 +
 +なんで、コードにごそごそと追加を行います。
 +
 +<code cpp>
 +        pClient = NimBLEDevice::createClient();
 +        Serial.println("A new client created.");
 +        pClient->setClientCallbacks(new ClientCallbacks(), false);
 +        pClient->setConnectionParams(12, 12, 0, 51);
 +        pClient->setConnectTimeout(5); // 5sec
 +        if (!pClient->connect(advDevice))
 +        {
 +            NimBLEDevice::deleteClient(pClient);
 +            Serial.println("Failed to connect.");
 +            return false;
 +        }
 +        if (!pClient->secureConnection())
 +        {
 +            Serial.println("Failed to establish secure connection.");
 +            return false;
 +        }
 +</code>
 +
 +そうそう。
 +初期化もちょいと変えてやります。
 +
 +<code cpp>
 +    NimBLEDevice::init("");
 +    NimBLEDevice::setSecurityAuth(true, true, true); // all true if requires secure connection.
 +    NimBLEDevice::setPower(ESP_PWR_LVL_P9);
 +    NimBLEScan *pScan = NimBLEDevice::getScan();
 +    pScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks());
 +    pScan->setInterval(48);
 +    pScan->setWindow(48);
 +    pScan->setActiveScan(true);
 +    pScan->start(scanTime);
 +    while (pScan->isScanning())  // NimBLE 2.xからは start()が非同期になった様子。
 +    {
 +        sleep(100);
 +    }
 +</code>
 +
 +NimBLEDevice::setSecurityAuth()は全部trueにしてやります。
 +これで無事につながらなかったキーボードがつながるようになりました。
 +
 +== キーボードが再接続できない ==
 +
 +BLEキーボードを接続した状態でしばらく何もしないで放置していると、キーボード側がパワーセーブモードに入って接続が切れます。
 +クライアント側には onDisconnect() の通知が送られて、接続が切れたことが認識されます。
 +
 +ここで、デバイスの再検索を始めて、打鍵するなどして復帰したキーボードから Advertiseがやってくればめでたく再接続になります。
 +
 +NimBLE のサンプルやそれを参考にした多くのコードが、次のような実装をしていたので私もそうしていました。
 +
 +<code cpp>
 +void
 +ClientCallbacks::onDisconnect(NimBLEClient* pClient)
 +{
 +    Serial.print(pClient->getPeerAddress().toString().c_str());
 +    Serial.println(" disconnected - Starting scan");
 +    NimBLEDevice::getScan()->start(BTKeyBoard::scanTime);
 +}
 +</code>
 +
 +一見すると何の問題もありません。
 +実際、シリアルコンソールにも  disconnected - Starting scan と表示されて、いかにも動いているように見えますが実は全然動いていません。
 +一向にデバイスが再接続されないのです。
 +困った。
 +これでは、一気通貫にハイハイスクールアドベンチャーを最後までプレイするのでなければ、ゲームの途中でにっちもさっちもいかなくなってしまいます。
 +
 +さて、原因はなんでしょう?
 +もしかすると、一度接続したデバイスからの Advertiseを無視しているのかもしれません。
 +ならば <code cpp>NimBLEDevice::getScan()->setDuplicateFilter(true);</code>とかすればいいのかもしれないと思って試してみました。
 +再接続はうまくいかないし、そもそも、最初の接続が完了するまで何度も Advertiseのコールバックがかかってきてしまいました。
 +筋違いのようです。
 +
 +じゃあ、なんなんだろう?
 +
 +ふと、callbackの中から NimBLEDevice::getScan()->start(0); とか呼んじゃダメなんじゃないか?ということに気づきました。
 +これ、つまりコールバック処理が完了しなくなって、おかしなことになっている可能性があるんじゃないかと。
 +
 +で、次のようにしました。
 +
 +<code cpp>
 +static volatile bool connected = false;
 +
 +void
 +ClientCallbacks::onDisconnect(NimBLEClient* pClient)
 +{
 +    Serial.print(pClient->getPeerAddress().toString().c_str());
 +    Serial.println(" disconnected");
 +    connected = false;
 +}
 +</code>
 +
 +で、メイン処理の中で、
 +
 +<code cpp>
 +    if (!connected)
 +    {
 +        Serial.println("Start scan.");
 +        NimBLEDevice::getScan()->start(scanTime);
 +    }
 +</code>
 +
 +という処理を足してやりました。
 +
 +結論。
 +
 +あっさり再接続しました_ノ乙(、ン、)_
 +
 +<code>
 +Connect to BLE Keybord.
 +Advertised HID Device found: Name: Notepad8, Address: XX:XX:XX:XX:XX:XX, appearance: 961, manufacturer data: YYYYYYYYYYYYYYYYYYYYYY, serviceUUID: 0x1812
 +Start connecting to server...
 +A new client created.
 +BLE Device connected.
 +Connected to XX:XX:XX:XX:XX:XX
 +RSSI: -60
 +Done.
 +XX:XX:XX:XX:XX:XX disconnected
 +Start scan.
 +Advertised HID Device found: Name: Notepad8, Address: XX:XX:XX:XX:XX:XX, appearance: 961, manufacturer data: YYYYYYYYYYYYYYYYYYYYYY, serviceUUID: 0x1812
 +Start connecting to server...
 +BLE Device connected.
 +Reconnected.
 +Connected to XX:XX:XX:XX:XX:XX
 +RSSI: -62
 +Done.
 +</code>
 +
 +== キーバッファ ==
 +
 +キーバッファの仕様は何種類か存在しているようである。
 +
 +最も多い実装は8bytesで、modifiers + padding + key buffer (6bytes) という構成で、手元ではバッファローのBSKBB335だけが11bytes で modifiers + key buffer (10bytes)というものである。
 +
 +キーバッファのサイズだとか、多分アラインメントを考慮したであろうパディングの有無や全体長などの差異はあれど、渡ってくる情報(modifiersやキーコード)に違いはない。
 +
 +が、キーバッファーの部分の扱いには、解釈違いのものがあるようである。
 +
 +当初、試したキーボード((BSKBB335/NimBLE版 Cardputer BT キーボード))では、キーが押されたときには先頭からキーコードが入って送られてきた。
 +なので、常にキーが押されればキーバッファの先頭にはキーコードがあるという想定だった。
 +
 +他のキーボードでもそれで問題は特にはなかった。
 +
 +が、ちょっと色気(?)を出して、シフトキーを押したりコントロールキーを押したりしたときにそれは起きた。
 +
 +文字が入力されない。
 +
 +仕方がないので、デバッグです。
 +調べてみたら、キーは渡ってきてます。
 +但し、なぜか二文字目で先頭は0です。
 +どうやら、キーボードによっては、modifiers が押されたのもキーバッファを消費する((ただしよりにもよってキーコードは0))ようです。
 +なので、0ならそこで打ち切りとしていた処理を、0なら飛ばすように変更しました。
 +
 +== NimBLE 2.2.3 ==
 +
 +NimBLEが1.x系から2.x系になりました。
 +メモリの使用量などが改善しているという触れ込みなので、メモリが余裕というわけではないESP32環境では乗り換えないという選択肢はないでしょう。
 +
 +移行の大筋は[[https://github.com/h2zero/NimBLE-Arduino/blob/master/docs/1.x_to2.x_migration_guide.md|Migrating from 1.x to 2]]あたりを参照しながらやればいいでしょう。
 +
 +大きな変更としては、ポインタ渡しだったものが参照渡しや値渡しになっていたり、Advertiseの Callbackを受けるクラスが NimBLEScanCallbacksになっていたりするのと、NimBLEClientCallbacsのイベントハンドラの名前や引数がちょいちょい変わっているのと、あとは時間に関する指定が、sec だったり msだったりしてたのが msに統一されたというあたりでしょうか。
 +
 +まあ、intellisenseが効いている VSCode で編集していれば、変更はほぼ機械的に、赤くなっているエラー行を入れ替えていって、あとは時間に関する値の見直しを入れていくくらいでしょうか?
 +
 +わたしは、NimBLEClient::setConnectTimeout()の値を修正し忘れて((1.xではsec指定だったのが 2.xで ms指定に変わっているので1000倍しないとダメ。))接続、即切断を繰り返して動かないというのをやらかしました。
 +
 +ただ、本当に厄介なのはそういう部分じゃなくて、同じな態でいるのに、実は挙動が違うという奴です。
 +具体的には **NimBLEDevice::getScan()->start(scanTime)**です。
 +
 +1.xでは、この関数を呼び出すと、Advertiseを拾って、stopがかかるまでここでブロックされていました。((だったはずです。キーボードが接続待ちになるまでここで止まっていたので。))
 +
 +ところが、2.xではこの関数は即座に戻ってきて処理を続行し、裏で待ち受け処理を行うようになっていました。
 +
 +何が問題なのかというと、Scan Stopがかかって、接続可能な状態になっている前提で組まれているコードが、Scan中なのにやってきて、しかもまだデバイスが見つかっていないので、管理用のステータスが「未接続」となっているものだから、別のところから再び scan をかけようとするのです。
 +
 +1.xでどうだったかは知りませんが、2.xではこの状況になるとデフォルトでは先行するスキャンでやりかかっていた処理やデータはクリアされて最初からやり直しをするようになっています。
 +
 +この時、先行するスキャンがデバイスを見つけて、データに触り始めている状態だと、触っているデータがクリアされてしまい例外を吐いて落ちてしまうのです。
 +
 +当然、この時どこを触っているのかはその時々で違うので、backtrace を手繰ってもその都度違うところで例外が出るので最初原因が何なのかわからずに大変困惑しました。
 +
 +対処として、start()をかけうるコードの前に、NimBLEDevice::getScan()->isScanning() のチェックを入れて、スキャン中に再スキャンを開始しないようにしてあります。
 +
 +また、初期化部分では、スキャンが終わるまでループするようにも修正しました。
 +
 +
 +
 +==== 画面のスケーリング ====
 +
 +Cardputerは別として、基本的に、M5シリーズ向けハイハイスクールアドベンチャーは320x240の表示装置を前提にデザインしている。
 +が、しかるのちさんは M5Atomに 240x240のパネルをつないでミニチュアをつくってらっしゃる。
 +
 +このため表示装置にあわせたスケーリングを行いたい。
 +
 +基本的に、M5GFXライブラリは、スプライトやバッファを使った画像データをアフィン変換して表示する機能があるので、これを使えばスケーリングできる。
 +
 +が、問題は、画像バッファはメモリを食うということだ。
 +
 +最初、ハイハイスクールアドベンチャーはRGB565の256x152のバッファをもって、ここに描画をして表示していた。ほかの部分についてはダイレクトに画面に書き込んで、画面のデータを使って、文字のスクロールなどを実装していた。利点はメモリを使わないで済むこと。
 +
 +ところがスケーリングをするためにはバッファを持たなければならない。
 +
 +単純に、文字表示領域を片っ端から createSprite()を使ってバッファリングしていくとあっという間にヒープを使い切ってしまい動作しなくなった。
 +
 +そこで、画像データをRGB332にしてバッファを半分にし、各スプライトも M5Canvas::setColorDepth(8)でRGB332にしてけちけち実装した。
 +
 +最後に、ダイアログを同じように8bitのバッファにしたら、ダイアログが表示されなくなってしまった。
 +どうやら、ヒープ不足でバッファをとれない様子。
 +
 +もともとダイアログは白黒((一部無効化しているボタンはグレー表示))なので、思い切ってM5Canvas::setColorDepth(1)にしたらあっさり動いた。グレーは白になってしまい全く表示されなくなったがまあご愛敬、ということで。
 +
 +=== 副作用 ===
 +
 +画像用のバッファをRGB332にしたために、幽霊先生のジャケットがMAROONからピンクになってしまった、まるで第二期から第三期に代わったときのルパン三世のように。
 +
 +これは、ペイント処理の関係で、RGB565-->RGB332-->RGB565と変換して、もとと同じ値に戻る組み合わせでない色はペイントに使えなくなってしまったためだ。この条件を満たしていないと、ペイント処理で境界色や塗った領域の検出ができなくなり移乗動作してしまうためである。
 +
 +==== M5ATOM + 外部ディスプレイ ====
 +
 +{{::hhsadv:m5atom_extlcddisplay.jpg?400|}}
 +
 +しかるのちさん(@shikarunoci)から M5ATOMに、240x240のSPI液晶モジュールを接続して遊ぶためのソースの変更分をいただき、これを本体にマージしました。
 +
 +液晶は、SPI接続で、env:m5atomExtDisplay でビルドする。
 +接続は次の通り。
 +
 +^液晶^M5ATOM^
 +|VCC|3V3|
 +|GND|GND|
 +|SLC|G23|
 +|SDA|G33|
 +|RES|G19|
 +|DC|G22|
 +|CS|-|
 +
 +データファイルはPlatformIOのUpload Filesystem Image メニューでSPIFFSに転送しておく。
 +この時、ビルド環境をARM64上に構築していると、mkspiffs コマンドがないため失敗する。
 +その場合自力でビルドして置き換えておく。
 +
 +<code bash>
 +$ git clone --recursive https://github.com/igrr/mkspiffs.git
 +$ cd mkspiffs
 +$ make clean
 +$ make dist BUILD_CONFIG_NAME="-arduino-esp32" CPPFLAGS="-DSPIFFS_OBJ_META_LEN=4"
 +$ cp mkspiffs ~/.platformio/packages/tool-mkspiffs/mkspiffs_espressif32_arduino
 +</code>
 +
  
ハイハイスクールアドベンチャー_m5stack_m5cardputer版.1707265244.txt.gz · 最終更新: by araki