T-Watch. Code simplifié pour extinction et réveil avec BMA423 ou AXP202 de l'ESP32 • Domotique et objets connectés à faire soi-même

Dans l’article précédent tiré de l’exemple développé par Lewis Le, nous avons vu comment réveiller l’écran et les fonctions de la montre connectée ESP32 TTGO T-Watch à l’aide de FreeRTOS. Le code est clairement destiné à des Makers qui ont déjà une très bonne maitrise de la programmation. Je vous propose donc une version ultra simplifiée plus accessible qui conviendra à des projets plus simples. 

Le code proposé ici peut certainement être encore amélioré et simplifié. Si vous avez encore mieux, n’hésitez pas à proposer vos améliorations sur le dépôts GitHub ou dans les commentaires 🙂

https://projetsdiy.fr/data/uploads/2020/11/ttgo-t-watch-deep-light-sleep-wakup-axp202-bma423.mp4?_=1

Eléments montrés dans le clip (dans l’ordre d’apparition) : réveil par le bouton principal (AXP202), extinction automatique après 5 secondes d’inactivité (light sleep), réveil de l’ESP32 par un tap sur l’écran, mise en deep sleep, réveil automatique par un Timer.

Comment réveiller la T-Watch sans FreeRTOS ?

La méthode “académique” pour réveiller l’ESP32 et les périphériques et d’utiliser le système temps réel de FreeRTOS dont voici l’architecture générale.

Bien évidemment pour des projets simples, et à fortiori lorsqu’on débute en C++, c’est un peu difficile à mettre en pratique.

Il est possible de faire quasiment la même chose en utilisant uniquement les interruptions de l’ESP32 ce qui donne une architecture plus simple.

On conserve donc les deux interruptions sur le GPIO qui vous nous permettre de sortir de veille via le bouton principal (par l’intermédiaire de l4AXP202) ou l’accéléromètre BMA423.

gpio_wakeup_enable ((gpio_num_t)BMA423_INT1, GPIO_INTR_LOW_LEVEL);
gpio_wakeup_enable ((gpio_num_t)AXP202_INT, GPIO_INTR_LOW_LEVEL); 
esp_sleep_enable_gpio_wakeup();
esp_light_sleep_start();

Lorsque l’utilisateur tapote l’écran de la montre ou appuie sur le bouton principal ou active un booléen. Ici, on utilise deux booléens ce qui permettra de déclencher un traitement différent en fonction du réveil. On pourrait par exemple afficher à l’écran l’activité physique lorsqu’on tapote l’écran et l’heure lorsqu’on appuie sur le bouton principal. C’est un détail.

Réveil par l’accéléromètre BMA423 Réveil par le bouton principal via l’AXP202
pinMode(BMA423_INT1, INPUT);
  attachInterrupt(BMA423_INT1, [] {
      irq_bma = true;
  }, RISING);
pinMode(AXP202_INT, INPUT);
  attachInterrupt(AXP202_INT, [] {
      irq_axp202 = true;
  }, FALLING);

Ensuite, il suffit de tester un changement d’état de ces deux booléens dans la loop() pour réveiller l’ESP32 et les accessoires de la montre (écran, carte d’extension…).

Lorsque le BMA423 détecte un mouvement ou que l’utilisateur tapote l’écran :

  • On passe le booléen à Faux
  • On ré-initialiser le chronomètre d’extinction automatique de l’écran
  • On attend la fin de l’interruption du BMA423 avant de continuer
  • On exécute la fonction de réveil (identique au projet précédent)
if ( irq_bma ) {
    Serial.println("irq_bma detectee");
    irq_bma = false;   
    resetChrono();   
    do {
      rlst = watch->bma->readInterrupt();
    } while (!rlst);
    low_energy();
  }

Lorsqu’on appuie sur le bouton principal (qui est connecté à l’AXP202) :

  • On récupère l’évènement qui a provoqué l’interruption avec la méthode watch->power->readIRQ()
  • Puis on test l’origine du réveil. On pourra adapter les actions en fonction de l’événement qui a provoqué le réveil. On dispose pour cela de plusieurs fonctions
    • isPEKShortPressIRQ appui simple
    • isPEKLongtPressIRQ appui long
  • Enfin, il ne faut pas oublier de purger le registre IRQ pour la prochaine action watch->power->clearIRQ()
if (irq_axp202) {
    Serial.println("irq detected");
    irq_axp202 = false;
    watch->power->readIRQ();
    if ( watch->power->isPEKShortPressIRQ() ) {
      Serial.println("Power button pressed >> wakeup / switch on light sleep");
      low_energy();
    }  
    watch->power->clearIRQ();
  } 

Le reste du code est inchangé par rapport au projet précédent.

Comment renvoyer l’ESP32 en deep-sleep (sommeil profond)

Ce qui est génial avec la T-Watch Touch (ou la plateforme M5Stack d’ailleurs) se sont les cartes d’extension disponibles et la batterie intégrée dans un mini boitier. Plus besoin de faire des soudures pour plein d’application.

Pour économiser au maximum la batterie, le mieux est d’activer la mise en veille profonde (deep sleep) de l’ESP32. Ensuite, on pourra réveiller l’ESP32 périodiquement avec un Timer. Tout est expliqué en détail dans cet article pour aller plus loin.

Le problème, c’est qu’il faut prévoir en amont un mécanisme pour replonger l’ESP32 dans son sommeil profond après le traitement ou une période d’inactivité.

Pour cela, il suffit de récupérer l’origine du réveil à l’aide de la méthode esp_sleep_get_wakeup_cause(). la fonction renvoie le numéro de la cause du réveil :

  1. RTC_IO
  2. RTC_CNTL
  3. Touch Pad touches capacitives
  4. Timer ce qui nous intéresse ici
  5. ULP co-processeur Ultra Low Power
  6. Inconnu généralement après une mise à jour du firmware

Connaissant le numéro du réveil, il suffit de passer un booléen à True que l’on testera à la mise en veille. Et voilà, le tour est joué !

void low_energy()
{
    if ( watch->bl->isOn()) {
      if ( return_to_deepsleep ) {
        goToDeepSleep();  // Retour en veille profonde
      } else {  
        mise en veille légère (light sleep)
        ...
      }
    } else {
       allume l'écran et les périphériques de la montre
       ...
    }
} 

Code du projet

Voici le code source Arduino complet de l’exemple que vous pouvez également récupérer directement sur GitHub. Le projet peut être compilé à l’aide de l’IDE Arduino ou de PlatformIO.

Le code source peut servir de base pour le développement de votre propre projet de montre connectée.

/* Arduino IDE - uncomment your watch */
//#define LILYGO_WATCH_2019_WITH_TOUCH
//#define  LILYGO_WATCH_2019_NO_TOUCH
//#define LILYGO_WATCH_BLOCK
//#define LILYGO_WATCH_2020_V1
/* PlatformIO -> Select your watch in platformio.ini file */
#include 
#include 
#include "esp_sleep.h"

/**************************/
/*    Static variables    */
/**************************/
TTGOClass *watch = nullptr;
BMA *sensor;
AXP20X_Class *power;
TFT_eSPI *tft = nullptr;
bool KeyPressed = false;
bool lenergy = false;
int awake = 0;
int seconde = 0;
int timeleft = 0;
static bool irq_axp202 = false;
static bool irq_bma = false;
static bool return_to_deepsleep = false;

#define DEFAULT_SCREEN_TIMEOUT  5*1000

#define uS_TO_S_FACTOR 1000000ULL  /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP  30          /* Time ESP32 will go to sleep (in seconds) */

/**************************/
/*   STATIC PROTOTYPES    */
/**************************/
void buildTFTPage(int timeleft);
void goToDeepSleep();
void resetChrono();
int get_wakeup_reason();

/**************************/
/*   Switch On/Off power  */
/**************************/
void low_energy()
{
    if ( watch->bl->isOn()) {
      if ( return_to_deepsleep ) {
        goToDeepSleep();
      } else {  
        // Si le rétro-éclairage est actif >> passe en mode light sleep. Le Core de l'ESP32 reste actif
        Serial.println("BL is ON >> activate light sleep");
        watch->closeBL();
        watch->bma->enableStepCountInterrupt(false);
        watch->displaySleep();
        lenergy = true;
        // Diminue la fréquence du CPU à 10MHz pour réduire la consommation
        setCpuFrequencyMhz(10);
        Serial.println("ENTER IN LIGHT SLEEP MODE");
        delay(50);
        // Réveil avec le bouton principal
        gpio_wakeup_enable ((gpio_num_t)AXP202_INT, GPIO_INTR_LOW_LEVEL);  
        // Réveil avec l'accéléromètre
        gpio_wakeup_enable ((gpio_num_t)BMA423_INT1, GPIO_INTR_LOW_LEVEL);
        // Active le réveille depuis le GPIO  
        esp_sleep_enable_gpio_wakeup();
        // Met en veille légère. Le Core du CPU continue de fonctionner
        esp_light_sleep_start();
      }  
    } else {
      // The backlight is off   
      // Le rétro-éclairage est éteint    
      lenergy = false;
      setCpuFrequencyMhz(160);
      resetChrono();
      Serial.println("Wake-up ESP32 and accessories");
      watch->displayWakeup();
      watch->openBL();
      watch->rtc->syncToSystem();
      delay(100);
    }
}
void setup() {
  Serial.begin(115200);

  // Retourne en sommeil profond si le réveil a été causé par le timer
  if ( get_wakeup_reason() == 4 ) return_to_deepsleep = true;

  watch = TTGOClass::getWatch();
    // Initialize the hardware
  watch->begin();
  power = watch->power;

  // Turn on the backlight | Allume le rétro-éclairage
  watch->openBL();
  
  // T-Watch with user button only
  watch->button->setClickHandler(goToDeepSleep);
  
  // Turn on the IRQ used
  watch->power->adc1Enable(AXP202_BATT_VOL_ADC1 | AXP202_BATT_CUR_ADC1 | AXP202_VBUS_VOL_ADC1 | AXP202_VBUS_CUR_ADC1, AXP202_ON);
  watch->power->enableIRQ(AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ, AXP202_ON);
  watch->power->clearIRQ();
  
  // Turn off unused power
  watch->power->setPowerOutPut(AXP202_EXTEN, AXP202_OFF);
  watch->power->setPowerOutPut(AXP202_DCDC2, AXP202_OFF);
  watch->power->setPowerOutPut(AXP202_LDO3, AXP202_OFF);
  watch->power->setPowerOutPut(AXP202_LDO4, AXP202_OFF);   
  
  //Connection interrupted to the specified pin
  pinMode(BMA423_INT1, INPUT);
  attachInterrupt(BMA423_INT1, [] {
      irq_bma = true;
  }, RISING);

  sensor = watch->bma;

  // Paramètre de l'accéléromètre BMA423 (optionnel)
  Acfg cfg;
  cfg.odr = BMA4_OUTPUT_DATA_RATE_100HZ;
  cfg.range = BMA4_ACCEL_RANGE_4G;
  cfg.bandwidth = BMA4_ACCEL_NORMAL_AVG4;
  cfg.perf_mode = BMA4_CONTINUOUS_MODE;
  sensor->accelConfig(cfg);
  
  // Active le BMA 423
  sensor->enableAccel();

  // Active la fonction de réveil du BMA423
  sensor->enableFeature(BMA423_WAKEUP, true);
  
  // Envoi un signal sur la broche 39 de l'ESP32 dès qu'un mouvement est détecté
  sensor->enableWakeupInterrupt();

  // Interruption qui permet de mettre en veille légère ou réveiller l'écran
  pinMode(AXP202_INT, INPUT);
  attachInterrupt(AXP202_INT, [] {
      irq_axp202 = true;
  }, FALLING);
  watch->power->enableIRQ(AXP202_PEK_SHORTPRESS_IRQ | AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_CHARGING_IRQ, true);
  watch->power->clearIRQ();

  // Démarre le chronomètre pour la mise en veille automatique
  awake = millis();
  resetChrono();
  // Build landing page 
  //créé la page principale
  buildTFTPage(timeleft);
}

void loop() {
  int16_t x, y;
  bool rlst;
  
  if ( watch->getTouch(x,y) ) {
    while (watch->getTouch(x, y) ) {}
    // Relance le chrono lorsque l'utilisateur touche l'écran
    awake = millis();
  }
  // Lorsqu'on dépasse le temps d'inactivité >> met en veille l'écran et les périphériques de la montre
  if ( millis() - awake > DEFAULT_SCREEN_TIMEOUT) {
    if ( !lenergy) {
      awake = millis();
      low_energy();
    }  
  }
  if ( millis() - seconde >= 1000 ) {
    if ( !lenergy) {
      seconde = millis();
      timeleft --;
      buildTFTPage(timeleft);
    }  
  }
  // L'utilisateur vient d'appuyer sur le bouton principal
  if (irq_axp202) {
    Serial.println("irq detected");
    irq_axp202 = false;
    watch->power->readIRQ();
    if ( watch->power->isPEKShortPressIRQ() ) {
      Serial.println("Power button pressed >> wakeup / switch on light sleep");
      low_energy();
    }     
    watch->power->clearIRQ();
  }  
  // L'utilisateur vient de tapoter l'écran
  if ( irq_bma ) {
    Serial.println("irq_bma detectee");
    irq_bma = false;   
    resetChrono();   
    Serial.println("Power button pressed >> wakeup / switch on light sleep");
    do {
      rlst = watch->bma->readInterrupt();
    } while (!rlst);
    low_energy();
  }  
  // On vérifie à chaque passage dans la bouche l'état du bouton utilisateur
  watch->button->loop();  
}
// Reset du chronomètre pour la mise en veille automatique de l'écran (mode light sleep)
void resetChrono()
{
  seconde = millis();
  awake = millis();
  timeleft = DEFAULT_SCREEN_TIMEOUT / 1000;
  buildTFTPage(timeleft);
}
void buildTFTPage(int timeleft){
  TFT_eSPI *tft = watch->tft;
  tft->fillScreen(TFT_BLACK);
  tft->setTextSize(2);
  tft->setTextColor(TFT_WHITE);
  tft->drawString("T-Watch Sleep Demo", 0,0);
  tft->drawFastHLine(0,20,240,TFT_WHITE);
  tft->drawString("Light sleep in", 10,60);
  char buf[20];
  tft->setTextSize(4);
  sprintf(buf, "%u s", timeleft);
  tft->drawString(buf, 50,100);
}

// Permet d'activer le mode deep sleep et le timer pour un éveil périodique (enregistreur de données par exemple)
void goToDeepSleep()
{
    Serial.println("Go To Deep Sleep Mode");
    
    // Set screen and touch to sleep mode
    watch->displaySleep();
    /*
    When using T - Watch2020V1, you can directly call power->powerOff(),
    if you use the 2019 version of TWatch, choose to turn off
    according to the power you need to turn off
    */
#ifdef LILYGO_WATCH_2020_V1
    watch->powerOff();
    // LDO2 is used to power the display, and LDO2 can be turned off if needed
    // power->setPowerOutPut(AXP202_LDO2, false);
#else
    power->setPowerOutPut(AXP202_LDO3, false);
    power->setPowerOutPut(AXP202_LDO4, false);
    power->setPowerOutPut(AXP202_LDO2, false);
    // The following power channels are not used
    power->setPowerOutPut(AXP202_EXTEN, false);
    power->setPowerOutPut(AXP202_DCDC2, false);
#endif
    esp_sleep_enable_ext0_wakeup((gpio_num_t)AXP202_INT, LOW);
    
    // Activate Timer Wakeup. Usefull for a GPS trcker for example
    // Active le réveil automatique. Utile pour un tracker GPS
    esp_sleep_enable_timer_wakeup(uS_TO_S_FACTOR * TIME_TO_SLEEP);

    esp_deep_sleep_start();
}

// Indique la raison du réveil de l'ESP32
int get_wakeup_reason(){
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason)
  {
    case 1  : Serial.printf("Wakeup caused by external signal using RTC_IO %u \n", wakeup_reason); break;
    case 2  : Serial.printf("Wakeup caused by external signal using RTC_CNTL %u \n", wakeup_reason); break;
    case 3  : Serial.printf("Wakeup caused by touchpad %u \n", wakeup_reason); break;
    case 4  : Serial.printf("Wakeup caused by timer %u \n", wakeup_reason); break;
    case 5  : Serial.printf("Wakeup caused by ULP program %u \n", wakeup_reason); break;
    default : Serial.printf("Wakeup was not caused by deep sleep %u \n", wakeup_reason); break;
  }
  return wakeup_reason;
}
[env:ttgo-t-watch]
platform = espressif32
board = ttgo-t-watch
framework = arduino
build_flags =
    -D LILYGO_WATCH_2019_WITH_TOUCH=1   
    ;-D LILYGO_WATCH_2019_NO_TOUCH=1
    ;-D LILYGO_WATCH_BLOCK=1
    ;-D LILYGO_WATCH_2020_V1=1
    ;-D LILYGO_WATCH_LVGL=1
    ; Important, activate LVGL support
    ;-D LILYGO_WATCH_LVGL=1
lib_deps =
    TTGO TWatch Library
upload_speed = 2000000
monitor_speed = 115200

Le code source a été testé sur la T-Watch Touch 2019

Ainsi que sur la T-Watch 2020

T-Watch et cartes d’extension

Mises à jour

27/11/2020 Publication de l’article

English Version

Avez-vous aimé cet article ?

[Total: 0 Moyenne: 0]