/* Basic deep sleep sketch based on examples provided with xiao-esp32-s3 *** PRE-ALPHA *** version rc-0; updated to use Serial1 UNTESTED Copyright 2024 @jstockdale/Off by One, Inc. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. by @jstockdale */ #define LED_PIN LED_BUILTIN // built in LED solid briefly on wakeup/boot, flashes dim while sensing, flashes bright if it detects t-beam is off or unresponsive #define SENSE_PIN A9 // one method we use to determine state is via an analog voltage threshold on GPIO0 #define POWER_PIN D8 // connect floating leg of t-beam power button to D8; used to power on the t-beam normally #define RESET_PIN D10 // connect RST on t-beam to D10; used to hard boot if the t-beam has power but no uart activity #define UART_RX_PIN D7 // connect TX on t-beam to D7; we read the tx line to see if the t-beam is alive or "stuck" #define UART_TX_PIN D6 // this can be a no connect; not currently used #define UART_BAUD 38400 // Any baudrate from 300 to 115200 #define MIN_LOOPS 100 #define MAX_LOOPS 600 #define WATCH_LOOPS 100 int sensorValue = 0; // variable to store the analog value from SENSE_PIN bool detectBool = false; int detectCount = 0; int okCount = 0; volatile int loopCount = 0; // used to track our total number of elapsed loops during boot and each wake cycle volatile int targetLoops = MIN_LOOPS; // current target number of loops; we add WATCH_LOOPS to the current loop count each time we reset the device to extend the runtime of the watchdog #define uS_TO_S_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */ #define TIME_TO_SLEEP 600 /* Time ESP32 will go to sleep (in seconds) */ RTC_DATA_ATTR int bootCount = 0; /* Method to print the reason by which ESP32 has been awaken from sleep */ void print_wakeup_reason() { esp_sleep_wakeup_cause_t wakeup_reason; wakeup_reason = esp_sleep_get_wakeup_cause(); switch (wakeup_reason) { case ESP_SLEEP_WAKEUP_EXT0: Serial.println("Wakeup caused by external signal using RTC_IO"); break; case ESP_SLEEP_WAKEUP_EXT1: Serial.println("Wakeup caused by external signal using RTC_CNTL"); break; case ESP_SLEEP_WAKEUP_TIMER: Serial.println("Wakeup caused by timer"); break; case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println("Wakeup caused by touchpad"); break; case ESP_SLEEP_WAKEUP_ULP: Serial.println("Wakeup caused by ULP program"); break; default: Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason); break; } } // volatile declaration will avoid any compiler optimization when reading variable values volatile size_t uart_received_bytes = 0; void onReceiveFunction(void) { // This is a callback function that will be activated on UART RX events size_t available = Serial1.available(); uart_received_bytes = uart_received_bytes + available; Serial.printf("onReceive Callback:: There are %d bytes available: ", available); while (available--) { Serial.print((char)Serial1.read()); } Serial.println(); } void setup() { // set up POWER_PIN and RESET_PIN as INPUT so that they are high impedence // we will set them to OUTPUT only when powering or resetting the device pinMode(POWER_PIN, INPUT); pinMode(RESET_PIN, INPUT); // GPIO0 (SENSE_PIN) sense pin is always INPUT pinMode(SENSE_PIN, INPUT); // built-in led is always OUTPUT pinMode(LED_PIN, OUTPUT); Serial.begin(115200); // setup serial Serial1.begin(UART_BAUD, SERIAL_8N1, UART_RX_PIN, UART_TX_PIN); delay(250); // wait a bit to let serial startup //Increment wake number and print it every wake cycle ++bootCount; Serial.println("Sleep cycle count: " + String(bootCount)); //Print the wakeup reason for ESP32 print_wakeup_reason(); /* here we configure the wake up source we set our ESP32 to wake up every TIME_TO_SLEEP seconds */ esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR); Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) + " Seconds"); } // loop until we think we should go to sleep void loop() { // turn on the built-in LED digitalWrite(LED_PIN, LOW); // bitbang read the uartTxPin for uartTxReadCycles to hopefully see a HIGH (1) // for(int i = 0; i < uartTxReadCycles; i++) { // uartTxValue = digitalRead(uartTxPin); // if(uartTxValue == HIGH) { // // we only care that the UART went high while we were looking // // UART is high between transmissions if the device is on and responding // // UART floats around 0V and reads LOW when the t-beam is powered but "stuck" (requires hard boot) // // since we think the device is okay, we won't reset after pressing power // resetAfterPower = false; // break; // } // } sensorValue = analogRead(SENSE_PIN); Serial.println("State: " + String(sensorValue) + ", " + String(uart_received_bytes)); digitalWrite(LED_PIN, HIGH); if(sensorValue > 2000 && uart_received_bytes > 0) { // if GPIO0 is above 2000 when reading the ADC and the UART has received data // we think the t-beam is turned on and most likely functioning properly // turn off the led, count the loop, and zero the detect count digitalWrite(LED_PIN, HIGH); detectCount = 0; ++okCount; } else { // if sensorValue is 2000 or less, the device is probably off // if sensorvalue is 4095 and uart_received_bytes == 0, the device is probably "stuck" digitalWrite(LED_PIN, LOW); delay(5); digitalWrite(LED_PIN, HIGH); okCount = 0; ++detectCount; detectBool = true; Serial.println(detectCount); // we add one to targetLoops (up to a max of MAX_LOOPS) if the device is off // this behaves well since the UART tx detection isn't perfect but is a weird hack // TODO(jstockdale): fix the way the various loop limits interact if(targetLoops < MAX_LOOPS) { ++targetLoops; Serial.println("++targetLoops"); } } if(detectCount >= WATCH_LOOPS) { // we have observed the t-beam to be off for WATCH_LOOPS consecutive sense-loops // we will power on the device by pulling the POWER_PIN to ground for 500ms Serial.println("Powering ESP32-S3 ON"); digitalWrite(POWER_PIN, HIGH); pinMode(POWER_PIN, OUTPUT); digitalWrite(POWER_PIN, LOW); digitalWrite(LED_PIN, LOW); delay(500); digitalWrite(POWER_PIN, HIGH); digitalWrite(LED_PIN, HIGH); pinMode(POWER_PIN, INPUT); delay(100); if(uart_received_bytes == 0) { // since we didn't see any serial data tx from the t-beam, we think it is "stuck" // hard boot / reset the device by pulling RESET_PIN to ground for 500ms Serial.println("Resetting ESP32-S3 by pulling RST to GND"); digitalWrite(RESET_PIN, HIGH); pinMode(RESET_PIN, OUTPUT); digitalWrite(RESET_PIN, LOW); delay(500); digitalWrite(RESET_PIN, HIGH); pinMode(RESET_PIN, INPUT); delay(100); } // zero counters okCount = 0; detectCount = 0; // increase targetLoops by WATCH_LOOPS so we can see if we're successful or try until MAX_LOOPS if (targetLoops < MAX_LOOPS) { Serial.println("Current loop count: " + String(loopCount)); Serial.println("Updating target loops from: " + String(targetLoops)); targetLoops = (loopCount + 1 + WATCH_LOOPS > MAX_LOOPS) ? MAX_LOOPS : loopCount + 1 + WATCH_LOOPS; Serial.println("Updated target loops to: " + String(targetLoops)); } } // deep sleep to save power if we've seen WATCH_LOOPS consecutive ok loops // or performed MIN_LOOPS sense-loops where the t-beam is always-on // TODO(jstockdale): improve this logic if(okCount > WATCH_LOOPS || (detectBool == false && loopCount > MIN_LOOPS) || loopCount > targetLoops) { Serial.println("Going to sleep now"); Serial.flush(); esp_deep_sleep_start(); } ++loopCount; delay(100); }