Closed LionsPhil closed 1 year ago
Hello LionsPhil,
I am having issues with this program. When I run it attached w/ USB (under Thonny) I am able to launch my application. No issues. For Thonny; I just need to run main.py, select my program and reconnect to the device and the program launches. However if I disconnect and run on battery power it fails to load the application. I just get a black screen, and the need to reboot.
Cheers
@LionsPhil Memory fragmentation (and, in fact, memory usage in general) has been a huge thorn in our side for working with Pico/RP2040. The W taking a decent slice of memory from MicroPython's GC Heap hasn't helped either.
I suspect the canonical answer would be to simply not do what we're doing- since it's admittedly quite weird for a micro-controller and we're begging for this kind of trouble. PicoSystem handily sidesteps this issue by allocating the fixed-size graphics buffer at compile-time and reducing the size of MicroPython's "gc_heap" accordingly.
Any solution I can contrive involves moving some distance towards PicoSystem's (very inflexible) approach and allocating a chunk of RAM to use across scripts.
That said I don't understand MicroPython's garbage collection and heap allocation well enough to suggest anything else, but there's probably something I'm missing there.
(Found the legend for those heap maps---it's a random float that's sometimes subdiving the large region, apparently. Guessing Tufty's Micropython port is one that heap-allocates floats...might be worth seeing what can be moved to fixed-point integers in main.py
.)
What is the native boot process on the Pico like? I haven't been able to find that---is there some C[++] stub that the system jumps to that is then responsible for loading micropython and making it exec (python sense, not libc) main.py? If that exec returns, could it pluck some return value out of it (in normal python, apparently you get a dict of globals it modified), tear down and re-init micropython to completely reset its heap, and run the script it indicated instead? That would allow for a better workaround withouth the full device restart of having to write to the filesystem, but I don't know if these assumptions hold.
Even if not, if micropython gets restarted if it exits (I noticed this was documented for a different micropython device), maybe using sys.exit()
instead of machine.reset()
in the workaround gist would at least upset Thonny less.
MicroPython's main loop is, in fact, here:
It's owned by the MicroPython project, though, which makes it rather difficult for us to make application specific changes to. Not unheard of, by any means, but the fewer hacks we rely upon the better :laughing:
Would fixing Thonny to gracefully handle soft resets into new scripts be feasible, I wonder? I don't really know much about the specifics of how it interacts with MicroPython.
It's possible to use a C/C++ module and C's own heap (unknown to MicroPython) to persist data across soft resets without a flash write. Not sure if it's sensible, but it's definitely possible.
Yeah, rats, sys.exit()
just drops it into the REPL, even if I sys.exit(0x100)
(which should be PYEXEC_FORCED_EXIT
, but it looks like it's not actually propagated). Guess that other board may indeed apply a hack.
What I have done is rearchitected main.py
a bunch so as little as possible is global, in the hopes it helps the GC collect it all before trying to load the application script, and I think that's helped. I seem to be fairly consistency getting 167 contiguous free lines now, and my own badge script is loading without issues (although it's changed in the meantime, so that's not a perfect test). It's also got type annotations because I was going to experiment with integer math and @micropython.viper
to keep things to stack-friendly int
s, but that might be turning out to be pointlessly overcomplicated. Updated the gist with a new revision: https://gist.github.com/LionsPhil/26c640356b074513d6c612a199db9d3f
If that seems to be promising I can rip the whole reboot-on-B part back out (and probably the memory debugging) and turn it into a PR.
The cool little Tufty 2040 menu/loader (https://github.com/pimoroni/pimoroni-pico/blob/1337d2abdb57d2995b2252186d591d8efcc2413f/micropython/examples/tufty2040/main.py) attempts to free up memory before chaining over to the selected script. Unfortunately this isn't perfect, and is sometimes causing PicoGraphics to fail to allocate a large enough contiguous block (I think) for the display. I can aggrivate this pretty reliably (~80% of the time...on a microcontroller without all the churn of a full OS I'm not actually sure why it's so random) with my heavily-modified
retro_badge.py
that uses 8bpp graphics mode, but haven't really managed to get a reliable repro with something cut-down that's still pulling in the same imports and doing the same init...I'm guessing simple script size is a factor too.See below for memory maps where I've been trying to identify if there's a better fix for this, or narrow down a culprit. It sorta seems like PicoGraphics is only able to allocate from a higher region that's getting a stray byte at
27c00
-ish subdividing it in the bad case, but there's a larger free block at04c00
-ish in either case. I'm guessing the script itself, modules, and statics are eating it up and the allocator isn't defensive about keeping large regions intact.Possible workaround
Reboot into the new script. I've reworked
meny.py
to reset inbetween the selection and loading, so it can avoid the large allocations of the display --- https://gist.github.com/LionsPhil/26c640356b074513d6c612a199db9d3f --- and put that functionality on the B button. The big downside is that Thonny hates the device resetting itself and gets in a bit of a mess, so it's not joyous to debug. It also causes a small amount of writing to flash where previously there was zero.(That gist also has the memory debugging enabled. Which of course causes more memory allocations.)
Alternatively I don't know if MicroPython has something more akin to an
exec()
, that could reset the interpreter state, python heap, etc. without fully resetting the device.Memory maps
In these:
lp202_badge.py
) has imported modules and initialized the display, if it reaches it.Exhaustion
Success