Modem — AT Commands
The nRF9151 modem is controlled over UART1 (GPIO16–19) with hardware flow control. The board ships with two Python drivers that share the same interface but differ in how they handle waiting for modem responses.
| Module | Class | When to use |
|---|---|---|
modem | Modem | Simple scripts, no concurrent tasks |
modem_uasync | ModemAsync | Applications using uasyncio — timers, sensors, networking running concurrently |
Both classes are singletons — only one instance exists at runtime. Do not use both in the same application.
Using Modem (blocking)
Section titled “Using Modem (blocking)”The synchronous driver is straightforward: every call blocks until the modem replies or the timeout expires.
from modem import Modem
m = Modem() # initialises nRF9151 and clears the RX bufferSend a raw AT command and check the response
Section titled “Send a raw AT command and check the response”ok = m.send_cmd("AT") # returns True if modem replies "OK"print(ok)Read the raw response string
Section titled “Read the raw response string”response = m.send_cmd("AT+CGMR", expected="OK", is_bool=False)print(response) # firmware version stringWait for a specific string
Section titled “Wait for a specific string”m.send(b"AT+CFUN=1\r\n")resp = m.wait_response(expected="OK", timeout_ms=5000)print(resp)Setting modem functionality (CFUN)
Section titled “Setting modem functionality (CFUN)”m.CFUN(1) # full functionality — connects to the networkm.CFUN(4) # flight modem.CFUN(0) # minimum functionality / power off RFValid CFUN modes: 0, 1, 2, 4, 20, 21, 30, 31, 40, 41, 44.
Using ModemAsync (non-blocking)
Section titled “Using ModemAsync (non-blocking)”ModemAsync extends Modem and replaces the blocking I/O layer with uasyncio events. Use it whenever you need the modem to coexist with other concurrent tasks — blinking LEDs, reading sensors, or managing multiple network connections.
from modem_uasync import ModemAsyncimport uasyncio
async def main(): m = ModemAsync() await m.start() # mandatory: launches the background serial reader ...
uasyncio.run(main())Rules when using ModemAsync
Section titled “Rules when using ModemAsync”These constraints come from MicroPython’s uasyncio event loop model and apply everywhere in your async code, not just to modem calls.
1. Every method call must be awaited.
Without await, the call returns a coroutine object — no AT command is sent, no value is returned.
# correctresult = await m.mqtt_publish("topic", "payload")
# wrong — AT command is never sent, result is a coroutine objectresult = m.mqtt_publish("topic", "payload")2. await m.start() must be called before anything else.
It creates the _reader() background task that reads incoming bytes from the modem. Without it, every wait_response() will time out.
3. Never use blocking sleep inside a coroutine.
time.sleep() and time.sleep_ms() freeze the entire event loop, preventing _reader() from running and causing spurious timeouts.
# correctawait uasyncio.sleep_ms(1000)
# wrong — blocks the event loopimport timetime.sleep_ms(1000)4. Long-running loops must be separate tasks.
Use uasyncio.create_task(coro()) to run a coroutine concurrently. Calling await coro() inline will block main() until coro() returns.
# correct — runs concurrently with the rest of main()uasyncio.create_task(on_message(m))
# wrong — main() waits here foreverawait on_message(m)5. recv() suspends the calling coroutine.
await m.recv() blocks the current coroutine until an MQTT message arrives. Always run it inside a dedicated task.
async def on_message(m): while True: topic, payload = await m.recv() print(topic, payload)
uasyncio.create_task(on_message(m)) # not: await on_message(m)Using UART directly (no module)
Section titled “Using UART directly (no module)”If you prefer a lower-level approach without the modem module:
from machine import UART, Pin
# Power on the modempwr_en = Pin("MODEM_PWR_EN", Pin.OUT, value=1)pwr_sync = Pin("MODEM_PWR_SYNC", Pin.OUT, value=0)reset = Pin("MODEM_RESET", Pin.OUT, value=1)
modem = UART(1, baudrate=115200, tx=Pin("MODEM_TX"), rx=Pin("MODEM_RX"), cts=Pin("MODEM_CTS"), rts=Pin("MODEM_RTS"))
modem.write(b"AT\r\n")print(modem.read(64))See the modem reference overview for links to the full Modem and ModemAsync API documentation.