ESP-IDF4.0入門 その2 プロジェクト構成

概要

前回は環境構築まで行いました。今回はプロジェクトの構成と、プログラムの構成を調べてみたいと思います。

hello_worldプロジェクト

HELLO_WORLD
│  CMakeLists.txt
│  Makefile
│  README.md
│
└─main
        CMakeLists.txt
        component.mk
        hello_world_main.c

環境構築で使ったhello_worldプロジェクトの構成を確認していきたいと思います。

CMakeLists.txt

# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(hello-world)

中身はcmakeの設定でしたが、他のサンプルみてもプロジェクト名以外は変わらないようでした。

Makefile

#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#

PROJECT_NAME := hello-world

include $(IDF_PATH)/make/project.mk

Makefileですが、ここではあまり設定をしないようです。

README.md

# Hello World Example

Starts a FreeRTOS task to print "Hello World"

See the README.md file in the upper level 'examples' directory for more information about examples.

ドキュメントファイルですので、なくても問題なさそうですね。

main\CMakeLists.txt

idf_component_register(SRCS "hello_world_main.c"
                    INCLUDE_DIRS "")

ここにある設定で、ソースファイルの列挙をしているみたいです。

main\component.mk

#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

このファイルも空でした。

main\hello_world_main.c

/* Hello World Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"


void app_main()
{
    printf("Hello world!\n");

    /* Print chip information */
    esp_chip_info_t chip_info;
    esp_chip_info(&chip_info);
    printf("This is ESP32 chip with %d CPU cores, WiFi%s%s, ",
            chip_info.cores,
            (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
            (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");

    printf("silicon revision %d, ", chip_info.revision);

    printf("%dMB %s flash\n", spi_flash_get_chip_size() / (1024 * 1024),
            (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");

    for (int i = 10; i >= 0; i--) {
        printf("Restarting in %d seconds...\n", i);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    printf("Restarting now.\n");
    fflush(stdout);
    esp_restart();
}

実際のプログラムがありました。

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"

このへんは、ESP-IDFでプログラムを組むときに必須になりそうなライブラリみたいですね。

void app_main()

ESP-IDFでは、ユーザープログラムはmain()関数ではなく、app_main()関数から開始されます。実際には別の場所にmain()関数があって、FreeRTOSやハードウエアの初期化をしてから、app_main()関数が呼ばれているのだと思います。

    printf("Hello world!\n");

どうやら、標準出力に文字列を出力することでSerial出力になるのですね。

    /* Print chip information */
    esp_chip_info_t chip_info;
    esp_chip_info(&chip_info);
    printf("This is ESP32 chip with %d CPU cores, WiFi%s%s, ",
            chip_info.cores,
            (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
            (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");

チップの情報を取得していますね。コアの数とBTがBluetooth Classicで、BLEがBluetooth LEのことかな?

該当関数の中身を見ると、BTとBLEは常に同時に設定されているので、どっちかしか使えないってことないないみたいです。

    printf("silicon revision %d, ", chip_info.revision);

シリコンのバージョンを表示していました。手元のM5StickCは1と表示されます。

    printf("%dMB %s flash\n", spi_flash_get_chip_size() / (1024 * 1024),
            (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");

フラッシュのサイズと、種類を表示します。M5StickCは内蔵は4MBなのですが、menuconfigを設定変更していないので、プロジェクトの設定上2MBと表示されます。種類はembeddedで内蔵と出ました。

    for (int i = 10; i >= 0; i--) {
        printf("Restarting in %d seconds...\n", i);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    printf("Restarting now.\n");

10秒間カウントしています。Arduino Coreとは違い、FreeRTOSのvTaskDelay()関数を直接呼び出しています。portTICK_PERIOD_MSは10でしたのでFreeRTOSの標準値ですね。

Arduino Coreは1だったので、ミリ秒のまま渡しても大丈夫でしたが、ESP-IDFの場合にはvTaskDelay(1000 / portTICK_PERIOD_MS)などのようにミリ秒からTick数に必ず変換してから呼び出す必要がありそうです。

    fflush(stdout);

標準出力をフラッシュして、バッファに残っている文字をすべて出力させています。この関数を呼び出さないと、シリアルに出力する前に次の再起動が動いてしまうことがあります。

    esp_restart();

この関数が本体の再起動の関数のようです。細かい関数は今後調べていきたいと思います。

出力例

Hello world!
This is ESP32 chip with 2 CPU cores, WiFi/BT/BLE, silicon revision 1, 2MB embedded flash
Restarting in 10 seconds...
Restarting in 9 seconds...
Restarting in 8 seconds...
Restarting in 7 seconds...
Restarting in 6 seconds...
Restarting in 5 seconds...
Restarting in 4 seconds...
Restarting in 3 seconds...
Restarting in 2 seconds...
Restarting in 1 seconds...
Restarting in 0 seconds...
Restarting now.

idf.py monitorで出力を確認した例です。M5StickCを利用しているので、本来フラッシュの容量は4MBのはずです。変更してみたいと思います。

フラッシュ容量の変更

idf.py menuconfig

menuconfigを呼び出して変更してみます。

Serial flasher configを選択。

Flash sizeを選択。

4MBを選択。

Saveを選択。

保存確認をOK。

保存が完了しました。このままメニューをすべて完了させます。

sdkconfigとsdkconfig.oldの比較

# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"

もともとは2MBに設定されていたのが、4MBに変更されていました。このファイルに各種設定が書き込まれているようでした。

再度ビルドと実行

idf.py build
idf.py flash
idf.py monitor

ビルドと転送してから、シリアル出力を開きます。

Hello world!
This is ESP32 chip with 2 CPU cores, WiFi/BT/BLE, silicon revision 1, 4MB embedded flash
Restarting in 10 seconds...
Restarting in 9 seconds...
Restarting in 8 seconds...
Restarting in 7 seconds...
Restarting in 6 seconds...
Restarting in 5 seconds...
Restarting in 4 seconds...
Restarting in 3 seconds...
Restarting in 2 seconds...
Restarting in 1 seconds...
Restarting in 0 seconds...
Restarting now.

フラッシュが4MBに変更されていますね。

blinkプロジェクト

xcopy /e /i %IDF_PATH%\examples\get-started\blink blink

get-startedにはもう一つblinkというサンプルがありましたので、こちらも動かしてみたいと思います。

BLINK
│  CMakeLists.txt
│  example_test.py
│  Makefile
│  README.md
│  sdkconfig.defaults
│
└─main
        blink.c
        CMakeLists.txt
        component.mk
        Kconfig.projbuild

ファイル構成がちょっとだけ違っています。

example_test.py

なにやらテスト用のプログラムがありました。調べてみたところ、サンプルプログラムのリリースのためのテストであり、通常はあまり利用しないもののようです。

sdkconfig.defaults

デフォルトファイルがありましたが、中身は実質空でした。

Kconfig.projbuild

menu "Example Configuration"

    config BLINK_GPIO
        int "Blink GPIO number"
        range 0 34
        default 5
        help
            GPIO number (IOxx) to blink on and off.

            Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to blink.

            GPIOs 35-39 are input-only so cannot be used as outputs.

endmenu

中身を見る限り、メニューで、ブリンクするLEDのピンを指定する設定みたいです。

menuconfigしてみる

Example Configurationが追加されていました。

M5StickCの内蔵LEDであるGPIO10に変更後にSaveして、終了します。

main\blink.c

/* Blink Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "sdkconfig.h"

/* Can use project configuration menu (idf.py menuconfig) to choose the GPIO to blink,
   or you can edit the following line and set a number here.
*/
#define BLINK_GPIO CONFIG_BLINK_GPIO

void app_main()
{
    /* Configure the IOMUX register for pad BLINK_GPIO (some pads are
       muxed to GPIO on reset already, but some default to other
       functions and need to be switched to GPIO. Consult the
       Technical Reference for a list of pads and their default
       functions.)
    */
    gpio_pad_select_gpio(BLINK_GPIO);
    /* Set the GPIO as a push/pull output */
    gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
    while(1) {
        /* Blink off (output low) */
	printf("Turning off the LED\n");
        gpio_set_level(BLINK_GPIO, 0);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
        /* Blink on (output high) */
	printf("Turning on the LED\n");
        gpio_set_level(BLINK_GPIO, 1);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

上記がプログラムです。

/* Can use project configuration menu (idf.py menuconfig) to choose the GPIO to blink,
   or you can edit the following line and set a number here.
*/
#define BLINK_GPIO CONFIG_BLINK_GPIO

このCONFIG_BLINK_GPIOが、menuconfigで設定された値が入っている変数のようです。プログラムの中ではBLINK_GPIOを利用していました。

    /* Configure the IOMUX register for pad BLINK_GPIO (some pads are
       muxed to GPIO on reset already, but some default to other
       functions and need to be switched to GPIO. Consult the
       Technical Reference for a list of pads and their default
       functions.)
    */
    gpio_pad_select_gpio(BLINK_GPIO);
    /* Set the GPIO as a push/pull output */
    gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);

Arduino CoreのpinMode()関数相当の処理のようです。OUTPUTに設定しています。

M5StickCの内蔵LEDはプルアップされているので、本当はオープンドレインの方が適切ですが、今回はこのまま行きます。

    while(1) {
        /* Blink off (output low) */
	printf("Turning off the LED\n");
        gpio_set_level(BLINK_GPIO, 0);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
        /* Blink on (output high) */
	printf("Turning on the LED\n");
        gpio_set_level(BLINK_GPIO, 1);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }

無限ループして、1秒ずつLEDの点灯と消灯を繰り返しています。gpio_set_level()でHIGHとLOWを出力していますね。

また、M5StickCの内蔵LEDはプルアップされていますので、実際には0のLOWレベルのときに点灯しているはずです。

まとめ

なんとなくプロジェクトの構成がわかってきました。個別の関数などは今後調べていきたいと思います。

続編

コメント