Skip to content

alif: Run the CYW43 SPI bus at 32MHz. - #19628

Open
kwagyeman wants to merge 3 commits into
micropython:masterfrom
kwagyeman:kwabena/alif_cyw43_perf
Open

alif: Run the CYW43 SPI bus at 32MHz. #19628
kwagyeman wants to merge 3 commits into
micropython:masterfrom
kwagyeman:kwabena/alif_cyw43_perf

Conversation

@kwagyeman

@kwagyeman kwagyeman commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Three improvements to CYW43 WiFi on the alif port, found while
benchmarking an OpenMV AE3 (CYW43439 on SPI):

  1. Make WL_IRQ level sensitive. The chip holds the IRQ line asserted
    for as long as it has pending frames, so a falling-edge trigger stops
    firing whenever a poll doesn't fully drain the chip — the wakeup is
    lost. The pin is now level-low sensitive, masked in the handler, and
    unmasked after every poll via CYW43_POST_POLL_HOOK (the same pattern
    the rp2 port uses): a still-asserted line immediately re-raises the
    IRQ, so no wakeup can be lost.
  2. Run the SPI bus at 32MHz (was 16MHz; the gSPI interface is rated to
    50MHz). Raising the clock alone fails — the chip's firmware download
    breaks at 24MHz and above — because the MISO round-trip exceeds the
    controller's default sample point. Setting the SPI RX sample delay to
    2 cycles makes 32MHz reliable.
  3. Increase lwIP TCP buffers (8→16 MSS window/send buffer, 16K→48K
    heap): TCP was window/RTT-limited below what the link carries, and the
    heap now comfortably covers the send buffer so lwIP's ERR_MEM retry
    path stays cold. The Ensemble parts have ample SRAM.

Measured on an OpenMV AE3 (2.4GHz, same board/AP/position, Python socket
benchmarks, 3+ runs per configuration):

before this PR change
TCP TX 11.3–11.5 Mbit/s 12.2–12.6 Mbit/s +10%
TCP RX 10.0–10.2 Mbit/s 10.2–11.8 Mbit/s +10–15%
UDP TX 13.4 Mbit/s 13.8–14.2 Mbit/s, 0% loss +3–5%
UDP RX, paced ≤10 Mbit/s @5 clean, @10 ~35% loss unchanged

The TCP gains come almost entirely from the lwIP buffer commit
(window/RTT-limited before). The 32MHz commit contributes the UDP TX bump —
small, because per-frame CPU cost rather than wire time dominates this SPI
path. The IRQ commit is throughput-neutral in these tests but closes a
real lost-wakeup class.

Testing

Tested on an OpenMV AE3 (CYW43439, SPI): repeated WLAN bring-up cycles and
Python socket TCP/UDP benchmarks in both directions (numbers above).
Interrupt servicing of every received frame was verified with the driver's
stats counters; 32MHz bring-up is stable across repeated
boot/scan/connect/traffic cycles, and reproducibly fails without the
sample-delay change (which is why both are in one commit).

Known pre-existing issue, unchanged by this PR: sustained UDP receive
above ~15Mbit/s collapses to 100% loss on this board. Instrumentation
shows the chip itself stops asserting the IRQ and holds an empty queue
during such floods — a chip-side power-save interaction (with power save
disabled via cyw43_wifi_pm() it degrades gracefully instead). Addressing
it properly needs DMA/service-rate work on the SPI path and is left for a
future change.

Build-tested: OPENMV_AE3.

Trade-offs and Alternatives

The lwIP heap increase costs 32K of SRAM on a part with megabytes of it.
The IRQ change adds a mask/unmask per poll cycle, which is noise compared
to the SPI transfers it brackets.

Generative AI

I used generative AI tools when creating this PR, but a human has checked
the code and is responsible for the code and the description above.

The WL_IRQ line from the CYW43 is configured falling-edge triggered, but
the chip holds the line asserted (low) for as long as it has pending
frames.  Under sustained receive the line never returns high between
frames, so no new falling edge is generated after a poll that does not
fully drain the chip, and the wakeup is lost.

Configure the pin level-low sensitive instead.  A level trigger would
re-fire continuously until PendSV gets to run the poll, so the handler
masks the GPIO interrupt after scheduling, and a new post-poll hook
(CYW43_POST_POLL_HOOK, already provided by cyw43-driver and used the
same way by the rp2 port) clears and unmasks it after every poll: if the
line is still low the interrupt immediately re-raises, so wakeups cannot
be lost regardless of how many frames are pending.

Tested on an OpenMV AE3 (CYW43439 on SPI): WLAN scan/connect/traffic all
behave as before, verified with the driver's stats counters that the
interrupt path services every received frame.

Signed-off-by: Kwabena W. Agyeman <[email protected]>
The CYW43 gSPI interface is specified up to 50MHz but the bus was run at
16MHz.  Simply raising the clock fails: at 24MHz and above the chip's
firmware download breaks ("Failed to start CYW43"), because the MISO
round-trip delay exceeds the controller's default sample point.  Set the
SPI RX sample delay to 2 spi_clk cycles to move the sample point, which
makes 32MHz operation reliable (verified across repeated WLAN bring-up
and traffic cycles on an OpenMV AE3).

Signed-off-by: Kwabena W. Agyeman <[email protected]>
The 8*MSS TCP window and send buffer limit TCP throughput to
window/RTT, which on WiFi round-trip times is below what the link and
the CYW43 can carry.  Double both to 16*MSS and raise MEM_SIZE to 48K
so the heap comfortably covers the send buffer (lwIP's ERR_MEM retry
path otherwise dominates).  The Ensemble parts have ample SRAM for
this.

Measured on an OpenMV AE3 (CYW43439): TCP receive from a Python socket
benchmark improves ~15-20%, transmit similarly; UDP is unaffected.

Signed-off-by: Kwabena W. Agyeman <[email protected]>
@kwagyeman kwagyeman changed the title Kwabena/alif cyw43 perf alif: Run the CYW43 SPI bus at 32MHz. Aug 16, 2026
@kwagyeman kwagyeman moved this to In progress in OpenMV Features Aug 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

2 participants