M5StickCの内部INT割込通知を利用する その1 RTC編

現時点の情報です。最新情報はM5StickC非公式日本語リファレンスを確認してみてください。

概要

M5StickCにはGPIO35にINT割込の結線がされています。しかしながらプルアップされていなかったので、使えないと思っていましたが使う方法がありました。

今回はRTCの時間を使った割込方法を説明したいと思います。

INTとは?

デバイスによって、イベントが発生した場合に通知する機能があります。今回はリアルタイムクロック(RTC)ですので、指定した時間になったことを通知するアラート機能と、インターバルタイマーがあります。

インターバルタイマーはTickerクラスなどを使ってもそれほどできることが変わらないので、指定した時間になったら通知してくれるアラート機能を使ってみました。

事前準備

多くのデバイスが通知を行う場合には、INTピンをLOWに落とす操作で通知を行います。そのため、回路上はプルアップされておりマイコンなどの通知を受信するデバイスはHIGHからLOWに信号レベルが変わった瞬間をトリガーにして、通知を受け取ります。

M5StickCはGPIO35がINT回路と接続しているのですが、デフォルトでプルアップされていません。GPIO35なのでESP32側でプルアップもできないので諦めていたのですが、接続されているデバイスをすべて調べたところ、IMUのMPU6886はアクティブハイという設定があり、プルアップすることができました。(SH200Qは同じ設定がありましたが、GPIO35はHIGHになりませんでした。途中で配線が変わった可能性があります)

M5.IMU.Init()を呼び出すとGPIO35の信号がプルアップされますので、INT割込通知を利用する場合にはIMUを利用しなくても初期化する必要があります。

上記の資料が参考になります。

ESP32の通知受信方法

複数あるのですが、GPIO35の信号レベルが変わったら割込を発生させる設定がありますので、今回はそれを使ってみました。

Arduinoの割込は結構難しくて、割込関数の中身で実行できる関数にはかなり制限があります。I2Cアクセスなどはできませんので注意してください。

おそらくは、別スレッドで動いている処理に、通知やキュー、もしくはセマフォを使って渡してあげる必要があります、、、

1秒程度の遅延が許されるのであれば、GPIO35がLOWになっているか定期的に確認して、LOWの場合に処理するようにすればかんたんにアラートが使えると思います。

RTCアラート設定スケッチ例

#include <M5StickC.h>

RTC_TimeTypeDef RTC_TimeStruct;
RTC_DateTypeDef RTC_DateStruct;

void intFunc() {
  Serial.println("Int");
}

void setAlarm(int minute, int hour = -1, int day = -1, int dayOfTheWeek = -1 ) {
  if ( minute == -1 ) {
    minute = 0x80;
  } else {
    // bin to bcd
    minute = minute % 60;
    minute = minute + 6 * (minute / 10);
  }
  if ( hour == -1 ) {
    hour = 0x80;
  } else {
    // bin to bcd
    hour = hour % 24;
    hour = hour + 6 * (hour / 10);
  }
  if ( day == -1 ) {
    day = 0x80;
  } else {
    // bin to bcd
    day = day + 6 * (day / 10);
  }
  if ( dayOfTheWeek == -1 ) {
    dayOfTheWeek = 0x80;
  } else {
    // bin to bcd
    dayOfTheWeek = dayOfTheWeek + 6 * (dayOfTheWeek / 10);
  }

  // Init RTC
  Wire1.beginTransmission(0x51);
  Wire1.write(0x00);
  Wire1.write(0x00);
  Wire1.write(0x00);
  Wire1.endTransmission();

  // Set Alarm
  Wire1.beginTransmission(0x51);
  Wire1.write(0x09);
  Wire1.write((uint8_t)minute);
  Wire1.write((uint8_t)hour);
  Wire1.write((uint8_t)day);
  Wire1.write((uint8_t)dayOfTheWeek);
  Wire1.endTransmission();

  // Enable Alarm
  Wire1.beginTransmission(0x51);
  Wire1.write(0x01);
  Wire1.write(0x02);
  Wire1.endTransmission();
}

void setup() {
  M5.begin();

  // GPIO35 pull-up
  M5.IMU.Init();

  pinMode(35, INPUT);
  attachInterrupt(35, intFunc, FALLING);

  M5.Rtc.GetTime(&RTC_TimeStruct);
  setAlarm(RTC_TimeStruct.Minutes + 1);
}

void loop() {
  M5.Rtc.GetTime(&RTC_TimeStruct);
  M5.Rtc.GetData(&RTC_DateStruct);
  Serial.printf("%04d-%02d-%02d ", RTC_DateStruct.Year, RTC_DateStruct.Month, RTC_DateStruct.Date);
  Serial.printf("%02d : %02d : %02d ", RTC_TimeStruct.Hours, RTC_TimeStruct.Minutes, RTC_TimeStruct.Seconds);
  Serial.println(digitalRead(35));

  // Next Alert
  if (digitalRead(35) == LOW) {
    setAlarm(RTC_TimeStruct.Minutes + 1);
  }

  delay(1000);
}

事前にRTCの時間をセットしておいてから、実行してみてください。RTCは一度セットすれば他のスケッチを転送しても設定されたままで、電源を切っても保持されます。

上記などで一度RTCに時刻を設定しておけば、それほど時間はずれないと思います。

setAlarm()の関数がアラートを設定する関数になります。毎時0分にアラートをセットする場合にはsetAlarm(0)。14時30分に設定するにはsetAlarm(30, 14)で設定します。

中身は理解しなくても大丈夫です。気になる人はRTCのデータシートを見てください。

肝としては、M5.IMU.Init()をかならず呼ぶこと。RTCは電源を切っても内部のバッテリーで動いているので、前回の設定値をクリアすることになります。

  // Init RTC
  Wire1.beginTransmission(0x51);
  Wire1.write(0x00);
  Wire1.write(0x00);
  Wire1.write(0x00);
  Wire1.endTransmission();

上記が初期化ですが、このコードを呼び出さないと、前回のアラート設定が残っているのでGPIO35をLOWに設定したままになったりします。

あとで、M5StickCのライブラリにプルリクエストを出して、初期化関数を追加したいと思います。

  // Next Alert
  if (digitalRead(35) == LOW) {
    setAlarm(RTC_TimeStruct.Minutes + 1);
  }

attachInterrupt()での割込は表示以外には利用していないので、上記が実質のアラート割込処理になります。現在時刻の1分後の0秒に再度アラートを仕掛けています。この設定で毎分0秒に通知が発生します。

設定する値は0-59ですが、setAlarm()の中で60のあまりを求めているので65などを設定しても、5になっているはずです。

RTCでのディープスリープ復帰スケッチ

#include <M5StickC.h>

RTC_DATA_ATTR int bootCount = 0; // RTCスローメモリに変数を確保

RTC_TimeTypeDef RTC_TimeStruct;
RTC_DateTypeDef RTC_DateStruct;

void setAlarm(int minute, int hour = -1, int day = -1, int dayOfTheWeek = -1 ) {
  if ( minute == -1 ) {
    minute = 0x80;
  } else {
    // bin to bcd
    minute = minute % 60;
    minute = minute + 6 * (minute / 10);
  }
  if ( hour == -1 ) {
    hour = 0x80;
  } else {
    // bin to bcd
    hour = hour % 24;
    hour = hour + 6 * (hour / 10);
  }
  if ( day == -1 ) {
    day = 0x80;
  } else {
    // bin to bcd
    day = day + 6 * (day / 10);
  }
  if ( dayOfTheWeek == -1 ) {
    dayOfTheWeek = 0x80;
  } else {
    // bin to bcd
    dayOfTheWeek = dayOfTheWeek + 6 * (dayOfTheWeek / 10);
  }

  // Init RTC
  Wire1.beginTransmission(0x51);
  Wire1.write(0x00);
  Wire1.write(0x00);
  Wire1.write(0x00);
  Wire1.endTransmission();

  // Set Alarm
  Wire1.beginTransmission(0x51);
  Wire1.write(0x09);
  Wire1.write((uint8_t)minute);
  Wire1.write((uint8_t)hour);
  Wire1.write((uint8_t)day);
  Wire1.write((uint8_t)dayOfTheWeek);
  Wire1.endTransmission();

  // Enable Alarm
  Wire1.beginTransmission(0x51);
  Wire1.write(0x01);
  Wire1.write(0x02);
  Wire1.endTransmission();
}

void setup(){
  M5.begin();

  // 起動回数カウントアップ
  bootCount++;
  Serial.printf("起動回数: %d ", bootCount);

  // 起動方法取得
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason){
    case ESP_SLEEP_WAKEUP_EXT0      : Serial.printf("外部割り込み(RTC_IO)で起動\n"); break;
    case ESP_SLEEP_WAKEUP_EXT1      : Serial.printf("外部割り込み(RTC_CNTL)で起動 IO=%llX\n", esp_sleep_get_ext1_wakeup_status()); break;
    case ESP_SLEEP_WAKEUP_TIMER     : Serial.printf("タイマー割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD  : Serial.printf("タッチ割り込みで起動 PAD=%d\n", esp_sleep_get_touchpad_wakeup_status()); break;
    case ESP_SLEEP_WAKEUP_ULP       : Serial.printf("ULPプログラムで起動\n"); break;
    case ESP_SLEEP_WAKEUP_GPIO      : Serial.printf("ライトスリープからGPIO割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_UART      : Serial.printf("ライトスリープからUART割り込みで起動\n"); break;
    default                         : Serial.printf("スリープ以外からの起動\n"); break;
  }

  // GPIO35(M5StickCのINT)がLOWになったら起動
  pinMode(GPIO_NUM_35, INPUT);
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_35, LOW);

  // タイマーセット
  M5.Rtc.GetTime(&RTC_TimeStruct);
  setAlarm(RTC_TimeStruct.Minutes + 1);

  // ディープスリープ
  esp_deep_sleep_start();
}

void loop(){
}

ディープスリープの例です。こちらのほうが割込はシンプルになっています。Arduino IDEのシリアルモニタにタイムスタンプを表示するオプションをつけて実行していると、毎分0秒にディープスリープから復帰する様子が確認できます。

まとめ

実は結構はまっていましたが、できてしまえばシンプルです。ただRTCは前回の設定を持ったままなので、このスケッチを実行したあとには前回のアラート設定が残ったままです。

INT通知をする場合にはIMUとRTCの初期化を必ずしてから使う必要がありそうです。あとはAXP192とMPU6886がINT割込通知がありますので、今後触ってみたいと思います。

続編

コメントする

メールアドレスが公開されることはありません。

管理者承認後にページに追加されます。公開されたくない相談はその旨本文に記載するかTwitterなどでDM投げてください。またスパム対策として、日本語が含まれない投稿は無視されますのでご注意ください。