m5stack/M5StickC

Enabling wake-on-motion for MPU6886

standarddeviant opened this issue · 10 comments

I made a proof of concept Arduino sketch to enable wake-on-motion for the MPU6886 inside the M5StickC. I'd like to fork the repo, add support for wake-on-motion (to GPIO Pin 35), and then submit a pull request. Is there a testing/integration process for doing that or coding guidelines I should follow?

For those curious, the proof-of-concept sketch is here:
https://gist.github.com/standarddeviant/ea0b7f12a32bf5de96992a8ef350351d

A demo video of that sketch is here:
https://www.youtube.com/watch?v=v5GpsvjFsEw

I documented a few notes on this here on the m5stack community page:
https://community.m5stack.com/topic/2039/wake-up-on-pick-up

Cheers!

I'm playing around with receiving interrupts from the AXP192 power managment chip, so here are some thoughts:

  • The AXP, MPU6886 and RTC are all connected to the SYS_INT pin (35)
  • Both the AXP and the RTC only have active-low, open drain interrupt pins (they can pull the line LOW, but not set it HIGH)
  • The ESP has no option for internal pull-up on pin 35
  • It is not documented whether the pin has an external pull-up on M5StickC, although I'm guessing it has one otherwise the example solution in #110 (specifically commit df5c99f) would not work (setting MPU6886 to active LOW and open drain, also)

-> I think therefore the best solution to make all 3 interrupts work is to configure all as active low, open drain and register the ISR routine as FALLING edge. The ISR can then set a flag as in your code, and the loop must read the status of all devices whose interrupt is active (in your project this is just the IMU, for me it's just the AXP, but it would be exciting to have all 3 possible, e.g. with the wake-up when power is supplied or power button is short pressed etc.)

If I get any further with this, I might also be submitting a PR to correctly init the INT_PIN_CFG on MPU6886. My idea is to add IMU.Init() (or a new function IMU.Reset()) to the M5.begin() method which sets the interrupts correctly and possibly puts the IMU in low power state. Then, if someone actually needs to use the IMU, they call IMU.Init() maybe with optional arguments to enable only Accel or Gyro, whether to enable interrupt and so forth.

@ldoyle That sounds interesting to me! I have wake-on-motion working from deep sleep on the ESP32 - I'll share a gist of that once I iron out a few things. I'd be happy to collaborate on APIs etc. That's neat info about the active-low open-drain config of the MPU6886 - I just checked the datasheet for myself. I plan to put some basic MPU6886 config logic in to a branch soon - maybe adding a special argument to enable "wake-on-motion" interrupts. Let's keep chatting!

@ldoyle I made a proof-of-concept that wakes up on motion from deep sleep, per your suggestion. It doesn't configure as open-drain, but it could probably be made to work that way. I'm a bit naive on details re: open-drain vs. not. Here's my updated sketch that just counts the number of boots as a variable in RTC memory. That sketch is here: https://gist.github.com/standarddeviant/85c31cf34eb51e10aa3bb02dcd0bcbd1

@ldoyle I want to clean up the API/example a bit, but I've made some progress here: https://github.com/standarddeviant/M5StickC/tree/mpu6886_wake_on_motion/examples/Advanced/IMU_Wake_On_Motion

I'm wondering if certain use-cases for waking up from GPIO35 from the 3 sources should coordinate. I'm thinking about better APIs to accomplish that example. There's still a few finnicky things in there that might intimidate newbies. Specifically, I'm thinking of more convenient APIs to handle the rtc_gpio_[de]init logic and where those APIs belong - i.e. AXP192 vs. IMU vs. RTC. More to come. :-)

I didn't have time to test much, but here are some thoughts:

  • For the open-drain, just change your line WOM_STEP2P5_INT_PIN_CFG_ACTIVE_LOW_NO_LATCH(r) ((r | 0x88) & 0xDF) to ((r | 0xC0) & 0xDF), so bit 6 to high, FSYNC seems to be disabled/not used, so no need to set bit 3 (counting from 0).
  • I like your style of reading + modifying only the necessary bits + writing to each register. Did you know you can also specify a number 0xC0 as 0b11000000, maybe this is more readable here?
  • As a personal side note, for me the definition of Macros does not make these bit manipulations more readable. Just putting regdata = ((regdata | 0b11000000) & 0b11011111) I can immediately see on the current line which bits are changed, and the comment which bit does what then can also be right there with the register name etc. Also, as I recently learned flooding the code with defines can have unwanted effects (see issue #113, although this problem only occurs with #defines in headers, yours in the .cpp file should not be a problem)
  • Are you sure you need the rtc_gpio_[de]init? Does it still work if you comment them out? I tested a simple script using button A (GPIO37) as interrupt source (pinMode(INPUT) and attachInterrupt) and also as deep sleep wakeup source, and it works fine without the init/deinit. I could not find clear documentation on this, but the doc suggests rtc_gpio_init is necessary only when using the analog functions and maybe when using rtc_gpio_hold_en. My guess (could be wrong) would be in deep sleep, the pin is configured as RTC pin anyway since the main processor won't need it, and once we're back in the main program the default GPIO mode is fine.
  • Why do you temporarily disable interrupts when waiting for the sleep? If it is to protect the read value of g_wom_last_millis from being corrupted? I might be too unexperienced here with ESP or C++ in general, but I think it should be fine without since you're only accessing that value a single time, after which a valid number in memory is used for subtraction etc. Or is it somehow because of millis()?
  • One thing that did not occur to me until now is if we do get it working to wake up from e.g. WOM, power button press, USB connect, real-time-clock alarm, then somehow we have to determine the reason for interrupt without e.g. resetting it (so before MPU6886.Init() currently).
  • Even without deep sleep, there can only be one "SYS_INT pin interrupt handler", and there has to be similar code which checks where the interrupt came from and could then possibly call further, user-defined callbacks in turn.
  • Final thought: Have you worked also with other M5Stack devices? I'm unsure how much of the API and code are shared, there seems to be a lot of overlap so my idea of just rigorously changing the registers in Init() would potentially make it harder to keep both libraries in sync and profit from mutual improvments on the long term.

Thanks for the detailed reply! Point-by-point...

  • Re: open-drain, I tried that setting, but it made the sketch not work. I'm curious about that.
  • Expressing registers in 0b... instead of 0x... seems like a good suggestion for readability. I'm okay with hex, and I still messed a few things up along the way. :-) I'll change that soon.
  • Yeah, I think doing away with the macros and clearly commenting each section makes sense. That's interesting and concerning about lots of #defines.
  • I don't think I need the rtc_gpio_deinit, but I'm pretty sure I need the rtc_gpio_init before going to deep sleep. I'll test this thoroughly at some point.
  • Disabling interrupts to read variables set by an interrupt is good defensive programming when reading multiple values set by an interrupt. Setting that global g_wom_last_millis is probably a single clock cycle, so in this case it's probably overly paranoid. This example should work without the disable/re-enable of interrupts.
  • For determining what triggered wake or SYS_INT from GPIO35, that might require reading various states of AXP/MPU/RTC. This probably requires digging in to multiple datasheets maybe along with careful init procedure, etc. This seems like a longer term goal, but interesting.
  • I have not worked with other M5Stack devices before, but I did get an M5Atom Matrix with my M5Stick-C. That model has the same MPU, but I don't know if SYS_INT is wired internally. I plan to check that wiring connection to GPIO35 (or some other pin) empirically. The same logic might be nice on that board too.

I'll ping this thread when I've made and tested those changes.

@ldoyle I made the changes you suggested. You were right about the rtc_gpio_[de]init - they don't seem to be needed. I still can't get the open drain to work - not sure what that's all about. After reading a bit on EIS FSYNC, that's definitely not needed for wake-on-motion and I think has to do with integrating in to a camera for image stabilization.

I also got rid of the macros and now use binary masks instead of hex masks. I'm going to test a bit more with this, but might submit a PR soon.

Nice to see you're making progress, I haven't found any time to play with my M5 unfortunately. Yeah, some tutorials seem to have the gpio_init, some not. Interesting point about disabling interrupts, you're absolutely right once you read both variables set by the Irq things can be confusing without the "lock".

I'm not sure what is going wrong with the open-drain issue, I got my Axp interrupt working according to #110, specifically by setting also the IMU (although not in use) to active-low with this command:

// set int active is LOW
M5.I2C.writeByte(0x68, 0x37, 0xc8); //actually also sets open-drain

I can think of 2 things that might be going wrong:

  • Another interrupt of either Axp or RTC is asserted, pulling the line LOW all the time. Maybe you can debug by just printing the state of Pin35 periodically on the LCD. The code in #110 disables and clears all interrupts on Axp and IMU before proceeding to only enable those desired. Maybe this is necessary in your case, too.
  • Since it is not documented, there is a small possibility that board versions exists without real pull-up. Mine seems to work and is labelled on the back side as 211-190415 and I bought it several weeks ago in Germany. Maybe yours is an older revision?

Both ways Push-Pull would work while open-drain does not because:
IMU has interrupt, sets line LOW, Axp or RTC has no interrupt: Pin35 is LOW correctly
IMU has interrupt, sets line LOW, Axp or RTC has interrupt (pulls LOW which already is): Pin35 is LOW correctly
IMU has no interrupt, sets line HIGH, Axp or RTC has no interrupt, so essentially "disconnected": Pin35 is HIGH correctly
IMU has no interrupt, sets line HIGH, Axp or RTC has an interrupt pulling to LOW: depending on internal resistances Pin35 might measure LOW correctly, but a high current can flow out of the IMU high pin into the e.g. Axp low pin, worst case maybe harming the device.

Hi Guys, there is official example for wake-up-on-motion MPU here: https://github.com/m5stack/M5StickC/blob/master/examples/Advanced/IMU_Wake_On_Motion/IMU_Wake_On_Motion.ino

Is it the same one you all been looking for?

Hi Zontex,
as you can see from the linked merge request, it's actually @standarddeviant's work in the official repo now :)
But yes, it seems the issue is therefore solved.