Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Requesting a WAKE example #75

Open
wz2b opened this issue Jun 28, 2022 · 12 comments
Open

Requesting a WAKE example #75

wz2b opened this issue Jun 28, 2022 · 12 comments

Comments

@wz2b
Copy link

wz2b commented Jun 28, 2022

I would like an example of how to use WAKE because I'm not quite sure how it's supposed to work. I'll contribute one if I can, but right now it doesn't work. Should it?

#
# Example of putting the main processor to sleep then waking it up
# via the ULP after a while
#
from esp32 import ULP
from machine import mem32
import machine

from esp32_ulp import src_to_binary

source = """\
#define RTC_CNTL_LOW_POWER_ST_REG 0x3FF480c0
#define RTC_CNTL_RDY_FOR_WAKEUP BIT(19)

entry:
        MOVE r0, 2000

loop:
        WAIT 40000  // at 8 MHz this is 5 msec, if we do this 1000 times that's 5 seconds
        SUB r0, r0, 1
        jump is_rdy_for_wakeup, eq
        jump loop


is_rdy_for_wakeup:                   // Read RTC_CNTL_RDY_FOR_WAKEUP bit
#
# This test was described in https://github.com/espressif/esp-idf/issues/8341
# See reference manual 31.19.
#
# The main issue with this is that it will hang if the main CPU wasn't in deep sleep.
#
       READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
       AND r0, r0, 1
       JUMP is_rdy_for_wakeup, eq    // Retry until the bit is set

       WAKE                          // Trigger wake up
       HALT                          // Stop the ULP program
"""

#
# Compile the ULP code
#
binary = src_to_binary(source)

load_addr, entry_addr = 0, 0
ULP_MEM_BASE = 0x50000000
ULP_DATA_MASK = 0xffff  # ULP data is only in lower 16 bits

ulp = ULP()
ulp.load_binary(load_addr, binary)

#
# The ULP starts out asleep (halted), so set a timer to wake it
# up the first time.
#
# ulp.set_wakeup_period(0, 50000)  # use timer0, wakeup after 50.000 cycles

mem32[ULP_MEM_BASE + load_addr] = 0x1000
ulp.run(entry_addr)

print("going to sleep")

# I think this should enable ULP wakeup
mem32[ 0x3ff48038 ] = 0x200 << 10
machine.deepsleep()


#
# We won't ever reach this, because WAKE is going to cause the main
# processor to reset and start over at the beginning.
#
print("I am awake again")
@wnienhaus
Copy link
Collaborator

Interesting case :)

The ULP code looks correct. (Except if you also want to stop the ULP timer from starting the ULP program again: as per here - but anyway, not related to your issue.)

So my assumption is that ULP wakeup is not enabled.

I see that you attempt to enable it with mem32[ 0x3ff48038 ] = 0x200 << 10, which looks correct, but either that statement has no effect, or the machine.deepsleep() call overwrites this, which feels more likely.

Looking at how Micropython's machine.deepsleep() internally calls esp_deep_sleep_start() from the ESP-IDF (here), and looking at how in turn it is implemented, it appears that ESP-IDF functions like esp_sleep_enable_ulp_wakeup() (as in this example) only set up some config object and then only later esp_sleep_start() will evaluate what's in that config object to determine what state to put the RTC registers into. So if your don't call methods like esp_sleep_enable_ulp_wakeup() the config object would not be configured for "ulp wakeup" and the RTC register at 0x3ff48038 would get set (overwritten) to what the config object contained, rather than what you wanted. That's my assumption...

As I now look at the very latest MicroPython code, I see that support for waking up the main CPU by ULP was just added 5 days ago :) - (commit).

But... until this is released in the next MicroPython version, I would try one thing (my go-to solution for everything of the ESP32 that MicroPython does not support): Try using the ULP to set the necessary register, i.e. do the mem32[ 0x3ff48038 ] = 0x200 << 10 from inside the ULP code.

This works for pretty much anything that I have tried so far (setting RTC GPIOs to the right mode, setting power configuration for the hall and temp sensor, configuring adc's, etc)... so it might work here too. Of course it might also not, because the register value at the time of going to sleep might impact some state elsewhere. But I think it's worth trying.

I would guess the ULP code would be:

WRITE_RTC_REG(RTC_CNTL_WAKEUP_STATE_REG, RTC_CNTL_WAKEUP_ENA_S, 11, 0x200);

but I also see some other approach here in the ESP-IDF, that you can also try, if the above does not work:

WRITE_RTC_FIELD(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_WAKEUP_FORCE_EN, 1);

@wz2b
Copy link
Author

wz2b commented Jun 28, 2022

I looked at the deepsleep() implementation, too, and convinced myself that it was only setting bits, not wiping out the whole wake up enable register ... but I find that code a little hard to follow so I'm not sure.

I'm a little concerned my hardware might be too old for this. A few things I have come across seem to suggest wake on ULP isn't super reliable, and I'm not sure why. The chip reports itself as:

Chip is ESP32D0WDQ6 (revision (unknown 0xa))
Chip ID: 0xbc78e36d10fd

which I think might be pretty old. I am not certain that matters.

Trying to set that flag from the ULP program is a really good idea, I'll try that next. The other thing I thought of is can I tell the python side to deepsleep(50000) then have the ULP program continuously update that number so it doesn't reach the timeout until the ULP halts. I guess my third workaround is to wire a pin from the ULP back to RST but that seems hacky.

Will report back.

@wz2b
Copy link
Author

wz2b commented Jun 28, 2022

I'm not proficient at this yet but I tried:

#define RTC_CNTL_WAKEUP_STATE_REG 0x3FF48038
entry:
        WRITE_RTC_REG(RTC_CNTL_WAKEUP_STATE_REG, 21, 11, 0x200);

which I think is right, and it didn't change the result.

I wonder if my chip is just too old.

@wnienhaus
Copy link
Collaborator

About the age of the chip - I am not sure it matters that much. Our assembler only supports the original ESP32 anyway (not the newer S2, C3, etc models), and the only change I am ware of in the ESP32 is that they had some revision 1, that fixed a few bugs. If I use esptool.py flash_id I get Chip is ESP32D0WDQ6 (revision 1) for mine, so it seems like the same model except the revision might be different (then again 0xa seems bigger than 1 😄 )

So assuming it does not matter, what I am wondering about the code you tried is, whether it runs too early (before the main processor has entered sleep (and effectively overwritten the register content). Then again, your ULP program might re-run many times, so it should eventually get the right value into that register. So probably a wrong assumption.

Perhaps try that "force" approach I mentioned last in my first comment? (also making sure it gets to run after the main processor is in deepsleep).

If that does not work, then maybe the a wire to the RST pin is not too bad a solution (it should work), but it would prevent you from determining the correct wakeup reason.

Regarding updating the deepsleep timeout value - interesting idea. Have you seen anywhere, where one can write/update that value?

Lastly, what MicroPython version are you using? I have a few ESP32s (all are rev 1) with different MicroPython versions, so I could try your code on one of them to see if it works for me.

@wz2b
Copy link
Author

wz2b commented Jun 28, 2022

Started webrepl in normal mode
MicroPython v1.19.1 on 2022-06-18; ESP32 module with ESP32

I'm trying to keep the test as simple as possible, so what it does is calls WAIT 40000 (5 msec, assuming an 8 MHz clock) in a loop 1000 times (so 5 seconds total).

I'll give you the short version. What I expect this to do is to put say "going to sleep" then reset the processor 5 seconds later.

#define RTC_CNTL_LOW_POWER_ST_REG 0x3FF480C0
#define RTC_CNTL_WAKEUP_STATE_REG 0x3FF48038
#define RTC_CNTL_RDY_FOR_WAKEUP BIT(19)

entry:
        // try forcing wake-on-ULP bit to enabled
        WRITE_RTC_REG(RTC_CNTL_WAKEUP_STATE_REG, 21, 11, 0x200);

        // Sleep loop initialization
        MOVE r0, 1000
loop:
        WAIT 40000  // at 8 MHz this should take 5 msec
        SUB r0, r0, 1
        jump wake_up, eq
        jump loop
wake_up:
        READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
        AND r0, r0, 1
        JUMP wake_up, eq    // Retry until the bit is set
        WAKE                          // Trigger wake up
        HALT                          // Stop the ULP program

then invoke it as:

binary = src_to_binary(source)
load_addr, entry_addr = 0, 0
ulp = ULP()
ulp.load_binary(load_addr, binary)
ulp.run(entry_addr)

print("going to sleep")
machine.deepsleep(0)

@wz2b
Copy link
Author

wz2b commented Jun 28, 2022

Regarding updating the deepsleep timeout value - interesting idea. Have you seen anywhere, where one can write/update that value?

Oh ... no. I haven't seen anything saying it's OK to do that. It's a multi-value register, there's no indication in the documentation about how safe it is for concurrent access and it's multiple registers, so I'm a little concerned this is a risky idea haha

@wz2b
Copy link
Author

wz2b commented Jun 28, 2022

Deep sleep is entered here which calls this but I think code-wise this is a dead end, as indicated by this comment:

// TODO: move timer wakeup configuration into a similar function
// once rtc_sleep is opensourced.

Because that function seems to take the wake up sources in as a parameter I think the only option (for now) is what you suggested: enabling ULP wakeup at the start of the ULP code itself.

@wz2b
Copy link
Author

wz2b commented Jun 28, 2022

I added a few simple things to make sure at least my ULP program is running (it is).

entry:
        jump start

data:
data1:  .long 0
data2:  .long 0


start:

        move r3, data
        move r2, 0xDEAD
        st r2, r3, 0
        move r2, 0xBEEF
        st r2, r3, 4


        // Force wake up on ULP on
        WRITE_RTC_REG(RTC_CNTL_WAKEUP_STATE_REG, 21, 11, 0x200);

        MOVE r0, 1000
loop:
        WAIT 40000  // at 8 MHz this is 5 msec, if we do this 1000 times that's 5 seconds
        SUB r0, r0, 1
        jump wake_up, eq
        jump loop

wake_up:
        READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
        AND r0, r0, 1
        JUMP wake_up, eq    // Retry until the bit is set
        WAKE                          // Trigger wake up
        HALT                          // Stop the ULP program

I just slept for 1 second before dumping them out, then slept:

ULP_MEM_BASE = 0x50000000
sleep(1)
print(hex(mem32[ULP_MEM_BASE+4] & 0xFFFFFFFF), hex(mem32[ULP_MEM_BASE+8] & 0xFFFFFFFF))

sleep(1)

print("going to sleep")
machine.deepsleep(0)

I knew from your counter example that would work but I just wanted to try it. So that process takes 2 seconds and the ULP should wake it up 3 seconds after that (it still doesn't)

@wz2b
Copy link
Author

wz2b commented Jun 28, 2022

Did I even tell you what my use case is? I'm making a wireless panic button, running off of a CR123A. That's why power consumption has to be minimal. A button wakes it up, but I want to use the ULP to debounce the button, and I also want to force a wake up once every hour or two just to report that the device is still communicating.

@wz2b
Copy link
Author

wz2b commented Jun 28, 2022

OOOKAY I'm going to go ahead and close this now.

I installed the latest NIGHTLY BUILD of MicroPython and it works. I don't understand why enabling wake-on-ulp in the ULP code didn't work. I think it should have. But regardless, it works.

I still think we should add an example. This is your code, so let me know how to do it:

  • I would be happy to clone your library and submit a P.R.
  • You could just add it yourself
  • We could wait until this micropython support is released, so we don't confuse anybody

I definitely think this deserves an example, though It really enables a lot of battery-powered possibilities even if you want to stick to the micropython world. And figuring this out confused me, because while I'm an experienced microcontroller guy I haven't taken the time to dig into the esp32 - that's partly why I'm using micropython in the first place. So recipes help :) Let me know what you'd like to do.

#
# Example of putting the main processor to sleep then waking it up
# via the ULP after a while
#
from esp32 import ULP
from machine import mem32
import machine

import esp32
from esp32_ulp import src_to_binary

source = """\
#define RTC_CNTL_LOW_POWER_ST_REG 0x3FF480C0
#define RTC_CNTL_WAKEUP_STATE_REG 0x3FF48038
#define RTC_CNTL_RDY_FOR_WAKEUP BIT(19)

#############################################################################

entry:
        jump start


#############################################################################
#
# Declare ram usage here if desired
#
#############################################################################


#############################################################################
start:
        MOVE r0, 2000
loop:
        WAIT 40000  // at 8 MHz this is 5 msec, if we do this 1000 times that's 5 seconds
        SUB r0, r0, 1
        jump wake_up, eq
        jump loop

wake_up:
        READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
        AND r0, r0, 1
        JUMP wake_up, eq    // Retry until the bit is set
        WAKE                          // Trigger wake up
        HALT                          // Stop the ULP program

"""

#
# Compile the ULP code
#
binary = src_to_binary(source)

# load_addr is where to start the program, entry_addr is an offset to
# the start point for cases where some memory (variables) are
# declared before the executable code begins
load_addr, entry_addr = 0, 0

ulp = ULP()
ulp.load_binary(load_addr, binary)
ulp.run(entry_addr)

esp32.wake_on_ulp(True)

print("going to sleep")
machine.deepsleep()

@wnienhaus
Copy link
Collaborator

Ok. Good to know the latest nightly build works. I am still curious however, if we could solve it another way.

One thing I noticed is you were pointing to references in the pycom/esp-idf-2.0 repository. (Unless you were using that somewhere) the best place to look is in espressif's repository and look at version 4.x which is what MicroPython is built against these days.

So the comment // TODO: move timer wakeup configuration into a similar function is actually not there anymore in the latest version of ESP-IDF and they do have the necessary logic (here).

Now looking at your code, where you introduced the 5 second wait, I do have the feeling there is a race there between the ULP and the main processor exactly between the "ulp.run()" call and the "machine.deepsleep()" call. The ulp.run() starts the ULP immediately* which then would then, with its first instruction, set the register to allow ULP wakeup, but we cannot be sure that this happens after machine.deepsleep() sets the register content. It could easily happen before, in which case machine.deepsleep() would then overwrite what the ULP has set. So a sleep of e.g. 1 second in the ULP code before setting the "ULP wakeup enabled" (i.e. pretty sure that it runs after we're already in deepsleep) could maybe help.

The other thing I noticed is that this instruction is wrong:

WRITE_RTC_REG(RTC_CNTL_WAKEUP_STATE_REG, 21, 11, 0x200);

The parameters would work for the reg_wr processor instruction, but WRITE_RTC_REG is a macro that has different parameters and "calculates" the correct params for the reg_wr processor instruction it will result in. The fields for reg_wr are "base_addr, high_bit, low_bit, data" while the params to WRITE_RTC_REG are "base_addr, low_bit, bit_width, data".

WRITE_RTC_REG is a convenience macro (implemented here in our assembler) that allows constructing the correct processor instruction more easily with only the low bit number and knowing how wide a given field is.

So in your code, the resulting processor instruction would have a low_bit of 21 (incorrect) and a high bit of 21+11-1 = 32 (not possible).

So perhaps try

#define RTC_CNTL_WAKEUP_STATE_REG  (0x3ff48000 + 0x38)
#define RTC_CNTL_WAKEUP_ENA_S  11
WRITE_RTC_REG(RTC_CNTL_WAKEUP_STATE_REG, RTC_CNTL_WAKEUP_ENA_S, 11, 0x200)

(which results in reg_wr 0x3ff48038, 21, 11, 0x200)

Of course, you got it working now with official support in the latest nightly MicroPython - so try the above only if you feel like it (it would be nice to know). Otherwise I will eventually get to trying it out, because I also have this need in one of my projects (just haven't gotten to it yet, to notice the missing esp32.wake_on_ulp() in MicroPython), and in general having an example would be great (even if the example will become much simpler once the next MicroPython is released).

@wz2b
Copy link
Author

wz2b commented Jun 29, 2022

Oh okay, I goofed up that macro for sure.

I think you're right about that race condition. There's another way that might work, too, which is to have the ULP program start by spinning waiting for the READY TO WAKE bit to go active, where I check this:

READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)

or, possibly this other bit RTC_CNTL_MAIN_STATE_IDLE that I found described HERE - but that one is a bit murkier (it's in a reserved section).

Oops about linking to the wrong code, doh. Still feeling my way around this ecosystem! Thanks for the correct pointer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants