ESP32学習　PlatformIOでESP IDFを使ってみる。GPIOを操作したが、ROMサイズ変更の設定が反映されなくて、大変だった話。


![IMG_4427.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/cdf98413-c855-d208-ac5b-89059e1e9bec.jpeg)
※スイッチの基板は、FETを３つ使って、ヒステリシスを作ったチャタリング対策回路になっています。

::: note warn
 **学習中なので、この方法で不具合があった場合、変更する可能性があります。**
:::

:::note
**ESP-IDF**で開発学習を始めました。**GPIOの入出力**を制御することから始めました。

開発環境：vsCode+platformIO
board:esp32dev
platform:ESP-IDF
:::



::: note warn
**スイッチ割込みでLEDを点滅させるようにしました。そこまでは、順調でした。ビルドの結果を見たら、PSRAMが有効になっていません。ROMも1MBまでしか使えません。PSRAMを有効にして、ROMのサイズも大きくしようとしたら、見事にハマりました。**

**ROMサイズの変更で、相当手こずりながら作業したので、これでいいのか、まだよくわかっていません。**
FlashROMサイズは、boardの設定がリミッターになっている感じです。

・platformioのnewPojectで、新しくプロジェクトを立ち上げます。その際に選択したboardが、Espressif ESP32 Dev Moudule。**このボードの初期設定は、ROMが4MB。RAMが300kB強。** そのままでは、WROVER-EのROM　8MB、RAM　8MBをフルに使用できません。 ボード設定(esp32dev.json)を変更したファイルを用意する必要がありました。

・menuConfigとパーティションテーブルを使用してビルドします。**ROMの使用量が4MBを超えるとエラーになります。** boardの設定4MBの範囲内ならOKです。

・最終的に、esp32dev.jsonファイルを書き換えることで、ビルドエラーを回避できるようになりました。詳細は、以下で説明していきます。

・**PSRAMの有効化は、platformio.iniまたは、menuConfigで設定。**

・オリジナルのboardファイルを書き換えたくないので、それを参考にしながら、**ESP32-WROVER-E用のmyboard.jsonを自作して、プロジェクト作成時に指定する方法をとっています。**

・**platformIOの掲示板でも、同様の質問がされていました。**
:::

https://community.platformio.org/t/esp32-flash-size-in-menuconfig-doesnt-work/13961

***
# ESP32-WROVER-E用に自作したboard.jsonファイル。
```myBoard.json
{
  "build": {
    "arduino":{
      "ldscript": "esp32_out.ld"
    },
    "core": "esp32",
    "extra_flags": "-DARDUINO_ESP32_DEV",
    "f_cpu": "240000000L",
    "f_flash": "40000000L",
    "flash_mode": "dio",
    "mcu": "esp32",
    "variant": "esp32"
  },
  "connectivity": [
    "wifi",
    "bluetooth",
    "ethernet",
    "can"
  ],
  "debug": {
    "openocd_board": "esp-wroom-32.cfg"
  },
  "frameworks": [
    "arduino",
    "espidf"
  ],
  "name": "my ESP32 dev", <=====ここを書き換えた。
  "upload": {
    "flash_size": "8MB",  <=====ここを書き換えた。
    "maximum_ram_size": 327680,
    "maximum_size": 8000000, <=====ここを書き換えた。
    "require_upload_port": true,
    "speed": 460800
  },
  "url": "https://en.wikipedia.org/wiki/ESP32",
  "vendor": "Espressif"
}
```

***


**<参考>GPIOの操作関連　Application example**にあるgitレポジトリが、分かりやすいです。

https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/gpio.html

**<参考>パーティションテーブル**

https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/partition-tables.html

https://qiita.com/yomori/items/743bc84c961bb921d8cb

https://kunsen.net/2018/07/30/post-756/


# 設定に必要なファイル
::: note

ESP-IDFで、プロジェクトをビルドしていきます。その際、必要になるファイルが4つあります。

**1.kconfig.projbuild**
**2.sdkconfig.esp32dev**
**3.partition table.csv**
**4.platformio.ini**



上記４つのファイルを必要に応じて、変更していきます。
:::

# １．kconfigファイル　
srcフォルダに`kconfig.projbuid`ファイルを置きます。このファイルで、GPIOのピン設定を行っています。

```Kconfig.projbuild
menu "Example Configuration"

    config GPIO_OUTPUT_0
        int "GPIO output pin 0"
        range 0 39
        default 32
        help
            GPIO pin number to be used as GPIO_OUTPUT_IO_0.

    config GPIO_OUTPUT_1
        int "GPIO output pin 1"
        range 0 39
        default 33
        help
            GPIO pin number to be used as GPIO_OUTPUT_IO_1.

    config GPIO_INPUT_0
        int "GPIO input pin 0"
        range 0 39
        default 34
        help
            GPIO pin number to be used as GPIO_INPUT_IO_0.

    config GPIO_INPUT_1
        int "GPIO input pin 1"
        range 0 39
        default 35
        help
            GPIO pin number to be used as GPIO_INPUT_IO_1.

endmenu
```

# ２．sdkcofigファイルとmenuConfig
platformIOで`Run Menuconfig`をクリックすると、esp-idfの開発設定画面が、ターミナルに表示されます。
![Run_menuConfig.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/ff3276d2-325b-81f0-677a-7bee1b2e1e95.png)

下の画面が、platformIO のターミナルに表示されます。
![menuConfig_FrameworkConfigration.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/7fa0a11e-6a2c-cd86-319f-3950730f1dd5.png)
ここで、各種設定を行います。kconfigファイルをsrcに置くと、
kconfigファイルの先頭に書いてある`Example Configration`が選択肢に追加されています。
kconfigファイルを編集せずに、menuConfigで再設定ができます。
各種の設定をここで行うと、変更内容が、sdkcofigファイルに反映されます。Serial flasher configでROMサイズをWROVER用に8MBに変更しました。ROMサイズを広げるには、パーティションテーブル作成が必要になります。

# ３．パーティションテーブルを作成
``` my_no_ota.csv
# Name,   Type, SubType, Offset,  Size, Flags
nvs,      data, nvs,     0x9000,  0x5000,
otadata,  data, ota,     0xe000,  0x2000,
app0,     app,  ota_0,   0x10000, 0x700000,
spiffs,   data, spiffs,  0x710000,0xE0000,
coredump, data, coredump,0x7F0000,0x10000,
```
WROVER-EのROMサイズを変更し、拡大させるため、パーティションテーブルをCSV形式で作成します。このファイルを、Pojectフォルダのルートの置きました。otaは"On the air"の略で、無線経由でROMを書き換えることを意味しています。SPIFFS は "SPI Flash File System "の略で、ROMにフォントなどを保存できる領域です。


# ４．platformIO　iniファイル

※boardが**esp32dev**だと、WROVER-EのROM,RAM領域が制限されます。
![platformIO_INIの内容.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/912d60c4-ad17-374c-ae0b-9e1b3a69ea13.png)

ESP32-WROVER-Eは、ROMが8MB、RAMは8MB　あります。それにあわせて、boardファイルのjsonフォーマットをみながら、iniファイルに設定を追加していきます。
boardは、esp32devになっています。このボード設定では、WROVER-Eのスペックをフルに使用することはできません。ワーニングとエラーがでます。project作成時に、オリジナルboard.jsonを読み込ませる方法が、現状のベストです。
PSRAMの有効化は、menuConfigまたは、platformio.iniで設定します。最初は双方で設定していました。

＜改変した自作ボードファイル＞
**my_esp32dev**（これで、ROM,RAMの容量を拡張できるようになりました。）
![platformIO_INIの内容2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3693934/692c31f8-71d7-d21c-c857-c367d03a59cf.png)

***
menuConfigでsdkConfigの内容を変更、iniファイルと、パーティションテーブルを作成したので、ビルドします。
```
RAM:   [          ]   0.2% (used 10932 bytes from 4536000 bytes)
Flash: [          ]   3.2% (used 234041 bytes from 7340032 bytes)
```
PSRAMが反映されて、4.5MBに、ROMは、7.3MBに拡がりました。

::: note warn
**Board：esp32dev.jsonを書き換えてたboarファイルを使用。** 最初に書いた通り、menuConfigとplatformio.iniの設定だけでは、ROMサイズの拡大ができませんでした。BoardはJSONフォーマットで書かれており、そこでのサイズは、4MBが最大と設定されています。menuConfigでWROVER用に、Flashサイズを８ＭＢに変更するとビルド時にwarningが表示されます。パーティションテーブルを作成し、それが４ＭＢを超えるとエラーになります。エラーを回避するため、boradJSONを変更しました。オリジナルを変更するよりは、**新規にmyboard.jsonをかいて、project作成時にそれを選択する方法をとっています。**
:::


# GPIO割込みのコード
```CPP:main.cpp
//**********************************//
//GPIO割込み
//**********************************//
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"

//自分で追加したヘッダー
//gpioレジスタの直打ち用。
#include "soc/gpio_reg.h"
//gpio_isr_func_tのサイズを求めるため。
#include "hal/gpio_types.h"

//OUTPUTピン　Kconfig.projbuildに詳細設定
#define GPIO_OUTPUT_IO_0    CONFIG_GPIO_OUTPUT_0
#define GPIO_OUTPUT_IO_1    CONFIG_GPIO_OUTPUT_1
#define GPIO_OUTPUT_PIN_SEL  ((1ULL<<GPIO_OUTPUT_IO_0) | (1ULL<<GPIO_OUTPUT_IO_1))
//INPUTピン   Kconfig.projbuildに詳細設定
#define GPIO_INPUT_IO_0     CONFIG_GPIO_INPUT_0
#define GPIO_INPUT_IO_1     CONFIG_GPIO_INPUT_1
#define GPIO_INPUT_PIN_SEL  ((1ULL<<GPIO_INPUT_IO_0) | (1ULL<<GPIO_INPUT_IO_1))
//GPIO_IN1_REGレジスタの値
#define GPIO_IN1_REG_VALUE  (*(volatile uint32_t*)GPIO_IN1_REG)
//GPIO割込み　RAMの指定値
#define ESP_INTR_FLAG_DEFAULT 0

#define HIGH 1
#define LOW  0

//gpio.cにあるgpio_isr_func_tのサイズを求める。
//hal/gpio_types.hを読み込む。
//gpio_isr_func_tはCファイル内の記述。
typedef struct {
    gpio_isr_t fn;   /*!< isr function */
    void *args;      /*!< isr function args */
} m_gpio_isr_func_t;

//GPIO個別処理割込み関数
static void IRAM_ATTR gpio_isr_handler(void* arg)
{
    static bool val1=true,val2=true;
    uint32_t gpio_num = (uint32_t)arg;

    if(gpio_num==GPIO_INPUT_IO_0)
    {
        gpio_set_level(GPIO_OUTPUT_IO_0,val1);
        val1 = !val1;
    }
    
    if(gpio_num==GPIO_INPUT_IO_1)
    {
        gpio_set_level(GPIO_OUTPUT_IO_1,val2);
        val2= !val2;
    }
}

void app_main() 
{
    //Output mode pin----------------------------
    //zero-initialize the config structure.
    gpio_config_t io_conf = {};
    //disable interrupt
    io_conf.intr_type = GPIO_INTR_DISABLE;
    //set as output mode
    io_conf.mode = GPIO_MODE_OUTPUT;
    //bit mask of the pins that you want to set,e.g.GPIO32/33
    io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
    //disable pull-down mode
    io_conf.pull_down_en = 0;
    //disable pull-up mode
    io_conf.pull_up_en = 0;
    //configure GPIO with the given settings
    gpio_config(&io_conf);

    //Input mode pin------------------------------
    io_conf.intr_type = GPIO_INTR_POSEDGE; //GPIO割込みエッジ指定
    io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL; 
    io_conf.mode = GPIO_MODE_INPUT;
    io_conf.pull_down_en = 1;
    io_conf.pull_up_en = 0;
    gpio_config(&io_conf);
    
    //gpio_isr_func_tについて
    //GPIO0..39用　割込み関数ポインタ　と　デフォルト引数を格納する。
    uint32_t size = sizeof(m_gpio_isr_func_t);
    printf("gpio_isr_func_t size:%"PRIu32"bytes\n",size);
    printf("install size:%"PRIu32"bytes\n",size*GPIO_NUM_MAX);

    //GPIO割込みを設定。
    uint32_t size0= esp_get_minimum_free_heap_size();
    printf("Minimum free heap size: %"PRIu32" bytes\n", size0);
    //install gpio isr service
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
    //hook isr handler for specific gpio pin
    gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
    //hook isr handler for specific gpio pin
    gpio_isr_handler_add(GPIO_INPUT_IO_1, gpio_isr_handler, (void*) GPIO_INPUT_IO_1);
    uint32_t size1 = esp_get_minimum_free_heap_size();
    printf("Minimum free heap size: %"PRIu32" bytes\n", size1);
    printf("Minumum free heap consumption size:%"PRIu32"bytes\n", size0-size1);
    
    
    int cnt = 0;
    //portTICK_PERIOD_MS デフォルトは100なので、menuConfigで1000で再設定。
    printf("portTICK_PERIOD_MS:%"PRIu32"\n",portTICK_PERIOD_MS);
    while(1)
    {
        printf("cnt: %d\n",cnt++);
        
        vTaskDelay(1000/ portTICK_PERIOD_MS);
    }

}
```
:::note warn
学習中なので、この方法で不具合があった場合、変更する可能性があります。
:::