今回は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 )
お疲れさまでした。