Skip to content

Kwabena/network halow - #147

Draft
kwagyeman wants to merge 15 commits into
openmv:openmv-1.28.0from
kwagyeman:kwabena/network_halow
Draft

Kwabena/network halow#147
kwagyeman wants to merge 15 commits into
openmv:openmv-1.28.0from
kwagyeman:kwabena/network_halow

Conversation

@kwagyeman

@kwagyeman kwagyeman commented Aug 13, 2026

Copy link
Copy Markdown
Member

This is a draft PR that tracks micropython#19617 and will be updated to match. Only commit mimxrt/boards/OPENMV_RT1060: Enable HaLow. is unique for our micropython fork since the RT1062 board support files aren't upstream.

Summary

This PR adds network.HALOW, a MicroPython network interface for the Morse Micro MM6108/MM8108 802.11ah (Wi-Fi HaLow) sub-GHz Wi-Fi transceivers, and wires it into the stm32, alif and mimxrt ports.

HaLow trades raw throughput for range and power, so the API follows the existing network.WLAN/cyw43 station model — active(), connect(), isconnected(), ifconfig(), config(), status() — plus the HaLow-specific pieces: regulatory network.country(), S1G channel/bandwidth reporting, OPEN/OWE/SAE (WPA3) security, and TWT-based power save.

What's included:

  • drivers/halow/ — the driver core: the SPI/SDIO transport to the transceiver, an lwIP netif, a packet-memory pool, and a small OSAL/scheduler shim so the vendor library runs cooperatively off the MicroPython scheduler + a PendSV dispatch (no vendor RTOS), mirroring how the cyw43 driver is integrated.
  • extmod/network_halow.c — the network.HALOW Python binding.
  • lib/mm-iot-sdk — the Morse Micro MM-IoT-SDK as a submodule, providing the prebuilt morselib and the transceiver firmware/board-config blobs.
  • Port integration for stm32/alif/mimxrt (each gains a small mp_hal_pin_interrupt C-level IRQ-registration helper), docs, and hardware tests under tests/extmod_hardware/.
  • HaLow enabled on OPENMV_N6 and OPENMV_AE3.

The driver is only compiled when a board sets MICROPY_PY_NETWORK_HALOW=1; it has zero impact on other boards.

Testing

Tested on hardware against a HaLow AP — association plus 20-minute TCP/UDP throughput soaks (hundreds of connect/transfer cycles, no leaks or asserts). All three integrated ports (stm32/alif/mimxrt) compile.

Throughput (bench soak means, station→AP, channel 28, 2 MHz bandwidth):

Board MCU Transport UDP up TCP up TCP down Assoc
OPENMV_N6 STM32N6 (Cortex-M55) hardware IRQ 8.1 Mbit/s 6.0 Mbit/s 5.0 Mbit/s 6.3 s
OPENMV_RT1060 i.MX RT1062 (Cortex-M7) hardware IRQ 6.1 Mbit/s 2.7 Mbit/s 3.2 Mbit/s 5.0 s
OPENMV_AE3 Alif Ensemble E3 (Cortex-M55) polled 3.2 Mbit/s 2.1 Mbit/s 2.6 Mbit/s 3.6 s

UDP-up is the cleanest capability figure (no retransmit/pacing); ~8 Mbit/s on the N6 is close to the practical ceiling for 2 MHz bandwidth. TCP is lower across the board due to link-level loss/retransmit behaviour.

OPENMV_N6 and OPENMV_AE3 are enabled in this PR. The mimxrt port integration is included and compiles; it's exercised on hardware by an out-of-tree board (the OpenMV RT1060, an i.MX RT1062, numbers above), but no in-tree board enables HaLow on mimxrt yet.

Trade-offs and Alternatives

  • morselib is a prebuilt binary (Morse Micro Binary Distribution Licence), added via the lib/mm-iot-sdk submodule — the same model as other vendor Wi-Fi stacks (cyw43, NINA-W10). Building it from source (HALOW_MORSELIB_SOURCE=1) is supported for debugging, but those sources are GPL-3.0 and incompatible with the rest of the firmware, so the prebuilt library is the default.
  • morselib is compiled against newlib, so on -nostdlib ports the driver links the target's libc/libm and ships weak newlib syscall stubs to satisfy it (a failing _sbrk, so the C library never touches MicroPython's heap). This is contained entirely within the driver and costs nothing when HaLow is disabled.
  • The cooperative-scheduler integration avoids pulling in a vendor RTOS; it reuses the MicroPython scheduler the same way the cyw43 driver does.

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.

Some drivers need to register a GPIO interrupt from C rather than
through the machine.Pin.irq() Python method -- for example a network
driver that wakes on a transceiver's IRQ line.  Add
mp_hal_pin_interrupt(), matching the stm32 and rp2 ports, plus
mp_hal_pin_interrupt_enable() to mask and unmask an already-registered
interrupt, which is useful for interrupt coalescing.

The registered interrupt is given the lowest priority, as eth.c already
does for the Ethernet IRQs: it only schedules work and must not preempt
USB or DMA.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
Some drivers need to register a GPIO interrupt from C rather than
through the machine.Pin.irq() Python method.  Factor the IRQ-object
allocation out of machine_pin_irq() into a shared helper, then build
mp_hal_pin_interrupt() and mp_hal_pin_interrupt_enable() on top of it.
The latter masks and unmasks an already-registered interrupt, which is
useful for interrupt coalescing.

The registered interrupt is given the same low priority this port
already uses for the cyw43 host-wake IRQ, since it only schedules work.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
Add mp_hal_pin_interrupt_enable() to mask and unmask an
already-registered pin interrupt, matching the mimxrt and alif ports.
A driver can use it for interrupt coalescing: mask on the edge, unmask
once the work is done.  It wraps the port's existing extint_enable() /
extint_disable().

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
Carries morselib -- the vendor's prebuilt WLAN library -- and the
transceiver firmware that the 802.11ah driver is built on.  Pinned at
release 2.12.3.  morselib is distributed as a prebuilt archive under the
Morse Micro Binary License; the rest of the SDK is Apache-2.0.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
The portable core that the network.HALOW binding is built on, sitting
between morselib and the MicroPython runtime:

- a cooperative scheduler that runs morselib's tasks on their own
  stacks, pumped from the network poll;
- an OSAL providing the RTOS primitives morselib expects -- tasks,
  mutexes, semaphores, queues and timers -- on top of that scheduler;
- a HAL driving the transceiver over SPI, with an optional pin
  interrupt for low-latency receive;
- packet memory and the lwIP interface, allocated from the MicroPython
  heap so the driver keeps no static pool of its own;
- build rules that link the prebuilt morselib archive and the firmware
  blobs.

Scheduler and allocator come with qemu and host unit tests.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
The MicroPython network interface on top of the drivers/halow core: a
station-mode network.HALOW class with connect/disconnect, scan, config
and status accessors, and an ioctl passthrough for certification
tooling, plus the SEC_/TWT_/DUTY_CYCLE_/PM_/STAT_ constants.

The constants are fixed to literals and static-asserted against the
morselib enums, so a future SDK renumbering fails the build here rather
than silently changing what a user's stored value means.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
Documents the network.HALOW 802.11ah station interface: its methods,
the config and status parameters, the security/power/duty-cycle/TWT
constants and the link states, with notes on the ways 802.11ah differs
from 2.4GHz Wi-Fi -- no WPA2-PSK, a mandatory country/channel plan, and
a transmit-only power-save mode.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
Two hardware suites for the network.HALOW interface.  network_halow.py
needs only the transceiver: it checks the constants, active() cycling,
config validation and out-of-range rejection, scan, and that
use-after-deinit and config-after-deactivate are refused rather than
fatal.  network_halow_sta.py needs a HaLow access point and credentials
in a halow_config module, and covers association, a socket round trip,
and the association-only settings.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
Wire the drivers/halow driver into the alif port: include its build
rules, tear it down on soft reset, and service it.  The transceiver is
polled from a dedicated 1ms soft timer -- more often than lwIP's 64ms
tick, because morselib always has timers due and this poll is what
notices a received frame -- and halow_schedule_poll() raises a PendSV
dispatch so a queued transmit does not wait for the next tick.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
Wire the drivers/halow driver into the mimxrt port: include its build
rules, tear it down on soft reset, and service the transceiver from the
network poll each systick, with halow_schedule_poll() raising a PendSV
dispatch so a queued transmit does not wait for the next tick.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
Wire the drivers/halow driver into the stm32 port: include its build
rules, tear it down on soft reset, and service the transceiver from the
network poll each systick, with halow_schedule_poll() raising a PendSV
dispatch so a queued transmit does not wait for the next tick.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
At the port's 16K MEM_SIZE, sustained TCP exhausts the lwIP heap: it
returns ENOMEM and throughput collapses, recovers, and collapses again
in a repeating pattern.  Raise it to 64K, matching the stm32 port,
which holds steady.

Measured on an AE3 driving a HaLow uplink: tens of ENOMEM failures per
20-minute soak at 16K, none at 64K.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
Enable the Morse Micro MM8108 802.11ah shield on the N6: SPI2 on
P0/P1/P2, the shield's control lines on the expansion header, and the
pin interrupt for low-latency receive.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
Enable the Morse Micro MM8108 802.11ah shield on the AE3's HP core: SPI0
on P0/P1/P2, with the shield's control lines sharing the JTAG pins on
the header.  The transceiver is polled -- the IRQ line is mapped but the
pin interrupt is left off, as it gives no throughput gain on this port.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
Enable the Morse Micro MM8108 802.11ah shield on the RT1060: LPSPI3
(logical SPI 1) on P0/P1/P2, the shield's control lines on the header,
and the pin interrupt for low-latency receive.  morselib is linked from
its Cortex-M7 build.

Signed-off-by: Kwabena W. Agyeman <kwagyeman@live.com>
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

Successfully merging this pull request may close these issues.

1 participant