-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Serious problem with millis() #2430
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
Is it a solution to implement esp8266's logic into esp32? Only difference is esp8266 starts from a uint32_t system timer whereas esp32 has a int64_t timer; both microseconds though. https://github.com/esp8266/Arduino/blob/master/cores/esp8266/core_esp8266_wiring.c#L64 |
@Jeroen88 try changing the equation of unsigned long IRAM_ATTR millis()
{
return (unsigned long) (esp_timer_get_time() / 1000LL);
} Chuck. |
isn't that conversion implicit? |
The compiler can choose long, because output type is long, one param is long, and processer natural is long. Converting to long first is more efficient. One conversion, natural division, no output conversion. Don't allow it the choice. Chuck. |
@bertmelis, @stickbreaker Bert, Chuck thnx for the reactions!! I read the extensive comment at the ESP8266 version of millis() here This uses some "magic" algorithm to do a fast division. It also states that it uses the full 32 bit range for the millis() counter (see here). The problem is that the ESP32 does not use the full 32 bit range, that is causing the issue. I tested the example code with an ESP8266 and there it works correctly. @stickbreaker I don't think explicitly changing the type to LL will help. The source counter micros() uses the full 32 bits causing the 'trick' to substract the start_Micros from micros() to always work because of the 2's complement arithmetic. However, because of the division, millis() is not using the full 32 bits, causing trouble around the moment of overflowing of the micros() |
So the esp8266 implementation should work? (after masking 64-bit micros value with 0xFFFFFFFF to get lower 32-bit part) Issue is indeed the mix of decimal math with binary rollover logic. |
@bertmelis I think it will work, and I think both the ESP32 and the ESP8266 use a unsigned long system timer, so masking should not be necessary |
Therefore how we can fix now on 1.0.1, I use a lot mills and I've strange issues link to this |
it is signed according to this: https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/esp_timer.html#_CPPv218esp_timer_get_timev Masking to avoid compiler warnings (storing 64 bit into 32). |
Just try modifying the
The problem you are seeing is the early conversion from Chuck. |
@stickbreaker Chuck, I believe you are right! Thanks for your insights. I have no access to my systems now, but will try it this weekend and, if succesful, make a PR. |
On Ideone.com: #include <iostream>
#include <stdint.h>
using namespace std;
int main() {
int64_t systemTimer = 0xFFFFFFFF;
uint32_t lastMillis = (unsigned long) (systemTimer / 1000LL);
systemTimer += 10; // 10 µsecs later...
uint32_t currentMillis = (unsigned long) (systemTimer / 1000LL);
if (currentMillis - lastMillis > 1000UL) {
cout << "This shouldn't happen!\n";
} else {
cout << "All ok!\n";
}
systemTimer += 1000000; // 1000 msecs later...
currentMillis = (unsigned long) (systemTimer / 1000LL);
if (currentMillis - lastMillis > 1000UL) {
cout << "1000 msecs barely passed.\n";
} else {
cout << "corner case\n";
}
systemTimer += 1000; // 1 msecs later...
currentMillis = (unsigned long) (systemTimer / 1000LL);
if (currentMillis - lastMillis > 1000UL) {
cout << "1001 msecs passed\n";
} else {
cout << "coups\n";
}
return 0;
} returns:
|
@bertmelis :), thnx! I think @stickbreaker suggestion will indeed work. I will make a PR this weekend |
I haven't looked at this core's code, and I haven't read this discussion in detail, so I don't know if this helps, but I thought to mention it anyways:
|
|
Solved by PR #2438 |
Hardware:
Board: ESP32 Dev Module nodeMCU 32s
Core Installation version: latest git
IDE name: Arduino IDE
Flash Frequency: 40Mhz
PSRAM enabled: ?no? ?yes?
Upload Speed: default speed
Computer OS: Ubuntu
Description:
millis() is not working as expected, causing time out problems every 72 minutes in many functions of the core and user programs
Sketch: (leave the backquotes for code formatting)
Debug Messages:
Issue #2330 was closed, but the issue is not solved in the release mentioned in that PR. In that release the type of micros() and millis() is changed to unsigned long, but this is not solving the issue!
In my honest opinion, this is a very serious error!
The problem is that many timeout loops depend on 2’s complement arithmetic and millis() being defined as:
See
arduino-esp32/cores/esp32/esp32-hal-misc.c
Line 109 in 25a2d94
Take for instance in Stream.cpp timedRead
arduino-esp32/cores/esp32/Stream.cpp
Line 31 in 86fdb5b
The default _timeout is 1000 ms.
Now assume that esp_timer_get_time() is about to roll over at the moment that _startMillis() = millis() is assigned. So assume esp_timer_get_time() is 0xFFFFFFFF, and millis() will be 0xFFFFFFFF / 1000 is 4294967295 / 1000 is 4294967. Thus _startMillis will be 4294967.
Next assume that one do { } while loop takes 100 microseconds (the exact duration is not important to demonstrate the effect.
At the time of the evaluation of the while condition, micros() will be 0xFFFFFFFF + 100 is 99, because of 2’s complement arithmetic. So millis() will be 99 / 1000 is 0.
The while condition at the loop will evaluate to 0 - 4294967 is -4294967. Also because of 2’s complement arithmetic this evaluates to 4290672329. The while condition evaluates to 4290672329 < 1000, which is obviously false. So the while loop stops after only 100 _micro_seconds of waiting (the assumed duration of one iteration of the while loop), instead of the intended 1000 _milli_seconds!!!!
In the above example the same thing happens in a real example. The last timeout, exactly after about 72 minutes of running lasted only 1932 ms instead of the wanted 3000 ms.
Stream is in the heart of many programs. And this is just one example. Many timeout loops use the same structure: millis() - start < timeout
@me-no-dev, could you take at look at this?
The text was updated successfully, but these errors were encountered: