目次
はじめに
USBメモリにスクリーンショットを保存できるオシロスコープを持っているのですが、わざわざ抜き差しするのがめんどくさい、ということで作りました。
類似製品はありますが、USBとWiFiからの同時アクセスが可能という特徴を考えると、USB SDカードリーダに挿した、FlashAir(もしくはezShare、PQI Air Card)みたいな感じです。
ソースコード
GitHub WiFi_shareable_SDcardReader
機能
・WiFi経由でWebブラウザでSDカードの内容を閲覧・編集(ファイルアップロード、削除、フォルダ作成、ファイル名変更)ができる
・USBとWebブラウザからの同時アクセスに対応
・Captive Portalを使用した簡単なWiFi接続設定
・OSによってキャッシュされたUSBメモリの内容を強制クリア
ESP32 S3でUSBからもWiFiを使ったブラウザからのアクセスにも対応したSDカードリーダーができた!
— Unagi Dojyou (@Unagi_Dojyou) November 3, 2024
どちらからも同時に読み書きできる
オシロスコープのUSBメモリや、古いフォトフレームとかに使えるhttps://t.co/0ZHgKVPQm3 pic.twitter.com/D2ikribWvv
制限事項
・APモード非対応、STAモードのみ
・WiFiから書き込んだ内容をUSB側に反映させるには、USBの再接続(自動or手動)が必要
・Raspberry Pi Pico Wはブラウザから、書き込みができません。読み取りのみです。
・複数端末からのWiFi経由での同時アクセスが不可能
対応しているマイコン
・ESP32 S3
・ESP32 S2
・Raspberry Pi Pico W (ブラウザからは読み込みのみ)(気が向いたら対応させるかも)
インストール方法
開発環境
ArduinoIDEを使用します。
ESP32のCoreはespressif/arduino-esp32
Raspberry Pi Pico WのCoreはearlephilhower/arduino-pico
必要なライブラリ
SdFat・・・greiman/SdFat(ArduinoIDEからインストール可能)
Captive Portal WiFi configure・・・UnagiDojyou/ArduinoIDE_Captive_Portal_WiFi_configure(zipでインストール)
SdFatライブラリの事前設定
ディフォルトの設定だとUTF-8が扱えず、日本語のファイル名の表示ができません。
ライブラリの設定が必要で、Windowsだと
“C:\Users\ユーザ名\ドキュメント\ArduinoData\libraries\SdFat\src\SdFatConfig.h”
を編集し、37行目の// #define USE_UTF8_LONG_NAMES 1
のコメントアウトを外してください。
ボードの選択(ESP32 S3の場合)
ボードのオプションを変更する必要があります。
基本的には、下の写真の通りに設定すれば動作します。
Events Run OnとArduino Run Onのコアが同じでないと動かないと思います。
フラッシュサイズは使用しているものに合わせたほうがいいとは思います。(合わせなくても動く)
ボードの選択(ESP32 S2の場合)
ほとんど変更は必要ありません。下の写真の通りです。
ボードをESP32S2 Dev Module以外のものを使用したところ、何故か、USBが使えなくなってしまうという事象が発生したので、ボードはESP32S2 Dev Moduleを選択するのを推奨です。
フラッシュサイズは使用しているものに合わせたほうがいいとは思います。(合わせなくても動く)
ボードの選択(Raspberry Pi Pico Wの場合)
USBの設定のところだけ変更をします。USB Stackを”Adafruit TinyUSB”にしてください。
各種ボタン、GPIOの設定
WIFI_BUTTON
WiFiの設定の初期化を行うのに使用するボタンのGPIOです。長押しするとWiFiの設定(SSID、パスワード)が初期化されます。押されたときがHigh、押されていないときがLowのプルアップされていないボタンの必要があります。ディフォルトでは、BOOTボタンである、GPIO0に割り当てられています。
SD_LED
SDカードのアクセス状況、異常の報告を行うLEDのGPIOです。SDカードに異常がある場合は、5Hzで点滅、SDカードにアクセスがあるときは光る(High)となります。ディフォルトでは、WIFI_LEDと同じ、LED_BUILTIN(Devボードに搭載されているLED)に割り当てられています。
WIFI_LED
WiFiの接続状況を知らせるためのLEDのGPIOです。WiFiに接続前でAPモードで起動しているときは素早く点滅します。接続試行中はゆっくりと点滅します。点灯時はHigh、消灯時はLowとなります。ディフォルトではSD_LEDと同じ、LED_BUILTINに割り当てられています。
SDカードの接続
事前にSDカードをFAT32でフォーマットしてください。
SDカードのSPIモードを使用します。SDカードのピンアサインは以下の様です。
それぞれのボードとの接続は、以下の表の様です。
ESP32 S3 | ESP32 S2 | Raspberry Pi Pico W | |
CS(SS) | GPIO10 | GPIO34 | GPIO5 |
DI(MOSI) | GPIO11 | GPIO35 | GPIO7 |
DO(MISO) | GPIO13 | GPIO37 | GPIO4 |
SCK(SCLK) | GPIO12 | GPIO36 | GPIO6 |
microSDカードをDIP化するのは、余っているmicroSDをSDに変換するアダプタを使うと安上がりで良いです。
MicroSDカードをDIPに変換するやつを作成!
— Unagi Dojyou (@Unagi_Dojyou) December 23, 2023
お手軽 pic.twitter.com/7JrLWBDFWG
USBの再読み込み(USB_REFRESH・SCSI_REFRESH)の設定(ESP32 S3 S2)
USBのホスト機器では、基本的にMSC(USBマスストレージクラス)のキャッシュを持っており、ファイルの一覧を取得するときなどに、USB機器にアクセスせずに、キャッシュの内容を参考にします。そのため、USBが接続されている状態でブラウザからSDカードに書き込みを行っても、ホスト機器のファイル一覧は更新されず、更新内容が反映されません。そのため、WiFi_shareable_SDcardReaderでは、USBが接続された状態でブラウザからSDカードに書き込みがあった場合は、USBが取り外されたときと同じ状態を作り出し、強制的にUSBホストのキャッシュを更新させます。この方法にUSB_REFRESHとSCSI_REFRESHの2種類が存在します。もちろんこの機能を無効にすることもできます。
ディフォルトではSCSI_REFRESHが選択されています。USB_REFRESHを使用する場合は、その行のコメントアウトを外し、SCSI_REFRESHをコメントアウトしてください。無効にする場合は、両方コメントアウトしてください。
SCSI_REFRESH(おすすめ)
MSCのストレージを一定時間無効化します。これによって、SDカードリーダのSDカードが一回抜かれて、数秒後にもう一度挿入されるという動作を再現します。ストレージが抜かれている状態をうまく検知できるWindowsなどの高級OS向けです。
USB_REFRESH
USBを一定時間無効化します。これによって、USBが一回抜かれて、数秒後にもう一度挿入されるという動作を再現します。ESP32本体が再起動するため、WiFiからアクセスできるようになるのに時間がかかります。SCSI_REFRESHではうまく動かない組み込み機器向けです。
REFRESH_TIME_LENGTH
取り外されている長さをms単位で設定します。ディフォルトでは2秒(2000)に設定してあります。
RESET_COUNT
USBがアクセスしているのにもかかわらず、REFRESHを行ってしまうのは危険なため、最後にUSBアクセスが実行されてから、しばらく経ったらREFRASHを行うようにしています。そのカウントです。単位はありませんが、大きい数字になるほど、時間が長くなります。あまりにも大きくすると、USBホストからの生存確認のアクセスよりも長くなってしまい、永遠にREFRESHされなくなってしまいます。ディフォルトでは、UINT16_MAX(65535)です。
使い方
SD_LEDの点灯
電源投入時にSD_LEDが高速点滅する場合は、SDカードの認識エラーです。SDカードが正しく接続されていることを確認してください。
WiFiへの接続
ArduinoIDE_Captive_Portal_WiFi_configureと同じ使い方です。
1. WIFI_LEDLEDが素早く点滅していることを確認します。
WiFiの設定が完了しておらず、SoftAPモードで起動していることを示しています。もし点滅しない場合は、LittleFSの設定が間違っている可能性があります。スケッチの見直しとシリアルでの出力を見てください。
2. 適当な端末(スマートフォン等)でWiFiの設定を開き、SSIDが「shareableSDReader-XXXXXX」となっているアクセスポイントを探し、接続します。
SSIDのshareableSDReaderは#define CP_BOARDNAME "shareableSDReader"
で定義されているので、好きに変えることができます。XXXXXXはMACアドレスの下6桁です。
3. キャピティブポータルによってSSIDとパスワード入力欄が自動的に現れます。
必要に応じて表示されているMACアドレスをメモしてください。
4. SSIDとパスワードを入力してからsendボタンを押してください。
5. WIFI_LEDがゆっくりとした点滅に変わったことを確認します。
指定されたアクセスポイントに接続しようとしています。
6. 点滅が停止すれば接続が完了したことを示します。
7. WiFiの設定をリセットしたい場合は、WIFI_BUTTONを長押しします。そうすると、WIFI_LEDが点灯するので、離します。その後、素早い点滅(1の状態)に変わるので、リセット完了です。
ブラウザでのファイル操作
ArduinoIDEでSDカードHTMLファイラー「ArduinoIDE_SD_FAT32_Fileserver」を作成と基本的な使い方は同じです。同時アクセス時は、USBの処理が優先されるので、読み込みが遅くなります。また、USB_REFRESHを利用する場合は、ファイル変更後にESP32の再起動が入るので、しばらくアクセスができなくなります。
開発秘話
元になったプロジェクト
今回のWiFi_shareable_SDcardReaderは
・ArduinoIDE_SD_FAT32_Fileserver(Sdfat)
SDカードの中身をブラウザで閲覧できるようにする
・ArduinoIDE_Captive_Portal_WiFi_configure
キャプティブポータルを使ってWiFiの設定をできるようにする
とSDカードをUSBメモリとして扱えるようにするコードを合体させたものです。
同時アクセスの実現について
ESP32等を使ってWiFiでSDカードの中身を編集できるし、SDカードのインターフェースも使えるという、プロジェクトは3Dプリンター用として、いくつも存在(WebDAV Server and a 3D Printer等)しますが、同時アクセスができるのは、(AirFlash等の製品を除いて)唯一無二だと思います。通常だと、WiFi側からの要求と、USB側からの要求が同時にくると、衝突してしまって、アクセスできなくなってしまったりします。
この実現方法ですが、RTOSに丸投げしています。
USB側の処理を行ってくれる優先度の高いタスクと、WiFi側の処理を行ってくれる優先度の低いタスクを作成しています。RTOSがいい感じにSPI通信も含めてタスクの切り替えを行ってくれるので、それに甘えています。
Raspberry Pi Pico Wの方は実装方法が異なっており(2024/12/05現在)、RTOSを使用せずに、Core0でUSB側の処理、Core1でWiFi側の処理を行うようにしています。そのままだと、同時アクセス時に、SPI通信が衝突してしまうので、使用中フラグを使用して、USBの方が優先度が高くなるように衝突を回避しています。はじめに思いついたのがこの方法なのですが、ArduinoIDE_SD_FAT32_Fileserverにかなり手を加えなければならず、面倒でした(読み込みのみ実装して面倒くさくて書き込みは挫折)。Raspberry Pi Pico WにもRTOSが乗っているようなので、ESP32と同様な方法で実装しようかなと考えてます(時間とやる気があったらやる)。
WiFiで書き込んだ内容がUSB側に反映されない問題
背景情報は、「インストール方法」の章の「USBの再読み込み(USB_REFRESH・SCSI_REFRESH)の設定(ESP32 S3 S2)」で書いたので省略しますが、強制的にデバイスを抜いた状態にすることで、再読込させるという、実装は他にないと思います。少なくとも、FlashAirはFlashAir Developers「FlashAirへのアップロード」の注意に書いてあるように、再挿入が必要なようです。
苦肉の策で、デバイスの再接続の再現としていますが、他にも最適解があるのかもしれないです。USB MSCはSCSIのコマンドを利用しているので、SCSIのプロトコルに、内容が書き換わる可能性のあるディスクなのでキャッシュを取得せずに毎度アクセスするように、みたいな命令があれば理想的です。しかしながら、SCSIのプロトコルの詳細は、無料では手に入らない(USB-IFは神)上に、実装も私の技術力では時間がかかってしまう上に、USBのホスト側が対応していない可能性なども考えてやめました。
おわりに
プロジェクト開始が、去年の年末で、完成までほとんど1年かかってしまいました。4月には、RTOSを利用しないRspberry Pi Pico Wの方が読み取りのみまでできていましたが、面倒でそれ以上進めず、RTOSを使った制御方法を思いついたのが、11月とかで、やっと完成しました。この記事自体もかなり長文になってしまって、書くのが大変でした。