モダンOSのお砂場(27) FreeRTOS、ESP-IDFで定番のLチカをタスクで実装

Joseph Halfmoon

ESP-EYE使いESP-IDF環境でLチカをプログラムするのを「敷居が高い」などといってMicroPythonに逃げてしまいました。そのままでは進歩がないので、手足があって分かり易いESP32-DevkitCにもどってESP-IDF環境でLチカしてみました。素のままではモダンOSにならないので、FreeRTOS機能を使う縛りでです。

ESP-IDF環境の「敷居が高い」とは言っても、「トホホな疑問第42回」でビルド環境(Windows上)をこさえているので、手順としては以下の感じでビルドして、ターゲットボード上で実行することができます。

  1. ビルド環境を作成するときにつかったHello Worldのプロジェクトをコピー(必要なCMakeLists.txtとかが既に存在しているので楽)
  2. CMakeLists.txt(階層あり複数)を修正
  3. ソースコードを書く
  4. ターゲット・ボードをUSB接続して仮想COMポートの番号を確かめる
  5. idp.py set-target esp32
  6. idf.py menuconfig
  7. idf.py build
  8. idf.py -p 仮想COMポート番号 flash

ちょっと引っ掛かるのが、トホホな疑問第42回のビルドで落ちた問題ですが、menuconfigのところでちょっと変更するだけなので慣れればどうということもありません。

また、buildの初回は、大量のファイルをコンパイルするのでそこそこ時間がかかります。そして、そのあげくに自分の書いたソースでエラーなど出てフェイルで終わっているとちとガッカリします。しかし、ビルドツールのお陰でエラー修正後のビルドはほとんど時間もかからずに終わります。

問題はESP-IDF環境用のソースコードを書くところですね。

今回学ぶFreeRTOS機能は vTaskDelay() と xTaskCreate()

さて今回実験に使用したLチカのコード全文を以下に掲げます。GPIO23番に接続されている赤色LEDを点滅させるだけのコードです。ただし、

  1. 点滅は xTaskCreate() で作ったLチカタスクの中で行う
  2. メインルーチンは標準出力にダラダラと出力をつづける
  3. LチカタスクもメインルーチンもヒマをつぶすのにvTaskDelay()を使う

です。

/* ESP32 Blink w/FreeRTOS
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
#include "driver/gpio.h"

#define REDLED        (23)
#define GPIO_OUTPUT_PIN_SEL ((1ULL<<REDLED))
#define STACK_SIZE    (2048)

static void blink_task(void * pvParameters) {
    const TickType_t xDelay = 500 / portTICK_PERIOD_MS; // 500ms 
    bool flag = false;
    while (1) {
        if (flag) {
            flag = false;
            gpio_set_level(REDLED, 0);
        } else {
            flag = true;
            gpio_set_level(REDLED, 1);
        }
        vTaskDelay(xDelay);
    }
}

void setupPINS(void) {
    gpio_config_t io_conf;
    io_conf.intr_type = GPIO_INTR_DISABLE; //Disable Interrupt
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL; //REDLED
    io_conf.pull_down_en = 0; //NO pull down
    io_conf.pull_up_en = 0; //NO pull up
    gpio_config(&io_conf);
}

void app_main(void) {
    static uint8_t pvParameter;
    TaskHandle_t xHandle = NULL;
    UBaseType_t uxPriority = 6;

    printf("ESP32 Blink w/FreeRTOS.\n");
    setupPINS();

    xTaskCreate(blink_task, "BLINK", STACK_SIZE, &pvParameter, uxPriority, &xHandle);
    int loopCounter = 0;
    while(1) {
        printf("Loop: %d\n", loopCounter++);
        vTaskDelay(10000 / portTICK_RATE_MS);
    }
}

参照すべき、FreeRTOSのドキュメントへのリンクをまず貼っておきます。

freeRTOS vTaskDelay

freeRTOS xTaskCreate

前々からちょっと気になっていたのが、vTaskDelayに与える引数です。500m秒の「待ち」といったら、以下のように記述するのが定石となっています。

500 / portTICK_PERIOD_MS

それぞれのターゲットボード上の実装を反映した値がportTICK_PERIOD_MSに設定されているみたいですが ミリ秒 をこれで割るのが個人的に引っかかっていました。今回改めて調べてみると以下のように定義されていました。定義の1文を引用させていただきます。

#define portTICK_PERIOD_MS ( ( TickType_t ) 1000 / configTICK_RATE_HZ )

タイマチックの周波数から逆算されている数字なのね。

gpioの操作

「敷居が高い」と感じた理由のほとんどが、ESP-IDF環境でのgpioの操作方法を知らなかったからです。ESP-IDFはESP32固有機能を操作しやすいようにできているので、操作が特有になるのは致し方ないです。周辺機能毎に調べていくしかありませぬ。

まずはgpioですが、gpioについては、ESPRESSIFの以下のページにAPIの説明があります。

GPIO & RTC GPIO

なお使用するソースでは、ヘッダのインクルードが必須でした。

#include "driver/gpio.h"

初期設定にちょっとクセがありますが、慣れてしまえば問題ない感じです。上記ソースの setupPINS()部分です。一端設定すれば

gpio_set_level( 端子番号, 出力レベル);

で端子出力ができます。

動作結果

冒頭のアイキャッチ画像に今回操作したLED(右下のブレッドボード上のLED)が点灯しているところを掲げました。定番のLチカ、成功。

一方、仮想端末の出力は以下のようです。ESP32は起動時に結構いろいろ情報を出力してから走り始めます。緑の表示は勝手に表示されてくるやつらの末尾付近です。標準出力にプログラムから出力している部分は白字です。Lチカとは無関係にちゃんとLoop回数を出力しています。

Result一応、メインとLチカの2つのタスクが並行して動作してますな。

折角、雛形が出来たので、

  1. FreeRTOSの機能を必ず使う
  2. ESP32のペリフェラル機能もESP-IDFのスタイルで使う

という縛りで、しばらくお砂場で戯れたいと思います。

モダンOSのお砂場(26) Zephyr RTOS、micro:bit v2でHello! へ戻る