-
Notifications
You must be signed in to change notification settings - Fork 13.3k
ESP.getResetReason() doesn't allow to distinguish between timer wakeup and physical reset during deep sleep #7067
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
Comments
Well, your "plenty of ways" are just two IMHO - store this info to the (simulated) EEPROM, or use RTC storage (the really only way to accomplish this without burning out the Flash). I'm also using RTC storage in my application/s so I don't like that somewhere in the core is something, that can sometime overwrite my own content (or block part of RTC memory even when I not need this feature). There is no need to put this technique to the core, but you can do it in your own code the same way as you described. What do you think? |
None of those will help. The second is what getResetReason() already does and it's what allows it to distinguish between case 1 on one hand, and case 2 or 3 on the other, but not between 2 and 3. It writes something to RTC storage just before it goes to sleep, and it resets it when it wakes up: so, when it wakes up (before it resets whatever it is it wrote), if that thing is in the storage, it knows that it has waken up from deep sleep (as opposed to resetting during execution); but it can't know whether it woke up because the timer went off or because RST was externally shorted to ground. I'm sure the RTC hardware interface provides a way to know whether it fired a wakeup or not. Some time ago I looked a bit into this and found out the following. Let's invent fake names for these functions whose actual names I don't remember, because otherwise this is confusing: get_system_time() : returns time since program execution began Because the SOFTWARE is resetting the RTC at startup, and there's nothing I can do to prevent it from doing that, those two return the same value (perhaps a few microseconds apart). If only the software wasn't resetting the RTC at startup, I could easily know how logn it's been since I went to deepsleep, and, with a little bit of work (just knowing how long I had set the deepsleep to be) I could know within a small margin of error whether or not this is an automatic wakeup or not (or whether or not I've been asleep as long as the deepsleep was supposed to be). |
My understanding was that it's not possible to distinguish the cases, but from your comments maybe that's wrong.
|
To clarify:
However, I don't know exactly where the espressif SDK ends and esp8266-Arduino begins, so I'm not sure whether it's something that you can fix in esp8266-Arduino or it requires a fix by espressif in the SDK itself. Perhaps there might be something in the RTC system memory (or somewhere) that they have "forgot" to reset and that you may use as a workaround?
Unfortunately when I found it I didn't take notes.
Because the RTC gets reset at startup, |
@php4fan we've discussed this internally. Assuming you're right, and it is in fact possible to differentiate between 2 and 3 via software, we consider the effort likely to be high for the return. |
Seriously?? I think you are seriously underestimating the "return". You are not thinking about the use cases.
That made me laugh hard. THE user. Right. When googling for a solution I found quite a few instances (even from years ago) of people asking how to accomplish this (of course the answer always being you cannot). Being able to know whether the board has been reset manually or woken up at the programmed time is a very basic need for any project, that currently is not satisfied. The "return" here is for any user (i.e. developer) to be able to do such a basic obvious thing. To overcome a huge, nonsensical limitation. I can't imagine an effort too big to be worth that. Here's my example use case: So my current workaround is to instruct my users to push the button twice rather than once. That I can easily distinguish (I leave the how as an exercise to the reader). However this is a shitty user interface, counterintuitive AF. A single button press is something the user is never supposed to do, and if they do, it results in nothing immediately, and in the next update happening at the wrong time (only the next, and again I leave how I manage to trigger the following updates at the right time as an exercise to the reader). If you replace "downloading data from the internet and refresh an image on an e-paper display" with "doing something", the description of my use case becomes: "an IoT device that spends most of its time in deep sleep and periorically wakes up to do stuff. But it can also be forced to do that stuff at any given time by pressing a button". To me, that seems like the description of practically any IoT device (and several kinds of non-IoT devices). Of course, you can add hardware to accomplish the task. Either by adding an external RTC clock, or by adding circuitry that, at the press of the button, will keep a voltage high for a short time, and having the software read that voltage via an input (that implies that the button must be "decoupled" from the contact between RST and GND, because otherwise, the system wakeup would trigger the same thing that the button does; so, the button needs to trigger that voltage change and drive a switch that closes the contact between RST and GND. Just in case you missed how much circuitry one would need to add). Having to add extra circuitry just because the software is too lazy to do its job - no, actually, because the software is overzealous and does something that it shouldn't even do, that is, reset the RTC. Now, that's for the "return". Regarding the effort. Ok so maybe you understand the above and are not underestimating the return, but the effort is indeed great. Now, if tackled where it should be tackled, that is at the SDK level, it cannot be a great effort at all. I'm not entirely sure, I can't find the source of Anyway, I can easily be wrong about that line of code, but it's clear that all is needed is to not reset the RTC at startup. Now, from a esp8266-Arduino perspective. Fixing it from esp8266-Arduino could indeed be difficult. Actually, it could be completely impossible. If the SDK code is resetting everything, I'm afraid there might be nothing you can do at the arduino level.
I'm afraid I wouldn't even know where to start. Anyway, here's what I tried and failed, in case it helps. Tried from the "user space" if I can call it that - from an arduino sketch. 1. Using
|
Scratch that, I think I was looking at the wrong SDK. The relevant one is the NONOS, right? Also, there seems to be no source code at all? No wonder you were saying it's going to be difficult to fix... |
Correct, now you're starting to understand: what you're requesting amounts to at least a partial reverse engineering of the rtc handling code, which is in the closed lib blobs from Espressif. The FreeRTOS sdk can provide some hints, but it means investing a large amount of effort, of the order of several months, and none of the current maintainers are willing to spend it. There is a very long list of much higher priority issues on our plates, and an even longer list of issues waiting after that. |
The RTC is reset by the blob during the boot phase, so the user needs to use RTC RAM to track changes across Deep Sleep. By the time the blob passes control to SYS the details needed to discern between Deep Sleep and external reset are gone. We aren't going to get help from Espressif on Non-OS SDK as they've abandoned it and moved on to IDF. I haven't looked at the BBS post yet, but I'd guess it's saying the same. I'll look later when I get home. Delay() doesn't run in preinit, it just returns immediately as the timers aren't set up yet. |
If you're talking about writing to RTC RAM and then reading what you (i.e. the user) have written in it, then again, you are talking about distinguishing between 1 and {2,3} (already accomplished by getResetReason()) but not between 2 and 3. |
So if I understand correctly, that is before the constructor of MyTestClass in my example runs, right? (I guess that ugly example of mine can be simplified by just writing instructions at the top of the sketch outside of setup(), but I was under the impression you can't do that) Then I guess there's nothing at all we (we users, and I'm afraid also you) can do, if we don't get a chance to read from the RTC before it is reset. |
Execution of the global object constructors is under our control. See file esp8266_main.cpp functions user_init() at the bottom and init_done() above it for the start sequence, including where preinit() and do_global_ctors() are called. |
Yes, I saw the same issue when I was researching Deep Sleep, and couldn't find a spot early enough to save the RTC count. It was already cleared, thus impossible without a binary patch to the blob to NOP the RTC clear. That could be problematic if the blob does an integrity check. |
@php4fan One thing you might look at to see if it helps: FRC2, the free-running 32-bit counter that's used to set the ETS_Timer increments. It's apparently not cleared by the bootloader, so you might be able to read it in the RF_PRE_INIT() stage before it gets re-initialized. No bet on that, as I haven't looked at it yet. I'm pretty sure the ETS_Timers aren't running until somewhere around preinit(). I think the resolution of FRC2 is around 3.2 microseconds. If you grab it before it's cleared, then subtract off the time since boot began and your Deep Sleep interval, and if the remainder is a small number then it probably woke via GPIO16 and not external reset. I don't know how variable the boot time is, so it'll take some experimenting. edit: No joy, sorry. On a cold start or after external reset, FRC2 shows a count of 222. After Deep Sleep (either RTC or external reset) it shows a count of 218, so it's cleared roughly 700us before RF_PRE_INIT(). FRC1 is no different: it's restarted before RF_PRE_INIT(). I also looked at the RTC counter, and it's count is all over the map in RF_PRE_INIT(). I didn't see anything that you could use there to determine reset cause. |
Thank you for the attempt. But do any of those counters/clocks keep running during deep sleep? Is patching the binary to nop the RTC reset something reasonably doable to try, or is it something that would require months of reverse engineering work? |
Patching itself is likely not hard. The hard part is figuring out where the magic happens, and what the sequence is. That involves disassembly of the blobs and investigation of the results. Every case is different, it could take a few weeks, or it could take several months, depending on your prior experience with reverse engineering. |
The RTC is running with a count up if I remember correctly; that's what fires the GPIO16 to drop when it reaches the end count. I think the Reset Cause is handled in the bootloader, not certain on that point. Search the dump for the address of the RTC count register and you might find it (objdump -S yourfile.elf > dump.txt) If it's in the bootloader you're out of luck. Everything but the RTC is shut down in Deep Sleep, and the RTC starts increasing in speed as the chip cools, so it's not an accurate clock. It's all you have, though. |
guess what? deepsleep wakeup works even without gpio16-rst connection, waking the chip and showing the correct reset reason/bootmode
but stops dead. |
Wow, that's pathetic. And now I also wonder about the fact that the deep sleep duration (i.e. the unit of increment of the RTC) is set in microseconds rather than milliseconds. Such precision is completely useless given that the clock drifts with a margin of error of around 10%. I bet sub- millisecond deep sleep is also useless as I'm pretty sure the time of going to sleep plus the boot time is way more than a ms anyway. So, there's no point in having the sleep time be set in microseconds, and if it was milliseconds, we would not be limited to 3 hours. |
The deep sleep issue is in the rom, and has been known for something like half a decade now. Fixing it would require making a new set of manufacturing masks, which has a 7 digit price tag, so no, especially given the gpio16-rst workaround, which works well enough. |
Although not 100% reliable, the code below is able to determine if an ESP8266 re-start from DeepSleep was caused by the GPIO-16 rtc-timer, or by a Reset-Button-press.
Note that this code uses the fact that all three of the below rtc-counters vary a little-bit on every reboot, and by assessing the change in rtc-counter each DeepSleep-wake (cf. the previous wake) you can guess what caused the DeepSleep-wake. (The code stores the previous reboot-count in RTCMEM so it is retained during DeepSleep.) uint32_t count=RTCSV; // NB. need to: #include "esp8266_peri.h" In my tests, I found the most reliable counters for this be “RTCSV”.& “RTCCV”, which are used in the above example. In my projects I test both and use EITHER between 9 and 60 to mean a Reset-Button-press. NB: all these counters normally INCREASE slightly on every GPIO16-DeepSleep-wake. However, on a Reset-Button-press DeepSleep-wake the counters DECREASE, and the longer the button is pressed before release gives a larger decrease (most of the time anyway). For me the best results were achieved using the above [negative] “(difference>=9) and (difference<=60)” test. (I typically use D1mini ESP8266’s.) Notes: (2) A very quick button-press gives too small a difference for the above code to register (and is instead registered as just a normal GPIO16-DeepSleep-wake). To work the button needs to be pressed for 2seconds or more. (3) Note that as all 3 of these counters seem to get gradually higher every normal GPIO16-DeepSleep wake: after a number of normal GPIO16 DeepSleep wakes the counters will reach a point when they re-start themselves at a low number again (hence the above negative “difference<=60” to try and catch and ignore this), or sometimes just have a small 1 to ~8 digit reduction (which the negative “difference>=9” ensures is ignored). (4) It is also interesting to note that the ESP8266 “processor-ticks-since-boot” given by “ESP.getCycleCount()” does NOT change across boot/wakes, so the fact that the above rtc-counters are changing is perhaps related to when in the boot process they are reset; and as button-press-duration changes the numbers, perhaps also what they are reset to. (5) I typically use a 560R resistor between the GPIO-16 pin and the Reset-Pin, as this seems to give a more stable ESP8266 (unless I am using a transistor/zener -arrangement as show in the below schematics). Note that this code does NOT work unless GPIO-16 is connected to the Reset-Pin in some way. As the above is not 100% reliable, it is obviously not ideal, but is reliable enough to be of use in many of my projects. However it does NOT give any indication of how long the DeepSleep has lasted up to that point. Note that you can test the above sketch using just the on-PCB RESET-Button on the D1mini; but in case useful for anyone, the schematic below shows how I connected the external-reset button in my projects. (Edit: the simpler 1-transistor circuit originally shown did NOT work, as the 2.7v zener stopped GPIO16 pulling the RST pin low enough to wake the ESP8266.) [I use a single 3 position DPDT non-latching toggle switch (on-off-on, normal centre position off): one side of which I use to wake the ESP8266 from DeepSleep (which the circuit then disables once awake to stop further presses causing another reboot); the other side of which I connect to two other GPIO pins and use during normal running to select options. But this DPDT switch can be replaced with a simple normally-open push-button (Reset/Wake-Press-Button) if the extra switch-sense options are not required. Note that as GPIO16 goes open-circuit during normal running, this disables the Momentary Reset-Switch (left-side) until DeepSleep is started.] |
u can use any schottky diode ... the second diode is useful to be able to flash the est without disconnect the wire |
Let me see if I understand it correctly. You are using an additional input pin (GPIO14). Assuming that works (I don't quite understand how the timer reset doesn't have the exact same effect and bring GPIO14 down exactly like using the physical button), that's a solution I had already thought of. The point of the issue is you shouldn't need to waste an additional input for that. I don't have an unused input pin in my usecase or I would already have followed this approach. |
I have tried many solution but for me only this is working....if you can find e better (and maybe easy solution) let me know...I need for my project also... |
Thank you for sharing |
Sorry I have done a mistake in the schematics.... the capacitor is 100µF and not 100nF |
@6leonardo I have not used a "Gizwits WiFi Witty ESP-12F", but doesn't the RST/REST pin need to be pulled up to 3v3 (usually via a resistor) to allow it to run? If this is the case, then if I understand correctly, pressing the button at any time (ie. both during sleep and during normal running) will reset the ESP8266, and your circuit can tell this had happened by reading GPIO14 after re-boot. FYI there are several discussion of these type of circuits about, three interesting webpages might be:
[Although more complicated, the example circuit I showed above was designed so that pressing the button during normal ESP8266 running would NOT trigger a reset. (When the EPS8266 is running normally, the zener diode drops the voltage fed back from the RST pin to the 2N2222 transistor base to below 0.6V, so the transistor is off, meaning that pressing the button then will NOT reset the ESP8266.) This means that a single Dual-Pole switch/button, as well as waking the ESP8266 from sleep, could also be used with a separate GPIO when the ESP8266 is running normally to make choices.] |
Consider these three different situations:
Expected:
ESP.getResetReason() should return different values in these situations, allowing you to distinguish 2 from 3 as well as from 1.
Observed:
ESP.getResetReason() returns "External System" in case 1, but it returns "Deep-Sleep Wake" both in cases 2 and 3, so they are not distinguishable.
I know that the way automatic wakeup works is that GPIO16 goes low, hence driving RST to ground exactly as you would do by physically shorting it to ground before the timer goes off, but there are plenty of ways you can internally accomplish the result of getting 3 different values for the 3 different sistuations. There is a built-in RTC that you are talking to after all, which keeps running during deep sleep.
The text was updated successfully, but these errors were encountered: