Dropbox

前回、手順は確認出来ました。次に探求すべきはどのモデルを使いepochsに何回指定すれば良いのかということです。まずはepcohs=200で各種モデルのテストをしてみようと思い立ちましたが、M2 MacBook AirではいくらGPU使ってもモデルによっては一晩では学習が完了しないということが分かりました。メモリーサイズの関係で生成出来ないモデルもあります。それは諦めるとしても時間がかかってしょうがない。目標が定まれば時間をかけて生成するのも悪くはないですが、試行錯誤のためには素早く終わらせたい。

高価なGPUを買いに走るわけにも行かないのでGoogle Colabを試してみることにしました。最初、無料版で試していましたが、色々と試しているとすぐに使用制限がかかって止まってしまいます。多分、何をするかが決まっていれば無料版だけでも大丈夫かも知れませんが、ストレス無くテストを行うために一番安いPay As You Goの1179円を支払って100コンピューティング単位を買いました。この単位がどれくらいのものかは分かりませんが、一連のテストで消費したのは17単位ほどでしたので、小規模なテストには十分でしょう。速度については申し分ないです。M2 MacBook Airで一晩で終わらない学習が無料で使えるGPUでも10分ほどで完了します。

以下、Colabでの実行例です。

前回、Label StudioからExportしたデータはGoogle Driveにアップロードしました。Colabノートブック内にアップロードする方法もありますが、ノートブックを接続解除(頻繁に行う可能性があります)するたびに消えてしまうので使い勝手が良くありません。

yamlファイルもGoogel Driveに作成します。

# fpvgoals dataset

# train and val data as 1) directory: path/images/, 2) file: path/images.txt, or 3) list: [path1/images/, path2/images/]
train: /content/drive/My Drive/fpvgates
val: /content/drive/My Drive/fpvgates

# number of classes
nc: 2

# class names
names: ['gate', 'goalgate']

Google Colabで新規ノートブックを作ります。

「ランタイム/ランタイムの変更」でT4 GPUを選択します。下のスクリーンショットは課金した後なので選択肢が多くなっています。

!pip install ultralyticsを実行しUltralyticsを導入します。

そして以下のコードを貼り付けて実行します。

import time
start = time.time()
print(f'start time: {start}')
from ultralytics import YOLO
model = YOLO('yolov9m.pt')
model.info()
model.train(data='/content/drive/My Drive/fpvgoals.yaml', epochs=200, imgsz=640)
trained = time.time()
print(f'elpased time: {trained-start}')
model.val()
end = time.time()
print(f'end time: {end - start}')

Colabの画面だとこんな感じです。

これでモデルを変えたりepochsに指定する数値を変更して試してみました。元になるデータは最初に用意した56枚の画像です。

ざっくりと目視でトラッキング画像で判断して、重いモデルを使用するほど精度は良くなりそう。またepochsは20回より200回がはるかに良いという当たり前のことが確認できました。精度(誤認識の程度)が目的に適っているかどうかは簡単なプログラムを書いて確認しました(プログラムについてはいずれ紹介します)。その結果、軽めのモデルでも十分な結果が出せそうだということも分かりました。

軽いモデルの利点は学習時間が短い、大きなメモリーを必要としないということは既に分かっていましたが、それに加えて画像認識に要する時間が短いという特性があります。これはリアルタイム処理能力を考えるうえで重要なポイントになります。

ベース・モデル 1フレームの処理時間 fps
yolov9t.pt 45ms 22.2
yolov9s.pt 45ms 22.2
yolov9m.pt 75ms 13.3
yolov9c.pt 86ms 11.6
yolov9e.pt 152ms 6.6
yolo10n.pt 42ms 23.8
yolo10s.pt 64ms 15.6
yolo10m.pt 57ms 17.5
yolo10b.pt 79ms 12.7
yolo10l.pt 90ms 11.1
yolo10x.pt 115ms 8.7

これはM2 MacBook Airでの実行例です。22FPS出ているとまずまず目的に適っている気がします。ということでyolov9s.ptを使用する事にしました。yolov10nの方が速度が出ているのですが、まあ何となく感で決めました。

epochsについては200,300と400で比較してみましたが、見た目での差はほとんど分かりませんでした。result.pngで表示されるグラフについても微妙な差でした。勘でしかありませんが当面はepoch=300で進めてみます。

あとは元となる画像を増やしていって何かしら問題点があるかどうかを試していきたいです。

入門などと書いていますが、解説的なことは一切なく、やってみたら出来た手順の紹介です。

目的はここでは明かしませんが(出来るかどうか分かっていないw)、サンプル画像などから自ずと分かってくることでしょう。

世の中にない目的物を認識するためには自分で学習させないといけません。それを教師付き学習という手法で行います。その手順を示すことが、この記事の主な目的です。

[ Python3で仮想環境を作る ]
とりあえずコマンドラインでpython3が起動することを確認してください。記憶は定かではありませんがMacOS Sonomaでも何かしら作業を行わないと動かないかも知れません。その辺りはググっていただきたいです。WindowsならばPython3の導入が必要ですが、ここでは詳細は省きます。また以下の手順はMacOSを使用しています。

後から簡単に無かったことに出来るのでpythonの仮想環境を作って試すことをお勧めします。ターミナルを開き作業用のディレクトリーにて以下の呪文を唱えれば良いです。

python3 -m venv .yolo
source ./.yolo/bin/activate

.yoloは任意の名前です。最初のピリオドは無くても良いです。

以下の作業は全てこの仮想環境で行なっています。

[ アノテーション ]
学習する元データとなるサンプル画像をなるべく多数用意します。そして一枚一枚の画像の中で目標物の範囲を指定し、それが何者であるかのラベルを付けていく作業です。根気と忍耐が試されるステージとなります。

使用するツールはいくつか存在しますが、最初に試したのはlabelImgというものでした。「YOLOv8自作データセットの学習方法(ローカルでも動かすよ)」という記事が詳しいです。手軽に試すには良いのですが、作業を中断したり、後から画像を追加したりするのが難しいので軽いテストで使用するに留まりました。

現在はLabel Studioというツールを使用しています。作業用のPCに入れてローカルでも使えますが、元々サーバーに入れて使うことを前提に考えられているものなので作業用PCとは別のUbuntuサーバーで動かしています。複数の作業者で一つのプロジェクトを完成させるような作りですので作業を途中で中断したり、データを追加するのも自由自在です。Label Studioの使用方法については「多機能アノテーションツールLabelStudioの使い方を解説」という記事が詳しいです。

とりあえず手順の確認のため50枚強の画像を準備しました。元データはFPVドローンのゴーグルで録画した動画ファイルです。動画に直接アノテーションを行う手法もあるようですが、まずは静止画で行います。動画ファイルから使えそうな画像を切り出した静止画(jpgファイル)をLabel Studioにアップロードし、一枚一枚にラベルを付けていきます。これくらいの枚数ならば大して時間はかかりません。

出来上がったらYOLO形式でExportして作業用のPCにダウンロードします。zipファイルになっているので適当なところに解凍して分かりやすいフォルダー名に変更しておきます。

[ YOLOv8 ]
いよいよ学習モデルの作成です。色々な事例を真似してみましたがUltralyticsのYOLOv8パッケージを使用する方法が簡単で確実です。ドキュメントもよく出来ています。これを利用した事例の紹介も多数ありますがサンプルコードは公式ドキュメントと同じものがほとんどです。このブログ記事も含めて、事例紹介を読むより公式ドキュメントをじっくり読むことをお勧めします。

良くわっていませんが、プログラムとしてはYOLOv8ですが学習の元となるモデルはYOLOv10まで進んでいるようです。自分の目的を達成するために、どのモデルを使い、どれくらい学習させれば良いかは試してみるしかないようです。何はともあれ動かしてみます。

[ M2 MacBook Airでの学習 ]
ultralyticsを導入します。

pip install ultralytics

で、いよいよ学習するためのスクリプトです。

import time
from ultralytics import YOLO

start = time.time()
print(f'start time: {start}')
model = YOLO('yolov9s.pt')
model.info()
model.train(data='fpv.yaml', epochs=10, imgsz=640, device='mps')
trained = time.time()
print(f'elpased time: {trained-start}')
model.val()
end = time.time()
print(f'end time: {end - start}')

yolov9s.ptというのが元になるモデルで、何かしらのラベルが付いた学習済みモデルになっています。ここにyolov9s.yamlというファイルを指定して学習させると、全くのゼロからの学習になるらしいですが、ptファイルを使った方が速く結果が出るように思えます。yolov9s.ptは比較的軽いモデルです。目的や学習環境に合わせて、ここを変えます。モデル毎の特徴についてはYOLOのドキュメントを見てください。YOLOv9YOLOv10のリンクを貼っておきます。

epochs=10というのは学習を繰り返す回数です。お試しなので少なめにしておきます。device=’mps’はAppleのM2チップのGPUを使用するためのおまじないです。device=を省略するとMacBookの場合はCPUによる稼働になります。NVIDIAのGPUが実装されている環境ではdeviceの指定がなくても自動的にGPUを使用します。

fpv.yamlというファイルでラベルを付けたデータの置き場所を指定します。内容は以下の通りです。

# fpvgoals dataset

# train and val data as 1) directory: path/images/, 2) file: path/images.txt, or 3) list: [path1/images/, path2/images/]
train: /Users/kozak/Downloads/fpvgates
val: /Users/kozak/Downloads/fpvgates

# number of classes
nc: 2

# class names
names: ['gate','goalgate']

trainとvalの所で指定しているのはLabel StudioからExportしたYOLO形式のzipファイルを展開したフォルダーです。ncは作成したラベルの数に合わせます。namesにはラベル名を書いておきます。

これで先のスクリプトを走らせれば自前の学習済みデータが出来上がります。結果は’runs/detect/train{n}/’ ({n}は数字と置き換えて読んでください)以下に入ります。学習結果を示す各種データがありますが、あまり良くは研究していないです。result.pngを見比べるとepochによる繰り返しが適当な回数になったかどうかが分かるような気もします。出来上がったモデルはweights/bets.ptです。これを使って画像認識を行います。

[ 動画に画像認識をかける ]
いきなりスクリプトを提示します。

import cv2
import time
from ultralytics import YOLO
model = YOLO("./runs/detect/train13/weights/best.pt")
model.to('mps')

video_path = "../video/test2.mov"
cap = cv2.VideoCapture(video_path)
t1 = time.time()
while cap.isOpened():
  success, frame = cap.read()
  if success:
    results = model.track(frame, persist=True)
    annotated_frame = results[0].plot()
    cv2.imshow("FPV Tracking", annotated_frame)
    t2 = time.time()
    print("elapsed: " + str(t2-t1))
    t1 = t2
    if cv2.waitKey(1) & 0xFF == ord("q"):
      break
  else:
    break
cap.release()
cv2.destroyAllWindows()

最初の方にあるbest.ptが自分で生成したモデルです。その直後にある’model.to(‘mps’)はApple M2チップのGPUを使用するためのおまじないです。
“../video/test2.mov”で用意した動画に対して画像認識を行います。単純なトラッキングならば動画を直接YOLOに渡してしまっても良いのですが、画像認識の結果(resultsに格納される各種データ)を後からプログラムで処理するためにOpenCV2にて画像をフレームごとに分解して解析を行うようにしています。

下の例は、もうちょっと深く学習させてからの例ですが、様子的には同じものが出てくきます。

少しテストしてみるとepochs=10では足りなくて200回か300回回した方が良いと分かります。問題は処理時間でM2 MacBook Airでは一晩走らせても終わらないということもあります。あと16GBメモリーのMBAでもメモリーが足りなくなりそうでした。

ということで、探求は次回に続きます。

ひょんなことからTinySA Ultraという簡便なスペクトルアナライザーが6GHzまで対応しているらしいことを知りました。これならVTXの電波が見えるのではないかと思い試すことにしました。

詳細な情報はTinySA Wikiにまとまっています。

[ セルフテストとキャリブレーション ]
最初にテストと校正を行えとのことなのでやってみます。

まずは付属しているケーブルで上下のポートを接続します。画面をタップ(今時の静電式ではなく感圧式です)するとメニューが出ますのでCONFIG/SELF TESTを実行します。

続いてLEVEL CALを実行します。

こんなふうに5.34GHzを境に分かれています。CALIBRATE 100kHz to 5.340GHzを実行します。残念ながらVTXの周波数が含まれるCALIBRATE above 5.340GHzの方はTinySA単体では実行できないようです。まぁ精密な測定を行うわけではないので校正せずに進めます。

[ ENABLE ULTRA ]
6GHzまで測定するためにはCONFIG/->MORE/ENABLE ULTRAを有効にします。この時Unlock Codeを聞かれるので4321と入力します。

[ VTXの電波を見る ]
細かい設定はまだ良くわかっていませんが、とりあえずはVTXの電波は見ることが出来ました。

 下のポートに適当なアンテナを接続して周波数を設定しただけです。

トップメニューのFREQUENCYに入ります。STARTとSTOPの周波数を設定します。私はそれぞれ5.6GHzと6GHzにしてみました。

実際の電波を見たところです。F4(5800MHz)が見えています。ピークを検出して周波数を教えてくれるのが便利です。規定値ではひとつの周波数しか教えてもらえないので設定を変更します。トップメニューのMARKERに入ります。私は4 MARKERSにしてみました。

そしてE1, F1, F4の電波を同時に出してみました。

大体良い感じです。残念ながら毒電波を吐くVTXが手元にないので、悪い事例を紹介することはできませんでした。

[ 設定の保管 ]
ここまで行った設定をいつでも呼び出せるように保管しておきます。
トップメニューからPRESET/STORE/STORE 1にVTXという名前で保管しました。

これでいつでも設定が呼び出せます。また’STORE AS STARTUP’で起動時に読み込まれます。

[ SCREEN COPY ]
STORAGE/SAVE CAPTUREで画面のコピーがSDカードにBMPフォーマットで保管できます。いちいち名前を付けないと行けないのがちょっと面倒です。このブログにしようしている画面はWindowsのアプリから取り込んだものです。こちらはPNGフォーマットでした。このアプリ、すぐに接続は切れるし思うように動かないしで今ひとつですので詳しいことは省きます。

[ Water Fall表示 ]
トップメニュー/DISPLAY/WATER FALLをオンにすると時間経過とともに一定強度の信号を受信した記録が残ります。つまり少しだけ時間を遡って電波の状態を調べることが出来ます。

[ おまけ ]
HDZeroのNarrowモードが本当に17MHz帯域幅なのかどうかを調べてみました。

RotorHazardはシステムとしての作り込みがとても素晴らしくレース運営に力を発揮しますが、すでにTinyViewPlusでレースの運営が確立していることも多いと思います。そういうケースにおいてRotorHazardを補助的に使ってみようという贅沢な試みです。発案はninjaMoonLightさんです。

現在、TinyViewPlusを使用していてARマーカーを読み損なった時の対策としてゲートを通過する時にAR認識と並行して手動でLAP追加を行うという作業を行なっています。これの代わりにRotorHazardが認識したLAPをOSCを介してTinyViewに伝えようというものです。ただし予想される問題としてゲート不通過で近くを通った場合もLAP検知が発生することです。これはやはり審判員による修正が必要になります。

動作テストの様子です。

RotorHazardのプラグインとして作りました。GitHubで公開しましたので以下のリンクをご覧ください。

https://github.com/nkozawa/rh_tinyviewplus

VTX電波を利用した高機能なFPVレース計測システムであるRotorHazardのプラグインを作るべく調査中です。結局のところ公式ドキュメントを読んでも今ひとつ分かりにくいので、実際に動きそうなプラグインを読んだりRotorHazardのソースを読むのが手っ取り早いです。

タイトルには作り方などと書きましたが、とりあえず最低限の動作可能なコードを示しておくに留めます。何かしらお作法的なことに抜けがあるかもしれませんので予めご了承ください。またプラグインを公開する際にはマニュフェストも書いた方が良いようですが、それもまだ未調査です。

[ 言語と実行環境 ]
プラグインはPython3で記述します。RotorHzard自体がPythonで記述されておりPythonの仮想環境で実行されています。もしpip3で新たなモジュールの導入が必要な場合は、その仮想環境に入ってpip3の実行を行う必要があります。仮想環境に入るにはRotorHazardが導入されているユーザーのホームでsource ./.venv/bin/activateを実行します。ターミナルでsystemctl cat rotorhazardを実行すればRotorHazardがどこに導入されていてどの仮想環境が使われているかは確認できます。

[ プラグインの作成 ]

/home/pi/RotorHazard/src/server/plugins

以下にディレクトリーを作ります。私はテスト用に

mkdir rh_myTestLog

などとしました。
そしてそのディレクトリー下に

__init__.py

というpythonコードを作成します。コードの中身は以下の通りです。

# my first plugin test

import logging
logger = logging.getLogger(__name__)

from eventmanager import Evt

def myTestLog(args):
  node = args['node_index']
  logger.info("My test plugin - LAP (" + str(node) + ")")

def initialize(rhapi):
  rhapi.events.on(Evt.RACE_LAP_RECORDED, myTestLog)

initialize(rhapi)が必須の関数で、ここでは主にトリガーとなるイベントを登録します。この例ではドローンが計測点を通過してラップタイムが記録されたタイミングでmyTestLog(args)という関数が呼ばれるように定義しています。トリガーとなるイベントはeventmanager.pyで定義されていますので、このコードを参照して名前から必要なイベントを探ってみてください。

myTestLog(args)の引数argsにはイベントに対応したデータが渡されます。この例ではargs[‘node_index’]にてラップタイムが記録されたノードの番号を取得しています。そしてそれをログに書き込んでいます。
このRACE_LAP_RECORDEDにどんなデータが付いてくるかはソースコードRHRace.pyに記述されていました。

[ テスト ]
プラグインを書き込んだらsudo systemctl restart rotorhazardの実行あるいはシステムを再起動します。

RotorHazard起動したらログを確認します。正常にプラグインが組み込まれれば以下のようなログが記録されます。

2024-07-06 08:21:47.296: __main__ [INFO] Loaded plugin 'rh_myTestLog'

WTW Kagawaの大先輩が65サイズのドローンに載せられるとっても小さなマイコンでRGB LEDを制御するための記事を書かれています。これを共同で発展させてみました。

WTW Shikou(WTW Tokushima + WTW Kagawa)ではタイニードローン・レースにおいてVTXチャネル毎に色を変えたLEDを搭載するというレギュレーションを推進しています。徳島県で行われたNAKA DRONE RACING 2024では色分けが観客視点でのドローンの識別に効果的であることが確認できました。

RGB LEDを搭載するためのオプションの一つとして、この独立したマイコン制御を提案したいと思います。一般的なFCによるRGB LED制御については過去の記事をご覧ください。


使用しているマイコンチップはATtiny85 SOP版です。本当に小さくてサイズ的にどんなドローンにも無理なく載せられます。重量などは計測する気にもならないくらいです。これ一つでRGB LEDを自在に光らせることができます。なんて素敵なことでしょう。

実装するとこんな感じです。プロポのスイッチを短押しで色変更、長押しで光り方の変更を行うようにプログラムしています。
使用しているRGB LEDはKou_chanの2.7mm幅のものです。

このマイコン制御によるLED搭載が活躍するシーンとしては次のようなことが考えられます。
– Mobula6 2024のようにLEDパッドがないFCを搭載している場合
– 某ベータのFCのようにLEDパッドがあるのにまともに動かないFCを使用する場合 <- 既知の回避策が効かないケースもあるそうです
– BetaflightのLED個数制限である32個を超えるLED数を扱いたい

この方法の欠点、難点は以下の通りです。
– 予めプログラムした限られたパターンのLEDの光らせ方しか利用出来ない
– ドローンに組み込んだ後からのプログラム変更はかなり難しい
– プログラムを書き込む環境作りが、電子工作に慣れた人に限られる

そもそもプログラムを書き込むこと自体が大変であることはかなり大きな問題ですので、プログラムを書き込み済みのマイコンチップATtiny85の頒布をいたします。プログラムは2種類あります。目的に応じてお選びください。以下簡単なガイドと頒布用ページのリンクを載せておきます。
FCとの接続方法、Betaflightの設定、自分でプログラムを書き込む方法などは別のページをご覧ください。

< FCに接続せず、小さなスイッチを搭載したい、あるいは信号線をGNDに触れさせることにより制御したい >
RGB LED制御プログラム書き込み済みATtiny85 – スイッチ/BUZZパッド接続用
< FCのBUZZパッドに接続して送信機から制御したい >
RGB LED制御プログラム書き込み済みATtiny85 – スイッチ/BUZZパッド接続用
< FCのUARTもしくはLED_Strip用パッドに接続して送信機から制御したい >
RGB LED制御プログラム書き込み済みATtiny85 – UART/LEDパッド接続用

実装方法については別記事にいたします。
ATtiny85によるLED制御 – ハードウェア実装編
ATtiny85によるLED制御 – BUZZパッドを利用した制御方法
ATtiny85によるLED制御 – UART/LEDパッドを利用した制御方法
ATtiny85によるLED制御 – プログラムの書き込み方法

[ 関連記事 ]
ドローンにRGB LEDを搭載するもう一つの方法 <- 親記事です
ATtiny85によるLED制御 – BUZZパッドを利用した制御方法
ATtiny85によるLED制御 – UART/LEDパッドを利用した制御方法
ATtiny85によるLED制御 – プログラムの書き込み方法

[ 配線についての概略 ]
実際の配線は写真を見てもらうとわかりやすいと思います。

この写真では必要のないピンをカットしています。実際のATtiny85には8本のピンがあります。チップ表面の丸い印が1番ピンを表します。そこから反時計回りにピンは数えます。
使用している4本のピンの説明です。

ピン番号 名前 リード線の色 役割
2 D3/A3 RGB LEDの制御信号出力、RGB LEDの信号入力に接続
4 GND GND端子、バッテリーのマイナスに接続
5 D0 LEDの色や光り方を変えるためのスイッチ入力、機械的なスイッチもしくはFCに接続
8 VCC 電源入力、バッテリーのプラスに接続

[ 電源 ]
FCの5Vを使用することも考えられますが、たくさんのLEDを光らせるには5V BECの電力に不安があります。1S機ならばVBAT、バッテリーに直接接続した方が安定動作が期待できます。ちなみにATtiny85の動作電圧は2.7Vから5.5Vです。LEDストリップはもう少し電圧の幅が少ないと思います。
ATtiny85とLEDの双方にバッテリーのプラス、マイナスを接続します。上の写真の例ではLEDストリップの信号入力と反対側のリード線がバッテリーに接続されています。ATtiny85にはLEDストリップを通った後の電圧が供給されています。全周巻きの場合は、この方法が配線しやすいと思います。


絵にするとこんな感じになっています。全周巻ではない場合はATtiny85チップとLEDに並行した電源線の接続でも構いません。

[ 信号線 ]
写真の緑のリード線のようにATtiny85からの制御信号出力をLEDの信号入力に接続します。

[ スイッチ ]
黄色い線はスイッチ入力になっています。機械的なスイッチもしくはFCに接続します。FCヘの接続はブザー、UART、LEDポートなどに接続しますが、ブザーとそれ以外で使用するプログラムが異なりますしBetaflighの構成も少し違いますので別記事にて詳しく説明します。
機械的スイッチとBUZZポート用のプログラムは同じものを使用します。この場合、黄色の線をGNDに落とすとスイッチONということになります。スイッチを使う代わりに、この線を適当にGND、例えばUSBコネクターの外枠などに接触させても良いです。

提供しているプログラムでは短押しで色の変更、長押し(1秒くらい)で光り方の変更になっています。

[ 配線 ]
写真のような空中配線で大丈夫だと思います。当然、配線後はショートしないよう絶縁対策が必要です。私は半田付けしたところを保護する目的でUVレジンで固めました。
大きめのドローンなら基板を使うのも良いと思います。その場合、サイズは大きくなりますがDIP版のATTiny85を使用した方が良いでしょう。

[ 関連記事 ]
ドローンにRGB LEDを搭載するもう一つの方法 <- 親記事です
ATtiny85によるLED制御 – ハードウェア実装編
ATtiny85によるLED制御 – UART/LEDパッドを利用した制御方法
ATtiny85によるLED制御 – プログラムの書き込み方法

「RGB LED制御プログラム書き込み済みATtiny85 – スイッチ/BUZZパッド接続用」の入力ポート(5番ピン)をFCのBUZZ用パッドに接続して送信機から制御する方法です。

[ FCに接続せずに使う方法 ]
FCに接続せず入力端子をGNDに落とすだけでも動作させることが出来ます。小さいスイッチを取り付けたり、単純にリード線をUSBの外枠などに接触させるだけでも使用出来ます。

以下、FCに接続して送信機からコントロールする方法です。

[ Betaflightファームウェアの前提 ]

Betaflightの更新の際に”その他のオプション”で”Pin IO”の指定が必要です。このオプションは規定値で有効になっていますので、自分で削除した記憶がなければ問題はないはずです。もし不確かな場合はCLI画面でresource pinioを投入してみてください。赤字で”INVALID RESOURCE NAME”と表示された場合は”Pin IO”オプションを指定してBetaflightの更新を行う必要があります。

[ FCへの接続 ]
電源への接続およびLEDへの接続は「ATtiny85によるLED制御 – ハードウェア実装編」をご覧ください。ATtiny85の5番ピンはそのままFCのBUZZに接続します。

[ Betaflight Configuratorの設定 ]
CLIタブを開いていくつかのコマンドを投入する必要があります。
クリティカルな操作ですので最初にdiff allで構成のバックアップを取得しておいてください。

– BEEPERのリソースを見つける –
CLI画面でresourcコマンドを投入します。resourceの一覧が表示されますので、その中からBEEPERを見つけます。

resource BEEPER 1 A14

右端の文字列、この例の場合は”A14″を記憶します。これがFCボード上のBUZZのパッドに付けられたIDです。

– PINIOの構成 –
引き続きCLIにて以下のコマンドを投入します。PINIOに割り当てるA14は上で実際に調べたものと置き換えてください。

resource BEEPER 1 NONE
resource PINIO 1 A14
SET PINIO_BOX = 40,255,255,255
save

実際の画面だとこんな感じになります。

– モードによる送信機スイッチの割り当て –
上のPINIOの設定によりモード画面にUSER1が現れます。PINIO_BOXの最初のパラメーターである40が、このUSER1を出現させています。詳しいことはBetaflight Wikiをご覧ください。

USER1にAUX4(私の送信機のモーメンタリースイッチ)を割り当てた例です。どの領域をアクティブにするかは環境により変わります。アクティブな状態でATTiny85に対するスイッチオンであると考えて良いと思います。
これで送信機とBUZZパッドが接続されました。

– 動作確認 –
スイッチを短押しして順次色が変われば成功です。長押しで光り方の変更です。もし意図したのと違う動作であればモード画面のアクティブになる範囲を調節してみてください。

[ BUZZパッドにまつわる話し ]
BUZZパッドはとてもユニークなものです。ブザーのプラス側をFCの+5V、マイナス側をBUZZに接続することからも想像出来るように、このポートは開放状態と接地(GND)を切り替えるようなハードウェアの作り込みがなされており、かつブザーを鳴らすのに十分な電流に耐えられます。必要な時にGNDに落とせるスイッチと機能的に透過であると考えられます。そのため今回のような目的で使うのに適しています。

[ 関連記事 ]

ドローンにRGB LEDを搭載するもう一つの方法 <- 親記事です
ATtiny85によるLED制御 – ハードウェア実装編
ATtiny85によるLED制御 – BUZZパッドを利用した制御方法
ATtiny85によるLED制御 – プログラムの書き込み方法

「RGB LED制御プログラム書き込み済みATtiny85 – UART/LEDパッド接続用」の入力ポート(5番ピン)をFCのUARTまたはLED_Strip用パッドに接続して送信機から制御する方法です。

[ Betaflightファームウェアの前提 ]
Betaflightの更新の際に”その他のオプション”で”Pin IO”の指定が必要です。詳細については「ATtiny85によるLED制御 – BUZZパッドを利用した制御方法」の「Betaflightファームウェアの前提」の項をご覧ください。

[ FCへの接続 ]
電源への接続およびLEDへの接続は「ATtiny85によるLED制御 – ハードウェア実装編」をご覧ください。ATtiny85の5番ピンをFCのLEDパッドに接続します。TX, RXなどを使用いることも出来ます。

以下、LEDパッドで説明しますが必要に応じてTX, RXなどと読み替えください。

[ FCへの接続 ]
電源への接続およびLEDへの接続は「ATtiny85によるLED制御 – ハードウェア実装編」をご覧ください。ATtiny85の5番ピンはそのままFCのLEDバッドに接続します。

[ Betaflight Configuratorの設定 ]
CLIタブを開いていくつかのコマンドを投入する必要があります。
クリティカルな操作ですので最初にdiff allで構成のバックアップを取得しておいてください。

– LED_STRIPのリソースを見つける –
CLI画面で
resourc
コマンドを投入します。resourceの一覧が表示されますので、その中からLED_STRIPを見つけます。

resource LED_STRIP 1 B01

右端の文字列、この例の場合は”B01″を記憶します。これがFCボード上のLED_STRIPのパッドに付けられたIDです。

もしFC上にLEDパッドがあるのにresourceにてLED_STRIPが見つからない場合はBetaflightを更新の際に”LED Strip”のオプション指定が無いのかも知れません。オプションを指定して更新し直してください。

– PINIOの構成 –
引き続きCLIにて以下のコマンドを投入します。PINIOに割り当てるB01は実際の環境に応じて置き換えてください。

resource LED_STRIP 1 NONE
resource PINIO 1 B01
SET PINIO_BOX = 40,255,255,255
save

実際の画面だとこんな感じになります。

– モードによる送信機スイッチの割り当て –
上のPINIOの設定によりモード画面にUSER1が現れます。PINIO_BOXの最初のパラメーターである40が、このUSER1を出現させています。詳しいことはBetaflight Wikiをご覧ください。

USER1にAUX4(私の送信機のモーメンタリースイッチ)を割り当てた例です。どの領域をアクティブにするかは環境により変わります。アクティブな状態でATTiny85に対するスイッチオンであると考えて良いと思います。
これで送信機とLED_STRIPパッドが接続されました。

– 動作確認 –
スイッチを短押しして順次色が変われば成功です。長押しで光り方の変更です。もし意図したのと違う動作であればモード画面のアクティブになる範囲を調節してみてください。

[ BUZZパッドとの違い ]
LED_STRIP,TXn,RXnなどとBUZZとはハードウェア的に大きく違います。BUZZは単純なスイッチと考えて良いです。オフの時は開放されていて、オンになるとGNDに接続されます。LED_STRIP,TXn,RXnなどは普通のI/Oピンです。出力として設定された場合は状態により+3.3Vもしくは0V(GND)が現れます。そのため、ATtiny85側の設定も変える必要があります。実際にはBUZZ接続用に作ったATtiny85をLED_Stripに接続しても動作はしますが、電子回路の作りとして好ましくない状態が存在します。

[ 関連記事 ]

ドローンにRGB LEDを搭載するもう一つの方法 <- 親記事です
ATtiny85によるLED制御 – ハードウェア実装編
ATtiny85によるLED制御 – BUZZパッドを利用した制御方法
ATtiny85によるLED制御 – UART/LEDパッドを利用した制御方法

注意 : Arduino IDEに組み込むFastLEDライブラリーをバージョン3.7.4以降に更新するとこの手順で作動しなくなりました。もし動かなくなった場合は3.7.3をインストールしてください。

[ 参考記事 ]
1. VTX周波数毎に決められた色にする
2. ミニAVRマイコン『ATtiny85』にブートローダーを書き込みArduinoとして使えるようにしてみる!ATtiny85の基本的な使い方!

この二つを読んで頂ければ、このプログラムの概要とATtiny85への書き込み方法が分かります。補足的な事柄を書いておきます。

[ ATtiny85について ]
ATtiny85にはいくつかのパッケージがあります。開発用、大きめのドローンに搭載するにはDIPバッケージ、Tinyに搭載するにはさらに小型のSOPパッケージが良いと思います。SOPにプログラムを書き込む時にはDIPへの変換ソケットがあると便利です。
10MHz動作版と20MHz動作版がありますが、どちらでも大丈夫と思います。

[ ツール類 ]
PCからATtiny85に接続するために何かしらツールが必要です。上のリンクでいくつか紹介されていますが、私は手元にあるArduino Nanoを使用しました。その他にブレッドボードがあると便利です。私はAmazonで色々と部品が付属したキットを購入しました。

[ 接続 ]
上の参考記事2にある「ArduinoとATtiny85の接続」の通りに接続します。記事ではArduino Unoを使用していますが、Arduino Nanoでも全く同じです。ピンの名前を頼りに接続します。写真はDIP版ATtiny85にプログラムを書き込んでいる様子です。

[ 書き込みソフトウェアの準備 ]
ATtiny85をArduinoとして使用します。お馴染みArduino IDEを使ってソフトウェアを書き込みます。

– Arduino Nanoに書き込み用のソフトウェアを導入します。
わたしのArduino Nanoの設定はこんな感じです。

ExmapleからArduinoISPを書き込んで準備完了です。

– ATtiny85を使用するための準備
ATtiny85を使用するためにはPreference/Additional boards manager URL欄に”https://www.arduino.cc/en/software”を追加しました。参考記事2に書かれていたURLでは機能しませんでした。
Toolsメニューの設定を下のように行いBootloaderの書き込みが成功すればATtiny85がAuduinoとして使えるようになります。

[ ATtiny85へのプログラムの書き込み ]
あとはプログラムを作成し書き込むだけです。設定はBootloaderの書き込みと同じです。Sketch(プログラム)のUploadは通常の方法とは異なり”Sketch/Upload using programmer”を使用します。新しいチップに書き込む時はBootloaderの書き込みも忘れないようにしてください。

今回使用したソースコードです。ninjaMoonLightさんが書き始めて、途中から私も参加して共同作業で書いたコードです。条件コンパイルするようになっています。下の状態ではBUZZパッドもしくはスイッチを使用するプログラムになっています。7行目のコメントを外し”USE_LEDPORT”というマクロを有効にするとLEDパッドを使用するためのプログラムになります。setup()の最後にdelay(500)を入れています。これはある構成においてFCの信号が期待通りになるまでに時間がかかった時に入れたものです。現在の使用方法だと必要はないかも知れませんが安全を見込んで入れたままにしています。

// Pin assign
// arduino PIN 0,button = Attiny PIN 5
// arduino PIN 4,RGB LED = ATTiny PIN 3

// Uncomment following macro (USE_LEDPORT) defintion if use UART/LED_Strip as switch
// UART/LED_Stripをスイッチとして使う場合は以下のマクロを有効にする
//#define USE_LEDPORT

#include <EEPROM.h>
#include <FastLED.h>

//LEDの数
#define NUM_LEDS 80
//LED信号ピン
#define DATA_PIN 3
//ボタンのピン
#define myButton 0

#define NUM_COLORS 4
#define NUM_MODES 3

CRGB leds[NUM_LEDS];
CRGB color = CRGB::Black;

int currentColor = 0;
int currentMode = 0;
int prevColor = -1;
unsigned long prevTime = 0;

int flgTimer = 1;
unsigned long btnTimer;
const unsigned long shortPress = 30;
const unsigned long longPress = 800;

void blinking() {
  unsigned long time = millis() / 100;
  if (time != prevTime) {
    FastLED.setBrightness( 255 );
    for (int i = 0; i < NUM_LEDS; i++) {
      if((time % 2) == 0){
        if((i % 2) == 0){
          leds[i] = color;
        } else {
          leds[i] = CRGB::Black;
        }
      } else {
        if((i % 2) == 0){
          leds[i] = CRGB::Black;
        } else {
          leds[i] = color;
        }
      }
    }
    FastLED.show();
    prevTime = time;
  }
}

void heartBeat() {
  unsigned long time = millis() / 100;
  if (time != prevTime) {
    for (int i = 0; i < NUM_LEDS; i++) {
      leds[i] = color;
    }
    FastLED.setBrightness((7 - time % 7) * 255 / 7);
    FastLED.show();
    prevTime = time;
  }
}

void normal() {
  if (currentColor != prevColor) {
    FastLED.setBrightness( 255 );
    for (int i = 0; i < NUM_LEDS; i++) {
      leds[i] = color;;
    }
    FastLED.show();
    prevColor = currentColor;
  }
}

void setup() {
  currentColor = EEPROM.read(0);
  if (currentColor < 0 || NUM_COLORS <= currentColor) currentColor = 0;
    currentMode = EEPROM.read(1);
  if (currentMode < 0 || NUM_MODES <= currentMode) currentMode = 0;
#ifdef USE_LEDPORT
  pinMode(myButton, INPUT); // button
#else
  pinMode(myButton, INPUT_PULLUP); // button
#endif
  ///////LED
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS); // GRB ordering
  // following delay may not be required
  delay(500);
}

void loop() {
#ifdef USE_LEDPORT
  if (digitalRead(myButton) == HIGH) {
#else
  if (digitalRead(myButton) == LOW) {
#endif
    if (flgTimer) {
      btnTimer = millis();
      flgTimer = 0;
    }
  } else {
    if (!flgTimer) {
      unsigned long diffTime = millis() - btnTimer;
      if (diffTime > longPress) { // Long pressed
        currentMode++;
        if (currentMode >= NUM_MODES) currentMode = 0;
          EEPROM.write(1, currentMode);
      } else if (diffTime > shortPress) {
        currentColor++;
        if (currentColor >= NUM_COLORS) currentColor = 0;
        EEPROM.write(0, currentColor);
      }
      flgTimer = 1;
    }
  }

  switch (currentColor) {
    case 0: color = CRGB::Red; break;
    case 1: color = CRGB::Green; break;
    case 2: color = CRGB::Blue; break;
    case 3: color = CRGB::White; break;
  }
  switch (currentMode) {
    case 0: normal(); break;
    case 1: blinking(); break;
    case 2: heartBeat(); break;
  }
}