ESP8266のDeepSleepをタクトスイッチひとつで制御したい

##記事の内容
本記事で紹介する内容は、一見簡単に出来そうで、ちょっとした工夫をしないと実装できない内容です。
本記事では、以下内容を紹介いたします。
一つのタクトスイッチで

* ESP8266をDeepSleepから復帰する
* ESP8266をDeepSleepにする
* 長押しで任意の処理を実行する

#なぜ、タクトスイッチ一つでDeepSleepのオンオフやスイッチ長押し判定を行うのが難しいか
複数のスイッチがあるのはユーザーにとってそれぞれのスイッチの役割を視認する必要があり、ユーザービリティを考えればこれを確認せずに制御できることが好ましいのは明白であります。
また、スイッチの増加は実装面積の増加や部品点数の増加を招くため、コスト面でも不利となります。このためなるべくコンポーネント数を削減したいというのが設計側の本音でしょう。
そこで、なるべくタクトスイッチなどの入手性が容易なものを用い、かつ1つのスイッチのみで必要な機能を提供する方法を考える必要があります。

ここでESP8266の仕様上、タクトスイッチ一つでDeepSleepの制御を行うにはいくつかの問題が発生します。

まず、ESP8266には、DeepSleepモードが用意されており、実質これが電源がオフになっている状態と認識して良いでしょう。
ここで、DeepSleepから復帰する方法は、ENピンやRSTピンをLOWにする方法と、復帰時間を指定する方法の二種類に限られており、GPIOピン割り込みによって起動する方法がないため、**実質的にENやRSTを一度LOWにする以外でDeepSleepから復帰できません。**
また、**ENやRSTをLOWにすると強制的に再起動に入ってしまうため、一つのタクトスイッチで押し下げ判定などを行うことができません。**
つまり、**EN・RSTやGPIOにタクトスイッチを繋いだだけでは、ESP8266をDeepSleepにすることが出来ない**ということです。
（ESP32ではピン割り込みによる起動が実装され、この問題は解決されております。）
何が言いたいかよくわからないと思うので、以下に例を示します。
これらは一つのタクトスイッチで制御したかった…例です。
###A. EN・RSTを使わない方法
この回路構成では、ESP8266の起動に必要な最小構成の回路に加え、IO5ピンにタクトスイッチが繋がっているだけです。

![回路A.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1751124/e8073425-2d32-06b7-9585-5e29789081bf.png)
例えば下記コードによってDeepSleepに入ることはできますが、前述したように復帰方法が存在しないため、**電源を入れなおさない限り二度とDeepSleepから復帰できません。**

```C:
void setup(){
    pinMode(5,INPUT_PULLUP); //IO5をPULLUPで読み取りモードに。
}

void loop(){
    if(!digitalRead(5)){  //もしIO5がHIGHでなければ
         ESP.deepSleep(0); //DeepSleepする。
    }
    delay(10);
}
```
###B. EN・RSTで復帰したい方法
当回路は、さきほどの回路にタクトスイッチとRSTを繋げることで、DeepSleepから復帰したかった回路です。
しかし、タクトスイッチを押した瞬間にRSTがLOWになってしまうため再起動がかかり、ESP8266は**DeepSleepに入ることができません。**
![回路B.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1751124/69df516c-ada7-33c2-4df5-516893e0c0b4.png)

#解決策とコード
**ここからが本題です**
では、どのようにして一つのタクトスイッチでDeepSleepのオン・オフ・長押しの処理をこなせるようになるでしょうか。
その解決策の一つは、**EN・RSTをESP8266の起動後に、別のIOピンでHIGHにしてやることで、タクトスイッチの押下でEN・RSTがLOWになるのを防ぐ方法**です。
また、ESP8266がDeepSleepになっている場合は、IOピンがLOWになっているので、タクトスイッチでリセット(起動)を行うことができます。
さらに、HIGHを保持するのと別のIOピンでタクトスイッチの状態を監視することで、プログラム上からタクトスイッチの状態を読み取ることが可能となります。

* 必要な電子部品は以下のものです。

ESP8266、タクトスイッチ、ショットキーバリアダイオード、抵抗4つ。

* ESP側で利用するのは以下のピンです。

ENかRST、適当なデジタルIOピン2つ。

それでは御託はいいので、回路図とコード例を見ていきましょう。

##回路図
以下が回路図です。


![回路C.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1751124/b4e6e41f-454a-d9cd-3ae6-074bec2a2d0d.png)
まず、ESP8266がDeepSleepに入っている場合を考えます。
SWを押さないとき、RSTピンはVCCとR1、R2によってプルアップされ、HIGHになっています。
次に、スイッチを押下したとき、R1とR4に着目すると、10:1の抵抗分圧によって、RSTの電圧はVCC * 1/11になることが分かります。(VCCが3.3Vなら、RSTにかかる電圧は0.3V程)
これはRSTがLOWになってリセットがかかるには十分に低い電圧であり、ESP8266にリブートがかかり、見事DeepSleepから復帰できます。

次に起動後に、digitalWriteでIO4がHIGHになるようにした場合を考えます。
IO4にはHIGHがかかっており、DiodeとSWを経由して、R4からGNDに落ちます。
このとき、RSTはショットキーバリアダイオードによりわずかに順電圧降下した、およそ3V弱の電圧がかかることが分かります。
また、IO5にも同様の電圧がかかり、マイコン側からHIGHとして読み取られることでしょう。
SWが押されていないときは、IO5はGNDにプルダウンされているため、LOWの判定になります。
これによって、ESP8266の起動中はRSTがLOWになることを防ぎ、IO5でスイッチ状態を読み取れることが明らかになりました。
あとはIO5が押下されたときに、DeepSleepに入るようにプログラムを書くだけです。
(RSTはENで代用することが可能です。違いは起動時のRTCメモリに再起動理由として何が記録されるかぐらいでしょう。)
##Arduinoのコード

Arduinoで実行する場合は、以下のようなコード例になります。


```C:
void setup(){
    pinMode(5,INPUT); //IO5を読み取りモードに。
    pinMode(4,OUTPUT); //IO4を出力モードに。
    digitalWrite(4, HIGH); //IO4をHIGHに。
}

void loop(){
     sw_proc();
     delay(10);
}

void sw_proc(){
    int sw_count = 0;
    while (digitalRead(5) == HIGH) { //スイッチが押されている間だけ実行される。
         if (sw_count > 5000) { //5000ms以上押されたとき、実行
             long_push();
         }
         delay(1); 
         sw_count++;  //1msごとにカウントを増加
     }
    
    if (sw_count < 5000 && sw_count > 50 ) { //5000ms以下かつ、50 ms以上押されていたとき、実行
      ESP.deepSleep( 0 );
    }  
}

void long_push(){
     //長押ししたときに実行したい処理
}
```

このコードでは、チャタリング防止と長押し判定も含まれています。
詳しい説明は不要でしょうが、以下部分はスイッチが押されている間だけ1msごとにsw_countを増加していき、
しきい値を超えたとき(コード内では5000ms)に、関数が実行されるようになっています。

```C:
    while (digitalRead(5) == HIGH) { //スイッチが押されている間だけ実行される。
         if (sw_count > 5000) { //5000ms以上押されたとき、実行
             long_push();
         }
         delay(1); 
         sw_count++;  //1msごとにカウントを増加
     }
```

そして、押し下げ時間が5000msに満たないが、50ms以上であったときには、DeepSleepに入るようになっております。

```C:
    if (sw_count < 5000 && sw_count > 50 ) { //5000ms以下かつ、50 ms以上押されていたとき、実行
      ESP.deepSleep( 0 );
    }  
```

このようにして、押し下げ時間で処理を分岐させることが可能です。
また、回路説明で記述したように、ESPがDeepSleepに入っている場合は、IO4がLOWになっているため、タクトスイッチは復帰ボタンとして機能します。
上記回路とコードを用いることで、一つのタクトスイッチに、DeepSleepの復帰、DeepSleepに入れる、長押し判定で特定のコードを実行する。という複数の機能を実装することができました。
めでたしめでたし。
#まとめ
少々煩雑で理解し辛いように見えますが、実装自体は抵抗4つ、タクトスイッチ1つ、ショットキーバリアダイオード1個と少なくかつ、安価なコンポーネントで複数の機能を1つのタクトスイッチに持たせることが可能となります。
スイッチを1つにして見た目をシンプルにしたい場合など、有効に使っていただければと思います。


###参考
海外に多少処理は違いますが、似たような回路で実装している人が居ました。
>https://hobbytronics.com.pk/esp-deep-sleep-input-wakeup/

###更新履歴
20210712 コードの一部を修正