esp-open-rtosを使ってみる

Queue機能

マルチタスクのシステムを設計する際に、一番重要なのはタスクの同期とタスク間通信です。
Linuxではタスクの同期とタスク間通信のために、MessageQueue機能が有りますが、
FreeRTOSでも同様の機能を使うことができます。

デフォルトで含まれているexamples/simpleのサンプルを使って、Queueの機能を確認することができます。
このプログラムは以下の2つのタスクで構成されています。

task1
xQueueSend()を使って100tick(1秒)ごとにメッセージを送信します。
裏で静かに動きkます。

task2
xQueueReceive()を使って、Queueからメッセージを取り出して表示します。

このサンプルを実行すると、Queueから取り出したメッセージ(0から始まる数字)を表示します。
見た目は単純ですが、マルチタスクシステムの基本です。
SDK version:0.9.9
Hello from task1!
Hello from task 2!
Got 0
Got 1
Got 2
Got 3
Got 4
Got 5



キュー領域の作成はxQueueCreate()、またはxQueueCreateStatic()で行います。
xQueueCreate()を使用してキューを作成した場合、キュー領域はFreeRTOSヒープから自動的に割り当てられます。
xQueueCreateStatic()を使用してキューを作成した場合、キュー領域はアプリケーション側で準備します。
こちらに、 xQueueCreateStatic()を使用してキューを作成する場合のサンプルが公開されています。

Queueについて調べていたら、このような記事を見つけました。

As an aside, many of the FreeRTOS demos use queues to pass characters into and out of interrupts to provide a simple example of tasks and interrupts communicating,
but unless the throughput is very low (a command console for example), it is not the recommended way of writing production code.
Using circular buffers, preferably with a DMA, is much more efficient.

Google君に翻訳してもらいました。

余談ですが、FreeRTOSの多くのデモはキューを使って文字を割り込みに出し入れすることでタスクと割り込み通信の簡単な例を提供します。
ただし、スループットが非常に低い場合(コマンドコンソールなど)を除いて、プロダクションコードを書くのにはお勧めできません。
循環バッファ(できればDMAで)を使用することははるかに効率的である

確かにグローバル変数とSemaphoreを使えば同じことができます。



Queueの使い方にはいくつかバリエーションが有ります。
以下のコードでそのバリエーションを試してみました。
/* The example of esp-free-rtos
 *
 * This sample code is in the public domain.
 *
 * You have to add this in FreeRTOSConfig.h.
 * #define configUSE_QUEUE_SETS     1
 */
#include <stdlib.h>
#include <string.h>
#include "espressif/esp_common.h"
#include "esp/uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "esp8266.h"

QueueHandle_t xQueue;

/* Define the lengths of the queues that will be added to the queue set. */
#define QUEUE_LENGTH 10

/* Define the size of the item to be held by queue 1 and queue 2 respectively.
The values used here are just for demonstration purposes. */
#define ITEM_SIZE_QUEUE sizeof( uint32_t )

// Send Queue
void task1(void *pvParameters)
{
    uint32_t count = 0;
    TickType_t nowTick;
    nowTick = xTaskGetTickCount();
    printf("[%s:%d] Start\n",pcTaskGetName(0),nowTick);
    for(int i=0;i<10;i++) {
#if 0
      xQueueSend(xQueue, &count, 0);
#endif
#if 1
      xQueueSendToBack(xQueue, &count, 0);
#endif
#if 0
      xQueueSendToFront(xQueue, &count, 0);
#endif
      count++;
    }
    while(1) {
      vTaskDelay(1000/portTICK_PERIOD_MS);
    }
}

void task2(void *pvParameters)
{
    TickType_t nowTick;
    uint32_t count;
    nowTick = xTaskGetTickCount();
    printf("[%s:%d] Start\n",pcTaskGetName(0),nowTick);
    while(1) {
      nowTick = xTaskGetTickCount();
      if(xQueueReceive(xQueue, &count, 0)) {
         printf("[%s:%d] count=%u\n",pcTaskGetName(0),nowTick,count);
      } else {
         printf("[%s:%d] No msg\n",pcTaskGetName(0),nowTick);
         break;
      }
    }
    while(1) {
      vTaskDelay(1000/portTICK_PERIOD_MS);
    }
}

void user_init(void)
{
    uart_set_baud(0, 115200);
    printf("SDK version:%s\n", sdk_system_get_sdk_version());
    printf("pcTaskGetName=%s\n",pcTaskGetName(0));

    /* Create the queues and semaphores that will be contained in the set. */
    xQueue = xQueueCreate( QUEUE_LENGTH, ITEM_SIZE_QUEUE );

    /* Check everything was created. */
    configASSERT( xQueue );

    xTaskCreate(task1, "task1", 256, NULL, 2, NULL);
    xTaskCreate(task2, "task2", 256, NULL, 2, NULL);
}

xQueueSendToBack()、またはxQueueSend()で書き込んだ場合、xQueueReceive()では古いものから順に 取り出すことができます。
xQueueSend()は昔のFreeRTOSとの互換性のために残っている関数です。
SDK version:0.9.9
pcTaskGetName=uiT
[task1:1] Start
[task2:1] Start
[task2:1] count=0
[task2:1] count=1
[task2:1] count=2
[task2:2] count=3
[task2:2] count=4
[task2:2] count=5
[task2:2] count=6
[task2:2] count=7
[task2:2] count=8
[task2:3] count=9
[task2:3] No msg

xQueueSendToFront()で書き込んだ場合、xQueueReceive()では新しいものから順に 取り出すことができます。
SDK version:0.9.9
pcTaskGetName=uiT
[task1:1] Start
[task2:1] Start
[task2:1] count=9
[task2:1] count=8
[task2:1] count=7
[task2:1] count=6
[task2:1] count=5
[task2:2] count=4
[task2:2] count=3
[task2:2] count=2
[task2:2] count=1
[task2:2] count=0
[task2:2] No msg

xQueueOverwrite()は、長さが1のキューで使用することを目的としています。
例えば現在の温度をQueueを使って他のタスクに渡す場合、最新の温度しか意味を持ちません。
1分前の温度は意味がないので、そのような場合は長さが1のキューを使って、xQueueOverwrite()で書き込みます。
xQueueReceive()では、データがないか、常に最新のデータだけを取り出すことができます。
最新のデータだけを他のタスクに渡す用途では、グローバル変数を使うこともできますが、グローバル変数を使う場合は、
Mutexを使った排他制御が必要になります。

続く....