Each prototype feature works alone. Will the PIO programs fit together?

The display demonstration runs. So does the light controller. A third interface works on the same development board in its own test program. When you combine them, initialization fails before the product can do anything useful. There are still unused pins, so why is the board out of resources?

If these features use RP2040's programmable I/O, or PIO, count two things separately: the small execution engines called state machines, and the instruction space their programs share. An unused state machine does not necessarily mean there is room for another program. More Python heap memory will not enlarge that dedicated program store.

This is a planning issue for teams combining working modules into one prototype. The examples below are illustrations for reviewing your own design; we have not reproduced a failed three-feature integration for this note.

Start with the two resource limits

Two boxes labeled PIO0 and PIO1, each with 4 state machines and 32 shared instruction slots
RP2040 has two PIO blocks, and each block has its own 32-slot instruction memory; these counts apply to RP2040, not every later chip.

Each of RP2040's two PIO blocks contains four state machines and one shared store holding 32 instruction slots. A state machine is the engine executing a short I/O program; the store is where that program lives. The four engines in a block do not each get their own 32-slot allocation. The RP2040 datasheet, section 3.2.2, describes how the system loads the store and the state machines execute its contents. It is loadable memory, not fixed ROM.

For a simple hypothetical example, suppose two distinct programs need 20 and 16 slots in the same block. Together they exceed its 32-slot capacity, even if only two state machines are needed. This arithmetic is an illustration, not a measurement of a particular display library. Count assembled instructions, not source lines including comments and labels.

Four state machines SM0 to SM3 all reading from one instruction memory of 32 slots, with per-state-machine FIFOs, GPIO mapping and IRQ flags shown alongside
Programs can be shared between state machines, but a free state machine does not guarantee free program space.

Conversely, several state machines may run the same loaded program. The MicroPython v1.27.0 StateMachine documentation says initialization reuses the offset when that program is already present. Do not assume that separately generated versions of a program will share storage simply because their Python source looks similar. Check how the actual driver constructs and loads them.

Budget the libraries as well as your own code

Make a short resource list for the integrated firmware. For each feature, record:

Include board support and communications libraries. A resource used underneath an API is still used, even when it does not appear in your application file.

A MicroPython issue opened on August 1, 2022 reported repeated PIO use ending in an ENOMEM error on a Pico W running a specified development build. The report involved PIO memory management alongside the CYW43 wireless driver. The issue is now closed, so it is not evidence that current firmware has that bug. It does show why the board variant, firmware version and other active drivers belong in a resource investigation.

Capture the exact operation that fails. MicroPython's versioned PIO documentation explicitly associates PIO.add_program() with OSError(ENOMEM) when its program memory lacks space. An error elsewhere needs its own diagnosis. "Out of memory" alone is not enough to choose between changing the PIO allocation and changing ordinary application memory use.

Reduce the work assigned to PIO before changing boards

PIO is useful when an interface needs a particular sequence of pin actions. It does not follow that every action belongs there. An e-paper display driver, for example, can be built with plain software, SPI, DMA or PIO, and in a PIO design a reset line used only at startup can stay under CPU control while the PIO program handles the repeated communication sequence.

That suggests useful questions for the integration review:

Each change needs checking against the product's timing and behavior, not just a smaller instruction count.

Do not treat program removal as a harmless cleanup command. MicroPython can remove a named program, or all programs when no argument is supplied. Before unloading anything, establish which state machines and libraries depend on it, stop or reconfigure them appropriately, and manage their outputs. Deleting a Python reference is not a complete resource-ownership plan.

Prove the combined product, including its restarts

Run the required features together on the intended board and firmware. Then repeat the lifecycle the product needs: cold start, normal operation, a supported restart, and any mode changes that load different interfaces. A demonstration that fits once may still have an allocation or cleanup problem when the user returns to the same mode later. These are proposed checks, not results we have recorded.

The review deliverable should name the owner of each resource and preserve the smallest setup that reproduces any failure. If everything fits with a clear allocation, keep that map with the firmware. If it does not, compare a different allocation, a simpler implementation and a different board against the actual feature requirements. More pins alone do not answer the program-space question.