中国製ドローンのハッキング
中国製ドローンの2.4GHz帯通信を解析し、セキュリティ上の脆弱性を検証しました。
Aliexpressで購入した激安ドローンをハッキングしてみた
⚠️ 法的注意事項
このプロジェクトは純粋に教育・研究目的で行われました。以下の点にご注意ください:
リバースエンジニアリングについて
- 本解析は個人所有のドローンに対して行われました
- 日本の著作権法では、個人的な研究・学習目的でのリバースエンジニアリングは認められています
- ただし、国や地域によって法律が異なる場合があります
使用上の制限
- 解析した情報を商業目的で使用しないでください
- 他者の権利を侵害する目的で使用しないでください
- ドローンの飛行は各国の航空法・電波法に従ってください
免責事項
- この情報の使用により生じた法的問題について、作者は一切の責任を負いません
- 各自の責任において、適用される法律を確認してください
- 不明な点がある場合は、専門家(弁護士等)に相談することをお勧めします
はじめに
皆さんは、Aliexpressを使ったことがありますか?私は、Aliexpressで掘り出し物を見つけるのが大好きです。ある日、いつものようにAliexpressを探索していると、2.4GHz帯で動作する激安ドローンを発見しました。その価格はなんと約3000円。この価格でドローンが手に入るなんて、買わない理由がありません。
しかし、ただ買って飛ばすだけでは面白くありません。せっかくなら技術的に深掘りして、通信プロトコルを解析してみようと考えました。ということで、このドローンのハッキングに挑戦することにしました。
購入したドローンについて
購入したドローンは、Aliexpressで多くの販売業者が取り扱っているモデルです。購入場所によって商品名に表記ゆれがありますが、ここでは最も多く使われている「GT3」という表記を用います。
Aliexpressで販売されているドローンのモデル
このドローン、非常に興味深い特徴があります。誰が設計して、誰がどのように販売しているのか、まったくわかりません。さらに面白いことに、ドローン本体に印字されている商品名は、それぞれの販売業者に適合した商品名になっています。つまり、このような怪しいドローンに印字までして、Aliexpressの販売業者に卸す業者が存在するということです。
好奇心から分解してみたところ、中のチップは全てレーザーで印字が消されていました。そのため、チップのデータシートも見つけられず、リバースエンジニアリングを行うには少し大変な状況でした。
直面した問題
ハッキングを始めるにあたって、まず以下の2つの大きな障害に直面しました。
- データシートが存在しない問題: チップにアクセスしようにも、印字が消されているためピン配置がわかりません
- 通信方式が不明: どういう方式で通信を行っているのかがわかりません
データシート問題については、どうしようもないので今回は諦めることにしました。代わりに、通信解析に焦点を当てることにしました。
当初、私はこのドローンがnRF24L01のような独自の2.4GHz通信方式(いわゆるRF24方式)で通信していると仮定していました。RF24方式とは、2.4GHz帯域を使用しながらも、802.11(Wi-Fi)などの標準プロトコルではなく、古来のラジコンのような独自の通信方式で通信する方法です。結果的には、この仮定が大きな遠回りとなりました。
ESP32とnRF24L01を用いたツールの開発
最初のアプローチとして、nRF24L01モジュールを使用することにしました。nRF24L01は、Arduinoなどのマイコンで使える2.4GHz帯の無線通信モジュールで、その辺で簡単に入手できます。このモジュールを使えば、周辺の2.4GHz帯の通信強度を測定したり、通信内容をキャプチャしたりできます。
詳細な使い方については、cifertech氏のページで解説されています。
使用したツール
次に、2.4GHz帯の中で具体的にどの周波数で通信しているのかを知りたかったので、ESP32を使った専用のスペクトラムアナライザーソフトウェアを設計しました。
以下からダウンロードできます。
OR
ミラーダウンロード ←私の個人サイトのリンクです
観測したシグナル
ドローンを実際に操縦しながら、どの周波数の信号強度が変化するかを観測しました。その結果、2427MHz、2462MHz、2437MHzあたりで強い信号が多く受信されました。
この時点で、私は重大な見落としをしていました。
- 2427MHzは802.11(Wi-Fi)のチャンネル4(正確には2427MHz)
- 2462MHzはチャンネル11(正確には2462MHz)
- 2437MHzはチャンネル6(正確には2437MHz)
つまり、これらはすべてWi-Fiの標準チャンネルだったのです。このデータを見た時点でWi-Fiだと気づくべきでしたが、私はRF24方式だと信じ込んでいたため、ずっと独自プロトコルとして通信キャプチャを行おうとしていました。今思えば、非常に愚かなことをしました。
nRF24L01での通信キャプチャの試み
次に、ドローンのON時とOFF時で大きく変化が見られた2427MHzに目星をつけて、nRF24L01で空中を飛んでいる電波を直接取得しようと試みました。
実際にデータを取得することはできたのですが、その内容は000000だったり111111だったりと、意味をなさないデータばかりでした。nRF24L01は802.11プロトコルには対応していないため、当然と言えば当然の結果でした。
もうお手上げかと思われた時、重要なことを思い出しました。
専用アプリの存在を思い出す
この怪しい中国製ドローンには、もちろん怪しい専用アプリが存在します。そのアプリでは、ドローンが発するWi-Fiアクセスポイントにスマートフォンを接続して、アプリから操縦する方式でした。
ここでやっと、ドローンがWi-Fi(802.11プロトコル)という標準規格の上に構築されたネットワーク機器であることに気が付いたのです。これは大きな進展でした。
しかし、新たな問題がありました。私は、Wi-Fiパケットをキャプチャできる、いわゆるモニターモード(promiscuousモード)対応のUSBアダプターを持っていなかったのです。
USBドングルの購入
Wi-Fiパケットをキャプチャするためには、モニターモードをサポートするWi-Fiアダプターが必要です。HackRFのような高性能なSDR(Software Defined Radio)が欲しかったのですが、高価で手が届きません(笑)。
そこで、GeminiとChatGPTに相談し、推奨されたUSBドングルを購入しました。
購入したUSBドングル
早速購入してPCに接続し、Wiresharkを使って2427MHz(チャンネル4)のパケットをキャプチャしようと試みました。しかし、うまくいきませんでした。
理由は明確でした。ノイズが多すぎるのです。私の家には、Wi-Fiを発する機器が多すぎました。ルーター、スマートフォン、IoTデバイスなど、無数のデバイスが2.4GHz帯で通信しています。目的のドローンからのパケットが、これらのノイズに埋もれてしまっているのです。
さらに、飛んでいるデータを見ても、内容が全然わかりません。このプロジェクトは失敗に終わったかと思いました。
ESP32で中継作戦
次に考えたのが、ESP32を中継機器として立てて、コントローラーとドローンの間のパケットを見るという作戦でした。
結果として、この方法でパケットをキャプチャすることは一応できました。しかし、いくつかの問題がありました。
- ポーリングレートをESP32の限界まで上げても、すべてのパケットを捕捉できない
- 途中で出力が途切れることがある
- ESP32が非常に高温になる(爆熱状態)
- かなり長いコードでEPS32同士をシリアル通信にしなければならい(同じAP名なので)
私の手元には、1500円で購入した貴重なESP32-WROOM-32Dが2つしかありません。これが壊れるのは困るので、この方法は断念しました。
専用アプリの解析という突破口
ここで、重要なひらめきがありました。専用アプリを解析すればいいのでは?
早速、APKファイルを入手して、Jadx(Androidアプリケーションのデコンパイルツール)を使って解析を開始しました。
すると、驚くべきことに、たくさんの送信パケット情報とポート番号の情報が出てきたのです!アプリ内には、ドローンとの通信に使用されるプロトコルの詳細が、ほぼ平文で記載されていました。
これは大きな成果でした。アプリのソースコードを読むことで、以下のような情報を得ることができました。
- 通信に使用されるUDPポート番号
- コマンドフォーマット
- パケット構造
- 制御コマンドの種類
解析した情報のまとめ
アプリの解析により、このドローンの通信プロトコルの詳細が明らかになりました。以下、判明した制御方法とパケット情報をまとめます。
通信方式の概要
このドローンの制御は、PCやESP32などのコントローラーがドローンのWi-Fiアクセスポイント(AP)に接続し、UDPパケットを一方的に送信し続けることで行われます。
- 接続方式: TCPのような接続確立(ハンドシェイク)は行われません
- 通信プロトコル: UDP(ポート
8800)を使用 - 送信方式: 「ファイア・アンド・フォーゲット(撃ちっぱなし)」方式
- フェイルセーフ機構: 制御ループは、ユーザーが何も操作していない待機中でも停止しません。常に秒間数十回(例: 80Hz)のレートで「中立パケット」を送信し続け、ドローンはこのパケットが途絶えると通信が切れたと判断します
スティック操作の計算方法
ユーザーのキー入力を、ドローンに送信するスティック値(範囲: 40〜220、中央値: 128)に変換する計算方法です。これは WifiUavRcModel クラスが担当し、非常に滑らかな操作感を実現しています。
1. 加速(キーを押した時)
キーが押されると、スティック値は中心(128)から最大/最小値に向かって徐々に加速します。
加速度 = accel_rate × Δt × (1 + expo_factor × dist_ratio)
expo_factor(指数係数)により、スティックが中心に近いほど加速度が上がり、敏感に反応します
2. 減速(キーを離した時)
キーが離されると、スティック値は徐々に中心(128)に戻ります。
減速度 = decel_rate × Δt × (1 + 0.5 × dist_ratio)
- スティックが中心から遠いほど減速度が上がり、素早く中心に戻ります。これにより、操作を止めた時に機体の動きがピタッと止まる感覚になります
3. 即時ブースト(ピッチとロールのみ)
左右や前後に操作方向を切り替えた瞬間、ごくわずかな値が即座に加算されます。これにより、キー入力に対する機体の反応がより「即時的」に感じられます。
4. コントロール・プロファイル
"normal", "precise", "aggressive" といった飛行モードがあり、上記の計算で使われる accel_rate(加速度)や expo_factor(感度)などのパラメータを切り替えることができます。
パケット構造の詳細
解析の過程で2種類のパケット形式が判明しました。最終的に使用されているのは、より複雑な最終パケット形式です。
A. 旧式/単純なパケット(全20バイト)
DroneController のテストコードに見られた古い形式です。
- ヘッダー:
0x66 - フッター:
0x99 - 構造:
[0]:0x66(ヘッダー)[1]: 速度[2-5]: ロール, ピッチ, スロットル, ヨー (中央値128)[6]: コマンド1 (離陸0x01, 着陸0x02, 停止0x04)[7]: コマンド2 (基本値0x0a+ 録画0x04)[8-17]: ゼロ埋め[18]: チェックサム (バイト2〜17のXOR)[19]:0x99(フッター)
B. 最終パケット形式(全123バイト)
WifiUavRcProtocolAdapter で使用される、リバースエンジニアリングに基づいた正規のパケットです。大量の固定バイト列と、3つのローリングカウンターが特徴です。
パケットは以下の6つのブロックで構成されています。
ブロック1: ヘッダー(12バイト)
0xef 0x02 0x7c 0x00 0x02 0x02 0x00 0x01 0x02 0x00 0x00 0x00
静的なマジックナンバーです。
ブロック2: カウンター1(8バイト)
_ctr1(2バイト): 16ビットのカウンター。送信ごとに +1 されます(リトルエンディアン)_COUNTER1_SUFFIX(6バイト): 固定値0x00 0x00 0x14 0x00 0x66 0x14
ブロック3: 制御データ(16バイト)
controls(6バイト): ここが操作の核となる部分です[0]: ロール(範囲: 40〜220、待機時: 128)[1]: ピッチ(範囲: 40〜220、待機時: 128)[2]: スロットル(範囲: 40〜220、待機時: 128)[3]: ヨー(範囲: 40〜220、待機時: 128)[4]: コマンド(詳細は後述)[5]: ヘッドレスモード(OFF:0x02, ON:0x03)
_CONTROL_SUFFIX(10バイト):0x00で埋められます
ブロック4: チェックサム(51バイト)
checksum(1バイト): ブロック3のcontrols(6バイト)のみをXORした単純なチェックサムです_CHECKSUM_SUFFIX(50バイト): 静的なマジックナンバーです
ブロック5: カウンター2(20バイト)
_ctr2(2バイト): 2つ目の16ビットカウンター。送信ごとに +1 されます_COUNTER2_SUFFIX(18バイト): 固定値
ブロック6: カウンター3(16バイト)
_ctr3(2バイト): 3つ目の16ビットカウンター。送信ごとに +1 されます_COUNTER3_SUFFIX(14バイト): 固定値
コマンド一覧
ブロック3の controls[4](コマンドバイト)に設定される値です。これらは「ワンショット」で動作し、一度送信されるとフラグがクリアされます。
| 値 | コマンド | 説明 |
|---|---|---|
0x00 | コマンド無し | 待機中(ニュートラル)の状態。常にこの値で送信される |
0x01 | 離陸 | ドローンに離陸を指示する |
0x02 | 着陸 / 緊急停止 | ドローンに着陸、またはモーターの緊急停止を指示する |
0x04 | ジャイロ・キャリブレーション | ドローンのジャイロセンサーを校正する |
解析した結果
実際にESP32で送信して、動作するか確認する
パケット送信テストの成功
アプリの解析により判明したパケット構造を基に、いよいよ実機テストを行う段階に入りました。ESP32にUDP送信プログラムを実装し、解析したパケットフォーマット通りにデータを組み立てて送信してみました。
最初は半信半疑でしたが、テストパケットを送信すると、なんとドローンが実際に反応しました!最初に試したのはジャイロ校正コマンド(0x04)だけでしたが、ドローンのLEDが点滅し、明らかに校正処理が実行されていることが確認できました。
ここまでの道のりは約3か月。最初にドローンを購入してから、nRF24L01での試行錯誤、WiFi解析の失敗、アプリの解析、そしてパケット構造の解明と、長い道のりでした。この時点で秋も深まり、かなり寒くなってきていたので、ようやく動作確認が取れて心から安心しました。
専用コントローラーの設計
動作確認が取れたことで、次のステップとして、ドローン専用のハードウェアコントローラーを設計することにしました。せっかく通信プロトコルが解明できたのですから、市販のコントローラーに頼らず、自分だけのカスタムコントローラーを作りたいという欲求が湧いてきました。
3DモデルPCB
何となく、かっこいい名前がついていた方がテンションが上がるので、このプロジェクトに**「MINE VESPER」**という名前を付けました。中二病?いいえ、こういうネーミングはモチベーション維持に重要なんです。プロジェクトに愛着が湧きますし、何より作業していて楽しいです。
設計コンセプト
このコントローラーの設計にあたって、以下のコンセプトを掲げました。
- コンパクトで持ち運びやすい: ポケットに入るサイズ感
- 直感的な操作性: アナログスティックによる滑らかな制御
- 視認性の高いディスプレイ: リアルタイムで制御状態を確認できる
- 拡張性: 将来的に機能追加が容易な設計
- 低コスト: できるだけ一般的な部品を使用して製作コストを抑える
PCB設計の詳細
PCBの設計には、KiCadを使用しました。基板は表裏両面実装で、表面にはユーザーインターフェース部品(スティック、ディスプレイ、ボタン)を配置し、裏面にはESP32モジュールを配置する設計としました。
当初は、ESP32-S3をSMD(表面実装)で直接実装する案も検討しました。atomic14氏が公開しているbasic-esp32s3-dev-boardの設計を参考にすれば、よりコンパクトで洗練されたデザインにできるはずでした。
しかし、SMD部品の実装には専用のヒートガンやリフロー設備が必要です。残念ながら私の作業環境にはそのような設備がなく、ハンダゴテだけでの実装は品質面でリスクが高いと判断しました。そのため、最終的にはモジュール式のESP32-WROOM-32Dを採用することにしました。モジュール式であれば、通常のハンダ付けで確実に実装でき、また万が一の故障時にも交換が容易です。
使用部品リスト
設計はできるだけシンプルに、かつ必要十分な機能を持たせることを心がけました。使用した部品は以下の通りです。
メインコンポーネント
- ESP32-WROOM-32D: マイコンモジュール
- Wi-Fi通信機能内蔵
- デュアルコア、240MHz動作
- 豊富なGPIOピン
- 価格: 約1,500円
入力デバイス
-
ALPS電気 RKJXV1224005: 2軸アナログジョイスティック
- 5ピン構成(X軸、Y軸、プッシュスイッチ、VCC、GND)
- 10kΩの可変抵抗内蔵
- 滑らかな操作感で、ドローンの精密な制御に最適
- 価格: 約800円
-
6mm タクトスイッチ × 5個: 離陸、着陸、校正などの機能ボタン用
- 標準的な4ピンタイプ
- プッシュ時の確実なクリック感
- 価格: 1個あたり約10円
出力デバイス
- SSD1306 OLEDディスプレイ: 0.96インチ 128×64ピクセル
- I2C通信で簡単に接続可能
- 高コントラストで視認性が良い
- 制御パラメータや接続状態をリアルタイム表示
- 価格: 約500円
その他部品
- プルアップ/プルダウン抵抗: 10kΩ × 4個
- ボタン入力の安定化用
- ノイズ対策
総製作コスト
部品代の合計は約1000円程度と、非常にリーズナブルな価格で実現できました。基板の製造費用(JLCPCBなどの格安基板製造サービスを利用)を含めても、2000円以内で製作可能です。
市販のドローンコントローラーが数千円から数万円することを考えると、自作ならではのコストパフォーマンスの良さと、何より自分で設計した達成感が得られます。
ソフトウェアの実装
ハードウェアの設計と並行して、ESP32用のファームウェアも開発しました。主な機能は以下の通りです。
- Wi-Fi接続管理: ドローンのAPへの自動接続
- UDP送信ループ: 80Hzでの高頻度パケット送信
- ジョイスティック入力処理: ADC読み取りとスティック値への変換
- ボタン入力処理: コマンド送信のトリガー
- ディスプレイ制御: リアルタイム情報の表示更新
特に、解析で判明した「常に中立パケットを送信し続ける」というフェイルセーフ機構を忠実に再現することで、安定した通信を実現しました。
次のステップ
基板の設計が完了したら、次は実際に基板を発注して組み立て、実機でのテストを行います。もし問題があれば設計を見直し、最終的には完全に自作のドローンコントローラーとして完成させる予定です。
今回はJLCPCBを選択しました。最近?になってカラー基板の値段がすべて同じになったので、パープルを選択してみました。黒と緑は大量に生産するためか、正味4日程度で届くんですが、今回は7日以上かかりました。
本当はもっと薄い紫色だと信じたい
専用コントローラーの実装
11月下旬に深圳でフライト遅延があったらしく、今回は今までで最も遅い到着となりました。注文から実際に手元に届くまで、通常よりも3日ほど長くかかってしまいました。海外の物流はこういうことがあるので、余裕を持ったスケジュールが大切ですね。
気になる基板の色は、想像よりもかなり濃い紫色でした。JLCPCBの製品ページで見たサンプル画像はもっと淡い薄紫だったので、少し驚きました。でも、この濃いパープルも悪くありません。むしろエヴァンゲリオンの初号機を彷彿とさせる雰囲気があって、かっこいいです。
濃い目の紫色
届いた基板を手に取ってみると、品質も良好でした。シルク印刷もクリアで、スルーホールもきれいに開いています。早速、作業を始めることにしました。
はんだ付け作業
とりあえず、必要な部品を全てはんだ付けしていきます。今回の基板はスルーホール実装が中心で、SMD部品がないため、作業難易度はかなり低めです。普通のはんだごてがあれば十分で、リフロー炉やホットエアーステーションのような特殊な設備は必要ありません。これは自作初心者にも優しい設計だと思います。
かっこいいー
部品点数もそれほど多くないので、集中して作業すれば1時間程度で完成します。特に難しい部分はありませんでしたが、ジョイスティックモジュールとESP32の実装だけは少し注意が必要でした。ジョイスティックは5ピンと少ないものの、ピン配置を間違えると動作しないので、データシートを確認しながら慎重にはんだ付けしました。
実装時の小さなTips
ところで、どうでもいいTipsですが、SSD1306 OLEDディスプレイは両面実装のため、このコントローラーのようにピンが外側に出ている状態で実装する場合は、SSD1306の裏面に帯電防止テープを貼っておいた方が良いです。
なぜかというと、ディスプレイの裏面には小さなSMD部品やトレースが露出しており、これが基板上の他の部品やパターンに接触してショートする可能性があるからです。特に今回のような小型基板では、部品同士の間隔が狭く、意図しない接触が起こりやすくなります。帯電防止テープ(カプトンテープでも可)を貼ることで、絶縁を確保しつつ、静電気からも保護できます。
こういった小さな工夫が、後々のトラブルを防ぐことにつながります。実際、以前別のプロジェクトでこれを怠ったために、ディスプレイが正常に動作せず、原因究明に数時間を費やした苦い経験があります。
ファームウェアを作る
さて、ハードウェアの準備が整ったので、次はソフトウェアの実装です。「ファームウェアを作る」と大層なことを言っていますが、実際にはすでにパケットの解析が完了しているので、あとはその仕様に準拠したコードを書くだけです。幸い、ESP32はArduinoフレームワークでプログラムできるため、開発環境の構築も簡単です。
WiFi接続機能の実装
まずは基本となるWiFi接続機能から実装していきます。Arduino IDEに標準で入っている「WiFiScan」のサンプルコードを参考にしつつ、ドローンのアクセスポイントを検索・接続する画面を作っていきます。
ディスプレイ制御には、Adafruitが提供しているSSD1306ライブラリを使用しました。このライブラリは非常に使いやすく、テキスト表示、グラフィック描画、さらには簡単なアニメーションまで対応しています。英語表示だけで十分なので、日本語フォントのような複雑な対応は不要です。
コードの基本的な流れは以下の通りです。
- WiFiスキャン: 起動時に周辺のアクセスポイントをスキャン
- リスト表示: 検出されたネットワークをディスプレイに一覧表示
- 選択: ボタン操作でドローンのAPを選択
- 接続: 選択したAPに自動接続
特に工夫した点は、スクロール機能です。検出されるネットワークが多い場合、すべてを一度に表示することはできません。そこで、上下ボタンでカーソルを移動すると、リストが自動的にスクロールする仕組みを実装しました。これにより、多数のネットワークがある環境でも、快適に目的のAPを選択できます。
接続テストの実施
実装が一通り完成したら、まずはWiFi接続だけをテストしてみます。コントローラーの電源を入れると、ディスプレイに「Scanning...」と表示され、数秒後にネットワークリストが現れました。
APを選択する
ボタンを操作してドローンのAPを選択し、決定ボタンを押すと、「Connecting to: [ドローンAP名]」という表示に切り替わります。そして数秒後...
Wifiアクセス完了
**「Connected!」**の文字が表示されました!おお、素晴らしい!ドローンのアクセスポイントへの接続が無事に成功しました。IPアドレスも正常に取得できており、ネットワーク層の通信は問題なく動作していることが確認できました。
この時点で、ハードウェアとソフトウェアの基本的な統合は成功したと言えます。ディスプレイ、ボタン、WiFiモジュール、すべてが正常に機能しています。
制御パケットの送信テスト
次はいよいよ本題、ドローンの制御です。先ほど解析したパケットフォーマットに従って、実際にUDPパケットを送信してみます。
初回テスト: ジャイロキャリブレーション
最初のテストとして、比較的安全な「ジャイロキャリブレーション」コマンド(0x04)を送信してみることにしました。このコマンドはドローンのジャイロセンサーを校正するもので、モーターが回転したり飛行したりすることはありません。
コントローラーから123バイトのパケットを送信すると...ドローンのLEDが激しく点滅し始めました!これは明らかにキャリブレーション処理が実行されている証拠です。数秒後、LEDの点滅が止まり、キャリブレーションが完了したことを示す緑色のLEDが点灯しました。
完璧です! パケットが正しく送信され、ドローンがそれを受信し、適切に処理していることが確認できました。
本格テスト: モーター始動
ジャイロキャリブレーションが成功したので、次は「離陸」コマンド(0x01)を試してみます。ただし、実際に飛行させるのはリスクがあるため、まずはドローンを床に置いた状態でモーター始動だけを確認することにしました。
ボタンを押してコマンドを送信すると...
接続→ジャイロキャリブレーション送信→モーター始動コマンド送信
プロペラが回り始めました! 4つのモーターすべてが同期して回転しており、ドローンは今にも浮上しそうな勢いです。スロットルを上げれば、間違いなく飛行できる状態です。
この動画は、ドローンのアクセスポイントに接続した後、まずジャイロキャリブレーションを実行し、その後モーター始動コマンドを送信している様子を撮影したものです。実際に飛ばそうと思えば飛ばせる状態なのですが、片手でスマートフォンを持って動画を撮影しながら、もう片方の手でドローンを操縦する器用さが私にはありません(笑)。そもそも室内で飛ばすのは危険ですし、墜落して壊れたらこれまでの苦労が水の泡です。なので、今回はモーター始動の確認までにしておきます。
ジョイスティック制御の実装
モーター始動が確認できたので、次はジョイスティックによる連続的な制御を実装します。これが最も重要な部分であり、ドローンを実際に操縦するための核となる機能です。
ADC読み取りとキャリブレーション
ESP32のADC(アナログ-デジタルコンバータ)は12ビット分解能で、0〜4095の値を返します。しかし、実際のジョイスティックは完全に中央に戻らなかったり、最大・最小値が理論値とずれていたりすることがあります。
そこで、実際にジョイスティックを動かしながらADC値を観察し、以下のキャリブレーション値を決定しました。
Y軸(スロットル/ピッチ):
- 中央値: 1963
- 最小値: 30
- 最大値: 4095
X軸(ヨー/ロール):
- 中央値: 1790
- 最小値: 50
- 最大値: 3770
これらの値を使って、ADC値をドローンが期待する制御値(40〜220、中央128)にマッピングします。
デッドゾーンの実装
ジョイスティックは完全に中央に戻っても、微妙なノイズや機械的な遊びにより、わずかに値が変動します。これをそのまま送信すると、ドローンが意図しない微小な動きをしてしまいます。
そこで、デッドゾーンを実装しました。中央値から一定範囲内(今回は±100)の値は、すべて「中央(ニュートラル)」として扱い、0.0を出力します。これにより、ジョイスティックを離した時にドローンがピタッと静止するようになります。
EMAフィルタによる平滑化
ADCから読み取った値は、電気的なノイズの影響で瞬間的に大きく変動することがあります。これをそのまま送信すると、ドローンの動きがギクシャクしてしまいます。
そこで、EMA(指数移動平均)フィルタを実装しました。これは、新しい値と過去の平均値を重み付けして平均化する手法で、ノイズを除去しつつ、応答性を維持できます。
cppavg_L_UD = (analogRead(PIN_LEFT_UD) * JOYSTICK_EMA_ALPHA) + (avg_L_UD * (1.0 - JOYSTICK_EMA_ALPHA));
今回は「ピーキー」な操作感を重視するため、JOYSTICK_EMA_ALPHAを1.0に設定していますが、この値を0.3程度に下げると、より滑らかで安定した操作感になります。
送信ループの実装
ドローンの制御は、**80Hz(約12.5ms間隔)**で連続的にパケットを送信し続けることで実現されます。パケットの送信が途絶えると、ドローンは通信が切れたと判断し、自動的に着陸モードに入ります。これはフェイルセーフ機構として非常に重要です。
コード内では、millis()を使って経過時間を管理し、13ms(約77Hz)ごとにパケットを送信するようにしています。
cppif (millis() - lastPacketTime > CONTROL_INTERVAL) { lastPacketTime = millis(); readJoystickInputs(); sendControlPacket_123byte(); }
ディスプレイ表示の最適化
当初、制御パケットを送信するたびにディスプレイも更新していたのですが、これが原因でESP32がクラッシュする問題が発生しました。SSD1306への描画処理は意外と重く、I2C通信のオーバーヘッドも無視できません。
そこで、画面更新を100msごとに制限することで、この問題を解決しました。
cppif (millis() - lastScreenUpdateTime > SCREEN_UPDATE_INTERVAL) { lastScreenUpdateTime = millis(); drawScreen(); }
これにより、制御ループは高速(80Hz)に動作しつつ、ディスプレイは程よい頻度(10Hz)で更新されるようになりました。人間の目には10Hzでも十分滑らかに見えますし、ESP32への負荷も大幅に軽減されました。
実機テストと調整
すべての機能が実装できたら、実際にドローンを飛行させてテストします...と言いたいところですが、やはり室内での飛行は危険です。そこで、屋外の広い場所で慎重にテストを行うことにしました。
最初の数回は、ジョイスティックの感度調整に苦労しました。少し倒しただけで急激に動いてしまったり、逆に反応が鈍すぎたりと、なかなか思い通りにいきません。デッドゾーンの幅や、Expoカーブのパラメータを何度も調整し、ようやく自分好みの操作感に仕上げることができました。
完成したコントローラーの使用感
最終的に完成したコントローラーは、想像以上に使いやすいものになりました。ジョイスティックの操作感は滑らかで、ドローンがまるで自分の手足のように動きます。ディスプレイには現在の制御値がリアルタイムで表示されるため、どのくらいスティックを倒しているかが一目でわかります。
また、ボタン一つで離陸・着陸・キャリブレーションができるのも便利です。市販のコントローラーと比べても遜色ないどころか、自分の好みに合わせてカスタマイズできる分、より愛着が湧きます。
今後の改善案
現在のコントローラーはほぼ完成していますが、まだ改善の余地があります。
- バッテリー駆動対応: 現在はUSB給電ですが、リチウムバッテリーを内蔵して完全にポータブルにしたい
- 録画機能の実装: ドローンには録画コマンドもあるので、これを実装したい
- FPV対応: ドローンからの映像をリアルタイムで受信して、ディスプレイに表示できれば最高です
- 3Dプリントケース: 基板がむき出しなので、かっこいいケースを設計して印刷したい
このプロジェクトはまだ終わりではありません。これからも改良を続けて、さらに完成度の高いドローンコントローラーに仕上げていきたいと思います。
長い記事を最後まで読んでいただき、ありがとうございました。もし同じようなプロジェクトに挑戦する方がいれば、この記事が少しでも参考になれば幸いです。
コメント (0)
まだコメントがありません。最初のコメントを投稿してみましょう!