PlatformIOでESP32を開発する

2つのFramework


PlatformIOは様々なボードをサポートしていますが、ESP32もサポートしています。
今回、LinuxマシンにPlatformIO Coreをインストールして、ESP32のファームを開発してみました。
PlatformIO Coreのインストールはこちらで 紹介しています。



始めにArduino Frameworkを使ったESP32の開発を紹介します。
ボードの選択でESP32のボードを選ぶときに、オプションを指定しないとArduino Frameworkとなり、
Arduino Framework用のiniファイルが作られます。
$ pio init -b esp32dev

$ cat platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino

ソースはArduinoIDEと同じC++の言語仕様で、setup関数とloop関数の2つの関数が必要になりますが、
ビルド時にこ のメイン関数が自動的にリンクされ、esp-idfのプロジェクトとしてビルドされます。
setup関数はloopTaskの最初に一度だけ実行され、メインループの中でloop関数が実行されます。

Arduino Frameworkでも、FreeRTOSのAPIを使うことができます。
以下のコードは、2つのタスクの間でQueueによるデータの受け渡しを行うサンプルです。
Arduino Frameworkを使うと、loop関数が動いてしまい、以下の様に何もしないloop関数でも、無駄なタスクスイッチが発生します。
setup関数の最後で自分自身をDeleteすることで、loop関数は実行されません。
#include "freertos/FreeRTOS.h"
#include "Arduino.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 )

void Task1(void *pvParameters) {
    TickType_t nowTick;
    UBaseType_t prio = uxTaskPriorityGet(NULL);
    nowTick = xTaskGetTickCount();
    printf("[%s:%d] Start prio=%d\n",pcTaskGetTaskName(NULL),nowTick,prio);

    for(uint32_t count=0;count<10;count++) {
      xQueueSend(xQueue, &count, 0);
      nowTick = xTaskGetTickCount();
      printf("[%s:%d] xQueueSend\n",pcTaskGetTaskName(NULL),nowTick);
    }

    while(1) {
      vTaskDelay(10 / portTICK_PERIOD_MS);
    }
}

void Task2(void *pvParameters) {
    TickType_t nowTick;
    UBaseType_t prio = uxTaskPriorityGet(NULL);
    uint32_t count;
    nowTick = xTaskGetTickCount();
    printf("[%s:%d] Start prio=%d\n",pcTaskGetTaskName(NULL),nowTick,prio);

    while(1) {
      if(xQueueReceive(xQueue, &count, portMAX_DELAY)) {
        nowTick = xTaskGetTickCount();
        printf("[%s:%d] xQueueReceive count=%u\n",pcTaskGetTaskName(NULL),nowTick,count);
      }
    }
}

void setup()
{
    TickType_t nowTick;
    vTaskDelay(2000/portTICK_PERIOD_MS);
    UBaseType_t prio = uxTaskPriorityGet(NULL);
    nowTick = xTaskGetTickCount();
    printf("[%s:%d] Start prio=%d\n",pcTaskGetTaskName(NULL),nowTick,prio);
    printf("[%s:%d] portTICK_PERIOD_MS=%d\n",pcTaskGetTaskName(NULL),nowTick,portTICK_PERIOD_MS);

    /* 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", 4096, NULL, 1, NULL);
    xTaskCreate(Task2, "Task2", 4096, NULL, 1, NULL);

    /* stop main task */
    nowTick = xTaskGetTickCount();
    printf("[%s:%d] End   prio=%d\n",pcTaskGetTaskName(NULL),nowTick,prio);
    vTaskDelete( NULL );

}

void loop() { // Never run
    printf("[%s:%d] loop\n",pcTaskGetTaskName(NULL),xTaskGetTickCount());
}

これをビルドして実行すると以下の表示となります。
$ pio run -e esp32dev -t upload && pio device monitor -b 115200
Processing esp32dev (platform: espressif32; board: esp32dev; framework: arduino)

(中略)

--- Miniterm on /dev/ttyUSB0  115200,8,N,1 ---
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
[loopTask:2038] Start prio=1
[loopTask:2038] portTICK_PERIOD_MS=1
[loopTask:2039] End   prio=1
[Task2:2039] Start prio=1
[Task1:2039] Start prio=1
[Task2:2041] xQueueReceive count=0
[Task1:2041] xQueueSend
[Task2:2046] xQueueReceive count=1
[Task1:2046] xQueueSend
[Task2:2051] xQueueReceive count=2
[Task1:2052] xQueueSend
[Task1:2057] xQueueSend
[Task2:2057] xQueueReceive count=3
[Task1:2059] xQueueSend
[Task2:2062] xQueueReceive count=4
[Task1:2064] xQueueSend
[Task2:2068] xQueueReceive count=5
[Task1:2070] xQueueSend
[Task2:2073] xQueueReceive count=6
[Task1:2075] xQueueSend
[Task2:2078] xQueueReceive count=7
[Task1:2080] xQueueSend
[Task2:2084] xQueueReceive count=8
[Task1:2086] xQueueSend
[Task2:2089] xQueueReceive count=9



ボードの選択でESP32のボードを選ぶときに、オプションとして[-O "framework=espidf"]を指定するとESP-IDF Frameworkとなり、
ESP-IDF Framework用のiniファイルが作られます。
$ pio init -b esp32dev -O "framework=espidf"

$ cat platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = espidf

ESP-IDF FrameworkではC++ではなくCの言語仕様でソースを作成します。
上で紹介したArduino Frameworkのコードを、ESP-IDF Frameworkで書き直すと、以下のコードとなります。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.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 )

void Task1(void *pvParameters) {
  TickType_t nowTick;
  uint32_t count;
  nowTick = xTaskGetTickCount();
  printf("[Task1:%d] Start coreID=%d\n",nowTick,xPortGetCoreID());
  while(1) {
    if(xQueueReceive(xQueue, &count, portMAX_DELAY)) {
      nowTick = xTaskGetTickCount();
      printf("[Task1:%d] xQueueReceive count=%u coreID=%d\n",nowTick,count,xPortGetCoreID());
    }
  }
}

void Task2(void *pvParameters) {
  TickType_t nowTick;
  static uint32_t count = 0;
  nowTick = xTaskGetTickCount();
  printf("[Task2:%d] Start coreID=%d\n",nowTick,xPortGetCoreID());
  while(1) {
    nowTick = xTaskGetTickCount();
    xQueueSend(xQueue, &count, 0);
    printf("[task2:%d] xQueueSend coreID=%d\n",nowTick,xPortGetCoreID());
    count++;
    vTaskDelay(1000/portTICK_PERIOD_MS);
  }
}


void app_main(void)
{
  TickType_t nowTick;
  vTaskDelay(2000/portTICK_PERIOD_MS);
  nowTick = xTaskGetTickCount();
  printf("[app_main:%d] Start\n",nowTick);
  //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 );

  xTaskCreatePinnedToCore(Task1,"Task", 4096, NULL, 2, NULL, 0);
  xTaskCreatePinnedToCore(Task2,"Task", 4096, NULL, 2, NULL, 1);

}

ビルドして実行すると、Arduino Frameworkと同じ表示になります。
$ pio run -e esp32dev -t upload && pio device monitor -b 115200
Processing esp32dev (platform: espressif32; board: esp32dev; framework: espidf)

(中略)

--- Miniterm on /dev/ttyUSB0  115200,8,N,1 ---
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
[app_main:200] Start
[Task1:200] Start coreID=0
[Task2:200] Start coreID=1
[task2:200] xQueueSend coreID=1
[Task1:200] xQueueReceive count=0 coreID=0
[task2:300] xQueueSend coreID=1
[Task1:300] xQueueReceive count=1 coreID=0
[task2:400] xQueueSend coreID=1
[Task1:400] xQueueReceive count=2 coreID=0
[task2:500] xQueueSend coreID=1
[Task1:500] xQueueReceive count=3 coreID=0
[task2:600] xQueueSend coreID=1
[Task1:600] xQueueReceive count=4 coreID=0
[task2:700] xQueueSend coreID=1
[Task1:700] xQueueReceive count=5 coreID=0
[task2:800] xQueueSend coreID=1
[Task1:800] xQueueReceive count=6 coreID=0
[task2:900] xQueueSend coreID=1
[Task1:900] xQueueReceive count=7 coreID=0
[task2:1000] xQueueSend coreID=1
[Task1:1000] xQueueReceive count=8 coreID=0

ESP-IDF Frameworkでは、menuconfigを使ってビルドオプションを変更することができます。
$ pio run -t menuconfig

デフォルトのままだと、ロギング関数のカラーコードが文字化けします。
?[0;32mI (5320) example: Example configured to blink GPIO LED. GPIO=22?[0m
?[0;32mI (5320) gpio: GPIO[22]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 ?[0m
?[0;32mI (5320) example: Turning the LED OFF!?[0m
?[0;32mI (6320) example: Turning the LED ON!?[0m
?[0;32mI (7320) example: Turning the LED OFF!?[0m
?[0;32mI (8320) example: Turning the LED ON!?[0m
?[0;32mI (9320) example: Turning the LED OFF!?[0m
?[0;32mI (10320) example: Turning the LED ON!?[0m
?[0;32mI (11320) example: Turning the LED OFF!?[0m
?[0;32mI (12320) example: Turning the LED ON!?[0m
?[0;32mI (13320) example: Turning the LED OFF!?[0m

Component config --> Log output --> Use ANSI terminal colors in log output を無効にすることで、文字化けは無くなります。
I (5278) example: Example configured to blink GPIO LED. GPIO=22
I (5278) gpio: GPIO[22]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (5278) example: Turning the LED OFF!
I (6278) example: Turning the LED ON!
I (7278) example: Turning the LED OFF!
I (8278) example: Turning the LED ON!
I (9278) example: Turning the LED OFF!
I (10278) example: Turning the LED ON!
I (11278) example: Turning the LED OFF!
I (12278) example: Turning the LED ON!
I (13278) example: Turning the LED OFF!

ESP-IDF Frameworkでは、.pio/buildの下にオブジェクトが作られます。
以下のコマンドでオブジェクトを削除することができます。make cleanと同じ動作です。
$ pio run -t clean

PatformIOが使用するコンポーネントはEspressIFが提供している公式のコンポーネントではなく、PlatformIOが独自に開 発しているものなので、
ESP-IDFとは挙動が完全に一致しない場合が有ります。
PlatformIOがベースとして使用するESP-IDFはかなり古いバージョンです。
ESP-IDF V5.3.1リリース時点で、PlatformIOは5.1.2のESP-IDFをベースに開発されています。
万一、コンポーネントに不具合が有っても、EspressIFのサポートは受けられません。
I (5275) MAIN: IDF_VER is 5.1.2

続く....