Morse Micro Wi-Fi HaLow (IEEE 802.11ah)
This driver supports the Morse Micro MM6108 and MM8108 Wi-Fi HaLow transceivers connected to the host through SPI, for example the Quectel FGH100M module used on the Seeed Studio Wi-Fi HaLow module for XIAO.
Wi-Fi HaLow (IEEE 802.11ah) works in the sub-1 GHz bands (e.g. 902-928 MHz
in the US, 863-868 MHz in Europe), giving a longer range and a lower power
consumption than the 2.4/5 GHz Wi-Fi. For the network stack it behaves like
any other Wi-Fi interface: the driver registers a wlanN network device
that is configured with the standard wireless IOCTLs, for example with the
wapi tool.
The driver is located in drivers/wireless/ieee80211/morse and supports
the station (STA) mode with open, OWE and WPA3-SAE security.
Architecture
+----------------------------------------------+
| NuttX network stack / wapi / dhcpc |
+----------------------------------------------+
| morse_netdev.c netdev lower half and |
| wireless operations |
+----------------------------------------------+
| libmorse 802.11ah UMAC, WPA3-SAE | pre-built
| (Morse Micro) supplicant, rate control, | library
| chip transport (SPI) |
+----------------------+-----------------------+
| morse_mmosal.c | morse_mmhal.c |
| OS abstraction: | hardware abstraction:|
| kthreads, mutexes, | SPI, GPIOs, IRQs, |
| semaphores, timers | firmware and BCF |
+----------------------+-----------------------+
| NuttX kernel | board lower half |
| | (morse_lower_s) |
+----------------------+-----------------------+
The 802.11ah MAC, the supplicant and the chip transport protocol are
provided by libmorse, the Morse Micro host library that is also used by
the Morse Micro FreeRTOS and Zephyr ports. The NuttX driver implements the
interfaces that libmorse needs from the platform:
mmosal(morse_mmosal.c): tasks, mutexes, semaphores, queues and timers on top of the NuttX kernel services. The timers run on the low priority work queue.mmhal(morse_mmhal.c): SPI transfers through the NuttX SPI driver, control lines and interrupts through the board lower half, and the chip firmware and board configuration file that are embedded in the image.the network interface (
morse_netdev.c): it uses the network lower half interface (netdev_lowerhalf_s) and its wireless operations. Thelibmorsedatapath uses 802.3 frames, so the interface is an Ethernet-likeNET_LL_IEEE80211link.
External files
The driver itself is Apache-2.0 licensed, but it needs a few files from the
Morse Micro MM-IoT-SDK. They
are downloaded at build time (about 3 MB) to
drivers/wireless/ieee80211/morse/mm-iot-sdk-<version> and removed by
make distclean:
File |
Content |
License |
|---|---|---|
|
libmorse API headers |
Apache-2.0 |
|
pre-built host library |
Morse Micro Binary Distribution License |
|
chip firmware ( |
Morse Micro Binary Distribution License |
|
channel lists of each regulatory domain |
Apache-2.0 |
|
packet memory allocator |
Apache-2.0 |
|
utility macros |
Apache-2.0 |
|
BCF conversion tool |
Apache-2.0 |
|
license texts |
libmorse.a is available for Cortex-M4F, Cortex-M7F and Cortex-M33F; the
driver selects the right one from the architecture configuration. With
the Make build the pre-built library is merged into libdrivers.a
(PREBUILT_LIBS in drivers/Makefile), with CMake it is added with
nuttx_add_extra_library(). In both cases only the referenced objects are
linked.
The chip also needs a board configuration file (BCF) with the RF
calibration and the regulatory limits of the module. The default one is the
Quectel FGH100M-H BCF, downloaded from the
morse-firmware
repository and converted to the mbin format with the SDK tool. This requires
python3 with the pyelftools package (pip install pyelftools).
The files that are already present are not downloaded again. To build
without network access, copy the files listed above (keeping the SDK
directory layout) to drivers/wireless/ieee80211/morse/mm-iot-sdk-<version>
and use CONFIG_IEEE80211_MORSE_BCF_SDK or
CONFIG_IEEE80211_MORSE_BCF_CUSTOM, or also copy the converted
bcf_fgh100mhaamd.mbin to drivers/wireless/ieee80211/morse.
Configuration
Option |
Description |
|---|---|
|
Enable the driver. |
|
Transceiver. |
|
SDK version the files are downloaded from (default |
|
BCF: Quectel FGH100M-H (default), one of the SDK BCFs, or a custom mbin file. |
|
Default regulatory domain (default |
|
Default SPI clock, if the board does not set one. |
|
Priority of the driver tasks. |
|
Time the connect request waits for the link (default 30 s). |
|
Route the libmorse messages to the syslog. |
The driver requires CONFIG_NETDEV_WIRELESS_IOCTL. libmorse is built
against the newlib C library headers: the driver selects
CONFIG_ARCH_SETJMP_H and CONFIG_LIBC_CTYPE_TABLE (the newlib
compatible _ctype_ table), and CONFIG_LIBC_LOCALTIME is not supported
(its mktime() writes the NuttX specific tm_gmtoff/tm_zone fields
of struct tm).
WPA3-SAE needs a good entropy source, so the driver depends on
CONFIG_DEV_RANDOM or CONFIG_DEV_URANDOM: enable the hardware random
number generator (e.g. CONFIG_STM32_RNG) and CONFIG_DEV_RANDOM. On the
STM32H7 the RNG kernel clock is HSI48, so CONFIG_STM32_HSI48 must be
enabled as well, otherwise the RNG reports a clock error.
The image includes the ~380 KB chip firmware: the driver needs about 1 MB
of flash. On the WeAct STM32H743 halow configuration the whole system
uses about 57 KB of heap while connected.
Board interface
The board logic describes how the transceiver is connected with a
struct morse_lower_s (include/nuttx/wireless/morse.h) and registers
the driver with morse_register():
Signal |
Description |
|---|---|
SPI (SCK, MOSI, MISO) |
SPI mode 0. The chip select is driven by the board
|
RESET_N |
Output, active low. Held low until the driver boots
the chip ( |
SPI_IRQ |
Input, active low, falling edge interrupt
( |
WAKE |
Output, active high ( |
BUSY |
Input, active high, rising edge interrupt ( |
#include <nuttx/wireless/morse.h>
static const struct morse_lower_s g_halow_lower =
{
.devid = SPIDEV_WIRELESS(0),
.frequency = 20000000,
.reset = halow_reset,
.irq_asserted = halow_irq_asserted,
.irq_attach = halow_irq_attach,
.irq_enable = halow_irq_enable,
/* WAKE and BUSY not connected: the chip is kept always awake */
};
int board_halow_initialize(void)
{
struct spi_dev_s *spi = stm32_spibus_initialize(1);
return morse_register(spi, &g_halow_lower);
}
morse_register() boots the chip (downloading its firmware through SPI),
reads its MAC address and registers the network interface.
boards/arm/stm32h7/weact-stm32h743/src/stm32_morse.c is a complete
example.
WAKE and BUSY lines (power save)
libmorse puts the chip in power save when the bus is idle (after a few
milliseconds): it de-asserts WAKE and stops listening to SPI_IRQ. When the
chip has an event for the host (a received packet, the end of a scan, …)
it asserts BUSY, which wakes up the host. So the power save only works if
both WAKE and BUSY are connected.
If the board lower half does not provide wake or busy (NULL),
the driver keeps the chip always awake: it reports BUSY as permanently
asserted, which prevents libmorse from suspending the host interface,
and it disables the 802.11 power save of the station. This costs more
power but works with only the SPI, SPI_IRQ and RESET_N lines.
Warning
On the Seeed Studio Wi-Fi HaLow module for XIAO (Quectel FGH100M-H)
the WAKE and BUSY pads (D1 and D4) are not connected to the module:
the series resistors near those pins are not populated (DNP). If the
board declares these lines anyway, the driver works until the first time
the chip is put to sleep and then it stops receiving events: a scan never
completes, the chip holds SPI_IRQ asserted and BUSY never rises. Leave
wake/busy unset for this module, or populate the two resistors to
use the power save.
Also note that this module needs the 5V pad connected: it supplies the RF front end, without it the module enumerates and receives but cannot transmit.
Usage
Wi-Fi HaLow only allows open, OWE and WPA3-SAE networks (no WPA2). A passphrase selects WPA3-SAE (with PMF required); WPA/WPA2 requests are mapped to SAE:
nsh> ifconfig wlan0 up
nsh> wapi scan wlan0
bssid / frequency / signal level / encode / ssid
1c:bc:ec:30:f7:fb 9.035e+08 -14 0800 silex-ap
nsh> wapi psk wlan0 mypassphrase 3 3
nsh> wapi essid wlan0 silex-ap 1
nsh> renew wlan0
nsh> ifconfig
wlan0 Link encap:Ethernet HWaddr 68:24:99:44:6b:6b at RUNNING mtu 1500
inet addr:10.0.3.245 DRaddr:10.0.0.1 Mask:255.255.240.0
nsh> wapi sense wlan0
-6
The frequency is reported in Hz. wapi sense reports the RSSI (dBm) of
the access point. wapi disconnect wlan0 disconnects (the carrier goes
down).
The regulatory domain selects the channels and the transmit power limits. It must match the country where the device is used and the access point configuration, otherwise the access point is not found. It can be changed while disconnected:
nsh> wapi country wlan0 AU
The supported codes are the ones of the SDK regulatory database (AU, CA, EU, GB, IN, JP, KR, NZ and US), restricted by the regions of the BCF.
Troubleshooting
The SPI data is shifted by one or more bits: signal integrity. With jumper wires, use the lowest GPIO slew rate for SCK and MOSI and keep a ground wire next to the SPI wires. On the WeAct STM32H743 the bus is reliable up to 18.75 MHz this way.
The scan never completes / no events after the boot: WAKE/BUSY declared but not connected, see above.
The system hangs while connecting: the SAE handshake reads a lot of random numbers; check that the RNG clock is configured (HSI48 on the STM32H7).
The access point is not found: check the country code and the BCF regions.
Enable
CONFIG_DEBUG_WIRELESS_INFOandCONFIG_IEEE80211_MORSE_LIBLOGfor more messages.
Limitations
Only the station mode is supported (no access point or relay mode).
The SPI transfers are polled (no DMA).
Only the SPI host interface is supported (no SDIO).