ESP32 GPIOを操作する

ESP32 GPIOを操作する

今回はGPIOを操作してみます。

ESP-IDFにはサンプルプログラムが提供されています。
まずはそれらを触ってみるのが理解への近道だと思います。

この記事は JTAG でデバッグすることを前提にして書いています。
環境構築については こちら をご覧になってください。

投稿時の開発環境を記しておきます。

PC:
Windows10 OS

開発ボード :
ESP32-DevKitCーVE
(Soc : ESP32-D0WD-V3)

デバッガー(H/W):
FT2232D

デバッガー (S/W) :
Visual Studio Code + PlatformIO + ESP-IDF Framework

APIリファレンスを見てみる

ESPRESSIFのサイトの APIリファレンス を覗いてみましょう。

いろいろな項目があるので、ひと通り眺めてみると良いかも知れません。

Prripherals の下に GPIO(including RTC low power I/O) があるのでクリックしてみましょう。

ここにも目を通しておきたいですね。

Application Example にサンプルプログラムがあります。

おそらくここには最新の情報が置かれていると思いますが、PlatformIOをインストールした以下のフォルダにあるものと同じです。

C:\Users\xxxxx\.platformio\packages\framework-espidf\examples

(xxxxxは皆さまのユーザー名)

コピペ(コピー&ペースト)するのであれば Cドライブのものを使えば良いでしょう。

今回は以下のフォルダにある gpio_example_main.c を参照します。

C:\Users\xxxxx\.platformio\packages\framework-espidf\examples\peripherals\gpio\generic_gpio\main

GPIOの番号

ESP32(Soc : ESP32-D0WD-V3)におけるGPIOの番号は0から始まり39まであります。
ではGPIOは40本使えるのかというと、そうではありません。

途中で抜けている番号があり、20, 24, 28, 29, 30, 31 が存在しません。

ということでGPIOの数は34です。

それから GPIO34~39 は入力専用ピンになっているので、こちらも気をつけてください。

詳しくは こちら を見てください。

更に ESP32-DevKitCーVE の場合には PSRAM が実装されていて、その制御のために GPIO16 , 17 を使います。

従って私たちユーザーは GPIO 16, 17 を他の用途に使うことはできませんのでご注意ください。

PlatformIOの新しいプロジェクトを作成する

VSCodeのEXPLORERで前回使ったESP32E-Hello_worldが存在すれば、File – Close Folder で閉じておきます。

VSCodeでPlatformIOをOpenしProject Wizard で

Name: ESP32E-GPIOinOut
Board: Espressif ESP32 Dev Module
Framework: Espressif IoT Development Framework
として、Locationのチェックをはずして、保存するフォルダを選択し Finish を押します。

platformio.ini に以下の3行を追加して、 Ctrl + s で保存しておきます。

COM[4]の4の部分はデバイスマネージャーのポート(COMとLPT)で Silicon Labs CP210x から始まるCOMの番号を記述します。

debug_tool = minimodule
upload_port = COM[4]
monitor_speed = 115200

配線を追加しておく

GPIO4 と GPIO18 をつないでおきます。

コーディングする

サンプルのgpio_example_main.cを少し編集して以下のものを動かしてみました。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"

#define GPIO_OUTPUT_IO_0    18
#define GPIO_OUTPUT_PIN_SEL  (1ULL<<GPIO_OUTPUT_IO_0)
#define GPIO_INPUT_IO_0     4
#define GPIO_INPUT_PIN_SEL  (1ULL<<GPIO_INPUT_IO_0)
#define ESP_INTR_FLAG_DEFAULT 0

static xQueueHandle gpio_evt_queue = NULL;

static void IRAM_ATTR gpio_isr_handler(void* arg)   // (12)
{
    uint32_t gpio_num = (uint32_t) arg;
    xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL); // (13)
}

static void gpio_task_example(void* arg)    // (14)
{
    uint32_t io_num;
    for(;;) {
        if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) { // (15)
            printf("GPIO[%d] intr, val: %d\n", io_num, gpio_get_level(io_num)); // (16)
        }
    }
}

void app_main(void)
{
    gpio_config_t io_conf;  // (1)
    io_conf.intr_type = GPIO_INTR_DISABLE;
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
    io_conf.pull_down_en = 0;
    io_conf.pull_up_en = 0;
    gpio_config(&io_conf);  // (2)

    io_conf.intr_type = GPIO_INTR_POSEDGE;
    io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL;
    io_conf.mode = GPIO_MODE_INPUT;
    io_conf.pull_up_en = 1;
    gpio_config(&io_conf);  // (2)

    //change gpio intrrupt type for one pin
    gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_POSEDGE); // (3)

    //create a queue to handle gpio event from isr
    gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));    // (4)
    //start gpio task
    xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);  // (5)

    //install gpio isr service
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);    // (6)
    //hook isr handler for specific gpio pin
    gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);   // (7)

    printf("Minimum free heap size: %d bytes\n", esp_get_minimum_free_heap_size()); // (8)

    int cnt = 0;
    while(1) {
        printf("cnt: %d\n", cnt++); // (9)
        vTaskDelay(1000 / portTICK_RATE_MS);    // (10)
        gpio_set_level(GPIO_OUTPUT_IO_0, cnt % 2);  // (11)
    }
}

デバッグしてみる

メニューから Run – Start Debugging するか、F5キーを押してデバッグを開始します。

(13)のところにブレークポイントを貼って、定期的に(13)に来れば成功です。

プログラムの概要

GPIO APIの概要は こちら をご覧になってください。

while(1)内で変数cntをカウントアップし、1秒間待ち、cntを2で割った余りをGPIO18に出力します。
その結果GPIO18の出力を Low / High (0 / 1) することになります。
GPIO18をGPIO4につないでいるので、この信号が0から1になると割り込みハンドラ(12)に飛んできます。
(13)でキューを送るので、(14)でキューを受け取ってGPIOの情報を出力します。

コード中に()をつけた部分の概略説明です。

(1)GPIOの設定を行うための構造体

GPIO初期化用の構造体は以下の構成になっています。

typedef struct {
uint64_t pin_bit_mask;
gpio_mode_t mode;
gpio_pullup_t pull_up_en;
gpio_pulldown_t pull_down_en;
gpio_int_type_t intr_type;
} gpio_config_t;

pin_bit_mask : 設定するピンのビットに1を立てる
mode : 入力にするか出力にするか 等
pull_up_en : プルアップを有効にするか否か
pull_down_en : プルダウンを有効にするか否か
intr_type : 割り込みタイプの設定

(2)GPIOの設定を行うAPI
 1回目のgpio_config()呼び出しでは、GPIO18を出力に、2回目ではGPIO4を入力に設定しています。
 同じ設定であれば複数のピンをio_conf.pin_bit_maskに指定することができます。
(3)割り込みトリガーの設定
 GPIOの番号とトリガーを指定します。
 トリガーは以下のいずれかを指定します。

typedef enum {
    GPIO_INTR_DISABLE = 0,     /*!< Disable GPIO interrupt                             */
    GPIO_INTR_POSEDGE = 1,     /*!< GPIO interrupt type : rising edge                  */
    GPIO_INTR_NEGEDGE = 2,     /*!< GPIO interrupt type : falling edge                 */
    GPIO_INTR_ANYEDGE = 3,     /*!< GPIO interrupt type : both rising and falling edge */
    GPIO_INTR_LOW_LEVEL = 4,   /*!< GPIO interrupt type : input low level trigger      */
    GPIO_INTR_HIGH_LEVEL = 5,  /*!< GPIO interrupt type : input high level trigger     */
    GPIO_INTR_MAX,
} gpio_int_type_t;

(4)キューをつくる
(5)タスク(スレッド)をつくる
(6)GPIOの割り込みハンドラサービスをインストールする
 引数には、ひとまず0を指定すれば良さそうです。
(7)指定したGPIOに対する割り込みハンドラを追加する
(8)ヒープザイズをprintf()出力する(GPIOとは無関係です)
(9)cntの値をprintf()出力する
(10)1秒間待つ
(11)GPIO18に 0 または 1 をセットする
(12)GPIO4の割り込みハンドラ
 立ち上がりエッジを検出すると、ここに来る
 (7)で指定した第3引数を、引数で受け取ることができるようです。
(13)キューを送る
(14) (5)でつくられたタスク(スレッド)
(15)キューを受け取る
(16)GPIOの情報をprintf()出力する

皆さんの環境では、うまく動作しましたか? (^_^)

github esp32e-GPIOinOut にプロジェクトをアップしましたので、よろしければご覧になってください。

( 環境: Visual Studio Code + PlatformIO + ESP-IDF Framework )

お疲れさまでした。

GPIOカテゴリの最新記事