- セマフォとは何ですか?
- FreeRTOSでセマフォを使用する方法は?
- セマフォコードの説明
- 回路図
- ミューテックスとは何ですか?
- FreeRTOSでMutexを使用する方法は?
- ミューテックスコードの説明
以前のチュートリアルでは、Arduinoを使用したFreeRTOSの基本と、FreeRTOSArduinoのQueueカーネルオブジェクトについて説明しました。ここで、この3番目のFreeRTOSチュートリアルでは、FreeRTOSとその高度なAPIについて詳しく学習します。これにより、マルチタスクプラットフォームをより深く理解できるようになります。
セマフォとミューテックス(相互排除)は、同期、リソース管理、およびリソースの破損からの保護に使用されるカーネルオブジェクトです。このチュートリアルの前半では、セマフォの背後にある考え方、それを使用する方法と場所について説明します。後半は、Mutexを継続します。
セマフォとは何ですか?
以前のチュートリアルでは、タスクの優先度について説明しました。また、優先度の高いタスクが優先度の低いタスクをプリエンプトするため、優先度の高いタスクの実行中に、優先度の低いタスクでデータが破損する可能性があることもわかりました。はまだ実行されておらず、データはセンサーからこのタスクに継続的に送られ、データの損失とアプリケーション全体の誤動作を引き起こします。
したがって、データの損失からリソースを保護する必要があり、ここではセマフォが重要な役割を果たします。
セマフォは、待機状態のタスクが実行のために別のタスクによって通知されるシグナリングメカニズムです。つまり、task1が作業を終了すると、フラグを表示するか、フラグを1つインクリメントし、このフラグを別のタスク(task2)が受信して、作業を実行できることを示します。task2が作業を終了すると、フラグが1つ減ります。
したがって、基本的に、これは「Give」および「Take」メカニズムであり、セマフォはリソースへのアクセスを同期するために使用される整数変数です。
FreeRTOSのセマフォの種類:
セマフォには2つのタイプがあります。
- バイナリセマフォ
- セマフォを数える
1.バイナリセマフォ: 0と1の2つの整数値があります。長さ1のキューにいくらか似ています。たとえば、task1とtask2の2つのタスクがあります。Task1はtask2にデータを送信するため、task2はキューアイテムが1の場合は継続的にチェックし、それ以外の場合は1になるまで待機する必要があります。データを取得した後、task2はキューをデクリメントして0にします。つまり、task1が再びtask2にデータを送信できます。
上記の例から、タスク間またはタスクと割り込み間の同期にはバイナリセマフォが使用されていると言えます。
2.セマフォのカウント: 0より大きい値があり、1より大きい長さのキューと考えることができます。このセマフォは、イベントのカウントに使用されます。この使用シナリオでは、イベントハンドラーはイベントが発生するたびにセマフォを「提供」し(セマフォカウント値をインクリメント)、ハンドラータスクはイベントを処理するたびにセマフォを取得します(セマフォカウント値をデクリメントします)。 。
したがって、カウント値は、発生したイベントの数と処理された数の差です。
それでは、FreeRTOSコードでセマフォを使用する方法を見てみましょう。
FreeRTOSでセマフォを使用する方法は?
FreeRTOSは、セマフォの作成、セマフォの取得、およびセマフォの提供のためのさまざまなAPIをサポートしています。
現在、同じカーネルオブジェクトに対して2種類のAPIが存在する可能性があります。ISRからセマフォを提供する必要がある場合、通常のセマフォAPIは使用できません。割り込み保護されたAPIを使用する必要があります。
このチュートリアルでは、理解と実装が簡単なバイナリセマフォを使用します。ここでは割り込み機能を使用しているため、ISR関数で割り込み保護APIを使用する必要があります。タスクを割り込みと同期するということは、ISRの直後にタスクを実行状態にすることを意味します。
セマフォの作成:
カーネルオブジェクトを使用するには、最初にそれを作成する必要があります。バイナリセマフォを作成するには、vSemaphoreCreateBinary()を使用します。
このAPIはパラメーターを受け取らず、SemaphoreHandle_t型の変数を返します。セマフォを格納するために、グローバル変数名sema_vが作成されます。
SemaphoreHandle_t sema_v; sema_v = xSemaphoreCreateBinary();
セマフォを与える:
セマフォを与えるために、2つのバージョンがあります。1つは割り込み用で、もう1つは通常のタスク用です。
- xSemaphoreGive():このAPIは、セマフォを作成するときに、上記のsema_vのようなセマフォの変数名である引数を1つだけ取ります。同期したい通常のタスクから呼び出すことができます。
- xSemaphoreGiveFromISR():これはxSemaphoreGive()の割り込み保護されたAPIバージョンです。ISRと通常のタスクを同期する必要がある場合は、ISR関数からxSemaphoreGiveFromISR()を使用する必要があります。
セマフォを取る:
セマフォを取得するには、API関数xSemaphoreTake()を使用します。このAPIは2つのパラメーターを取ります。
xSemaphoreTake(SemaphoreHandle_t xSemaphore、TickType_t xTicksToWait);
xSemaphore:この場合sema_vで取得されるセマフォの名前。
xTicksToWait:これは、タスクがブロック状態でセマフォが使用可能になるまで待機する最大時間です。このプロジェクトでは、 xTicksToWait を portMAX_DELAY に設定して、sema_vが使用可能になるまでtask_1がブロック状態で無期限に待機するようにします。
それでは、これらのAPIを使用して、いくつかのタスクを実行するコードを記述しましょう。
ここでは、1つの押しボタンと2つのLEDが接続されています。プッシュボタンは、ArduinoUnoのピン2に接続されている割り込みボタンとして機能します。このボタンを押すと割り込みが発生し、ピン8に接続されているLEDがオンになり、もう一度押すとオフになります。
したがって、ボタンが押されると、 xSemaphoreGiveFromISR() がISR関数から呼び出され、 xSemaphoreTake() 関数がTaskLED関数から呼び出されます。
システムをマルチタスクのように見せるために、他のLEDをピン7に接続します。ピン7は、常に点滅状態になります。
セマフォコードの説明
ArduinoIDEを開いてコードを書き始めましょう
1.まず、 Arduino_FreeRTOS.h ヘッダーファイルをインクルードします。これで、キューセマフォのようにカーネルオブジェクトを使用する場合は、ヘッダーファイルも含める必要があります。
#include #include
2. SemaphoreHandle_t型の変数を宣言して、セマフォの値を格納します。
SemaphoreHandle_tinterruptSemaphore;
3. void setup()で、xTaskCreate()APIを使用して2つのタスク(TaskLEDとTaskBlink)を作成し、次にxSemaphoreCreateBinary()を使用してセマフォを作成します。同じ優先度でタスクを作成し、後でこの番号で遊んでみます。また、ピン2を入力として構成し、内部プルアップ抵抗を有効にして、割り込みピンを接続します。最後に、以下に示すようにスケジューラを起動します。
void setup(){ pinMode(2、INPUT_PULLUP); xTaskCreate(TaskLed、 "Led"、128、NULL、0、NULL); xTaskCreate(TaskBlink、 "LedBlink"、128、NULL、0、NULL); InterruptSemaphore = xSemaphoreCreateBinary(); if(interruptSemaphore!= NULL){ attachInterrupt(digitalPinToInterrupt(2)、debounceInterrupt、LOW); } }
4.次に、ISR関数を実装します。関数を 作成し 、 attachInterrupt() 関数の2番目の引数と同じ名前を付けます。割り込みを正しく機能させるには、ミリまたはマイクロ機能を使用し、デバウンス時間を調整することにより、押しボタンのデバウンスの問題を取り除く必要があります。この関数から、以下に示すように interruptHandler() 関数を呼び出します。
long debouncing_time = 150; 揮発性のunsignedlong last_micros; void debounceInterrupt(){ if((long)(micros()-last_micros)> = debouncing_time * 1000){ interruptHandler(); last_micros = micros(); } }
では 割り込みハンドラ() 関数呼び出し xSemaphoreGiveFromISR() API。
void interruptHandler(){ xSemaphoreGiveFromISR(interruptSemaphore、NULL); }
この関数は、TaskLedにセマフォを与えてLEDをオンにします。
5. TaskLed 関数を作成し、 while ループ内で、xSemaphoreTake() APIを呼び出して、セマフォが正常に取得されたかどうかを確認します。pdPASS(つまり1)と等しい場合は、以下に示すようにLEDを切り替えます。
void TaskLed(void * pvParameters) { (void)pvParameters; pinMode(8、OUTPUT); while(1){ if(xSemaphoreTake(interruptSemaphore、portMAX_DELAY)== pdPASS){ digitalWrite(8、!digitalRead(8)); } } }
6.また、ピン7に接続されている他のLEDを点滅させる機能を作成します。
void TaskLed1(void * pvParameters) { (void)pvParameters; pinMode(7、OUTPUT); while(1){ digitalWrite(7、HIGH); vTaskDelay(200 / portTICK_PERIOD_MS); digitalWrite(7、LOW); vTaskDelay(200 / portTICK_PERIOD_MS); } }
7.voidループ関数は空のままになります。それを忘れないでください。
void loop(){}
これで、このチュートリアルの最後に完全なコードがあります。次に、このコードをアップロードし、回路図に従ってLEDとプッシュボタンをArduinoUNOに接続します。
回路図
コードをアップロードした後、200ms後にLEDが点滅し、ボタンを押すと、最後に表示されているビデオに示すように、すぐに2番目のLEDが点灯します。
このように、セマフォはArduinoを使用するFreeRTOSで使用でき、データを損失することなく1つのタスクから別のタスクに渡す必要があります。
それでは、Mutexとは何か、FreeRTOSの使用方法を見てみましょう。
ミューテックスとは何ですか?
上で説明したように、セマフォはシグナリングメカニズムです。同様に、ミューテックスは、インクリメントとデクリメントの別々の機能を持つセマフォとは異なり、ロックメカニズムですが、ミューテックスでは、関数がそれ自体を取得して提供します。これは、共有リソースの破損を回避するための手法です。
共有リソースを保護するために、トークンカード(ミューテックス)をリソースに割り当てます。このカードを持っている人は誰でも他のリソースにアクセスできます。他の人はカードが戻るまで待つべきです。このようにして、1つのリソースのみがタスクにアクセスでき、他のリソースはチャンスを待ちます。
例を使用して、FreeRTOSのミューテックスを理解しましょう。
ここでは、3つのタスクがあります。1つはLCDにデータを印刷するため、2つ目はLDRデータをLCDに送信するため、最後のタスクはLCDに温度データを送信するためです。したがって、ここでは2つのタスクが同じリソース(LCD)を共有しています。LDRタスクと温度タスクが同時にデータを送信する場合、データの1つが破損または失われる可能性があります。
したがって、データ損失を保護するために、タスク1のLCDリソースを、表示タスクが完了するまでロックする必要があります。次に、LCDタスクのロックが解除され、task2がその作業を実行できるようになります。
次の図で、ミューテックスとセマフォの動作を確認できます。
FreeRTOSでMutexを使用する方法は?
ミューテックスもセマフォと同じように使用されます。最初にそれを作成し、次にそれぞれのAPIを使用してギブアンドテイクします。
ミューテックスの作成:
ミューテックスを作成するには、 xSemaphoreCreateMutex()APIを使用します 。その名前が示すように、ミューテックスはバイナリセマフォの一種です。それらは、さまざまなコンテキストと目的で使用されます。バイナリセマフォはタスクを同期するためのものであり、ミューテックスは共有リソースを保護するために使用されます。
このAPIは引数をとらず、 SemaphoreHandle_t 型の変数を返します。ミューテックスを作成できない場合、 xSemaphoreCreateMutex()は NULLを返します。
SemaphoreHandle_t mutex_v; mutex_v = xSemaphoreCreateMutex();
ミューテックスを取る:
タスクがリソースにアクセスする場合、 xSemaphoreTake() APIを使用してミューテックスを 取得し ます。バイナリセマフォと同じです。また、2つのパラメーターを取ります。
xSemaphore:この場合 mutex_vで 取得されるミューテックスの名前。
xTicksToWait:これは、タスクがブロック状態でミューテックスが使用可能になるまで待機する最大時間です。このプロジェクトでは、 xTicksToWait を portMAX_DELAY に設定して、 mutex_v が使用可能になるまで task_1 がブロック状態で無期限に待機するようにします。
ミューテックスを与える:
共有リソースにアクセスした後、他のタスクがアクセスできるように、タスクはミューテックスを返す必要があります。 xSemaphoreGive() APIは、ミューテックスを返すために使用されます。
xSemaphoreGive()関数は、この場合はmutex_vで指定されるMutexである引数を1つだけ取ります。
上記のAPIを使用して、ArduinoIDEを使用してFreeRTOSコードにミューテックスを実装しましょう。
ミューテックスコードの説明
ここでのこの部分の目標は、シリアルモニターを共有リソースとして使用し、2つの異なるタスクを使用してシリアルモニターにアクセスし、メッセージを印刷することです。
1.ヘッダーファイルはセマフォと同じままです。
#include #include
2.タイプ SemaphoreHandle_tの 変数を宣言して、ミューテックスの値を格納します。
SemaphoreHandle_t mutex_v;
3. void setup()で、 シリアルモニターを9600ボーレート で 初期化し、 xTaskCreate() APIを使用して2つのタスク(Task1とTask2)を作成します。次に、 xSemaphoreCreateMutex() を使用してミューテックスを作成します 。 同じ優先順位でタスクを作成し、後でこの番号で遊んでみてください。
void setup(){ Serial.begin(9600); mutex_v = xSemaphoreCreateMutex(); if(mutex_v == NULL){ Serial.println( "Mutexを作成できません"); } xTaskCreate(Task1、 "Task 1"、128、NULL、1、NULL); xTaskCreate(Task2、 "Task 2"、128、NULL、1、NULL); }
4.次に、Task1とTask2のタスク関数を作成します。で しばらくの タスク機能のループ、シリアルモニターにメッセージを印刷する前に、私たちが使用してミューテックス取らなければならない )xSemaphoreTakeを( その後、メッセージを印刷してから使用してミューテックスを返す xSemaphoreGiveを()。 その後、少し遅れてください。
void Task1(void * pvParameters){ while(1){ xSemaphoreTake(mutex_v、portMAX_DELAY); Serial.println( "Hi from Task1"); xSemaphoreGive(mutex_v); vTaskDelay(pdMS_TO_TICKS(1000)); } }
同様に、500msの遅延でTask2関数を実装します。
5. Void loop() は空のままになります。
次に、このコードをArduino UNOにアップロードし、シリアルモニターを開きます。
task1とtask2からメッセージが印刷されているのがわかります。
Mutexの動作をテストするには、 xSemaphoreGive(mutex_v);に コメントするだけ です。 任意のタスクから。プログラムが最後の印刷メッセージでハングしていることがわかります。
これは、SemaphoreとMutexをArduinoを使用してFreeRTOSに実装する方法です。セマフォとミューテックスの詳細については、FreeRTOSの公式ドキュメントをご覧ください。
セマフォとミュートの完全なコードとビデオを以下に示します。