ユーザ用ツール

サイト用ツール


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

差分

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

この比較画面へのリンク

両方とも前のリビジョン前のリビジョン
次のリビジョン
前のリビジョン
ハイハイスクールアドベンチャー_raspberry_pico_lcd版 [2025/04/14 00:42] – [BLEキーボード] arakiハイハイスクールアドベンチャー_raspberry_pico_lcd版 [2025/04/17 01:04] (現在) – [BLEキーボード] araki
行 19: 行 19:
 STT7789 という、よくあるコントローラで 240x320の液晶(タッチパネル/TFカードスロット込み)です。 STT7789 という、よくあるコントローラで 240x320の液晶(タッチパネル/TFカードスロット込み)です。
 [[https://github.com/lovyan03/LovyanGFX|LovyanGFX]] であっさり動くだろうと思っていたら、全然表示が来なくて大変苦労しました。 [[https://github.com/lovyan03/LovyanGFX|LovyanGFX]] であっさり動くだろうと思っていたら、全然表示が来なくて大変苦労しました。
 +
 +ちなみにピン配置は以下の通り。
 +
 +^名前^番号^
 +|LCD_SLCK/SD_SCK|10|
 +|LCD_MOSI/SD_TX|11|
 +|LCD_MISO/SD_RX|12|
 +|LCD_BL|13|
 +|LCD_DC|14|
 +|LCD_RST|15|
 +|SD_CS|22|
 +
 +ちなみに、液晶パネルとSDカードはSPI1を共有しています((なので一部ピンが共用されている。))
  
 原因はバックライトのコントロールにありました。 原因はバックライトのコントロールにありました。
行 27: 行 40:
 まあ、描画はされていたけれど、バックライトが真っ暗で何も見えていなかっただけというのが実際だったわけです。 まあ、描画はされていたけれど、バックライトが真っ暗で何も見えていなかっただけというのが実際だったわけです。
  
-<code>+<code cpp>
 namespace lgfx namespace lgfx
 { {
行 60: 行 73:
 </code> </code>
  
-<code>+<code cpp>
 namespace lgfx namespace lgfx
 { {
行 94: 行 107:
 </code> </code>
  
 +SPI1を共用しているので、LCD/SDの初期化には注意が必要です。
 +LCDを先に初期化している前提で話しますが、この場合SDカードインターフェイスの初期化時に SPI1.setCS(22)のように、CSピンをセットしに行くと固まってしまいます。
  
 +SDカードのCSピンは SD.begin(22,SPI1)のように、SDインターフェイスの開始時に渡せばいいので、setCS()は呼ばないようにしてください。
  
 ==== USBキーボード ==== ==== USBキーボード ====
 +
 +実は真っ先に考えたのはBLEキーボードを使う方法。
 +M5ATOM他ではうまくいっているし、NimBLEが使えるんじゃないかという甘い見込みがあったので始めて見たものの、NimBLEはrp2040をサポートしていないのであえなく玉砕。
 +btstackで地道にやろうかと思ったけれどリンクがうまくいかず玉砕。((シンボルの宣言が必要だった。BLEキーボードのセクション参照
 +。))
 +
 +じゃあTinyUSBでUSBキーボードでいいじゃん。
 +電源供給できるOTGケーブルもあるしさ!
 +
 +ということで、USBキーボードで逃げようと思ったら、最終的には動いたものの、それはそれで面倒なあれこれがありました。
 +
 +=== 間違ったWEAK宣言の呪縛 ===
 +
 +コンパイラというかリンカーへの指示として、WEAKシンボルというのが太古の昔から存在しています。
 +何かというと、そのシンボルはリンク時に他にWEAKではない同名のシンボルがあればそれで置き換えるというものです。
 +普通同じシンボルが別々に実体をもっていたらリンク時に重複エラーになるんですが、WEAKがついていれば、それが起きません。
 +
 +で、こんなもの何に使うのかというと、たとえばライブラリの中で、ある処理をする下請け関数があったときに、それを自分の独自のもので置き換えたい、なんて欲求を満たすためなんです。
 +
 +要するにライブラリの側で、テンプレとかデフォルトの処理を用意しているけれど、ユーザがそれを自前の処理で置き換えられますよーっていうものです。
 +
 +TinyUSBは、コールバックの実装にこのフレームワークを使っていて、決まった名前の関数を実装することで、特にコールバックを登録とかしないでも、自動的に呼び出されるようになるという便利な仕様になっているのです。
 +
 +HIDデバイスを接続するので、
 +
 +  * tuh_hid_mount_cb()
 +  * tuh_hid_umount_cb()
 +  * tuh_hid_report_received_cb()
 +
 +の三つを実装すればコールバックを受け取ってキー入力をもらえるという寸法です。
 +
 +とても簡単。
 +間違いようがない。
 +
 +というわけで早速実装したのですが、まったく、このコールバックが呼び出されません。
 +
 +デバッガつないでトレースしていくと、どうやってもライブラリ内の空のダミーの方が呼び出されて、わたしの実装が呼び出されません。
 +
 +ちょっとWeb界隈を漁ると、同じ現象で困っている人もいらっしゃる。
 +
 +で、さらに調べていったら、**ヘッダファイルの中でコールバックのテンプレート宣言に_ _attribute( (weak) )_ _つけてる**じゃありませんか!
 +ダメ、絶対!
 +WEAK属性は実装の方にだけつけて宣言につけちゃダメ!
 +
 +つまり、TinyUSBのヘッダを includeしたら、もれなく自前のコールバックもWEAKになっちゃうんです。
 +WEAKとWEAKがあったら、まあリンカー次第ですが、大体の場合先に現れた方が採用されるようです。
 +手でリンクの順番を指定すれば回避できそうですが、platformio にお任せの身としてはちょっと難しい、多分。
 +
 +じゃあこのライブラリの間違ったWEAKの使い方が直るのを待つしかないのか?
 +
 +いや、そうじゃない。
 +つまり、TinyUSBのヘッダに汚染されないところで、実装してやればいいんです。
 +そして、その中から、TinyUSBのヘッダに汚染されている実装を呼び出すだけのラッパーを作れば回避可能です。
 +
 +それが usbkbd.cppなのです。
 +<code cpp>
 +// Tiny USB HID callback entry points to avoid wrong weak attribute usabe in Adafruit TinyUSB
 +
 +#include <Arduino.h>
 +
 +#if defined(USBKBD)
 +void my_hid_mount_cb(uint8_t, uint8_t, uint8_t const *, uint16_t);
 +void my_hid_umount_cb(uint8_t, uint8_t);
 +void my_hid_report_received_cb(uint8_t, uint8_t, uint8_t const *, uint16_t);
 +
 +extern "C"
 +void 
 +tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len)
 +{
 +    my_hid_mount_cb(dev_addr, instance, desc_report, desc_len);
 +}
 +
 +extern "C"
 +void 
 +tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance)
 +
 +    my_hid_umount_cb(dev_addr, instance);
 +}
 +
 +extern "C"
 +void 
 +tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len)
 +{
 +    my_hid_report_received_cb(dev_addr, instance, report, len);
 +}
 +#endif
 +</code>
 +
 +あとは、Keyboard.cpp の側で my_hid_mount_cb(), my_hid_umount_cb(), my_hid_report_received_cb()をそれぞれ実装してやれば完了です。
 +
 +あっさり動きました。
 +
 +=== キーが数秒に一回しか入力できない呪い ===
 +
 +さて、TinyUSBでUSBキーボードをつなぐサンプルはさすがにあちこちにあったので、基本的にそれをまねて実装しているわけですが、なぜかキーが数秒に一回しか入らないという、事実上使い物にならない現象が出ました。
 +何故?
 +
 +btstackと違い、TinyUSBではloop()の中でtuh_task()を呼び出してやらなければなりません。
 +ただ、普通に呼べばいいだけですし、別段loop()が遅いというわけでもなさそうです。
 +
 +一度、multicore機能を使って、core1に処理をさせてみたんですが特に変わりはありませんでした。
 +
 +さて何が原因でしょう?
 +Geminiに聞いてみました。
 +
 +直接原因は教えてもらえませんでしたが、提示してきたサンプルに、わたしが引き写したサンプルと違う部分が一か所あります。
 +
 +tuh_hid_report_received_cb()の最後に次のコールバックを要求する処理が入っているのです。
 +
 +<code cpp>
 +if (!tuh_hid_received_report(dev_addr, instance))
 +{
 +    // エラー処理
 +}
 +</code>
 +
 +恐る恐るわたしのmy_hid_received_report_cb()の最後にもつけてみたら、あっさり普通にキー入力ができるようになりました。
 +たったこれだけ。
 +されどこれだけ。
 +
 +=== キーマッピング ===
 +
 +US配列のキーボードがターゲットなのは、Keyboard.cpp内の keycode2asciiという配列がそうなっているからです。
 +HID_KEYCODE_TO_ASCIIというTinyUSB由来のマッピングを横着して使っているのでこうなっています。
 +
 +これを、JISキーボードにあうように直せばJISキーボードでも使えます。
 +
 +<code cpp>
 +static uint8_t const keycode2ascii[128][2] = { HID_KEYCODE_TO_ASCII };
 +</code>
 +
 ==== BLEキーボード ==== ==== BLEキーボード ====
  
行 149: 行 296:
 わたしの手元にあるバッファローのBLEキーボードとM5 CardputerのおまけでついてくるBLEキーボードだけがセキュア接続でなくてもつながるキーボードなので、つまりは、大体の場合セキュアじゃないとダメなのです。((ちなみにCardputerのそれはセキュアでは接続できません。バッファローはセキュアでもOK)) わたしの手元にあるバッファローのBLEキーボードとM5 CardputerのおまけでついてくるBLEキーボードだけがセキュア接続でなくてもつながるキーボードなので、つまりは、大体の場合セキュアじゃないとダメなのです。((ちなみにCardputerのそれはセキュアでは接続できません。バッファローはセキュアでもOK))
  
-さて世間に、実は Pico W + btstack でセキュア接続をして BLE Centralとして動作させてるという事例があまりないよう、AIもいろいろ教えてくれるんでが、簡単にいうと、セキュア接続要求がstate=8で失敗して先へ進めないが現状です。+とりあえずボンディング行わない単純にコネクションだけをセキュアにできればいいので、そのように設定を。 
 +<code cpp> 
 +  sm_init();                        // セキュリティマネージャの初期化 
 +  sm_set_request_security(true);    // セキュリティ要求を有効化 
 +  sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT); // IOキャパビリティ設定 
 +  sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION); // 認証要件を設定 
 +  sm_set_encryption_key_size_range(7, 16); // 暗号化キーサイズ範囲を設定
  
-まあ、キュアじゃなくもつながキーボードなら多分つながるのでそれでよしとるというのもありしょうけれど、んかあとちょっと工夫動くんじゃないかっていう気がする、あきらめがつきません+  sm_event_callback_registration.callback = &sm_event_handler; 
 +  sm_add_event_handler(&sm_event_callback_registration); 
 +</code> 
 + 
 +IO_CAPABILITY_NO_INPUT_NO_OUTPUTとSM_AUtHREQ_SECURE_CONNECTIONのみがットされ場合、Just in Workでの接続行われようです。なで、sm_event_handler側でそれを処理ればいようです。 
 + 
 +<code cpp> 
 +static void 
 +sm_event_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { 
 +  if (packet_type != HCI_EVENT_PACKET) return; 
 + 
 +  switch (hci_event_packet_get_type(packet))  
 +  { 
 +    case SM_EVENT_JUST_WORKS_REQUEST: 
 +      { 
 +        // Just Works Confirmを自動的に承認 
 +        bd_addr_t addr; 
 +        sm_event_just_works_request_get_address(packet, addr); // アドレスを取得 
 +        Serial.printf("Just works request from %s\n", bd_addr_to_str(addr)); 
 +        sm_just_works_confirm(sm_event_just_works_request_get_handle(packet)); 
 +        break; 
 +      } 
 +    case SM_EVENT_PAIRING_STARTED: 
 +      Serial.println("Pairing started."); 
 +      break; 
 +    case SM_EVENT_PAIRING_COMPLETE:  
 +    { 
 +      uint8_t status = sm_event_pairing_complete_get_status(packet); 
 +      if (status == ERROR_CODE_SUCCESS)  
 +      { 
 +        Serial.println("Pairing complete. Connection is now secure."); 
 +        hci_con_handle_t connection_handle = sm_event_pairing_complete_get_handle(packet); 
 +        uint16_t hids_cid; 
 +イスに接続 
 +        gatt_client_discover_primary_services_by_uuid16(&handle_gatt_client_event, connection_handle, HID_SERVICE_UUID);  // GATT event handler 
 +      }  
 +      else  
 +      { 
 +        Serial.printf("Pairing failed with status %u.\n", status); 
 +      } 
 +      break; 
 +    } 
 +    case SM_EVENT_PASSKEY_DISPLAY_NUMBER: 
 +      // パスキー表示が必要な場合の処理 (今回は No Input No Output なので基本的には発生しない
 +      display.printf("Passkey: %06u\n", sm_event_passkey_display_number_get_passkey(packet)); 
 +      break; 
 +    case SM_EVENT_REENCRYPTION_STARTED: 
 +      { 
 +        // 再暗号化開始された場合の処理 
 +        bd_addr_t addr; 
 +        sm_event_reencryption_started_get_address(packet, addr); // アドレスを取得 
 +        uint8_t addr_type = sm_event_reencryption_started_get_addr_type(packet); 
 +        Serial.printf("Re-encryption started for address: %s (%d)\n", bd_addr_to_str(addr), addr_type); 
 +        break; 
 +      } 
 +    case SM_EVENT_REENCRYPTION_COMPLETE: 
 +      { 
 +        // 再暗号化が完了した場合の処理 
 +        uint8_t status = sm_event_reencryption_complete_get_status(packet); 
 +        if(status == ERROR_CODE_SUCCESS) { 
 +          Serial.println("Re-encryption successful."); 
 +        } else { 
 +          Serial.printf("Re-encryption failed.(%d)\n", status); 
 +        } 
 +        break; 
 +      } 
 +    default: 
 +      break; 
 +  } 
 +
 +</code> 
 + 
 +あとは接続時にセキュア接続を要求するだけ 
 + 
 +<code cpp> 
 +    case HCI_EVENT_LE_META: 
 +      if (hci_event_le_meta_get_subevent_code(packet) == HCI_SUBEVENT_LE_CONNECTION_COMPLETE)  
 +      { 
 +        // 暗号化を有効化 
 +        hci_con_handle_t connection_handle = gap_subevent_le_connection_complete_get_connection_handle(packet); 
 +        gap_request_security_level(connection_handle, LEVEL_2); 
 +        sm_request_pairing(connection_handle); 
 +        //gatt_client_discover_primary_services_by_uuid16(&handle_gatt_client_event, connection_handle, HID_SERVICE_UUID);  // GATT event handler 
 +        Serial.printf("Connected to HID device: %s (%08x)\n", bd_addr_to_str(keyboard_address), connection_handle); 
 +      } 
 +      break; 
 +</code>
  
-識者の見解を待ちす!+これでコネクションはセキュアになりした。
  
 +<del>ただ、まだキーボードから通知が拾えていません。
 +ble/hids_client.c というのがあるので、これを使えば簡単かもしれませんが、サンプルが見当たらないので、ソースを読みながら手探りで調査しています。</del>
  
 +ついに、文字が拾えるようになりました。
 +まだ、テスト用のコードなので、これを整理して、こちらに組み込まないといけないのですが、長かった。
  
 +ポイントは、Notificationを受け取りたいCharacteristicに対して、ディスクリプタを検索してCCCDを見つけたら、Notifyを送信するように要求する((0x0001 -- little endian なら {0x01, 0x00}を送信する。))のと、最後に gatt_client_listen_for_charcteristic_value_updates()でGATT_EVENT_NOTIFICATIONを受け取るようにすることの両方。
  
 +更に gatt_client_listen_for_characteristic_value_updates()のcharacteristicにはnullptrを渡して一括でやる方がいいということですかね。((サンプルに従って notification_listenerを一つだけ作っているので、個別の characteristicに対してやりたいなら、多分これもcharacteristicごとにわけないといけないんだと思いますが、一括で動いているのでよしとします。))
  
  
ハイハイスクールアドベンチャー_raspberry_pico_lcd版.1744591326.txt.gz · 最終更新: 2025/04/14 00:42 by araki