ESP32を軽くいじってみたらめちゃくちゃすごかった件

#正直なめてました！ESP３２すごい！！！

Jetson nanoを買いに行って、まぁ安いしArduinoでも動くってことだし。。。と何気なしにテスト用として買ったESP32開発ボード。
こんなにすごいとは思わなかったです。。。

Arduinoより安いし、WiFi,Bluetoothついてるし、ちょっとしたセンサーも内臓されてるし、もうESP32でいいんじゃないか？と思う。

何がすごいって、割り込みを3個使って超正確なストップウォッチを作ったのですが、そのついでにマルチタスク性能を試そうと書いたLチカコードの両方が遅れずに処理できるってこと！
Lチカは1秒間隔で回してて、それをやってると最大で通常の書き方だと、1秒のディレイが発生して、表示が遅れてしまったりする。
そういったものが一切なくてほんとすごい！！！
まぁ普通のArduinoでもできなくはないけどね〜
何がすごいか、まずは動画をみてください！

https://youtu.be/E9kRPJOSZa4


**ESP32-IDFのインストール方法やArduinoで使う方法は他の人にお任せします。
いっぱい記事書いてる人がいるので**
*注意　Arduinoでコードを書いていません、ESP-IDFのC言語で記述してます。*
割り込みってなんなの？というのは、以前私の記事がありますので、それを参照してください。
https://qiita.com/developerwaiwai/items/6803480b77275c6c02e8

#コード


```gpio_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"
#include "sdkconfig.h"


#define GPIO_INPUT_IO_0     21
#define GPIO_INPUT_IO_1     22
#define GPIO_INPUT_IO_2     23
#define GPIO_INPUT_PIN_SEL  ((1ULL<<GPIO_INPUT_IO_0) | (1ULL<<GPIO_INPUT_IO_1) | (1ULL<<GPIO_INPUT_IO_2))
#define ESP_INTR_FLAG_DEFAULT 0

#define BLINK_GPIO 5


static volatile unsigned long counter = 0;
static volatile unsigned long lap_counter = 0;
static volatile unsigned long start_counter = 0;
static volatile int lap_flag = 0;
static volatile int start_flag = 0;


static void IRAM_ATTR gpio_isr_handler(void* arg)
{
    counter++;
}


static void IRAM_ATTR gpio_isr_handler_button_start(void* arg)
{
    counter = 0;
    start_counter = 0;
    lap_counter = 0;
    start_flag = 1;
}


static void IRAM_ATTR gpio_isr_handler_button_lap(void* arg)
{
    start_counter = lap_counter;
    lap_counter = counter;
    lap_flag = 1;
}


static void blink(void* arg)
{
    uint32_t io_num = 0;
    for(;;) {
        gpio_set_level(BLINK_GPIO, io_num % 2);
        vTaskDelay(1000 / portTICK_RATE_MS);
        io_num++;
    }
}


void app_main(void)
{
    gpio_config_t io_conf;

    //interrupt of rising edge
    io_conf.intr_type = GPIO_PIN_INTR_POSEDGE;
    //bit mask of the pins, use GPIO4/5 here
    io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL;
    //set as input mode
    io_conf.mode = GPIO_MODE_INPUT;
    //enable pull-up mode
    io_conf.pull_up_en = 1;
    gpio_config(&io_conf);

    //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_button_lap, (void*) GPIO_INPUT_IO_0);
    //hook isr handler for specific gpio pin
    gpio_isr_handler_add(GPIO_INPUT_IO_1, gpio_isr_handler_button_start, (void*) GPIO_INPUT_IO_1);

    gpio_isr_handler_add(GPIO_INPUT_IO_2, gpio_isr_handler, (void*) GPIO_INPUT_IO_2);

    gpio_pad_select_gpio(BLINK_GPIO);
    gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
    xTaskCreate(blink, "blink", 2048, NULL, 10, NULL);


    while(1) {
        if(start_flag == 1) {
            start_flag = 0;
            printf("start\n");
        }

        if (lap_flag > 0) {
            unsigned long diff = lap_counter - start_counter;

            lap_flag = 0;

            int total_sec = diff / 10000;
            int minute = total_sec / 60;
            int sec = total_sec % 60;
            int milli = (diff - (minute * 10000 * 60) - (sec * 10000) ) /10;

            char lap_str[100];
            sprintf(lap_str, "lap = %02d:%02d:%03d\n", minute, sec, milli);
            printf(lap_str);
        }

        vTaskDelay(10 / portTICK_RATE_MS);
    }
}
```

#解説
割り込みを3つ登録しています。
##1. PIN23への割り込み
クリスタル1MHzから1/100分周したクロックを立ち上がりエッジで割り込みかけています。
1/100なので、10kHz（毎秒10000万回 100μs毎）の割り込みです。
このクロックのカウント数で秒数を計測します
ちなみに、最初はクリスタルのクロックそのままをPIN23に突っ込みました。そうしたら他が動かなくなり、ウォッチドックが動いてしまったため分周するに至ってます。多分1/10でもいけると思うけど、念の為1/100に設定しました。


##2. PIN22への割り込み
スタートボタンの割り込み
チャタリング対策で、RSフリップフロップ(RSラッチ)回路を入れています。

チャタリングって何？って方は、以下をみてみてください。
https://www.marutsu.co.jp/pc/static/large_order/1405_311_ph

##3. PIN21への割り込み
ラップ計測ボタンの割り込み
スタートボタンと同じく、チャタリング対策を入れています。

##1/100分周
74HC390というICを使えば簡単です。
[このサイトのように回路を組めばOK](http://select.marutsu.co.jp/list/detail.php?id=136)

##チャタリング対策
74HC00というICで簡単に作れます。RSフリップフロップはNANDゲートを二つ組み合わせて作ります。
[こんな感じ](http://home.a00.itscom.net/hatada/dc2/chap07/rsff.html)

##gpio_isr_handler
クリスタルから1/100に分周されたクロックをひたすら受ける割り込みロジック。
counterという変数を割り込みが入るたびにカウントアップしていきます。

##gpio_isr_handler_button_start
スターボボタンが押された時の割り込みロジック
ラップを計測するための各種変数を0クリアして、スタートボタン押下フラグをセット

##gpio_isr_handler_button_lap
ラップボタンが押された時の割り込みロジック。
ラップ計測用の変数にcounter値を入れて、ラップボタン押下フラグをセット

##blink
LEDを点滅させるためのロジック。
1秒おきに点滅を繰り返します。

##app_main
GPIOの設定、割り込みの設定とLED点滅タスクを作成しています。
後半部分はwhile(1)で無限ループになっていて、ここで、ボタン押下判定を行い、表示をし、フラグをクリアしています。
10msのディレイを入れてますが、ESP32はウォッチドックがあり、ウォッチドッククリアのために十分な時間を作るために入れてます。


#まとめ
クリスタルは温度補償をすることで10^-9程度の誤差しかありません。相当長い期間の計測が必要な場合はまた別ですが、通常の使用には全く問題ないレベルです。
割り込みを使用することにより、遅延を可能な限り取り除いています。ちなみに割り込みロジックでは出来るだけ早く処理を終えるのが鉄則です。
ESP32のクロックは240MHz（変更は可能）なので、遅延はほぼゼロと言っていいでしょう。
マルチタスク性能を試すために作ったLED点滅タスクも同時に動いています。これ、ほんとにIoTマイコンではすごいことです。

割り込みを使う上での注意点が、実はこのコードにテクニックが隠されています。

**start_flagやlap_flagをセット、リセットする位置は超重要**です。この位置じゃないといけません。
**割り込みで書き込む変数にはvolatileをつけてる**のもポイントです。これも**必ず必要**です。

こんなところかなぁ〜