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

as_GPS: issues when gps module sampling > 1Hz #52

Closed
dukeduck1984 opened this issue Dec 17, 2020 · 26 comments
Closed

as_GPS: issues when gps module sampling > 1Hz #52

dukeduck1984 opened this issue Dec 17, 2020 · 26 comments

Comments

@dukeduck1984
Copy link

Hi Peter,

The as_GPS driver worked great when the gps module was at 1Hz standard sampling rate, however after I had set the module to sample at 5Hz, the example code shown in "3 Basic Usage" (the one using callback) behaved like it got stuck - it might print one or two lines of satellite data, but most of the time, it printed nothing, only 'waiting for GPS data' till it returned.

I connected the gps module by USB-TTL to PC, and it did sample at 5Hz.

What could be the issue?

Thanks.
Kaiyuan

@peterhinch
Copy link
Owner

What MicroPython hardware are you using?

@dukeduck1984
Copy link
Author

ESP32 Wrover, firmware 1.13 stable

@dukeduck1984
Copy link
Author

Usually in the first run right after setting the module to 5Hz, it would print several lines, but in the next few runs it was like it got stuck. Is it a memory issue?

@peterhinch
Copy link
Owner

It doesn't sound like one. The ESP32 has about 110K of free RAM which should be plenty. If MicroPython does run out of RAM it usually throws an exception.

I'm rather in the dark on this one. You could experiment with the UART rxbuf arg. Try setting it to something large, say 1024.

@dukeduck1984
Copy link
Author

I set the rxbuf to 2048 and the baudrate of the uart to 11520 (it was 9600). ESP32's CPU freq was set to 240MHz. Now it won't get stuck. But the performance was not very stable.

In the callback, I added code to print epoch, so I can count how many times the callback was run. When the GPS module is set at 5Hz, the callback was run 1-3 times per second, most of the time, it was twice. I assume the parser somehow could not keep up with the update rate of the GPS module?

Is there anyway to speed it up? In my use case, I use the GPS to measure speed only, if I have the parser to parse only that sentence, will it be faster?

Thanks a lot.
Kaiyuan

@dukeduck1984
Copy link
Author

dukeduck1984 commented Dec 18, 2020

Below is the fragment of the epoch printed by the callback, as you can see, it was not stable.

It printed total 143 lines within 60 seconds, so merely 2.3Hz on average.
661593218 661593219 661593220 661593220 661593220 661593221 661593221 661593221 661593223 661593223 661593223 661593223 661593224 661593224 661593226 661593226 661593226 661593226 661593226 661593227 661593228 661593228 661593229 661593229 661593229 661593229 661593231 661593231 661593231 661593232 661593232 661593232 661593234 661593234 661593234 661593234 661593234 661593235 661593236 661593236 661593237 661593237 661593237 661593237 661593239 661593239 661593239 661593239 661593240 661593240 661593240 661593242

@peterhinch
Copy link
Owner

I can only agree that the CPU may be failing to keep up with the workload. One thing to try is to set FULL_CHECK False. This will save a little work. The other might be to modify the code, reducing supported sentences dict to RMC only. This saves it parsing the other data (if I remember correctly - it's a while since I worked on this).

Most of my testing was done on Pyboards. The ESP32 is a very different animal as it has an underlying OS which can cause latency. I'm sure that I tested at 10Hz on a Pyboard, the maximum speed supported by the Adafruit GPS. No UART tweaks were required.

@dukeduck1984
Copy link
Author

Thanks for pointing the direction.

Was that Pyboard you tested with STM32F4 or STM32F7?

@peterhinch
Copy link
Owner

Development was done on a Pyboard 1.1 (STM32F405RG), and the timings in the code comments relate to that hardware.

@romeocontrol
Copy link

romeocontrol commented Dec 18, 2020 via email

@dukeduck1984
Copy link
Author

I tried to flash the ESP32 Wrover with the firmware without psram, thinking that might make the esp32 perform a bit faster. Also changed code to FULL_CHECK = False, and reduced supported_sentences to only RMC. The speed difference is minimal, still around 2ish Hz.

@dukeduck1984
Copy link
Author

Another issue happened when supported_sentences dict was reduced to only RMC, the callback example broke - it wouldn't stop printing even when 60 seconds had passed.

@dukeduck1984
Copy link
Author

dukeduck1984 commented Dec 18, 2020

@romeocontrol So you think it's related to the uasyncio performance on ESP32? BTW, my firmware is 1.13 stable, so it's uasyncio V3.

@romeocontrol
Copy link

romeocontrol commented Dec 18, 2020 via email

@dukeduck1984
Copy link
Author

dukeduck1984 commented Dec 18, 2020

I think you are right @romeocontrol . This time I tried without callback, rather I used another coro in which I had the time_since_fix printed every 200ms for 60 seconds, (only) 36 out of 300 time_since_fix's were greater than 200ms - not bad!

@peterhinch
Copy link
Owner

I agree that this would be worth moving to the forum where it will be seen by more people who may have ideas to contribute or experience to report.

I am certainly open to the idea of revisiting this. I initially wrote it when the Pyboard was the only platform. I recently adapted it for uasyncio V3. I checked it worked on ESP32, but only to verify compatibility; ~2Hz seems unreasonably slow.

I need to get a better handle on the issue of ESP32 performance, and how best to optimise applications.

I recently improved ESP32 support for my touch GUI for the official display - another uasyncio application. The difference in performance between ESP32 and Pyboard 1.1 is pronounced. In studying the ESP32 it's clear that it can suffer from high levels of latency responding to interrupts. But the GUI doesn't use interrupts (or serial ports). Just I2C and SPI, which work well on the ESP32. I'd been hoping for the same snappy response delivered by Pyboards.

In nano-gui I did a detailed study of the code which performs a display refresh. The performance of that critical fragment was close to that of a Pyboard. It seems that you can optimise critical routines effectively, yet overall application performance seems disproportionately poor. There have been issues raised regarding benchmarking MicroPython on ESP32. I gather it's difficult to get consistent results for reasons connected with its memory architecture.

I don't think the issues are to do with uasyncio as nano-gui is synchronous. The demo scripts make simple use of uasyncio to produce dynamic results. Some of these demos have been run on an ESP8266 with 18KB of RAM free, so an ESP32 will be wallowing in free RAM.

@dukeduck1984
Copy link
Author

Thank you so much for detailed explanation, Peter!

I'm closing this issue and will move this to the forum for further discussion.

Thanks again!

Kaiyuan

@peterhinch
Copy link
Owner

I've been thinking about how to measure CPU hogging in an application and have come up with this.

It is undocumented but basically it runs a "do nothing" task and performs some measurements on how frequently the task gets scheduled and the max, min and average time between schedules. The code in lines 32-37 illustrates how it might be called: the idea is that your application, once started, issues this line. After that a report will be printed every 10s.

Interestingly, running it as presented, performance on an ESP32 is an order of magnitude worse than a Pyboard D. The maximum interval between calls was 368μs on a Pyboard D, and 8530μs on an ESP32. Average call frequency was 2800Hz vs 453Hz. I will do some tests tomorrow in a real application.

I'm still tied up optimising my graphics drivers, but you might like to see if it gives any insight into the GPS issue.

@romeocontrol
Copy link

romeocontrol commented Dec 22, 2020 via email

@peterhinch
Copy link
Owner

Hi Rainer,
thank you for testing. I raised this issue. With the GUI running the worst-case delay in scheduling the coroutine was 1.46s which is awful. The difference between a Pyboard 1.1 and an ESP32 is very visible indeed.

I will be interested to see if the ESP32 experts have a view on this.

@dukeduck1984
Copy link
Author

I tested my ESP32 Wrover with external spiram, the result seemed to be slightly better...

Firmware 1.13 stable with spiram, IDF V4. CPU full power at 240MHz.

MicroPython v1.13 on 2020-09-02; ESP32 module (spiram) with ESP32
Type "help()" for more information.
>>> import uasyncio as asyncio
>>> import machine
>>> machine.freq(240000000)
I (44990) pm_esp32: Frequency switching config: CPU_MAX: 240, APB_MAX: 240, APB_MIN: 240, Light sleep: DISABLED
>>> from metrics import *
>>> asyncio.run(main())
Max 1658ᅫᄐs Min 1590ᅫᄐs Avg 1609ᅫᄐs No. of calls 6214 Freq 621
mem free 4093328
Max 2511ᅫᄐs Min 1568ᅫᄐs Avg 1587ᅫᄐs No. of calls 6298 Freq 629
mem free 4093344
Max 2938ᅫᄐs Min 1617ᅫᄐs Avg 1636ᅫᄐs No. of calls 6112 Freq 611
mem free 4093360
Max 2560ᅫᄐs Min 1616ᅫᄐs Avg 1635ᅫᄐs No. of calls 6115 Freq 611
mem free 4093344
Max 2510ᅫᄐs Min 1617ᅫᄐs Avg 1636ᅫᄐs No. of calls 6112 Freq 611
mem free 4093360
Max 2555ᅫᄐs Min 1616ᅫᄐs Avg 1635ᅫᄐs No. of calls 6115 Freq 611
mem free 4093344
Max 2538ᅫᄐs Min 1617ᅫᄐs Avg 1635ᅫᄐs No. of calls 6112 Freq 611
mem free 4093360
Max 2539ᅫᄐs Min 1616ᅫᄐs Avg 1635ᅫᄐs No. of calls 6115 Freq 611
mem free 4093344
Max 2544ᅫᄐs Min 1617ᅫᄐs Avg 1635ᅫᄐs No. of calls 6112 Freq 611
mem free 4093360
Max 2563ᅫᄐs Min 1616ᅫᄐs Avg 1635ᅫᄐs No. of calls 6115 Freq 611
mem free 4093344
Max 2525ᅫᄐs Min 1617ᅫᄐs Avg 1636ᅫᄐs No. of calls 6112 Freq 611
mem free 4093360
Max 2537ᅫᄐs Min 1616ᅫᄐs Avg 1635ᅫᄐs No. of calls 6115 Freq 611
mem free 4093344
Max 2528ᅫᄐs Min 1617ᅫᄐs Avg 1636ᅫᄐs No. of calls 6112 Freq 611
mem free 4093360
Max 2539ᅫᄐs Min 1616ᅫᄐs Avg 1635ᅫᄐs No. of calls 6115 Freq 611
mem free 4093344
Max 2545ᅫᄐs Min 1617ᅫᄐs Avg 1636ᅫᄐs No. of calls 6112 Freq 611
mem free 4093360
Max 2579ᅫᄐs Min 1616ᅫᄐs Avg 1635ᅫᄐs No. of calls 6115 Freq 611
mem free 4093360
Max 2510ᅫᄐs Min 1617ᅫᄐs Avg 1635ᅫᄐs No. of calls 6113 Freq 611
mem free 4093328
Max 2530ᅫᄐs Min 1616ᅫᄐs Avg 1635ᅫᄐs No. of calls 6115 Freq 611
mem free 4093328
Max 2553ᅫᄐs Min 1616ᅫᄐs Avg 1635ᅫᄐs No. of calls 6114 Freq 611
mem free 4093328

@dukeduck1984
Copy link
Author

I have a bad feeling about my another on-going ESP32 project in which the callback function is triggered by hardware interrupts, and the shortest interval between triggers is 20ms (at 50Hz). I haven't wired the hardware to actually test the sensor, but I'm really worried now...

@peterhinch
Copy link
Owner

As you probably know, ESPx interrupts are soft IRQ's. If an event happens when a GC is in progress, the ISR won't be triggered until the GC is complete. A GC on an ESP32 with SPIRAM can take 100ms. On an ESP32 without SPIRAM you can keep the latency down by regularly doing explicit gc.collect() calls. You should be able to keep it well below 20ms.

On the other hand I'm baffled by what my ESP32 was doing for up to 1.4s, so my understanding of ESP32 behaviour is evidently incomplete.

@dukeduck1984
Copy link
Author

dukeduck1984 commented Dec 26, 2020

I compiled the firmware with IDF3 per @tve 's comments in that issue, and since I'm using the ESP32 Wrover module, I assumed it's fine to add CONFIG_SPIRAM_SPEED_80M=y in sdkconfig.board.

Below is the test result with the custom firmware, seemed better, but I'm not sure if the difference would mean anything...

Max 5833ᅫᄐs Min 1364ᅫᄐs Avg 1467ᅫᄐs No. of calls 6816 Freq 681
mem free 4083920
Max 3693ᅫᄐs Min 1379ᅫᄐs Avg 1481ᅫᄐs No. of calls 6750 Freq 675
mem free 4083936
Max 4221ᅫᄐs Min 1381ᅫᄐs Avg 1480ᅫᄐs No. of calls 6752 Freq 675
mem free 4083936
Max 3663ᅫᄐs Min 1392ᅫᄐs Avg 1495ᅫᄐs No. of calls 6686 Freq 668
mem free 4083968
Max 3985ᅫᄐs Min 1381ᅫᄐs Avg 1481ᅫᄐs No. of calls 6748 Freq 674
mem free 4083936
Max 6244ᅫᄐs Min 1395ᅫᄐs Avg 1496ᅫᄐs No. of calls 6683 Freq 668
mem free 4083952
Max 4086ᅫᄐs Min 1375ᅫᄐs Avg 1482ᅫᄐs No. of calls 6744 Freq 674
mem free 4083936
Max 3773ᅫᄐs Min 1387ᅫᄐs Avg 1493ᅫᄐs No. of calls 6697 Freq 669
mem free 4083952
Max 4919ᅫᄐs Min 1376ᅫᄐs Avg 1484ᅫᄐs No. of calls 6735 Freq 673
mem free 4083952
Max 3724ᅫᄐs Min 1391ᅫᄐs Avg 1495ᅫᄐs No. of calls 6687 Freq 668
mem free 4083936
Max 4017ᅫᄐs Min 1375ᅫᄐs Avg 1480ᅫᄐs No. of calls 6753 Freq 675
mem free 4083904
Max 3862ᅫᄐs Min 1395ᅫᄐs Avg 1492ᅫᄐs No. of calls 6698 Freq 669
mem free 4083968
Max 3652ᅫᄐs Min 1380ᅫᄐs Avg 1482ᅫᄐs No. of calls 6747 Freq 674
mem free 4083936
Max 3925ᅫᄐs Min 1394ᅫᄐs Avg 1495ᅫᄐs No. of calls 6685 Freq 668
mem free 4083968
Max 5657ᅫᄐs Min 1381ᅫᄐs Avg 1482ᅫᄐs No. of calls 6745 Freq 674
mem free 4083952
Max 3806ᅫᄐs Min 1389ᅫᄐs Avg 1494ᅫᄐs No. of calls 6692 Freq 669
mem free 4083968
Max 5032ᅫᄐs Min 1370ᅫᄐs Avg 1480ᅫᄐs No. of calls 6755 Freq 675
mem free 4083952

@peterhinch
Copy link
Owner

peterhinch commented Dec 27, 2020

One thing to bear in mind with SPIRAM boards is the time taken for a GC (garbage collect). I have measured 100ms with standard firmware. This may explain the fact that you need large recieve buffer on the UART. It might be instructive to try a non-SPIRAM build so that the board ignores the SPIRAM. This drastically reduces GC time and might improve application performance.

It would also be informative to run metrics as a task in your application. In metrics.py ensure the call to main() is commented out. In your application issue:

from metrics import metrics

and somewhere in your startup code issue

asyncio.create_task(metrics())

You may find a difference in the maximum time between calls, notably between SPIRAM and non-SPIRAM firmware.

@dukeduck1984
Copy link
Author

Noted, thanks! I will try and report back.

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

3 participants