SCoop ライブラリは、86Duino Coding 500 から利用可能です。強力なマルチスレッド プログラムや簡単なマルチタスク ソリューションを作成するための軽量でシンプルな環境にアクセスするための、Arduino 用の Simple Cooperative Scheduler の API を提供します。
最高のRTOSから、最もシンプルな「タイマー」や割り込みライブラリまで、インターネット上には様々な選択肢があります。このRTOSはその中間に位置し、使いやすく、ほとんどの人にとって十分なパワーを備えています。主にJava Threadライブラリに着想を得ており、ChibiOSやfreeRTOSといった他のRTOSから採用した、従来型のコンテキスト切り替えメカニズムを採用しています。
使ってみましょう
以下は、非常に基本的なスケッチで SCoop ライブラリを使用する方法の例です。
#include "SCoop.h" // create an instance of the scheduler called mySCoop
defineTask(Task1) // user definition of task1 object
volatile long count; // force any read/write to memory
void Task1::setup() {
count=0;
};
void Task1::loop() {
sleepSync(1000);
count++;
};
defineTaskLoop(Task2) { // user quick definition of task2 object
digitalWrite(13, HIGH);
sleep(100);
digitalWrite(13,LOW);
sleep(100);
}
void setup() {
Serial.begin(115200);
mySCoop.start();
}
void loop() {
long oldcount=-1; yield();
if (oldcount!=count) {
Serial.print("seconds spent :");
Serial.println(count);
oldcount=count;
}
} メイン スケッチの setup() では、命令 mySCoop.start() がスケジューラを準備し、Task2 と Task1 のそれぞれの setup() メソッドを起動します (宣言とは逆の順序)。
メイン loop() では、コードは「count」変数への変更を出力します。
マイクロプロセッサがメモリ内にタスク オブジェクトを保持し、許容可能な時間内に実行するのに十分なリソースを持っている限り、必要な数のタスク オブジェクトを作成できます。
各タスクは、ローカル変数を保持し、戻りアドレスを記憶しながら他の関数を呼び出すために、独自の「スタック」コンテキストを持ちます。デフォルトでは、defineTaskマクロはAVRタスクごとに150バイト、ARMタスクごとに256バイトの配列を作成しますが、defineTaskマクロの2番目のパラメータとして独自の値を追加することで、この設定をオーバーライドできます。例:defineTask(Task2,200) は、タスクに200バイトの専用ローカルスタックを提供します。
SCoopTaskオブジェクトのインスタンスを作成し、プログラムの後半でメソッドを呼び出してコードとスタックを初期化するだけで、defineTaskマクロを使用せずにタスクを作成することもできます。
SCoopTask myTask; … myTask.init(…)
スケッチでのタスクの動作方法:
協調スケジューラ ライブラリは、タスクとイベントの管理を担当し、yield() と呼ばれる非常に単純な関数を中心に構成されています。
このyieldキーワード(英語を母国語としない方には少し奇妙に聞こえるかもしれませんが)は、Arduino due Schedulerライブラリの一部です。これは最近、ビルド1.5以降の標準Arduinoライブラリに導入されました。また、pjrc Teensyコアにも含まれています。delay()などの一部の関数はこれを使用しており、Teensyコアの主要なloop()関数は、ユーザーコードの末尾にyield()をデフォルトで呼び出すようになりました。yieldはJavaプログラミングでも使用され、Java Threadライブラリで宣言されています。
基本的に、プログラムが空き時間のあるコードセクションにあるときは、yield() を呼び出すだけで、最終的にスケジューラに行き、スケッチに登録されている次のタスクに切り替わります。
他のタスクも yield() 関数を呼び出すとすぐに、スケジューラは元のコードに戻ります。そうでない場合、プロセスはタスク内でスタックしてしまい、スケジューラは制御を取り戻すことができません。そのため、他のプリエンプティブRTOSと比較して、ここでは「協調的」という言葉が重要です。
協調型マルチタスクの大きな利点は、ほとんどのライブラリが再入可能呼び出し (関数が完了する前に再度呼び出すこと) を受け入れないため、プリエンプティブ メカニズムによって中断されることがなく、既存のすべてのライブラリがマルチタスク環境で適切に動作することです。
タスク オブジェクトの詳細:
タスクの定義とスタックの提供:
例に示されているこのライブラリの主な考え方は、タスクを、独自のスタックと、SCoopTask フレームワークから継承された setup() や loop() などのメソッドを持つオブジェクトと見なすことです。
タスクを作成するには、オブジェクトのヘッダー コードを生成する defineTask または defineTaskRun マクロを使用します。
デフォルトでは、このオブジェクトに対して、第2パラメータで宣言されたバイト数でスタック(バイト配列)が作成されます。スタックサイズパラメータを省略した場合、配列はデフォルト値(AVRの場合は150、ARMの場合は256)に設定されます。これは、いくつかのローカル変数に対する基本的な計算や、例えばSerial.print関数の呼び出しなどを行うのに非常に便利です。
コードにさらに多くのローカル変数が必要な場合、またはより多くのメモリを必要とする関数を呼び出す場合は、宣言時にスタックサイズを多めに割り当てることで、これを予測する必要があります。残念ながら、必要な最大スタックサイズを簡単に計算する方法はありませんが、ライブラリにはstackLeft()というメソッドが含まれており、このメソッドはスタック内で現時点でまだ使用されていないメモリ量を返します。
タスクが宣言されると、オブジェクトが作成され、Arduino環境はプログラムの先頭で、メインのsetup()またはloop()に入る前に、自動的にそのオブジェクトの「コンストラクタ」を呼び出します。これにより、オブジェクトはアイテムリストに自動的に登録され、スケジューラは後で各タスク(またはイベントやタイマー)を1つずつ開始および起動するために使用します。
タスクを自分で宣言するには、基本オブジェクト SCoopTask をインスタンス化し、init(…) メソッドを3つのパラメータ(スタックのアドレス、スタックのサイズ、そして loop() メソッドの代わりに呼び出される関数のアドレス)と共に呼び出します(以下の例を参照)。この場合、defineStack マクロを使用することで、ARM プラットフォームに必要な8バイトのアライメントを持つスタック配列の宣言が容易になります。
SCoopTask myTask;
defineStack(mystack,128)
void mycode() { count+++; }
void setup() { myTask.init(&myStack,sizeof(mystack),&mycode); … } init() メソッドの呼び出しは、次のようにオブジェクト宣言とグループ化することもできます。
defineStack(mystack,128)
Void mycode() { count+++; }
SCoopTask myTask(&myStack,sizeof(mystack),&mycode); // implicit call to init()
Void setup() { … } タスクの開始:
defineTaskマクロで定義された各タスクは、それぞれ独自のsetup()メソッドを持つ必要があります。スケジューラは、リストに登録されたすべてのタスクのsetup()メソッドを、コマンドmySCoop.start()で起動します。最後に登録されたタスクが最初に起動されます(スケッチでの宣言とは逆の順序です)。
このコマンドはメインのsetup()関数内に配置する必要があり、スケジューラを初期化し、時間計測に使用する変数をリセットします。これにより、各タスクは「実行可能」とみなされ、それぞれのloop()メソッドに安全に移行できるようになります。
タスクを実行してyield()で終了する
defineTaskまたはdefineTaskLoopマクロで定義された各タスクには、それぞれ独自のloop()メソッドが必要です。スケジューラは、スケジューリングプロセス全体の一部として、このメソッドを定期的に実行します。このloop()内のタスクコードは、yield()(またはsleep)メソッドがどこかで呼び出される限り、必要に応じて永久にブロックすることができます。
タスクへの参加と終了のメカニズムは、スケジューラのyield() 関数によって実現されます。タスクからyield() メソッドを呼び出すと、ほとんどの場合、リスト内の次のタスクに切り替えられ、最後のタスクに到達するとスケジューラに戻ります。その後、スケジューラは保留中のすべてのタイマーまたはイベント(ドキュメントの後半を参照)を起動し、タスクの起動を1つずつ再開します。
他のすべての潜在的なタスク(およびタイマーやイベント)の実行が完了すると、yield() メソッドの呼び出しから単純に制御が戻ったかのように、元のタスクに制御が戻されます。その後、実行はそのまま継続されます。
mySCoop.yield() を使用して、Arduino のメインスケッチ loop() から yield() を体系的に呼び出すことをお勧めします。これにより、ループは自身の loop() コードを実行する前に、すべてのタスクと保留中のイベント/タイマーを最初に起動するようになります。この動作は、SCoop.h ファイルの先頭で SCoopYIELDCYCLE という定義済み変数を 0 に設定することで変更できます。これにより、スケジューラが常にスイッチングメカニズムを制御できるようになります。このアプローチでは、メイン loop() は他のすべてのコードの制御塔と見なされるため、タスクの切り替えやイベント/タイマーの起動の間に実行されるメイン loop() 内のコードは、より優先度の高い実行になります。
注:
SCoopライブラリは、ユーザーのタスク loop() の終了時にyield()を強制的に呼び出します。独自のループ機構(例えば、タスク loop() 内で while(1) { .. } など)を実装した場合、プログラムはループのどこかでyield()メソッド(またはsleepメソッド)を定期的に呼び出す必要があります
SCoopライブラリは、Arduinoのオリジナルのyield()弱関数を、スケジューラライブラリで宣言されたmySCoop.yield()でオーバーライドします。そのため、プログラム内やインクルードファイルのどこでも、yield()という単語を使用できます。また、これはArduino標準のdelay()関数をフックするメカニズムも提供します。delay()関数は現在yield()関数を呼び出しています(Arduinoライブラリ1.5以降のみ)。これにより、スケジューラが常にプログラム全体の制御下に置かれることが保証されます。
タスクを終了?
326 / 5,000 実際にはそうではありません。ライブラリはスタックに一定量の静的メモリを割り当てるため、タスクは一度開始したら終了することはありません。そのため、タスク loop() 内で停止、開始、またはイベントを待機するコードは、最終的には pause() と resume() を使用して、ユーザーが用意する必要があります。(動的タスクについては「Android スケジューラ」を参照してください)
タスク オブジェクトで使用可能なメソッド:
タスク オブジェクトのコード内では、SCoopTask ベース オブジェクトから継承されたいくつかのメソッドを呼び出すことができます。
sleep(time): 標準の delay() 関数と同じ動作ですが、空き時間を利用して制御をすぐにスケジューラに戻すか、他の保留中のタスクを実行します。sleepSync(time): sleep と同じ機能ですが、スリープまでの時間は sleep() または sleepSync() 関数の前回の呼び出しと同期されるため、厳密な定期的な時間処理 (「ジッターなし」) が可能になります。sleepUntil(Boolean): ブール変数がtrueになるまで待機し(その後falseに設定し)、その間に制御をスケジューラに戻します。ステータスの変更は別のタスクまたはメインのloop()プログラム自体から発生するため、このフラグにはvolatile変数を使用する必要があります。sleepUntil(Boolean,timeOut): (V1.2以降) sleepUntilと同じですが、指定されたtimeOut期間が経過すると必ず戻ります。式の中で関数として使用し、タイムアウトの場合はfalseを返すことができます。stackLeft(): タスクが開始されてからタスク スタック内で使用されたことのないバイト数を返します。
この関数は、タスク オブジェクト自体またはグローバル ポインターを使用して参照することにより、タスク オブジェクト コンテキストの外部にある Arduino スケッチのメイン loop() から呼び出すこともできます。
void printstack(){
SCoopEvent* ptr=SCoopFirstTask; // global library type and variable
while (ptr) {
Serial.println(reinterpret_cast<SCoopTask*>(ptr)->stackLeft());
ptr=ptr->pNext;
}
}
Void loop() {
printstack(); … or … Serial.println(Task1.stackLeft());
} クロスタスクトークとVolatile
鶏の話は関係ありません。コンパイラの最適化により、変数がローカル変数になったり、一時レジスタを使用したりすることがよくあります。タスク間で情報を渡したり、マルチスレッドプログラム(例えば、1つのタスクが書き込み、もう1つのタスクが読み取り)で変数を使用したりするには、共通変数をvolatileとして宣言する必要があります。これにより、コンパイラはアクセスのたびに変数のメモリへの読み書きを強制的に実行します。
変数宣言を簡素化するために、SCoop ライブラリは、int8,16,32 と uint8,16,32、および Boolean のいくつかの型を事前定義し、次のように元の Arduino 型名の前に「v」を付けて「_t」を削除します。
vui16 mycount; // exact same as volatile uint16_t mycount vbool OneSecond = false; // exact same as volatile Boolean OneSecond
タイマー
このライブラリは、スケジューラによって制御される定期的なアクションを作成するために使用できる補完的な SCoopTimer オブジェクトを提供します。必要な数のタイマーをインスタンス化でき、関数やタスク内で一時的にローカルオブジェクトとして宣言することもできます。タイマーは、開始時にスタックコンテキストを必要としません。タイマーを終了するには、単一のアトミックなyield() 操作を実行します。タイマーは、Arduinoのメインスケッチの通常のスタックを使用します。タイマーは、いかなる場合でもスケジューラに制御を戻すことができないため(ローカルのyield() メソッドは無効)、高速である必要があります。タイマーはMCUタイマー割り込みに似ていますが、システムクラッシュのリスクなしに、既存の関数やライブラリを呼び出すことができます。
タイマーの定義:
特定のマクロ defineTimerRun を使用すると、1つまたは2つのパラメータを持つ独自のタイマーオブジェクトを宣言できます。最初のパラメータはオブジェクトの名前、2番目のパラメータはタイマーの周期です。
defineTimerRun(myTimer,1000){ ticSecond=true; countSecond++ } このタイマーのコードは、myTimer::run() メソッドに暗黙的にアタッチされています。このメソッドは高速かつノンブロッキングである必要があります。タイマーはタスクと同じリストに登録されるため、yield() の呼び出し時にスケジューラによってトリガーされます。最後の呼び出しから経過した時間が定義された期間に達すると、run() メソッドが1回実行されます。
タイマーの完了に時間(例えば数ミリ秒)が必要な場合は、代わりにTaskを使用することをお勧めします。Taskでは、loop()メソッドの先頭にsleepSync()関数を記述し、ブロッキングまたは低速セクションでyield()またはyield(0)を呼び出します。以下のコードは、timer(1000)の定義と全く同じ動作になります。
defineTaskLoop(myTimer,100) { sleepSync(1000); ticSecond=true; countSecond++ }注: この例では、タスク loop() は他の関数を呼び出さず、ローカル変数も持っていないため、それほど多くのスタックサイズを必要としないため、2 番目のパラメータで 100 を強制的に設定することでデフォルトのスタックサイズを減らしています。これはケースバイケースで実験する必要があります。
タイマーの開始と監視:
mySCoop.start() を呼び出すと、登録されているすべてのタイマーが初期化され、defineTimerRun マクロの2番目のパラメータとして時間間隔が指定されている場合は有効になります。指定されていない場合は、プログラム側で後ほど schedule(time) メソッドを呼び出して初期化する必要があります(下記参照)。
次のメソッドは、タイマーを変更または監視するための他の手段を提供します。
schedule(time): タイマーを有効にし、「time」ミリ秒ごとに起動できるように準備します。schedule(time, count)スケジュール(時間)と同じですが、実行の最大回数を追加します。getPeriod(): このタイマーの時間間隔に登録された値を返します。setPeriod(): 期間の値を強制します。getTimeToRun(): タイマー run() メソッドの次の実行までの時間をミリ秒単位で返します。
定義済みマクロを使用してタイマーを宣言する別の方法:
defineTimer(T1, オプションの期間) を使用すると、このオブジェクトの T1::setup() メソッドと T1::run() メソッドの両方を宣言できるようになります。これは、メインの setup() にセットアップ コードを書き込むのではなく、このオブジェクト内にセットアップ コードを追加する場合に便利です。setup() が必須になったため、これは V1.1 との下位互換性がありません。setup() が必要ない場合は、代わりに defineTimerRun() を使用するようにプログラムを修正してください。
defineTaskLoop(myTimer,100) { sleepSync(1000); ticSecond=true; countSecond++ }拡張機能により、タイマークラスを定義するために2つのマクロを使用できます:
defineTimerBegin(event[,period]) と defineTimerEnd(event)。例として、ライブラリ内の例2をご覧ください。
イベント
ライブラリは、外部イベントまたはトリガー発生時にスケジューラが実行するコードを処理するための補完オブジェクト SCoopEvent を提供します。例えば、割り込み (isr、signal) からイベントをトリガーできますが、対応する run() コードは割り込みコンテキスト外でスケジューラによってのみ実行されます。これにより、ハードウェア割り込みのコーディングに必要な厳密さを必要とせずに、複雑なイベント処理やライブラリ関数の呼び出しを記述できます。
SCoopTimerオブジェクトと同様に、SCoopEventはスタックコンテキストを持たず、スケジューリングをスムーズに行うために可能な限り高速に実行されることが期待されます。イベントの完了に長い時間を要する場合は、sleepUntil() メソッドを使用して、揮発性フラグを待機する永続タスクとして宣言する必要があります。
イベントの宣言と使用例:
defineEventRun(myevent){ Serial.println("trigger received"); }
isr(pin) { myevent.set(); } // or myevent=true; イベント オブジェクトには、トリガー フラグを設定するパブリック メソッドが 1 つだけあります。
set()イベント オブジェクトには、トリガー フラグを設定するパブリック メソッドが 1 つだけあります。set(value): トリガー値を設定します。falseの場合は何も起こりません。trueの場合はset()と同等です。
ライブラリがこのオブジェクトの標準の「=」演算子をオーバーロードするため、イベント トリガー フラグは「myevent=true」のような割り当てによって直接設定することもできます。
defineEvent(event) は、タイマーの場合と同様に、event::setup() と event::run() の両方を定義するために使用できます。これはV1.1との下位互換性がありません。setup() が不要な場合は、代わりに defineEventRun(event) を使用してください。
拡張機能により、イベントクラスを定義するために2つのマクロを使用できます:
defineEventBegin(event) と defineEventEnd(event)。例として、ライブラリ内の例2をご覧ください。
Fifo / first in first out new in V1.1
SCoopライブラリは、補完的なオブジェクトであるSCoopFifoとマクロdefineFifoを提供します。これらは、バイト、整数、long、または256バイト未満のあらゆる構造体データに対する先入先出バッファの管理を効果的にサポートします。これは、タスク、イベント、タイマーと、プロデューサー・コンシューマーまたはセンダー・レシーバーモデルを持つ別のタスク間で同期的にデータを交換する場合に非常に便利です(タスク間の同期は不要です)。
ここでは、100 個の整数の FIFO バッファを宣言し、アナログ サンプリングを実行するタイマーと、それらを使用して変動を監視するタスクの間でそれを使用する方法の例を示します。
defineFifo(analogBuf,int16_t,100)
int16_t A,B;
defineTimer(anaRead,10) // read analog every 10ms
void anaRead::run() {
A=analogRead(1); analogBuf.put(&A);
}
defineTaskLoop(task1) {
while(analogBuf<32) yield(0); // wait for 32 items in the buffer
int16_t avg=0;
for (int i=0; i<32; i++) {
analogBuf.get(&B);
avg += B;
}
avg /= 32; yield();
Serial.print("average analog value for 32 samples = ");Serial.println(avg);
} SCoopFifo には次のメソッドも用意されています。
put(& var): 変数の値をバッファに追加します。結果が成功した場合はtrueを、バッファが既にいっぱいの場合はfalseを返します。putChar(val)/putInt(val)/putLong(val): を使用すると、中間変数を必要とせずに、特定の値をバッファに直接追加できます。get(& var): バッファから古い値を取得し、渡された変数に格納します。成功した場合はtrueを、バッファが空の場合はfalseを返します。flush(): バッファを空にして、宣言に従ってバッファのサイズ (項目数) を返します。flushNoAtomic(): フラッシュと同じですが、割り込みには影響しません。ISRと組み合わせて使用しないでください。count(): バッファ内で利用可能なサンプルの量を返します。
オブジェクトの名前を整数式内で使用してメソッドと同じ値を返すことも可能です。 count().
Virtual timer/delay: SCoopDelay と SCoopDelayus new in V1.2
SCoopライブラリは、遅延やタイムアウトの測定を容易にするシンプルなオブジェクト「SCoopDelay」クラスを使用・提供するようになりました。これはTimerDownに似ています(ドキュメントの後半を参照)。
オブジェクトは自動リロード値の有無にかかわらず宣言できます。使用しない場合、リロード記憶変数はリンカーによって自動的に削除される可能性があります。使用した場合、タイマー/遅延が自動的に開始されます。
一般的な使用法:
SCoopDelay time;
time = 10; while (time) yield(); // this launch yield() during 10ms
SCoopDelay t10seconds(10000);
If (T10seconds.reloaded()) { /* do something every 10 seconds */ } SCoopDelay オブジェクトのメソッド:
set(time): 遅延の開始時間を定義します。遅延は0までカウントダウンを開始します。get(): 遅延の値を返します。遅延が経過した場合、結果は 0 になります。add(time): 遅延に一定の時間を追加します。sub(time): 遅延に一定の時間を減算します。
これらの 4 つのメソッドは、演算子オーバーロードのおかげで、x=delay、delay=x、delay+=x、delay-= x などの式で SCoopDelay オブジェクトを直接使用することで透過的に使用することもできます。
elapsed(): 遅延が経過した場合は true を返します。reloaded(): elapsed と同じですが、定義されている場合はリロード値を使用して遅延を自動的に再開します。setReload(time): オブジェクトにリロード値を事前定義し、割り当てます。オブジェクトの宣言時に指定した場合と同じです。getReload(): オブジェクトに添付されたリロードパラメータの値を返します。reload(): SCoopDelay オブジェクトにリロード値を追加します。initReload(): SCoopDelay オブジェクトをそのリロード値で設定します。
すべての値は、SCoop.h ファイルの先頭にある SCDelay_t 変数で定義されているように int32 で定義されます。
拡張として、ライブラリはSCoopDelayusオブジェクトも提供します。こちらも同様ですが、パラメータはマイクロ秒単位です。値は、SCoop.hファイルの先頭にあるmicros_t変数で定義されているように、AVRの場合はint16、ARMの場合はint32です。
時間カウンター:
SCoopライブラリは、スケジューラから完全に独立した2つの強力なオブジェクトを提供し、時間カウントのアップとダウンを処理できます。ティックカウントの時間基準は、1ミリ秒から30秒(int16)まで指定できます。宣言できるタイマーの数に制限はありません。
- TimerUp: 0からオブジェクト宣言で定義された最大値(0も可)まで時間をカウントするオブジェクトを提供します。最大値に達するとカウンターは0に戻り、rollOver() メソッドでロールオーバーイベントを監視してアクションを開始できます。例:
#include <TimerUp.h>
TimerUp counterMs(0); // do nothing, need further initialization
TimerUp myMillis(1000); // will count up from 0 to 999 every 1ms
TimerUp mySeconds(3600,1000); // will count from 0 to 3599 every 1000ms
…
void loop() {
if (myMillis.rollOver()) {
Serial.print("one more second elapsed")
}
if (mySeconds.rollOver()) {
Serial.print("one more hour elapsed")
}
} オブジェクトは、式または割り当て内で直接使用することもできます。
TimerUp myTime(1000);
…
void loop() {
if (skip) && (myTime > 500) { mytime = 800; } // will read and force timer ロールオーバーの宣言中に設定された値は、後でメソッド setRollOver(timeMax) を使用して変更できます。
TimerDownは、最大値から0までカウントダウンする強力なオブジェクトです。カウントダウンが0に達すると、プログラムによって別の値に強制されるまで、カウンタは必ず0の値を維持します。例えば、次のようになります。
#include <TimerUp.h>
#include <TimerDown.h>
TimerUp tic(100,10) // count up from 0 to 99 every 10ms
TimerDown msLeft(0); // will count down every ms
TimerDown secondsLeft(3600,1000); // count from 3599 to 0 every 1000ms
…
void loop() {
while (secondsLefts) {
if (tic.rollOver()){
msLeft=1000; // restart this counter
Serial.print(secondsLeft);
Serial.print(" seconds and ");
Serial.print(msLeft);
Serial.println("ms before end");
}
}
} さらに、TimerUp オブジェクトと TimerDown オブジェクトには以下のメソッドを使用できます。
Init(time, timeBase): 指定されたtimemaxとtime baseでカウンターを初期化します。例えば、counter.init(5,1000) は、カウンター(カウントアップまたはカウントダウン)を5秒に相当する値で初期化します。TimerUpの場合は0から4までカウントし、TimerDownの場合は5から0までカウントします。set(time): カウンターに新しい値を強制的に設定。これは、counter=100; のような値を設定するのと同じです。reset(): カウンターを強制的に 0 にします。また、TimerUp オブジェクトのロールオーバー フラグをクリアします。get(): タイマーの値を返します。タイマーは、 x=counterMs; のような整数式内で直接読み取ることもできます。pause(time): タイマーは再開されるまで実際の値を保持します。resume(): タイマーが一時停止されていた場合は再開され、この値から再びカウントを開始します。
ライブラリコンパイルオプション:
TimerUp.hとTimerDown.hというファイルを編集して、#defineステートメントの最初の行の値を変更すると、コンパイルの前処理に影響を与える可能性があります。
例えば、ライブラリをコンパクトにするために、時間ベースの処理を無効にすることができます。その場合、デフォルトの時間基準はミリ秒になります。また、必要に応じて、元のオブジェクトクラス TimerUp (または TimerDown )の名前を、例えば TimeCounter のような独自のオブジェクト名に変更することもできます。
メソッドパラメータを「long」ではなく「int」に強制できるようになりましたが、内部タイミング計算は32ビットモードのままです。(注:ARMの場合、intは依然として32ビットです!)
より技術的なこと
この図は、オブジェクトクラスといくつかのインスタンス、そしてArduinoスケッチの標準のsetup()関数とloop()関数がどのように連携して動作するかを示しています。これは、前章で説明した内容を明確にすることを目的としています。

Arduino® ARM & AVR用のシンプルな協調スケジューラ「SCoop」。詳細やリソースについては、 ここ.
ライブラリリファレンスホーム
86Duinoリファレンスのテキストは、Arduinoリファレンスを改変したもので、Creative Commons Attribution-ShareAlike 3.0ライセンスに基づいてライセンスされています。リファレンス内のコードサンプルはパブリックドメインとして公開されています。