2022年6月22日水曜日

二之袋ベースでの初飛行

今年の2月くらいまでは、 自作のドローンを東京板橋区の狭い自室で飛ばしても、見えてくるものは何も無くなって、どうしようかと苛立っていた。

3月になって、ドローンを飛ばす自分の土地を手に入れようと重箱の隅をつつくようにネットを漁っていて、千葉東金市の二之袋というところに100坪の土地を見つけた。たぶん、超格安だと思う。ネットを見過ぎて頭に相場感覚が刷り込まれてしまっている(笑)

成田空港の規制からも距離的にギリギリ外れていて、DID地区でもないので、自分の土地でやる限り特に規制がない状況だ。ただ、古い造成地なので、近所の皆さんに迷惑をかけない努力は怠ることはできない。

ただ、二之袋は九十九里浜から4キロ程度しか離れていないせいだと思うが、海風、陸風なのか、いつも弱い風が吹いている。安定感の脆弱なドローンにとって、風は大敵だ。そこで、塩ビのパイプを組み合わせてフレームを組み、工事用のシートを組み合わせて、4畳くらいの広さの風よけテントを作った。



一面は、物の出し入れなどのために、シートは貼らない。風向きによって開ける面は変える。高さは、2.5メートルほどで大したことはない。シートは、滑車で上げ下げする。普段は、塩ビのパイプフレームだけになっている。

塩ビのパイプフレームは、ボンドを使っていない。安定は、何本も張られた鉄線によって確保されている。シートが張っていない状況では、台風でも倒れない地震はある。一方、壊す時は30分もかからずにできると思う。

本当は、もっと高さと広さが欲しいが、一人で組み立てるのはこれが精一杯だ。手前のガーデンテーブルは、2X4木材で、自作したもので必需品だ。

初飛行の状況は次のyoutubeを見てほしい。

https://youtu.be/ml3PBZchcJI



一本の紐で、どこでも飛んで行かないようにした。今までになかった、5メートルという長さにしたが、それでもそれ以上に飛ぼうとして墜落してしまった。ドローンは、コントローラーで飛ばすのではく、事前に組み込んだ飛行プログラムに剃って飛ぶようにしている。プログラムは、自作のインタープリターだ。(プログラム使用についてはこちらを参照)そのプログラムのホバリング時の設定スロットルが大きすぎた。

5メートルというのは風よけテントよりも高いので、テントを超えた途端風に煽られたという問題もあった気がする。

飛行のログを以下で示そう。

(1)高度と機体の傾き

青は、超音波高度センサーのデータ、赤が機体のロール方向の傾き、緑が同じくピッチ方向の傾きである。大きな揺れが収束しない状況が読み取れる。

高度の揺れは、超音波センサーが機体が揺れると同じように揺れるので、とらえる高度が変わってしまうために発生する。HCSRを使う限り避けられない。次の機体バージョンでは、別の光センサー使う予定だ。

(2)PID制御の信号強度

先の期待の傾きに対して、カウンターリアクションを作るための、P制御の強度、およびD制御の強度を、高度のグラフとともに示している。機体の傾きにD制御は先行していて、それを収めようとしている。細かいインパルスは、制御センサーが送り込んでくる値に含まれる、細かい揺れに反応しているためである。

(3)モータスピードの制御値

信号自体は安定している。プロペラの回転数を測るタコメーターが、故障していたため、これがどのように実際化していたのかは調べられない。

結論的に、揺れが抑えられていないのは、D制御の値が少し小さいためであるように考えている。


2022年5月29日日曜日

プロペラ回転の遅延とモーターの機械的時定数

この間、ESCからの信号に対してモーターの回転が遅延する問題をめぐって、面倒なシミュレーションを何度も繰り返してきた。私の自作ドローンの場合200msくらいの遅延がある。ただし、ドローンを剛体として組み立てたモデルでは、この遅延はPID制御で致命的なものにはならない感じになり、やや関心が薄れてしまった。

ところで、最近図書館から「現代制御工学--基礎から応用へ」(江上正、土谷武士共著、2017年刊)という本を借りてきて読んでいたら、とても面白い。結局古本をAmazonで購入した。その初めの方で、DCモーターの「機械的時定数」について書いてあって、これがまさに自分が問題にしていた遅延の基本的内容であることに気づいて、強い感銘を受けたので、ここに記録しておくことにした。

 モーターは内部抵抗とコイルからなる電気システムとコイルとローターの回転という機械システムから成立しているとみる。

機械システムの方から見ると、1軸固定の剛体の運動方程式(角運動量の微分はそのトルクの和に等しい)から入る。先のシミュレーションでも使っているので、そちらの記事が少しは参考になるかもしれない。

$$J\frac{d\omega(t)}{dt}+B\omega(t)=Ki_(t)-T_{L}(t)$$

$\omega$はローターの角速度、$J$はローターの慣性モーメント、$i_{t}$はローターコイルを流れる電流で$K$はそれをトルクに変換する定数、$T_{L}$は負荷トルクである。

(書きかけ)

クアッドコプターの地面効果に関する論文を読む

 論文は2017年に書かれたものらしい(著者は河野将佳氏ら四人、東北大)。発行誌などの詳細は分からない。ネットの検索で出てくるはずだ。タイトルは「小型クアッドロータ機のロータ軸間距離と地面効果の関係の検証」というものだ。

自作ドローンにかかわる、この間の様々な 不思議な動きの理由をしっかりと与えるものになっていて、驚いた。

 高度が極めて低い状態では、ドローンロータの下降気流が地面にあたることによって、上空よりも大きな推力が出て、結果的に、機体が不安定になることが分析の前提となっている。

11インチロータのクアッドコプターを実質の小さな領域で、最大許容高度60センチで飛ばしていた時、垂直離陸を妨げる、考えられる、あらゆる要因(ロータの回転のバラツキ、重心の偏り、機体のゆがみなどなど)を取り除いても垂直離陸しないときがあった。それでもう事実上お手上げとなった。

 ところが、方向性を変えてもっと広い場所で飛ばそうとなって、差し当たって、S工務店が使わせてくれた屋内スペース(2.5m四方、高さくらい)で、自室よりも余裕を持たせて飛ばしたら、1メータを超える高度でも結構案て飛ぶようになった。

それでも、バッテリーのパワーが低下して、高度が稼げないときには予想外の離陸になった。

論文に、次のような Cheesemanさん等 の地面効果の式が引用されている。これはヘリコプターのような単独ロータの場合の地面効果を表している。ここで、$R$は、ロータの半径、$h$は高度、$T$は地面効果が含まれた推力、$T_{out}$は、それがない状態での水力である。

$$\frac{T}{T_{out}}=\frac{1}{1-(R/4h)^{2}}$$

この式に、数値(ロータ半径$=11/2$インチ)を入れて図を描くと次のようになる。

ロータ高度が20センチを切るあたりから、地面効果が加速度的に現れてくる。

私のドローンの離陸前のロータ高度はほぼ20センチだから、ぎりぎり影響を回避しているように見える。しかし、あくまでこれは単独ロータの場合の結果で、河野氏らの論文では、クワッドロータの場合は、ロータ間の距離が近い場合はより大きな地面効果が表れるとしている。

そして、論文では、クアッドコプターでは、$h/R=3.0$以下では、地面効果が顕著になるので、足の長さ($L$)をロータ直径の1.5倍くらいにすることを提案している。この数値は、私のドローンの場合、$L$を42㎝にすることを意味している。現在の2倍の長さということになる。

2倍にすることは足の構造上難しいが、さっそく30センチくらいの高さまではあげようと思っている。

少し立ち止まって、地面効果について考えてみる。

(1)ドローンの上昇あるいは高度の維持は、回転するプロペラの上部の気圧と、下部の気圧の差によって実現する。地面効果がなければ、つまり上空では、確実に実現するのは、下部よりも上部の気圧の薄さだと思う。したがって、プロペラは釣りあげられるようになるのだろう。

しかし、低空で、地面効果が強いと、下から押し上げられる力が強くなる。これは、おそらく吊り上げられるより、方向性を欠いた力になりがちなのだと思う。下から吹き上げられた風に舞う落ち葉のようなものだ。だから、ドローンの安定性を相対的に損なう結果になる。 

(2)私のドローンは、必ずプロペラガードを持っている。しかも、プロペラの回転の円形を外側から覆うようなガードだ。これは、一面では、下部に吹き付ける風の方向性を与えるようだが、複数のロータの相互作用はなくならない。

このようなプロペラガードによって、地面効果はより強くなるような気がする。

2022年3月30日水曜日

プログラム仕様

----------------------------------------------------------------

Hotal プログラム仕様

2022年1月10日から

例 https://drone-ai.blogspot.com/2022/03/blog-post_30.html

----------------------------------------------------------------

(1)プログラムはステートメント(命令文)の並びから構成される。ステートメントは1行に書かれ、複数の行にまたがることはできない。ステートメントの塊はブロックである。ブロックは、それを一つのプログラムとして実行できる。ただし、空行は無視される。1行の中に'#'があると、それも含めそれより右側の記述は無視される。インデントは、空白で行われることが前提である。

(2)実行可能ステートメントは1行から成り立ち、loop、if、マクロを除き、順に実行される。

1,代入文:変数 = 式(評価される、演算子と変数、値を返す関数から成立している)

2,if文

3,loop文

4,関数(実行される)

5.マクロ

のいずれかである。

(3)数値

数値はすべて倍精度数で把握される。小数は小数点を使って表される。浮動小数点は正しく処理されない。

(4)変数

変数は、$から始まる文字列でなければならない。$以降は、アルファベット、数字、'_'アンダーラインのいずれかでなければならない。文字列を変数に入れることはできない。値は全て倍精度である。

例. $abcdef,  $e123,   $abc_d123 など

変数は、最初に現れたところで定義される。同じものが再定義されると、値が無条件に上書きされる

(5)関数

関数は、'^'で始まり、名前、引数をつなげたものである。引数は '('と')'に挟まれる。実引数がない場合も'()'が必要である。関数名は変数名の規則に準じる。引数が複数ある場合は、カンマで区切る。関数は、値を返す機能と機体に関わる何らかの動作を実行するものとがある。また、その両方の場合もある。

(注1) 複数の値を返す関数(getPropellaRpmなど)は、その関数自体は値を返さない(ゼロが返される)が、実行後に、その関数名に '_0', '_1', '_2',・・・がついた一連の変数に値が保存される。次に実行されるときには、上書きされてしまう。持続的に使いたい場合は、それまでに別な名前の変数に代入、保存しておくなどの処理が必要。配列を返す関数が代入文の左辺にある場合、その値はゼロになってしまう。

(6)マクロ

マクロは、プログラムの中(位置はどこでも良い)で定義され

defmacro マクロ名

の行から始まり

endmacro

の行で終わっているブロックである。その部分は、メインの実行行には含まれず、無視される。マクロを呼び出すには、

@マクロ名

と書いた行をメインプログラムの中に置くことにより、その位置でマクロ全体が実行される。何度呼び出されてもよい。マクロの内容は通常の実行ブロックと同じである。変数も、名前空間はないので、同じ名前の変数は、メインと共通になる。変数を区別するためには、変数名の頭にマクロ名をつけるなどの工夫が必要になる。マクロの中で、再帰的にそのマクロを呼び出してもよいが、マクロの中で同じ、あるいは別のマクロを定義してはならない。マクロを正常に呼び出すことができなくなる。

(7)if文

if文は、ステートメントを空白で分解したときに最初にifが記載され、次に論理式が現れるものである論理式が真の場合は、対応するendifか対応するelseのいずれか最初に現れたところまでが実行される。論理式が偽の場合は対応するendifの前に、対応するelseがあれば、そのelseからendifまでが、実行される。elseがなければ、endif以降のブロックの実行に移る。

(8)論理式

論理式は ==, <=, >=, <, >, !=の演算子を持ち。両辺いずれも式として評価され、数値的値の比較が行われ、真偽が判定される。論理式は、現状では単独でしか評価できない。いずれ、論理式も評価できるようにしたい。

(9)loop文

loop文は、ステートメントを空白で分解したときに、最初の項がloopであるもの。続いて、論理式が与えられる。論理式が真である限り、対応するendloopのステートメントまでが繰り返される。論理式が偽になったばあいは、endloopの次のステートメントからの実行に移る。

(10)今の段階で、実行、評価が可能な関数(今後さらに拡張される)

^throttle():

    スロットルレベルを指定する。引数にスロットルレベル(%)を与える。0が最小。100は、ESCのキャリブレーションで、MAXで設定された電圧レベルになる。

^sleep():

    実行を引数秒だけ中断する。秒は実数(小数可)で与える。

^pidbase():

    PIDの基底値を与える(実数)。3つの値をそれぞれ引数とする

^pidscale():

    PIDのスケール値を与える(整数)。スケールは、50がちょうど、PIDの規定値と同じになるように調整される。3つの値をそれぞれ引数とする。内部的には倍精度で処理されるが、実際の速度調整時にはまた整数化されて処理。PIDスケールは、configファイルで、一定高度にならないと実行されない設定もあるので注意。

^motorSpeedAjustment():

    モータースピードの修正値を与える。引数に四つのモーターの修正値を与える。

^getHight():

    その瞬間の高度を整数で取得する。超音波距離センサー HCSR04を使っている

^getAverageHight():

    4ループ分の平均高度を得る HCSR04を使っている

^print():

    引数の内容を標準出力する。引数は、文字列または変数を+でつなげることができる。変数でなければ、文字列とみなしそのまま出力する。変数は、単独か、+ で区切られていなければならない。

^getTakeoffAltitude(): 

    60センチまでの高度をより正確に取るために使う。倍精度で値を返す。光距離センサーGP2Y0E03を使っている。

^getPropellaRpm(): 

    現在のプロペラ回転数を取得する。変数、getPropellaRpm_0. getPropellaRpm_1. getPropellaRpm_2. getPropellaRpm_3 に自動的に代入される。 


プログラムモードとモーター回転数バランスの自動調整

 ドローンの離陸をマニュアルで試みるのは、なかなか困難だ。私が不器用なのか、歳をとって手の運動神経が老化しているのもあるだろう。そこで、離陸はプログラムで制御するようにしている。

https://youtu.be/RCNzAaKuwZU

これは、私の部屋にちょっとした離陸ベースを置いてプログラムで飛ばしたものだ。コマンドは、1行ずつ実行する。途中に、最後で定義されているマクロが挟まれる。$で始まる名前は、変数だ。^で始まるものは関数。@で始まるものがマクロだ。その他、細かいプログラミングルールは、こちらに書いておいた。

離陸直前のスロットルの状態をすこい維持して、その間にプロペラの回転バランスをとるように、モータースピードを調整するマクロ(@setspeedadjustment)が挟んである。

#-------------------------
# pidscale ver.0 2022年2月28日
# プログラムオリジナル作成
# pidscale ver.1 2022年3月20日
# ホバリング高度に到達した時点でPID制御に入る
# pidscale ver.1 2022年3月27日
# モータースピードの調整を自動化するマクロ追加
#
# Attitudeの姿勢制御ループに対するコマンドは、
# 繰り返しの時間を念頭に置かないとコマンドが有効にならない
#-------------------------
#基本定数
# テスト用
#$takeoffthrottle = 30
#$hoveringthrottle_max = 30;
#$hoveringthrottle_min = 30;
# 本番用
$takeoffthrottle = 39
$hoveringthrottle_max = 43;
$hoveringthrottle_min = 37;
#
^motor(on)
# motor on の有効化を0.1秒待つ:必須
^sleep(0.1)
# Arming
^throttle(20)
# sleep の引数は実数値可
^sleep(1.5)
^throttle(30)
# 回転数が上がる時間を稼ぐ
^sleep(0.7)
# モータースピード調整マクロの実行
# このスピードで、1秒使われる
@setspeedadjustment
# 調整に馴染ませる時間をとる
^sleep(0.5)
# 離陸
^print(離陸開始します)
^throttle($takeoffthrottle)
##############################
# 離陸高度チェックループ 開始 
# ループを正しく抜けれるかどうか事前テストすること
##############################
$count = 0
$flag = -1
loop $flag < 0
    $altitude = ^getTakeoffAltitude()
    ^print(離陸:No.+$count+ 現在高度 [ +$altitude+ ])
    # 高度が指定の高さ以上になるまで空回りする
    if $altitude > 30
        ^print(離陸:指定高度の30cmを超えたのでループを抜けます)
        $flag = 1
    endif
    # 10msec 停止する
    ^sleep(0.01)
    $count = $count + 1;
    # 指定高度に0.5秒で到達しなかったら強制的にループを抜ける
    if $count > 50
        ^print(離陸:規定チェック回数を超えたのでループを抜けます)
        $flag = 1
    endif
endloop
##############################
# 離陸高度チェックループ 終了 
##############################
^print(PID制御を開始します)
# PID制御に入る
# 最高高度に入る直前だと思われる
^pidscale(50,20,50)
^throttle($hoveringthrottle_max)
^sleep(0.7)
^throttle($hoveringthrottle_min)
##############################
# ホバリングループ 開始 
# $hoveringthrottle_min では、下降してしまうので
# スロットルを調整する
##############################
# 変数再初期化
$count = 0
$flag = -1
loop $flag < 0
    # 60cm 以上の高度を想定していないので、こちらの高度センサーを使う
    $altitude = ^getTakeoffAltitude()
    ^print(ホバリング:No.+$count+ 現在高度 [ +$altitude+ ])
    # 高度が下がったら再上昇を試みる
    if $altitude < 45
        ^print(ホバリング:高度が45cm以下になったのでスロットルを上昇させます)
        ^throttle($hoveringthrottle_max)
    endif
    if $altitude > 55
        ^print(ホバリング:高度が55cm以上になったのでスロットルを低下させます)
        ^throttle($hoveringthrottle_min)
    endif
    # 10msec 停止する
    ^sleep(0.01)
    $count = $count + 1;
    # ホバリングを指定時間試みたらループを抜ける
    if $count > 100
        ^print(ホバリング:1秒経過したのでループを抜けます)
        $flag = 1
    endif
endloop
##############################
# ホバリングループ 終了
##############################
# ^sleep(0.5)
#########################
# 着陸に入る
# スロットルを少しずつ下げる
#########################
^print(着陸します)
^throttle($hoveringthrottle_min)
^sleep(0.3)
^throttle(30)
^sleep(0.4)
^throttle(25)
^sleep(0.4)
# throttleの有効化の前にmotor off にならないように待つ。0.1秒以上
# また、モーターが完全に停止するまでのログを取るという意味もある。それで3秒にしている
^print(モーターを停止させます)
^throttle(0)
^sleep(2)
^motor(off)
# メインプログラム終了
defmacro setspeedadjustment
#################
# スピード調整設定マクロ
# スロットル関係の関数は使っていないので、
# ここに入る前にスロットルは調整すべき
 #################
^print(スピード調整設定マクロを実行します)
# 調整スピードをゼロに設定:初期化
^motorSpeedAjustment(0.0,0.0,0.0,0.0)
# 各プロペラ回転数の合計
$psum_0 = 0
$psum_1 = 0
$psum_2 = 0
$psum_3 = 0
^print(調整スピードをゼロに設定:初期化 ループの開始)
# 誤差取得のループ
$flag = -1
$count = 0
loop $flag < 0
    #プロペラ回転数を取得する
    ^getPropellaRpm()
    ^print(No.+$count+ 回転数 + $getPropellaRpm_0 +,+ $getPropellaRpm_1 +,+ $getPropellaRpm_2 +,+ $getPropellaRpm_3)
    # 取得した回転数を加える
    $psum_0 = $psum_0 + $getPropellaRpm_0
    $psum_1 = $psum_1 + $getPropellaRpm_1
    $psum_2 = $psum_2 + $getPropellaRpm_2
    $psum_3 = $psum_3 + $getPropellaRpm_3
    $count = $count + 1
    if $count >= 100
        # 1秒測る
        $paverage_0 = $psum_0 / $count
        $paverage_1 = $psum_1 / $count
        $paverage_2 = $psum_2 / $count
        $paverage_3 = $psum_3 / $count
        ^print(1秒経過で測定終了 平均: + $paverage_0 +,+ $paverage_1 +,+ $paverage_2 +,+ $paverage_3)
        # 終了フラグを立てる
        $flag = 1
    endif
    ^sleep(0.01)
endloop
# 4つのモーターについてさらに平均を求める
$totalaverage = ($paverage_0 + $paverage_1 + $paverage_2 + $paverage_3)/4
^print(全体平均回転数 + $totalaverage + rpm)
# 平均との差から調整値を求める
# 調整係数
$adjusttingdeflator = 200
$adjust_0 = ($paverage_0 - $totalaverage)/$adjusttingdeflator
$adjust_1 = ($paverage_1 - $totalaverage)/$adjusttingdeflator
$adjust_2 = ($paverage_2 - $totalaverage)/$adjusttingdeflator
$adjust_3 = ($paverage_3 - $totalaverage)/$adjusttingdeflator
# 調整値をセットする
^print(モータースピード調整値: + $adjust_0 +,+ $adjust_1 +,+ $adjust_2 +,+ $adjust_3)
^motorSpeedAjustment($adjust_0,$adjust_1,$adjust_2,$adjust_3)
# マクロの終了
^print(スピード調整設定マクロを終了します)
endmacro

2022年3月8日火曜日

剛体のシミュレーションとフライトコントローラーのパラメータをリンクさせる

先の 剛体としてののドローンのシミュレーションでは、無駄時間を考慮してもPD制御で安定化が図れることがわかった。

新たな機体が組み上がったので、そのフライトコントローラーのシステム(以下Hotalとよぶ)にシミュレーションのパラメーターを適応させることにした。

Hotalでは、加速度センサーから取られた値を相補フィルターにかけて、ラジアンで機体角度を出してきている。その値を100倍して(スロットルの%をイメージしている)PあるいはD制御のパラメーターをかけて、PWMモジュール(PCA9685)にその値を送り、ESCに回転速度要求信号として送り込んでいる。

PWMモジュールに送った値とモーターの回転数との関係がわからなければ、この関係をとらえられないので最新のフライトのログをもとに回帰分析をした。

飛行を開始した時点から終了までの、モーター1だけを取り出してまず相関関係をみた。



まあ、ばらついているが正の相関を確認できる。

回帰分析した結果は、

これをもとに計算する。シミュレーションの300に対するHotalのPパラメーターを$H_{p}$とすると、
$$100H_{p}\times 117.48=300$$
よって、
$$H_{p}=0.0255$$
となるDパラメータも同じである。

これは直感的に、いいところをついたパラメータとなっている。このパラメータの前後で実機のフライトコントローラーのPD制御パラメータを探れば良い。


2022年3月1日火曜日

raspberrypi 起動時にIPアドレスをAQM0802に表示する

 基本、wifiでドローンと繋げているので、以前は固定IPをラズパイに設定してつないでいた。しかし、ラズパイの固定IP設定はトラブルが多いので、やめた。ルーターDHCPからIPアドレスをとるとある程度固定していて、たまに変わっても1増やすくらいなので、それでもいいのだが、やはり面倒。

そこで、DHCPで取った IPアドレスを起動時にLCD、キャラクタディスプレイに表示させるようにした。

LCDは、AQM0802という定番のものを使った。基盤付きのやつがいい。最初間違って、Arudinoのようのものを買ったが、プルアップ電圧が違うようで、I2Cアドレスの認識すらできなかった。ラズパイ用にセットアップされたものが820円で秋月電子で売っている。

https://akizukidenshi.com/catalog/g/gK-11354/

これを使った。自分でプログラムを書かなければと思っていたが、全く同じことをやっている方がおられた。

https://qiita.com/JHiyama/items/158ab35ed0247a7fc406

助かった。

なお、無線LANのみの場合はほぼそのままでいいが、同時にケーブルを繋いでいる場合は、f2にしなければ正しく取れない。無線LANのIPアドレスは2番目に書かれているので。

二之袋ベースでの初飛行

今年の2月くらいまでは、 自作のドローンを東京板橋区の狭い自室で飛ばしても、見えてくるものは何も無くなって、どうしようかと苛立っていた。 3月になって、ドローンを飛ばす自分の土地を手に入れようと重箱の隅をつつくようにネットを漁っていて、千葉東金市の二之袋というところに100坪の土...