ESP-IDFを使ってみる

FreeRTOSの新機能 MessageBuffer


ESP-IDF V4.3から、組み込まれているFreeRTOSがV10になり、StreamBufferと同時にMessageBufferが使えるようになりまし た。
MessageBufferの実態は以下の様にトリガーレベル=0でStreamBufferを呼び出しているだけです。
components/freertos/include/freertos/message_buffer.h

#define xMessageBufferCreate( xBufferSizeBytes ) ( MessageBufferHandle_t ) xStreamBufferGenericCreate( xBufferSizeBytes, ( size_t ) 0, pdTRUE )

#define xMessageBufferSend( xMessageBuffer, pvTxData, xDataLengthBytes, xTicksToWait ) xStreamBufferSend( ( StreamBufferHandle_t ) xMessageBuffer, pvTxData, xDataLengthBytes, xTicksToWait )

#define xMessageBufferReceive( xMessageBuffer, pvRxData, xBufferLengthBytes, xTicksToWait ) xStreamBufferReceive( ( StreamBufferHandle_t ) xMessageBuffer, pvRxData, xBufferLengthBytes, xTicksToWait )

StreamBufferとの違いを以下のコードで確認してみました。
/* The example of ESP-IDF
 *
 * This sample code is in the public domain.
 */

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/message_buffer.h"
#include "esp_log.h"

StreamBufferHandle_t xMessageBuffer = NULL;

void ReceivingTask(void *pvParameters)
{
        ESP_LOGI(pcTaskGetName(NULL), "Start");
        char cRxBuffer[ 20 ];
        memset( cRxBuffer, 0x00, sizeof( cRxBuffer ) );

        while(1) {
                size_t readBytes = xMessageBufferReceive(xMessageBuffer,
                                                        cRxBuffer,
                                                        sizeof(cRxBuffer),
                                                        pdMS_TO_TICKS(1000) );
                ESP_LOGD(pcTaskGetName(NULL), "readBytes=%d", readBytes);
                if (readBytes != 0) {
                        ESP_LOGI(pcTaskGetName(NULL), "readBytes=%d cRxBuffer=[%.*s]", readBytes, readBytes, cRxBuffer);
                }
        }
}

void SendingTask(void *pvParameters)
{
        ESP_LOGI(pcTaskGetName(NULL),"Start");
        static size_t xNextByteToSend = 0;
        static size_t xTotalByteToSend = 0;
        const BaseType_t xBytesToSend = 2;
        static const char * pcStringToSend = "_____Hello FreeRTOS_____";
        ESP_LOGI(pcTaskGetName(NULL),"length=%d", strlen(pcStringToSend));

        for(int i=0; i<strlen(pcStringToSend); i++) {
                xMessageBufferSend( xMessageBuffer,
                                                ( void * ) ( pcStringToSend + xNextByteToSend ),
                                                xBytesToSend,
                                                portMAX_DELAY );
                xTotalByteToSend += xBytesToSend;
                ESP_LOGI(pcTaskGetName(NULL),"xTotalByteToSend=%d", xTotalByteToSend);
                xNextByteToSend += xBytesToSend;
                if( xNextByteToSend >= strlen( pcStringToSend ) )
                {
                        xNextByteToSend = 0;
                }
                vTaskDelay(10);
        }
        vTaskDelete(NULL);
}

void app_main()
{
        // Get current priority
        UBaseType_t priority = uxTaskPriorityGet(NULL);
        ESP_LOGI(pcTaskGetName(NULL), "priority=%d", priority);

        // Create Message Buffer
        xMessageBuffer = xMessageBufferCreate(100);

        // Check everything was created
        configASSERT( xMessageBuffer );

        /* Create task */
        xTaskCreate(&SendingTask, "SEND", 1024*2, NULL, priority, NULL);
        xTaskCreate(&ReceivingTask, "RECV", 1024*2, NULL, priority, NULL);
}

ビルドして実行すると以下の様に表示されます。
トリガーが0バイトなので、送信側でバッファにデータを積むと、受信側で直ちにデータを受け取ります。
I (0) cpu_start: Starting scheduler on APP CPU.
I (322) main: priority=1
I (322) RECV: Start
I (322) SEND: Start
I (332) SEND: length=24
I (332) SEND: xTotalByteToSend=2
I (332) RECV: readBytes=2 cRxBuffer=[__]
I (442) SEND: xTotalByteToSend=4
I (442) RECV: readBytes=2 cRxBuffer=[__]
I (542) SEND: xTotalByteToSend=6
I (542) RECV: readBytes=2 cRxBuffer=[_H]
I (642) SEND: xTotalByteToSend=8
I (642) RECV: readBytes=2 cRxBuffer=[el]
I (742) SEND: xTotalByteToSend=10
I (742) RECV: readBytes=2 cRxBuffer=[lo]
I (842) SEND: xTotalByteToSend=12
I (842) RECV: readBytes=2 cRxBuffer=[ F]
I (942) SEND: xTotalByteToSend=14
I (942) RECV: readBytes=2 cRxBuffer=[re]
I (1042) SEND: xTotalByteToSend=16
I (1042) RECV: readBytes=2 cRxBuffer=[eR]
I (1142) SEND: xTotalByteToSend=18
I (1142) RECV: readBytes=2 cRxBuffer=[TO]
I (1242) SEND: xTotalByteToSend=20
I (1242) RECV: readBytes=2 cRxBuffer=[S_]
I (1342) SEND: xTotalByteToSend=22
I (1342) RECV: readBytes=2 cRxBuffer=[__]
I (1442) SEND: xTotalByteToSend=24
I (1442) RECV: readBytes=2 cRxBuffer=[__]
I (1542) SEND: xTotalByteToSend=26
I (1542) RECV: readBytes=2 cRxBuffer=[__]
I (1642) SEND: xTotalByteToSend=28
I (1642) RECV: readBytes=2 cRxBuffer=[__]
I (1742) SEND: xTotalByteToSend=30
I (1742) RECV: readBytes=2 cRxBuffer=[_H]
I (1842) SEND: xTotalByteToSend=32
I (1842) RECV: readBytes=2 cRxBuffer=[el]
I (1942) SEND: xTotalByteToSend=34
I (1942) RECV: readBytes=2 cRxBuffer=[lo]
I (2042) SEND: xTotalByteToSend=36
I (2042) RECV: readBytes=2 cRxBuffer=[ F]
I (2142) SEND: xTotalByteToSend=38
I (2142) RECV: readBytes=2 cRxBuffer=[re]
I (2242) SEND: xTotalByteToSend=40
I (2242) RECV: readBytes=2 cRxBuffer=[eR]
I (2342) SEND: xTotalByteToSend=42
I (2342) RECV: readBytes=2 cRxBuffer=[TO]
I (2442) SEND: xTotalByteToSend=44
I (2442) RECV: readBytes=2 cRxBuffer=[S_]
I (2542) SEND: xTotalByteToSend=46
I (2542) RECV: readBytes=2 cRxBuffer=[__]
I (2642) SEND: xTotalByteToSend=48
I (2642) RECV: readBytes=2 cRxBuffer=[__]



もともとESP-IDFにはRingBufferの機能が有りました。
RingBufferでは各データ毎に8バイトのヘッダが消費されますが、MessageBufferでは4バイトのヘッダーが消費されます。
MessageBufferの方が少しだけ効率がいいです。



空き領域の扱いがRingBufferとMessageBufferでは違います。
RingBufferではxRingbufferGetCurFreeSize()を使って空き領域を確認することができます。
RingBufferの空き領域の最大は(pxRingbuffer-> xSize / 2) -  8に制限されています。

MessageBufferではxMessageBufferSpacesAvailable()を使って空き領域を確認することができます。
MessageBufferの空き領域の最大は確保したバッファのサイズとなります。

以下のコードで空き領域を確認してみました。
RingBufferもMessageBufferも1000バイトの領域を確保します。
MessageBufferでは4バイトのヘッダーが消費されるので、96バイトのデータを積んでいます。これで100バイトの領域が消費されま す。
RingBufferでは8バイトのヘッダが消費されるので、92バイトのデータを積んでいます。これで、こちらも100バイトの領域が消費され ます。
どちらも10個のデータを積むので、最後は空き領域が無くなります。
/* The example of ESP-IDF
 *
 * This sample code is in the public domain.
 */

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/ringbuf.h"
#include "freertos/message_buffer.h"
#include "esp_log.h"


/* Size of buffer */
#define BUFFER_SIZE 1000
#define COUNTER 10

static MessageBufferHandle_t xMessageBuffer;
static RingbufHandle_t xRingBuffer;

static void SendingTask( void *pvParameters )
{
        ESP_LOGI(pcTaskGetName(NULL),"Start");
        char cString[ 100 ];

        for(long i=0; i<COUNTER; i++) {
                xMessageBufferSend( xMessageBuffer, ( void * ) cString, 96, portMAX_DELAY );
                size_t space = xMessageBufferSpacesAvailable( xMessageBuffer );
                ESP_LOGI(pcTaskGetName(NULL), "space=%d", space);
        }

        for(long i=0; i<COUNTER; i++) {
                xRingbufferSend( xRingBuffer, ( void * ) cString, 92, portMAX_DELAY );
                size_t free = xRingbufferGetCurFreeSize(xRingBuffer);
                ESP_LOGI(pcTaskGetName(NULL), "free=%d", free);
        }

        vTaskDelete(NULL);
}

void app_main()
{
        // Create Message Buffer
        xMessageBuffer = xMessageBufferCreate( BUFFER_SIZE );
        configASSERT( xMessageBuffer );

        // Create Ring Buffer
        xRingBuffer = xRingbufferCreate(BUFFER_SIZE, RINGBUF_TYPE_NOSPLIT);
        configASSERT( xRingBuffer );

        xTaskCreate( &SendingTask, "SEND", 1024*2, NULL, 1, NULL );
}

結果は以下の様になります。
spaceがMessageBufferの空き容量で、freeがRingBufferの空き容量です。
MessageBufferではヘッダも含めた空き領域サイズを戻すので、空き領域の判定が簡単です。
RingBufferでは、途中まで[1000 / 2 -  8]の492バイトを戻しますが、これを下回ると初めて実際の空き領域の大きさを戻します。
I (0) cpu_start: Starting scheduler on APP CPU.
I (0) SEND: Start
I (0) SEND: space=900
I (10) SEND: space=800
I (10) SEND: space=700
I (10) SEND: space=600
I (10) SEND: space=500
I (20) SEND: space=400
I (20) SEND: space=300
I (20) SEND: space=200
I (30) SEND: space=100
I (30) SEND: space=0
I (30) SEND: free=492
I (40) SEND: free=492
I (40) SEND: free=492
I (40) SEND: free=492
I (40) SEND: free=492
I (50) SEND: free=392
I (50) SEND: free=292
I (50) SEND: free=192
I (60) SEND: free=92
I (60) SEND: free=0

送信側よりも受信側の処理の方が遅い事がよくあります。
処理スピードの差を吸収する目的で使われることが多いバッファです。
バッファの空容量によって送信側の処理を変える場合、RingBufferは空き領域の判定が厄介です。



両者のパフォーマンスを比較してみました。
コードは以下の様に単純なもので、MessageBufferとRingBufferを使って可変長のメッセージを1002個、
受信が完了するまでに要した Tick数を計っています。
/* The example of ESP-IDF
 *
 * This sample code is in the public domain.
 */

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/ringbuf.h"
#include "freertos/message_buffer.h"
#include "esp_log.h"


/* Size of buffer */
#define BUFFER_SIZE 1000
/* Size of payload */
#define PAYLOAD_SIZE 100

static MessageBufferHandle_t xMessageBuffer;
static RingbufHandle_t xRingBuffer;

static void SendingTask( void *pvParameters )
{
        ESP_LOGI(pcTaskGetName(NULL),"Start");
        char cString[PAYLOAD_SIZE];
        long MessageCount = 1000;

        sprintf( cString, "First Message");
        xMessageBufferSend( xMessageBuffer, ( void * ) cString, strlen( cString ), portMAX_DELAY );
        for(long i=0; i<MessageCount; i++) {
                sprintf(cString, "Message is %ld", i);
                xMessageBufferSend( xMessageBuffer, ( void * ) cString, strlen( cString ), portMAX_DELAY );
        }
        sprintf( cString, "Last Message");
        xMessageBufferSend( xMessageBuffer, ( void * ) cString, strlen( cString ), portMAX_DELAY );
        ESP_LOGI(pcTaskGetName(NULL),"xMessageBufferSend Finish");

        sprintf( cString, "First Message");
        xRingbufferSend( xRingBuffer, ( void * ) cString, strlen( cString ), portMAX_DELAY );
        for(long i=0; i<MessageCount; i++) {
                sprintf( cString, "Message is %ld", i );
                xRingbufferSend( xRingBuffer, ( void * ) cString, strlen( cString ), portMAX_DELAY );
        }
        sprintf( cString, "Last Message");
        xRingbufferSend( xRingBuffer, ( void * ) cString, strlen( cString ), portMAX_DELAY );
        ESP_LOGI(pcTaskGetName(NULL),"xRingBufferSend Finish");

        vTaskDelete(NULL);
}

static void MessageReceivingTask( void *pvParameters )
{
        ESP_LOGI(pcTaskGetName(NULL),"Start");

        size_t xReceivedBytes;
        char cReceived[PAYLOAD_SIZE];
        TickType_t FirstTick = 0;
        TickType_t LastTick, DiffTick;

        for( ;; )
        {
                xReceivedBytes = xMessageBufferReceive( xMessageBuffer, cReceived, sizeof( cReceived ), portMAX_DELAY );
                cReceived[xReceivedBytes] = 0;

                ESP_LOGD(pcTaskGetName(NULL),"cReceived=[%.*s]", xReceivedBytes, cReceived);
                if (strncmp(cReceived, "First Message", xReceivedBytes) == 0) {
                        FirstTick = xTaskGetTickCount();
                } else if (strncmp(cReceived, "Last Message", xReceivedBytes) == 0) {
                        LastTick = xTaskGetTickCount();
                        DiffTick = LastTick - FirstTick;
                        ESP_LOGI(pcTaskGetName(NULL),"DiffTick=[%d]", DiffTick);
                }
        }
        vTaskDelete(NULL);
}

static void RingReceivingTask( void *pvParameters )
{
        ESP_LOGI(pcTaskGetName(NULL),"Start");

        size_t xReceivedBytes;
        TickType_t FirstTick = 0;
        TickType_t LastTick, DiffTick;

        for( ;; )
        {
                char *cReceived = (char *)xRingbufferReceive( xRingBuffer, &xReceivedBytes, portMAX_DELAY );

                ESP_LOGD(pcTaskGetName(NULL),"cReceived=[%.*s]", xReceivedBytes, cReceived);
                if (strncmp(cReceived, "First Message", xReceivedBytes) == 0) {
                        FirstTick = xTaskGetTickCount();
                } else if (strncmp(cReceived, "Last Message", xReceivedBytes) == 0) {
                        LastTick = xTaskGetTickCount();
                        DiffTick = LastTick - FirstTick;
                        ESP_LOGI(pcTaskGetName(NULL),"DiffTick=[%d]", DiffTick);
                }
                //Return Item
                vRingbufferReturnItem(xRingBuffer, (void *)cReceived);
        }
        vTaskDelete(NULL);
}

void app_main()
{
        // Create Message Buffer
        xMessageBuffer = xMessageBufferCreate( BUFFER_SIZE );
        configASSERT( xMessageBuffer );

        // Create Ring Buffer
        xRingBuffer = xRingbufferCreate(BUFFER_SIZE, RINGBUF_TYPE_NOSPLIT);
        configASSERT( xRingBuffer );

        xTaskCreate( &SendingTask, "SEND", 1024*2, NULL, 1, NULL );

        xTaskCreate( &MessageReceivingTask, "MESSAGE", 1024*2, NULL, 1, NULL );

        xTaskCreate( &RingReceivingTask, "RING", 1024*2, NULL, 1, NULL );

}

ビルドして実行すると、どちらも同じパフォーマンスでした。
MessageBufferは受信する領域を使用する前に確保しておく必要が有りますが、
RingBufferは受信する領域を動的に確保してくれます。
I (315) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (325) MESSAGE: Start
I (0) SEND: Start
I (325) RING: Start
I (375) SEND: xMessageBufferSend Finish
I (375) MESSAGE: DiffTick=[4]
I (415) SEND: xRingBufferSend Finish
I (415) RING: DiffTick=[4]



MessageBufferとQueueのパフォーマンスを比較してみました。
コードは以下の様に単純なもので、MessageBufferとQueueを使って100バイト固定長のメッセージを1002個、
受信が完了 するまでに要したTick数を計っています。
MessageBufferにもQueueにも最大10個のデータを貯めることができます。
/* The example of ESP-IDF
 *
 * This sample code is in the public domain.
 */
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/message_buffer.h"
#include "freertos/queue.h"
#include "esp_log.h"


/* Size of buffer */
#define BUFFER_SIZE 1000
/* Size of payload */
#define PAYLOAD_SIZE 100

static MessageBufferHandle_t xMessageBuffer;
static QueueHandle_t xQueue;

static void SendingTask( void *pvParameters )
{
        ESP_LOGI(pcTaskGetName(NULL),"Start");
        char cString[PAYLOAD_SIZE];
        long MessageCount = 1000;

        sprintf( cString, "First Message");
        xMessageBufferSend( xMessageBuffer, ( void * ) cString, PAYLOAD_SIZE, portMAX_DELAY );
        for(long i=0; i<MessageCount; i++) {
                sprintf(cString, "Message is %ld", i);
                xMessageBufferSend( xMessageBuffer, ( void * ) cString, PAYLOAD_SIZE, portMAX_DELAY );
        }
        sprintf( cString, "Last Message");
        xMessageBufferSend( xMessageBuffer, ( void * ) cString, PAYLOAD_SIZE, portMAX_DELAY );
        ESP_LOGI(pcTaskGetName(NULL),"xMessageBufferSend Finish");

        sprintf( cString, "First Message");
        xQueueSend( xQueue, ( void * ) cString, portMAX_DELAY );
        for(long i=0; i<MessageCount; i++) {
                sprintf( cString, "Message is %ld", i );
                xQueueSend( xQueue, ( void * ) cString, portMAX_DELAY );
        }
        sprintf( cString, "Last Message");
        xQueueSend( xQueue, ( void * ) cString, portMAX_DELAY );
        ESP_LOGI(pcTaskGetName(NULL),"xQueueSend Finish");

        vTaskDelete(NULL);
}

static void MessageReceivingTask( void *pvParameters )
{
        ESP_LOGI(pcTaskGetName(NULL),"Start");

        char cReceived[PAYLOAD_SIZE];
        TickType_t FirstTick = 0;
        TickType_t LastTick, DiffTick;

        for( ;; )
        {
                size_t xReceivedBytes = xMessageBufferReceive( xMessageBuffer, cReceived, sizeof( cReceived ), portMAX_DELAY );
                cReceived[xReceivedBytes] = 0;

                ESP_LOGD(pcTaskGetName(NULL),"cReceived=[%.*s]", xReceivedBytes, cReceived);
                if (strncmp(cReceived, "First Message", strlen("First Message")) == 0) {
                        FirstTick = xTaskGetTickCount();
                } else if (strncmp(cReceived, "Last Message", strlen("Last Message")) == 0) {
                        LastTick = xTaskGetTickCount();
                        DiffTick = LastTick - FirstTick;
                        ESP_LOGI(pcTaskGetName(NULL),"DiffTick=[%"PRIu32"]", DiffTick);
                }
        }
        vTaskDelete(NULL);
}

static void QueueTask( void *pvParameters )
{
        ESP_LOGI(pcTaskGetName(NULL),"Start");

        char cReceived[PAYLOAD_SIZE];
        TickType_t FirstTick = 0;
        TickType_t LastTick, DiffTick;

        for( ;; )
        {
                BaseType_t xQueueStatus = xQueueReceive(xQueue, cReceived, portMAX_DELAY);

                ESP_LOGD(pcTaskGetName(NULL),"cReceived=[%s]", cReceived);
                if (strncmp(cReceived, "First Message", strlen("First Message")) == 0) {
                        FirstTick = xTaskGetTickCount();
                } else if (strncmp(cReceived, "Last Message", strlen("Last Message")) == 0) {
                        LastTick = xTaskGetTickCount();
                        DiffTick = LastTick - FirstTick;
                        ESP_LOGI(pcTaskGetName(NULL),"DiffTick=[%"PRIu32"]", DiffTick);
                }
        }
        vTaskDelete(NULL);
}

void app_main()
{
        // Create Message Buffer
        xMessageBuffer = xMessageBufferCreate( BUFFER_SIZE );
        configASSERT( xMessageBuffer );

        // Create Queue
        xQueue = xQueueCreate( BUFFER_SIZE/PAYLOAD_SIZE, PAYLOAD_SIZE );
        configASSERT( xQueue );

        xTaskCreate( &SendingTask, "SEND", 1024*2, NULL, 1, NULL );

        xTaskCreate( &MessageReceivingTask, "MESSAGE", 1024*2, NULL, 1, NULL );

        xTaskCreate( &QueueTask, "QUEUE", 1024*2, NULL, 1, NULL );

}

ビルドして実行すると、どちらも同じパフォーマンスでした。
I (310) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (320) MESSAGE: Start
I (0) SEND: Start
I (320) QUEUE: Start
I (360) SEND: xMessageBufferSend Finish
I (360) MESSAGE: DiffTick=[3]
I (390) SEND: xQueueSend Finish
I (390) QUEUE: DiffTick=[3]

続く....