モーターをリアルタイムで制御したいとします。通常、マイクロコントローラーまたはPCを使用します。 Cプログラミング言語。したがって、命令型のアプローチを使用します。モーター制御を行う方法をマイクロコントローラーに正確に伝えます。また、決定論では、特定の時間枠を使用して制御を行います。
私の質問は、例えば、宣言型アプローチを使用することも可能でしょうか?関数型プログラミング言語?宣言型アルゴリズムでは、モーター制御を行う方法を制御デバイスに指示します。例えば。 F#を使いたいです。関数型プログラミングの長所と短所は何ですか?それも決定論的でしょうか?リアルタイム制御に使用しますか?そうでなければ、なぜあなたはそれを使わないのですか?
この種のコードの多くは、あなたが思っているよりもはるかに命令的ではありません。これは通常、 関数型リアクティブプログラミング モデルに非常に近く、外部イベント(この場合はタイマーティック)に応答して関数が呼び出されます。
通常、その関数は、引数としてある種のcontext
データ構造を受け取ります。これは、以前の状態を判別するためにいくつかのレジスターを読み取るとともに使用できます。次に、通常、タスクを実行するためにいくつかのレジスタを変更し、次の状態を追跡するためにコンテキストを変更します。
古いコンテキストを変更する代わりに、新しいコンテキストを返し、状態を登録することは、実際にはそれほど多くの変更ではありません。また、古いコンテキストがスコープ外になっていることがわかっているかなり良い境界があり、コンパイラはガベージコレクションを決定論的にスケジュールできます。
私は実際に、マイクロコントローラーで使用できる [〜#〜] frp [〜#〜] 言語を作成することを検討してきました。これは、人々が認識しているよりもはるかにハードウェアに近いものです。
リアクティブシステム用のデータフロープログラミング言語には長い歴史があります。 80年代に、いくつかの言語が 同期プログラミング アプローチの下で開発されました。 離散時間SIMULINKをSIGNALに変換する は、Simulink、Signalに触れるための良い出発点になると思います。また、DCモーターのユースケースがあります。
同期言語は、値を操作する代わりに、同期フローで方程式を定義します。各論理クロックステップで、入力が読み取られ、ローカル変数と出力に瞬時に伝播されます。ゼロ遅延の仮定は、状態を更新するのに一定の時間がかかることを意味します(実際の実行時間が常に必要な物理期間に適合することを証明するのはあなた次第です)。 Lustre の内部状態の例を次に示します。
node sum (in : int) returns (sum : int);
let
sum = in + (0 -> pre(sum));
tel;
矢印は、最初に実行されるコード(初期化、左側)と他のすべての瞬間に更新されるコード(一般的な場合、右側)を分離します。したがって、矢印の右側では、sum
の過去の値を参照できます。これがpre
の目的です。
上記の定義により、事前に依存関係を分析し、コードの静的スケジューリングを行い、init
関数とstep
関数に基づいてCコードを生成できます。これらは基本的に次のとおりです。
/* local globals */
inline int sum, in, tmp;
/* Interface with other code */
extern int read_in();
extern void write_sum(int);
void sum_init()
{
tmp = 0;
}
void sum_step ()
{
in = read_in(); /* external */
sum = tmp + in;
write_sum(sum); /* external */
/* Update memory */
tmp = sum;
}
今日、関数型リアクティブプログラミングに対する新たな関心があるようです。 Real-Time FRP に関するいくつかの調査がありますが、一般的に提供されるFRPは、組み込み/クリティカルシステムで必要とされるのと同じ種類の静的保証を提供しません。
一定のメモリ使用量:メモリは静的に割り当てられ、事前に完全に決定されます(GCなし、ありがとう)。過去の値は遅延演算子(例:pre
)を介してのみアクセスされるため、ネストされた遅延演算子の数は、静的に既知の、保持する変数の以前のバージョンの数を示します。方程式で記述された無限の流れを観察するために、メモリのスライディングウィンドウを構築することを想像できます。
一定の実行時間:各タイムステップの最悪の場合の実行時間は、計算がはるかに簡単です。
因果関係分析:操作の順序は事前にわかっています。分散型リアクティブシステムを設計する場合、デッドロックが発生しないことを保証できます。
同期言語は、計算がいつ発生する必要があるかを決定するクロックも使用します。ブールデータの場合にexitと計算される値のみが計算されるフローを作成するためにx when b
を記述できます。 -flow b
はtrueと評価されます(そして、b
はデータフロー自体であり、クロックを持つこともできます)。
Signal 言語では、分散された計算単位を記述できます。たとえば、クロックを使用すると、分離された非同期の単位間で必要な同期情報のみを出力できます。2つのシステム間でティックカウンターを送信する場合、クロックの関係から、フロー[〜#〜] a [〜#〜]が5ティックごとに存在し、フロー[〜# 〜] b [〜#〜]7ティックごとに、[〜#〜] a [〜#〜]の実際のブールクロックフローも送信する必要はありません。および[〜#〜] b [〜#〜]:受信者はの値をいつ読み取るかを正確に認識しています。/[〜#〜] a [〜#〜]および[〜#〜] b [〜#〜]の場合、ティック。クロックはブール関係を形成し、他のクロックの既知のステータスから暗黙的に推測できる場合があります。
これらの言語は、一般に、より大きなツールチェーンの一部として産業コンテキストで開発されています。SCADEなどを探してください。使用法によっては、アカデミックコンパイラを使用することをお勧めします。
私の知る限り、F#はガベージコレクションされています。十分に注意し、デバイスが正確なタイミングを必要としない場合、ガベージコレクション環境でcanコントローラーを実行すると思う傾向があります。実行中にメモリが割り当てられないように、既存の変数を個人的に変更して、制御ループの実際のメモリ消費量がゼロになるようにします。
メモリが継続的に割り当てられる(そして、うまくいけば収集される)場合、平均して、GCが一時停止する可能性がある一定のメモリ使用量になる可能性があります。ガベージコレクターはリアルタイムではありませんが(リアルタイムGCが存在する場合でも)、物理的な期間が十分に長い場合は、期限を逃す可能性は低くなります。
期限を逃すことが問題にならない場合は、上記の言語から生成されたコードを使用するか、単純な制御ループのみが必要な場合は手動で記述してください。 CやAdaなどの低水準言語を使用します(おそらくリアルタイムOS内で、またはマイクロコントローラー上で直接)。
はい。ガベージコレクションがリアルタイムの要件に干渉せず、十分なメモリとプロセッサの処理能力がある限り、可能です。
命令型言語を使用すると、使用されているコンピューティングリソースをより細かく制御できます(正確には、howを記述しているためです)。リアルタイムの要求でモーターを実行している場合は、このレベルの微調整が必要になる場合があります。宣言型言語は通常、設計上、これらの詳細をあなたから遠ざけるように抽象化します。
関数型の純粋関数(つまり、完全な不変性)を使用する場合は、データ構造を十分に機能させるために、データ構造を非常に巧妙にする必要があります( 岡崎 を参照)。
F#を使用するには、F#をサポートするエコシステムが必要になることに注意してください。少なくとも、 。NET Compact Framework をサポートする必要があります。 Netduino にも興味があるかもしれません。
モーター制御を最適化しながら、必要な宣言型機能を提供する命令型言語(関数の小さなセットでもかまいません)で小さなDSLを作成することで、両方の長所を活かすことができます。