Paper S3 wake on IMU?
-
The Paper S3 has a BMI 270 that should let it wake from sleep when moved. Has anyone figured out how to do this from deep sleep or light sleep? Me, ChatGPT, and the M5Stack chat tool have not been able to figure it out! We get it to sleep but cannot wake it from the IMU
-
Hello @alexxai
short answer: not possible
long answer: the IMU interrupt line (INT1) is not connected to any ESP32S3 GPIO and therefore it cannot wake up ESP32S3 from deep or light sleep. See schematics here.
That said IMU interrupt line is connected to the power on / off logic (IC PMS150 via E_TRIG) so it should be able to turn on M5PaperS3.
Thanks
Felix -
@felmue Thanks so much for your reply and clear info - and sorry for my delay in replying, I didn't get an email alert that there was a response.
Can you elaborate on the PMS150 idea - basically I should power off the device fully and then use the IMU int to power it up again when movement is sensed?
On the store page for the device it mentions "wake-up by lifting", which is why I thought I could kick it out of light or deep sleep with IMU movement. But I guess this is what they mean by that?
-
Hello @alexxai
yes, that is what I meant. Setup IMU for wake on movement, then power M5PaperS3 off (which turns off everything except IMU and PMS150) and when IMU is triggered it turns M5PaperS3 on again.
Well, marketing speech isn't always crystal clear. That is why I try to distinguish between power on/off (or turn on/off) the device vs ESP32 deep and light sleep.
Thanks
Felix -
@felmue Thanks again for your quick and informative reply! Your advice got me on the right track, you're a life saver!
I was able to power down the Paper S3 and have it reboot from IMU movement, which was what I wanted. My remaining challenge is that when powered down I'm still reading ~2.4mA on my USB power meter (with battery disconnected). I'm not sure what could still be drawing, from what I'm reading the BMI270 and PMS150 should be <1mA together? If you have any insight on getting this power draw down please let me know.
For anyone else that's trying to do this, I'm attaching a working example for the Paper S3. It uses the Bosch BMI270 API. To run the below example, copy/paste the example code below into the Arduino IDE and save it. Then copy bmi2.h, bmi2.c, bmi2_defs.h, bmi270.c, and bmi270.h from the Bosch API into the directory with your saved example script. It will look something like this:
Then flash the example code to your Paper S3. You should see the S3 device boot up and then power down, showing this on the screen. If you move the device you'll hear it wake up (if you have USB audio alerts you'll hear it reconnect to your computer to you know it worked!)
Example code:
/** * PaperS3 + BMI270 "Move-to-Reboot" Demo * * Motion-activated power-on using M5Stack PaperS3 with Bosch BMI270 IMU. * Device powers off and wakes up via motion detection through BMI270 any-motion interrupt. * * Hardware: M5Stack PaperS3 * Authors: Lex Kravitz and Cursor AI, 2025 */ // Include required libraries #include <M5Unified.h> // M5Stack unified framework for PaperS3 #include <Wire.h> // Arduino I2C communication #include "bmi2.h" // Bosch BMI2 base driver #include "bmi270.h" // Bosch BMI270 specific driver // PaperS3 I2C pin configuration static const int I2C_SDA = 41; // I2C Data pin static const int I2C_SCL = 42; // I2C Clock pin static const uint32_t I2C_FREQ = 400000; // I2C frequency (400kHz) // BMI270 I2C address configuration static const uint8_t BMI270_I2C_ADDR = 0x68; // Any-motion detection sensitivity configuration - control the sensitivity of motion detection static const uint8_t ANYMOTION_THRESHOLD = 50; // Threshold in Bosch units (~0.1 g) static const uint8_t ANYMOTION_DURATION = 2; // Duration in samples at current ODR // Global Bosch driver device object static bmi2_dev dev; // Display functions void initDisplay() { M5.Display.clear(); M5.Display.setTextColor(BLACK, WHITE); M5.Display.setFont(&fonts::Font4); M5.Display.setRotation(1); M5.Display.setTextDatum(middle_center); } void showScreen(const String& title, const String& status) { M5.Display.drawString("PAPER S3", M5.Display.width()/2, 100); M5.Display.drawString(title, M5.Display.width()/2, 160); M5.Display.drawString(status, M5.Display.width()/2, 220); M5.Display.display(); } // Bosch BMI270 API glue functions static int8_t i2c_read(uint8_t reg_addr, uint8_t* data, uint32_t len, void* intf_ptr) { uint8_t addr = (uint8_t)(uintptr_t)intf_ptr; Wire.beginTransmission(addr); Wire.write(reg_addr); if (Wire.endTransmission(false) != 0) return -1; Wire.requestFrom(addr, (uint8_t)len); for (uint32_t i = 0; i < len; ++i) { if (!Wire.available()) return -1; data[i] = Wire.read(); } return 0; } static int8_t i2c_write(uint8_t reg_addr, const uint8_t* data, uint32_t len, void* intf_ptr) { uint8_t addr = (uint8_t)(uintptr_t)intf_ptr; Wire.beginTransmission(addr); Wire.write(reg_addr); for (uint32_t i = 0; i < len; ++i) Wire.write(data[i]); return (Wire.endTransmission() == 0) ? 0 : -1; } static void delay_us(uint32_t period, void* /*intf_ptr*/) { delayMicroseconds(period); } // BMI270 setup static bool bmi270_setup_anymotion_latched() { int8_t rslt; dev.intf = BMI2_I2C_INTF; dev.read = i2c_read; dev.write = i2c_write; dev.delay_us = delay_us; dev.read_write_len = 32; dev.config_file_ptr = NULL; dev.intf_ptr = (void*)(uintptr_t)BMI270_I2C_ADDR; rslt = bmi270_init(&dev); if (rslt != BMI2_OK) return false; uint8_t sensors[2] = { BMI2_ACCEL, BMI2_ANY_MOTION }; rslt = bmi270_sensor_enable(sensors, 2, &dev); if (rslt != BMI2_OK) return false; bmi2_sens_config cfg{}; cfg.type = BMI2_ANY_MOTION; if (bmi270_get_sensor_config(&cfg, 1, &dev) != BMI2_OK) return false; cfg.cfg.any_motion.threshold = ANYMOTION_THRESHOLD; cfg.cfg.any_motion.duration = ANYMOTION_DURATION; if (bmi270_set_sensor_config(&cfg, 1, &dev) != BMI2_OK) return false; bmi2_int_pin_config int_cfg{}; int_cfg.pin_type = BMI2_INT1; int_cfg.int_latch = BMI2_INT_LATCH; int_cfg.pin_cfg[0].lvl = BMI2_INT_ACTIVE_HIGH; int_cfg.pin_cfg[0].od = BMI2_INT_PUSH_PULL; int_cfg.pin_cfg[0].output_en = BMI2_INT_OUTPUT_ENABLE; int_cfg.pin_cfg[0].input_en = BMI2_INT_INPUT_DISABLE; if (bmi2_set_int_pin_config(&int_cfg, &dev) != BMI2_OK) return false; bmi2_sens_int_config sens_int{}; sens_int.type = BMI2_ANY_MOTION; sens_int.hw_int_pin = BMI2_INT1; if (bmi270_map_feat_int(&sens_int, 1, &dev) != BMI2_OK) return false; // Enable advanced power save mode for lower power consumption rslt = bmi2_set_adv_power_save(BMI2_ENABLE, &dev); if (rslt != BMI2_OK) { Serial.println("Warning: Failed to enable BMI270 advanced power save mode"); } else { Serial.println("BMI270 advanced power save mode enabled"); } return true; } // Clear latched interrupt static void bmi270_clear_latched_if_any() { uint16_t st = 0; if (bmi2_get_int_status(&st, &dev) == BMI2_OK && st != 0) { delay(1); bmi2_get_int_status(&st, &dev); // re-read to clear } } void setup() { auto cfg = M5.config(); M5.begin(cfg); initDisplay(); showScreen("REBOOTING...", "Initializing BMI270..."); delay(2000); Wire.begin(I2C_SDA, I2C_SCL, I2C_FREQ); bmi270_setup_anymotion_latched(); bmi270_clear_latched_if_any(); // Configure BMI270 for minimal power consumption Serial.println("Configuring BMI270 for minimal power..."); // Set accelerometer to lowest power mode (if supported) bmi2_sens_config accel_cfg{}; accel_cfg.type = BMI2_ACCEL; if (bmi270_get_sensor_config(&accel_cfg, 1, &dev) == BMI2_OK) { // Set to lowest ODR (Output Data Rate) for power savings accel_cfg.cfg.acc.odr = BMI2_ACC_ODR_0_78HZ; // Lowest ODR accel_cfg.cfg.acc.range = BMI2_ACC_RANGE_2G; // Lower range = less power accel_cfg.cfg.acc.bwp = BMI2_ACC_NORMAL_AVG4; // Normal bandwidth if (bmi270_set_sensor_config(&accel_cfg, 1, &dev) == BMI2_OK) { Serial.println("BMI270 accelerometer configured for low power"); } } // Power down the device showScreen("POWERED DOWN", "Move device to wake"); delay(2000); M5.Power.powerOff(); } void loop() { // The code never gets here }