DSHOT信号を作ってみる。

先日自作フライトコントローラーでRPMフィルターをいじった時DSHOT関係でハマって以来、気になっていました。
何でも理解を深める為には作ってみるのが一番、という事でSTM32マイコンボードでDSHOT信号を作ってモーターを回してみます。

DSHOT とはフライトコントローラーからESCに伝える信号のプロトコルで、 これによりモーターの回転量をESCに伝えます。
同種のプロトコルには色々(PWM,ONESHOT,MULTISHOT等)ありますが、恐らく現在のドローンレースで一番使われているのがDSHOTなのです。

DSHOT の概要については以前NONSAYAさんが紹介されています。
これを読んで大体のところは理解できましたが、実際に信号を作るためにはもう少し詳細な情報が必要です。で、色々探したけれど全体を通した仕様というか、本家のサイトの仕様書みたいなのが見つからないのです。

それでもこの辺りのサイトが参考になりました・・・

DSHOT概略

詳しい内容は上記サイトを読んで頂くとして、ここではサラッと概略を書きます。
まずDSHOT波形の例・・・

  • まず16発のHパルスを1セットとし、少しの時間を置いてこれを繰り返します。PWMっぽくも見えますが一発ずつパルス幅が長かったり短かったりします。
  • Hパルスの幅が短い場合が0、長い場合は1を示し、これにより16ビットのデータを表します。
  • 左から11bit分がスロットル量を表します(左がMSB)。この11bitの値は48から2047迄なので2000個の値を表せます。なお0~47は特別な意味を持ち、例えば0だとディスアームです(それ以外の値は資料が見当たりませんが、モーター逆転等が含まれる様です)。
  • 12bit目は基本は0ですが、ここを1にするとテレメトリ要求の意味となり、16発のパルスを送り終えた後に今度はESC側から信号を送ってきます。RPMフィルターではこの機能を使って回転数を読み出している様ですが今回の実験ではそこまでの域には達しておらず、一方的に回転数を伝えるのみです。
  • 13bit目から16bit目の4bitはこれまでの12bitデータに対するCRC値で、次の式で算出しています。CRC=(DATA ^ (DATA >> 4) ^ (DATA >> 8)) & 0xf
  • これまで一言でDSHOTと言ってきましたが実際にはDSHOT150、
    DSHOT300、 DSHOT600、 DSHOT1200等、速度に応じた名前がついています。150とか300とかの数字は1bitを表すパルス幅が150Kbpsとか300Kbpsという意味みたいです。なのでDSHOT300での1bit分のパルス間隔は1/300Kbps=3.33μSとなります。
  • DSHOT300の場合、パルス間隔は上記の通り3.33μSで、L(を表す短いパルス幅)は1.25μS、H(を表す長いパルス幅)は2.5μSです。

マイコンボードSTM32 Nucleo-F411RE

マイコンボードにはNucleo-F411REを使用します。このボード、フライトコントローラの実験で使ったときに3.3Vのレギュレータを壊してしまい、それ以来外付けレギュレータで無理やり動かしています。

ピロッと付いているのが3.3Vレギュレータ。

開発環境

開発環境にはSTマイクロエレクトロニクス社純正のCubeIDEを使用します。
これにはCubeMXというコード生成ツールが付属していて、周辺機能の初期化をGUIで設定できるので便利です(でもドキュメントが分かりにくい気がする)。

構想

DSHOTの信号をどうやって作りだしましょう?DSHOT150でも6.66μS毎に異なる幅のパルスを生成する必要があります。手持ちのESCがなぜかDSHOT150では動作しないのでDSHOT300を使うとなると3.33μS周期という結構な速さで更新が必要です。
タイマーをPWMモードで使うのがやり易そうですが、1パルス毎に幅を変える必要があるのでDMAを使って都度書き換えてみます。
これにはあらかじめRAM上のバッファにパルス数分のデータを格納しておき、1パルス毎にDMAコントローラーが (パルス幅を決める) CCRレジスタに転送していくのです。図にするとざっとこんな感じ。

作る・・・

実際にはRAM上のバッファはデータの16個分に加えて空白期間34個分の合計50個分を用意し、後半の34個分は常に値を0にしています。
以上の動作は一旦タイマやDMAコントローラーを設定すればCPUとは係りなく動き続けます。
なおRAM上のバッファはDMAが呼び出す割り込み処理ルーチン内で書き換える事にします。DMAの割込みはバッファの内容を半分出力した時点で呼ぶ事もできるので、これを用いて空白期間の間にデータを書き換えています。

そしてメインプログラム側ではDSHOTに出力する値を決めます。
今回はスイッチと可変抵抗を1個ずつ使い、スイッチはアーミング、可変抵抗は回転数を決める事にします。なのでGPIO一つとA/D入力一つが必要ですが、これもCubeMXの機能で簡単に設定できます(設定後の使い方がドキュメント見てもわかりずらいのですが)。

メインルーチンは初期化後無限ループに入ります。このループ内で可変抵抗の値を取るためAD変換を実施し、ESCに送る16bitのデータを作ります。
これをRAM上の仮バッファに置くところまでがメインルーチンの処理です。
そして仮バッファの値をDMAバッファに転送するのがDMA転送が半分終了した時点で呼び出す割り込みコールバックルーチンで行います。わざわざバッファを2段階にしているのはDMA転送中にバッファを書き換えてしまう事を防ぐためです。

詳細はソースを参照ください。
これ→DSHOT_MX.zip
プロジェクトディレクトリ丸ごとなので他のプロジェクトと同列の場所に置いて下図のメニューで開けると思います(試した限り大丈夫でしたがIDEのバージョンが違うとエラーとか出るかもしれません)。

動かしてみる

ESCに繋いでモーターを回してみたところを動画で・・・

そして・・・

あとモーターを反転させるのを試したいのですが、このあたりの情報が見つかりません。Betaflightのソースを解読すべきなのでしょうか?