003 BM8563のタイマー機能を使ってM5StickCPlusをDeep Sleepから復帰させたかった話。

# 1.はじめに
M5StickCPlusにはRTCが搭載されています。このRTCを使って、M5StickCPlusをDeep Sleepモードから復帰させたりできるはずなのですが、いまいち上手く使えませんでした。

 ~~なので、データシートを確認した上で手順を追って、ちゃんと使えるようにしたいと思います。~~

:::note warn
ちゃんと使えるようにできませんでした。
以降、当初の目論見と、いろいろやった結果を書いていきたいと思います。
:::

:::note warn
対象は、M5StickC Plusになります。
新しいM5StickC Plus2では未検証です。
:::

# 2.使い方
データシートは[002 BM8563 データシート要約](https://qiita.com/ukinju/items/033584c4fc6dd3535895)で確認したので完全に ~~理解しました。~~

:::note warn
現在進行形で理解できていません。
:::

この記事では、BM8563のタイマー機能を使って、タイマーを設定してから所定時間後にINTピンからの割込み信号を ~~生成させたいと思います。~~

:::note warn
結果として、INTピンからの割込みを生成できませんでした。
:::

# 3.基本設計
BM8563を使って以下の動作をするプログラムを作っていきたいと思います。

## 3-1.動作フロー
以下フローで動作するプログラムを作りたいと思います。

```plantuml
@startuml
card "1.BM8563 初期化\n---\n09h～0Ch：分/時/日/週アラームレジスタ\n・各アラーム無効化\n0Eh：Timer control\n・タイマー無効化\n01h：Control/Status2\n・INTピンパルス出力無効化\n・アラーム・タイマーフラグ削除\n・アラーム・タイマー割込み無効化\n00h：Control/Status1\n・RTC動作" as init

card "2.タイマ機能 設定\n---\n01h：Control/Status2\n・タイマー割込み有効化\n0Eh：Timer control\n・タイマークロックソース選択\n0Fh：Timer ountdown\n・タイマー設定値書き込み" as set

card "3.タイマスタート\n---\n01h：Control/status2\n・アラーム・タイマーフラグクリア\n0Eh：Timer control\n・タイマー有効化" as start

card "4.待機\n---\n" as wait

" " -r-> init

init -r-> set

set -r-> start

start --> wait : INTピン = HI
start --> init : INTピン = LO

wait --> set : INTピン = LO

@enduml
```
なお、デバッグのため以下の動作を入れます。
* 各状態に遷移したら、状態名をシリアル出力させる
* 状態4から状態2に遷移するときにLEDを反転させる（正常に動いている場合は、タイマー設定間隔でLEDが点滅します。）


## 3-2.タイマー設定値
　タイマーは8bitのダウンカウンタとなっており、`TD0/TD1`でカウントソースを`1/60Hz`、`1Hz`、`64Hz`、`4096Hz`から選択できるので、設定できる最大値はクロックソースを`1/60Hz`、カウント値を`255`に設定した場合で、`255分 = 4時間15分`になります。

　今回はテストのため、`クロックソース周波数＝1Hz`、`タイマー値=5`で設定したいと思います。

## 3-3.プログラム
以下が作成したプログラムです。

結果として期待の動作をさせることはできませんでした。BM8563のレジスタをダンプして表示してみたり、いろいろ頑張ったんですが、これ以上の対策を思いつかなくなったのでいったんここまでとします。
M5StickCPlus2での検証に備えて、備忘録として残しておきます。

```c
#include <M5StickCPlus.h>
#include <Wire.h>

#define LED          10
//#define SYS_INT      GPIO_NUM_35
#define BM8563       0x51
#define AXP192       0x34
#define MPU6886      0x68
#define DELAY        1000

static uint8_t rtc_sq;
#define RTCSQ_INIT   0
#define RTCSQ_SET    1
#define RTCSQ_START  2
#define RTCSQ_WAIT   3

void i2c_byteWrite(uint8_t dev, uint8_t adr, uint8_t val);
uint8_t i2c_byteRead(uint8_t dev, uint8_t adr);
void dumpBM8563(void);
void zprintf(const char *format, ...);
void dumpAXP192(void);
void dumpMPU6886(void);
void IRAM_ATTR sysint_isr(void);

void IRAM_ATTR sysint_isr(void)
{
    rtc_sq = RTCSQ_SET;
    digitalWrite(LED, digitalRead(LED)^1);
}

void setup()
{
    M5.begin();

    /* USB Serial */
    Serial.begin(115200);

    /* LED */
    pinMode(LED, OUTPUT);
    digitalWrite(LED, LOW);

    /* SYSINT */
    pinMode(GPIO_NUM_35, INPUT);
    attachInterrupt(GPIO_NUM_35, sysint_isr, FALLING);


    /* Internal I2C : IMU(MPU6886)/PMIC(AXP192)/RTC(BM8563) */
    Wire1.begin(21, 22);
    Wire1.setClock(100000);
    Wire1.write(0);

    // disable AXP192 IRQ
    i2c_byteWrite(AXP192, 0x40, 0x00);
    i2c_byteWrite(AXP192, 0x41, 0x00);
    i2c_byteWrite(AXP192, 0x42, 0x00);
    i2c_byteWrite(AXP192, 0x43, 0x00);
    i2c_byteWrite(AXP192, 0x4A, 0x00);
    
    // clear AXP192 All IRQstatus 
    i2c_byteWrite(AXP192, 0x44, 0xFF);
    i2c_byteWrite(AXP192, 0x45, 0xFF);
    i2c_byteWrite(AXP192, 0x46, 0xFF);
    i2c_byteWrite(AXP192, 0x47, 0xFF);
    i2c_byteWrite(AXP192, 0x4D, 0xFF);

    // disable MPU6886 IRQ
    i2c_byteWrite(MPU6886, 0x37, 0b11011000);
    i2c_byteWrite(MPU6886, 0x38, 0x00);


    // clear MPU6886 All IRQstatus
    uint8_t ret;
    ret = i2c_byteRead(MPU6886, 0x36);
    ret = i2c_byteRead(MPU6886, 0x39);
    ret = i2c_byteRead(MPU6886, 0x3A);
}

void loop()
{
    switch(rtc_sq)
    {
        case RTCSQ_INIT:
            // debug
            Serial.println("RTCSQ_INIT");

            // RTC動作開始
            i2c_byteWrite(BM8563, 0x00, 0x00);

            // 各アラーム無効化
            i2c_byteWrite(BM8563, 0x09, 0x80);
            i2c_byteWrite(BM8563, 0x0A, 0x80);
            i2c_byteWrite(BM8563, 0x0B, 0x80);
            i2c_byteWrite(BM8563, 0x0C, 0x80);
            
            i2c_byteWrite(BM8563, 0x0D, 0x83);

            // タイマー無効化
            i2c_byteWrite(BM8563, 0x0E, 0x03);

            // INTピン設定、アラーム・タイマー割込みフラグ削除・無効化
            i2c_byteWrite(BM8563, 0x01, 0x00);


            // wait
            dumpBM8563();
            dumpAXP192();
            dumpMPU6886();
            delay(DELAY);

            // 状態遷移
            rtc_sq = RTCSQ_SET;
            break;
        
        case RTCSQ_SET:
            // debug
            Serial.println("RTCSQ_SET");

            // タイマー割込み有効化
            i2c_byteWrite(BM8563, 0x01, 0x00);
            // タイマークロックソース選択
            i2c_byteWrite(BM8563, 0x0E, 0x02);
            // タイマー設定値書き込み
            i2c_byteWrite(BM8563, 0x0F, 0x05);

            // wait
            dumpBM8563();
            delay(DELAY);

            // 状態遷移
            rtc_sq = RTCSQ_START;
            break;
        
        case RTCSQ_START:
            // debug
            Serial.println("RTCSQ_START");

            // タイマー有効化
            i2c_byteWrite(BM8563, 0x0E, 0x82);

            // アラーム・タイマーフラグクリア
            i2c_byteWrite(BM8563, 0x01, 0x03);

            // wait
            dumpBM8563();
            delay(DELAY);

            // 状態遷移
            if(digitalRead(GPIO_NUM_35) == HIGH)
            {
                rtc_sq = RTCSQ_WAIT;
            }
            else
            {
                rtc_sq = RTCSQ_INIT;
            }
            break;
        
        case RTCSQ_WAIT:
            // debug
            Serial.println("RTCSQ_WAIT");

            // wait
            dumpBM8563();
            //dumpAXP192();
            //dumpMPU6886();
            delay(DELAY);

            // 状態遷移
            if(digitalRead(GPIO_NUM_35) == LOW)
            {
                rtc_sq = RTCSQ_SET;
                digitalWrite(LED, digitalRead(LED)^1);
            }
            break;
        
        default:
            rtc_sq = RTCSQ_INIT;
            break;
    }
}

void i2c_byteWrite(uint8_t dev, uint8_t adr, uint8_t val)
{
    Wire1.beginTransmission(dev);
    Wire1.write(adr);
    Wire1.write(val);
    Wire1.endTransmission();
}

uint8_t i2c_byteRead(uint8_t dev, uint8_t adr)
{
    uint8_t ret;

    Wire1.beginTransmission(dev);
    Wire1.write(adr);
    Wire1.endTransmission();

    Wire1.requestFrom( (int)dev, 1, 1 );

    while(Wire1.available() < 1);

    ret = Wire1.read();

    return ret;
}

void dumpBM8563(void)
{
    uint8_t dump[16];
    uint8_t i;

    for(i=0; i<16; i++)
    {
        dump[i] = i2c_byteRead(BM8563, i);
    }
    
    Serial.println("DUMP BM8563 00h-0Fh");
    Serial.println("00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
    for(i=0; i<16; i++)
    {
        zprintf("%02x ", dump[i]);
    }
    Serial.println("");
}

#define MAX_ZPRINTF_LENGTH 80  // 一度に変換する最大文字数
void zprintf(const char *format, ...) {
  char s[MAX_ZPRINTF_LENGTH];
  va_list args;

  va_start(args, format);
  vsnprintf(s, MAX_ZPRINTF_LENGTH, format, args);
  va_end(args);

  Serial.print(s);
}

void dumpAXP192(void)
{
    uint8_t dump[16];
    uint8_t i;
    
    for(i=0; i<16; i++)
    {
        dump[i] = i2c_byteRead(AXP192, i+0x40);
    }
    
    Serial.println("DUMP AXP192 40h-4Fh");
    Serial.println("00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
    for(i=0; i<16; i++)
    {
        zprintf("%02x ", dump[i]);
    }
    Serial.println("");
}

void dumpMPU6886(void)
{
    uint8_t dump[16];
    uint8_t i;
    
    for(i=0; i<16; i++)
    {
        dump[i] = i2c_byteRead(MPU6886, i+0x30);
    }
    
    Serial.println("DUMP MPU6886 30h-3Fh");
    Serial.println("00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
    for(i=0; i<16; i++)
    {
        zprintf("%02x ", dump[i]);
    }
    Serial.println("");
    
    for(i=0; i<16; i++)
    {
        dump[i] = i2c_byteRead(MPU6886, i+0x60);
    }

    Serial.println("DUMP MPU6886 60h-6Fh");
    Serial.println("00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
    for(i=0; i<16; i++)
    {
        zprintf("%02x ", dump[i]);
    }
    Serial.println("");
}
```

# 4. 発生した問題と実施した対策

## 4-1. 問題1：SYS_INTピンがLOのまま。
　プログラムを書いてまず最初に躓いたのは、`SYS_INT(GPIO35)`がローレベルのままとなっている。つまり、割込み発生状態が続いてしまっているという点でした。
 　BM8563のINTピンは、M5StickCPlusの内部回路としては`SYS_INT`ラインに接続されています。`SYS_INT`ラインは抵抗R2(2.2kΩ)でプルアップされているため、オープンドレイン端子のINTがローレベルに落ちることで、割込みが発生するはずです。
　しかし、まだタイマーがタイムアップしていない筈なのに、`SYS_INT`ラインがローに落ちている・・・。

　この問題の原因として考えられるのは、以下3点でした。
 1. AXP192が`SYS_INT`をローに引っ張っている。
 2. MPU6886が`SYS_INT`をローに引っ張っている。
 3. BM8563が`SYS_INT`をローに引っ張っている。

　原因1/2を取り除くためAXP192とMPU6886のIRQ無効化、割込みフラグクリアを行うことにしました。対策を行うにあたり、追加したコードが以下になります。

```c
#define BM8563       0x51
#define AXP192       0x34
#define MPU6886      0x68

...

    // disable AXP192 IRQ
    i2c_byteWrite(AXP192, 0x40, 0x00);        // IRQ1  
    i2c_byteWrite(AXP192, 0x41, 0x00);        // IRQ2
    i2c_byteWrite(AXP192, 0x42, 0x00);        // IRQ3
    i2c_byteWrite(AXP192, 0x43, 0x00);        // IRQ4
    i2c_byteWrite(AXP192, 0x4A, 0x00);        // IRQ5
    
    // clear AXP192 All IRQstatus 
    i2c_byteWrite(AXP192, 0x44, 0xFF);        // IRQ1 status 
    i2c_byteWrite(AXP192, 0x45, 0xFF);        // IRQ2 status
    i2c_byteWrite(AXP192, 0x46, 0xFF);        // IRQ3 status
    i2c_byteWrite(AXP192, 0x47, 0xFF);        // IRQ4 status
    i2c_byteWrite(AXP192, 0x4D, 0xFF);        // IRQ5 status

    // disable MPU6886 IRQ
    i2c_byteWrite(MPU6886, 0x37, 0xD8);
    i2c_byteWrite(MPU6886, 0x38, 0x00);

    // clear MPU6886 All IRQstatus
    uint8_t ret;
    ret = i2c_byteRead(MPU6886, 0x36);
    ret = i2c_byteRead(MPU6886, 0x39);
    ret = i2c_byteRead(MPU6886, 0x3A);
```

　この対策を施したことで`SYS_INT(GPIO35)`がハイレベルになり、プログラムも`RTCSQ_WAIT`まで進むようになりました。
DUMP表示していたBM8563のタイマカウントレジスタ(0Fh)も1秒間隔でカウントダウンしているので、これで完成かと思ったのですが・・・

## 4-2. 問題2：今度はSYS_INTピンがHIGHのまま。
　DUMP表示を見てタイマーがタイムアップしTFフラグもセットされているのに、今度はSYS_INTピンがローにならずにずっとハイレベルのまま。つまり、BM8563からタイマー割込みが発生していません。
　シリアルモニタのデバッグ表示を以下に貼り付けます。

 ![cap002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186520/f08ada25-dfe2-8ac6-8ff7-df9615a970c8.png)

　アドレス01h:Control/Status2レジスタのbit3:TF(Timer Flag?)が`1`になっているのですが、SYS_INTがローに落ちていないようです。TIEbitも1:enableにしてあります。結論から言うと、ここで詰みました。　以下、いろいろやったけどダメだった対策を箇条書きにします。
 
* TIEbitの設定論理を逆にしてみる。(TIE=1:enableと書きましたが、データシートに誤記がありどちらがenableなのか、はっきりしたことが実は分かりません。）
* タイマー割込みは諦めて、アラーム割込みで割込みを発生させようとしてみる。
* TI/TPビットを1にしてINTピンからの出力をパルス出力設定にしてみる。
* GPIO35の立下りエッジ割込みを追加して、割込みで`SYS_INT = ロー`をとらえようとしてみる。

　いずれの対策を行ってもダメでした。

# 5. おわりに
　もともとはM5StickCPlusのdeep sleepモードからの復帰にRTCを使えればと思い、チャレンジしてみたのですがうまくいきませんでした。検索も活用したのですが、M5StickCPlusをRTCを用いて復帰させたという記事は見つからず。　できていないの自分だけでしょうか・・・？

　MPU6886の割込みピンは、初期設定がアクティブハイでプッシュ-プルの設定となっていたので、もしかすると貫通電流でRTCのINTピンが破損しているのかもしれません。（もしこれが原因だとすると、設計が悪い気がします。）M5StickCPlus2の回路図を見ると、INTピンはRTCだけが接続されるようになっておりMPU6886のINTピンは接続されないように修正が加えられているので、ダウトな気もしますが。
 
　もしM5StickCPlus2を入手する機会があれば、再度チャレンジしてみたいと思います。