同じコードブロックで複数のことを行わずに、プログラムの複数の部分を一緒に実行する方法はありますか?
1つのスレッドが外部デバイスを待っている間、別のスレッドのLEDも点滅します。
同じコードブロックで複数のことを行わずに、プログラムの複数の部分を一緒に実行する方法はありますか?
1つのスレッドが外部デバイスを待っている間、別のスレッドのLEDも点滅します。
回答:
Arduinoでは、マルチプロセスもマルチスレッドもサポートされていません。ただし、いくつかのソフトウェアを使用すると、複数のスレッドに近いものを実行できます。
あなたは見てみたいProtothreads:
プロトスレッドは、小型の組み込みシステムやワイヤレスセンサーネットワークノードなど、メモリの制約が厳しいシステム向けに設計された非常に軽量なスタックレススレッドです。Protothreadsは、Cで実装されたイベント駆動型システムに対して線形コード実行を提供します。Protothreadsは、基盤となるオペレーティングシステムの有無にかかわらず、イベントハンドラーをブロックするために使用できます。プロトスレッドは、複雑なステートマシンや完全なマルチスレッドを使用せずに、連続した制御フローを提供します。
もちろん、ここにサンプルコードを含むArduinoの例があります。このSOの質問も役に立つかもしれません。
ArduinoThreadも良いものです。
AVRベースのArduinoは(ハードウェア)スレッドをサポートしていません。ARMベースのArduinoには慣れていません。この制限を回避する1つの方法は、割り込み、特に時限割り込みの使用です。特定のその他のルーチンを実行するために、非常に多くのマイクロ秒ごとにメインルーチンを中断するタイマーをプログラムできます。
Unoでソフトウェア側のマルチスレッドを実行することは可能です。ハードウェアレベルのスレッドはサポートされていません。
マルチスレッドを実現するには、基本的なスケジューラの実装と、実行する必要のあるさまざまなタスクを追跡するためのプロセスまたはタスクリストの維持が必要です。
非常に単純な非プリエンプティブスケジューラの構造は次のようになります。
//Pseudocode
void loop()
{
for(i=o; i<n; i++)
run(tasklist[i] for timelimit):
}
ここでtasklist
は、関数ポインタの配列にすることができます。
tasklist [] = {function1, function2, function3, ...}
次の形式の各関数で:
int function1(long time_available)
{
top:
//Do short task
if (run_time<time_available)
goto top;
}
各関数は、function1
LED操作の実行やfunction2
フロート計算の実行など、個別のタスクを実行できます。割り当てられた時間を遵守することは、各タスク(機能)の責任です。
うまくいけば、これで開始できます。
要件の説明に従って:
最初の「スレッド」に対して1つのArduino割り込みを使用できるようです(実際には「タスク」と呼びます)。
Arduino割り込みは、外部イベント(デジタル入力ピンの電圧レベルまたはレベル変化)に基づいて1つの関数(コード)を呼び出すことができます。これにより、すぐに関数がトリガーされます。
ただし、割り込みに関して留意すべき重要な点の1つは、呼び出される関数が可能な限り高速であることです(通常、delay()
呼び出しやに依存する他のAPI はありませんdelay()
)。
外部イベントトリガーでアクティブにする長いタスクがある場合は、潜在的に協調スケジューラを使用し、割り込み関数から新しいタスクを追加することができます。
割り込みに関する2番目の重要な点は、その数が制限されていることです(たとえば、UNOで2つのみ)。したがって、より多くの外部イベントの処理を開始する場合は、すべての入力を1つに多重化する何らかの種類を実装し、実際のトリガーである多重化された入力を割り込み関数に決定させる必要があります。
簡単な解決策は、スケジューラを使用することです。いくつかの実装があります。これは、AVRおよびSAMベースのボードで利用可能なものを簡単に説明しています。基本的に、1回の呼び出しでタスクが開始されます。「スケッチ内のスケッチ」。
#include <Scheduler.h>
....
void setup()
{
...
Scheduler.start(taskSetup, taskLoop);
}
Scheduler.start()は、taskSetupを1回実行する新しいタスクを追加し、Arduinoスケッチが機能するようにtaskLoopを繰り返し呼び出します。タスクには独自のスタックがあります。スタックのサイズはオプションのパラメーターです。デフォルトのスタックサイズは128バイトです。
コンテキストの切り替えを許可するには、タスクはyield()またはdelay()を呼び出す必要があります。条件を待機するためのサポートマクロもあります。
await(Serial.available());
マクロは、次の構文糖衣です:
while (!(Serial.available())) yield();
Awaitは、タスクの同期にも使用できます。以下にスニペットの例を示します。
volatile int taskEvent = 0;
#define signal(evt) do { await(taskEvent == 0); taskEvent = evt; } while (0)
...
void taskLoop()
{
await(taskEvent);
switch (taskEvent) {
case 1:
...
}
taskEvent = 0;
}
...
void loop()
{
...
signal(1);
}
詳細については、例を参照してください。複数のLED点滅からデバウンスボタンまでの例と、ノンブロッキングコマンドライン読み取りのある単純なシェルがあります。テンプレートと名前空間を使用して、ソースコードの構造化と削減に役立てることができます。以下のスケッチは、マルチ点滅用のテンプレート関数の使用方法を示しています。スタックには64バイトで十分です。
#include <Scheduler.h>
template<int pin> void setupBlink()
{
pinMode(pin, OUTPUT);
}
template<int pin, unsigned int ms> void loopBlink()
{
digitalWrite(pin, HIGH);
delay(ms);
digitalWrite(pin, LOW);
delay(ms);
}
void setup()
{
Scheduler.start(setupBlink<11>, loopBlink<11,500>, 64);
Scheduler.start(setupBlink<12>, loopBlink<12,250>, 64);
Scheduler.start(setupBlink<13>, loopBlink<13,1000>, 64);
}
void loop()
{
yield();
}
このフォーラムの以前の呪文から、次の質問/回答は電気工学に移されました。メインループを使用してシリアルIOを実行している間に、タイマー割り込みを使用してLEDを点滅させるサンプルのarduinoコードがあります。
再投稿:
割り込みは、何かが進行している間に物事を成し遂げるための一般的な方法です。次の例では、を使用せずにLEDが点滅していdelay()
ます。Timer1
起動するたびに、割り込みサービスルーチン(ISR)isrBlinker()
が呼び出されます。LEDのオン/オフを切り替えます。
他のことが同時に発生する可能性があることを示すためloop()
に、LEDの点滅に関係なく、foo / barをシリアルポートに繰り返し書き込みます。
#include "TimerOne.h"
int led = 13;
void isrBlinker()
{
static bool on = false;
digitalWrite( led, on ? HIGH : LOW );
on = !on;
}
void setup() {
Serial.begin(9600);
Serial.flush();
Serial.println("Serial initialized");
pinMode(led, OUTPUT);
// initialize the ISR blinker
Timer1.initialize(1000000);
Timer1.attachInterrupt( isrBlinker );
}
void loop() {
Serial.println("foo");
delay(1000);
Serial.println("bar");
delay(1000);
}
これは非常にシンプルなデモです。ISRははるかに複雑で、タイマーと外部イベント(ピン)によってトリガーできます。共通ライブラリの多くは、ISRを使用して実装されます。
マトリックスLEDディスプレイを実装しているときにも、このトピックに取り組みました。
一言で言えば、Arduinoでmillis()関数とタイマー割り込みを使用して、ポーリングスケジューラを構築できます。
ビルアールの次の記事をお勧めします。
https://learn.adafruit.com/multi-tasking-the-arduino-part-1/overview
https://learn.adafruit.com/multi-tasking-the-arduino-part-2/overview
https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview
私のThreadHandlerライブラリを試してみることもできます
https://bitbucket.org/adamb3_14/threadhandler/src/master/
割り込みスケジューラを使用して、yield()またはdelay()で中継せずにコンテキストを切り替えることができます。
ライブラリを作成したのは、3つのスレッドが必要であり、他のスレッドが何をしていても正確に実行するには2つのスレッドが必要だったからです。最初のスレッドはシリアル通信を処理しました。2つ目は、Eigenライブラリでフロート行列乗算を使用してカルマンフィルターを実行していました。そして、3番目は高速な電流制御ループスレッドであり、マトリックス計算を中断できる必要がありました。
各周期スレッドには、優先順位と期間があります。現在実行中のスレッドよりも優先度の高いスレッドが次の実行時間に達すると、スケジューラは現在のスレッドを一時停止し、優先度の高いスレッドに切り替えます。優先度の高いスレッドの実行が完了すると、スケジューラは前のスレッドに戻ります。
ThreadHandlerライブラリのスケジューリングスキームは次のとおりです。
スレッドはC ++継承を介して作成できます
class MyThread : public Thread
{
public:
MyThread() : Thread(priority, period, offset){}
virtual ~MyThread(){}
virtual void run()
{
//code to run
}
};
MyThread* threadObj = new MyThread();
またはcreateThreadおよびラムダ関数を介して
Thread* myThread = createThread(priority, period, offset,
[]()
{
//code to run
});
スレッドオブジェクトは、作成時にThreadHandlerに自動的に接続します。
作成されたスレッドオブジェクトの実行を開始するには、以下を呼び出します。
ThreadHandler::getInstance()->enableThreadExecution();
そして、もう1つのマイクロプロセッサ協調マルチタスクライブラリであるPQRSTがあります。これは、単純なタスクを実行するための優先度キューです。
このモデルでは、スレッドはのサブクラスとして実装されますTask
。これは将来の時間にスケジュールされます(一般的には、LoopTask
代わりにサブクラスになる場合は、定期的に再スケジュールされます)。run()
タスクの期限が切れると、オブジェクトのメソッドが呼び出されます。run()
この方法は、いくつかの原因作業を行い、その後(これは協調ビットである)を返します。通常、連続した呼び出しでのアクションを管理するために、ある種のステートマシンを維持します(簡単な例は、light_on_p_
以下の例の変数です)。コードの編成方法を少し考え直す必要がありますが、かなり集中的な使用で非常に柔軟で堅牢であることが実証されています。
それは時間単位についてとらわれないですので、単位で実行されているように幸せであるmillis()
ようにmicros()
、または便利である任意の他のダニ。
このライブラリを使用して実装された「点滅」プログラムを次に示します。これは、実行中の単一のタスクのみを示していますsetup()
。通常、他のタスクが作成され、内で開始されます。
#include "pqrst.h"
class BlinkTask : public LoopTask {
private:
int my_pin_;
bool light_on_p_;
public:
BlinkTask(int pin, ms_t cadence);
void run(ms_t) override;
};
BlinkTask::BlinkTask(int pin, ms_t cadence)
: LoopTask(cadence),
my_pin_(pin),
light_on_p_(false)
{
// empty
}
void BlinkTask::run(ms_t t)
{
// toggle the LED state every time we are called
light_on_p_ = !light_on_p_;
digitalWrite(my_pin_, light_on_p_);
}
// flash the built-in LED at a 500ms cadence
BlinkTask flasher(LED_BUILTIN, 500);
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
flasher.start(2000); // start after 2000ms (=2s)
}
void loop()
{
Queue.run_ready(millis());
}
run()
メソッドが呼び出された後、メソッドは中断されないため、合理的に迅速に終了する責任があります。ただし、通常は、作業を実行してから、LoopTask
将来の時間のために(おそらくサブクラスの場合は自動的に)スケジュールを変更します。一般的なパターンは、タスクlight_on_p_
が次の期日になると適切に動作するように、内部状態マシンを維持することです(簡単な例は上記の状態です)。
run()
。これは、たとえばyield()
またはを呼び出すことでCPUを譲ることができる協調スレッドとは対照的delay()
です。または、プリエンプティブスレッド。いつでもスケジュールできます。スレッドを検索するためにここにやってくる多くの人々が、ステートマシンよりもブロッキングコードを書くことを好むため、スレッドを検索する多くの人がそうしているのを見たように、区別は重要だと感じています。CPUを生成する実際のスレッドをブロックしても問題ありません。RtCタスクをブロックすることはできません。