Four hundred apps, one at a time
NucleoOS-P4 has a Store with over 400 apps and an assistant, on a microcontroller. I chose the features. Memory decided the shape.
by Nicola Sabaini 5 min read Project: NucleoOS-P4
In July the board stopped booting. Black screen, reboot, black screen. I had just changed the partition table, the way the flash memory is divided up, and I blamed that.
It wasn’t that. The serial cable said something else: at boot the system tries to set aside 32 kilobytes of internal RAM, and it couldn’t find them anymore.
They had gone into three lists of file names. Music, video and the recorder each kept a list of 256 names, 32 kilobytes apiece, in memory from power-on even with the app closed. Ninety-six kilobytes for three lists nobody was looking at. The recorder was the third one, and that was enough.
Two memories, one of them small
NucleoOS-P4 runs on an ESP32-P4. Next to the chip there are 32 MB of external RAM, the PSRAM: a lot for a microcontroller, but slower. Copying data into it runs at about 32 MB a second, so one full frame of the screen costs more than thirty milliseconds.
Inside the chip is the internal RAM, the fast one, the only one some peripherals can use. With Wi-Fi and the interface running, between 80 and 100 kilobytes of it are free, and the largest continuous piece is about 31.
On top of that run Doom, a Store with over four hundred apps, a spreadsheet that opens Excel files and an assistant. Those are features, and I chose them. The shape of the system was decided by memory.
One app at a time
On a phone, apps stay open in the background. Not here. One app runs, the one in front, and it takes the memory it needs. When it closes, it gives all of it back.
The rule came from that black screen: no buffers that live from boot. An app asks for memory when it opens, in PSRAM, and frees it when it closes. The three lists now exist only while their app is open.
On a seven-inch screen you look at one thing at a time anyway.
Asking for the real number
The camera wants four buffers of just over 4 MB each, and each one has to be a single continuous block. At first it declared it needed 8 in total. The check at open passed, then the allocation failed halfway and the app opened broken.
Now it declares 18 MB, the real number. Before saying no, a memory manager empties the other caches: the one of ANIMA, the assistant, and the games’ ones. If that still isn’t enough, the camera doesn’t open, and it says so straight away.
In the code there is a note next to that number: don’t lower it to make the camera open. A clean refusal makes sense. An app opened halfway doesn’t.
The same file, a different error every time
Library, the offline Wikipedia reader, failed at random. Sometimes memory ran out, sometimes the archive looked damaged. Something different every time, and the file was always the same.
The reason was how a WebAssembly app’s memory grows: when it needs more room, the runtime allocates a bigger block and copies everything into it. After a while the PSRAM is full of holes, and a big continuous block is no longer there, even when the total free memory would be enough.
I tried asking for 16 MB from the start. Sometimes the app didn’t even launch. With 12 MB, taken and given back right at startup while the big block is still free, it holds.
A hundred and forty-five seconds
Copying a 21 MB file on the SD card took 145 seconds.
The SD controller can move data by itself, without going through the processor: that’s called DMA. But it only does it if the buffer starts at an aligned address. Otherwise it goes one 512-byte sector at a time, each with its own command.
Same file, buffer aligned to 64 bytes: 11 seconds. No new algorithm. An address that starts in the right place.
The blue screen
During updates, and now and then at random, the screen flashed light blue for a moment.
While the chip writes to flash it can’t read code from flash. The routine that restarts the panel on every frame lived right there, so during every write it waited, and the panel went without data.
Moving it into internal RAM cost 92 bytes. Over 300 test writes, before there were four or five late frames per run. After, none.
The free eleven percent
On paper the P4 runs at 400 MHz. On this board, at 400 it doesn’t start: it reboots forever, because the PSRAM clock can’t be derived from that frequency.
I found out with an update over Wi-Fi. The system never got as far as confirming it, and it went back to the previous version by itself: the safety net did its job. Then it took a reflash over the cable. Now it runs at 360, and next to the setting there is a note not to try again.
The written rules
Each of these stories became a line in the project’s rules file, with the failure that produced it. No PSRAM stacks for tasks that write to flash. No static buffers for apps. Aligned buffers for the SD card. The reason is written next to each one, because sooner or later someone removes a rule that has no reason.
It’s the same point as A constraint is a specification, seen from inside a system that actually runs.
The Store has more than four hundred apps. In memory, there’s room for one.