Stamp UWB
Stamp UWB is a QM33120 ultra-wideband transceiver. The StampUWB class
selects the pin mapping for the current Stamp host and provides the PHY, frame,
status, and timestamp operations. The PHY channel is fixed to channel 9, and
only one active
StampUWB instance is supported.
Support the following products:
Supported hosts:
StampS3Mini
StampC6
StampC5
DS-TWR Ranging Principle
The simple examples use a three-message double-sided two-way ranging exchange:
The Tag transmits a Poll frame and records the Poll TX timestamp
T1. The Anchor receives it and records the Poll RX timestampT2.The Anchor transmits a Response frame and records the Response TX timestamp
T3. The Tag receives it and records the Response RX timestampT4.The Tag schedules a delayed Final frame, records its TX timestamp
T5, and includesT1,T4, andT5in the frame. The Anchor receives the Final frame and records timestampT6.
The examples use the following compact frame format. Multi-byte values are little-endian, and the sequence number associates the three frames:
Poll:
[0x01, sequence]Response:
[0x02, sequence]Final:
[0x03, sequence, T1, T4, T5], where each timestamp occupies the low 32 bits of a device timestamp
The Anchor calculates the time of flight while handling 32-bit timestamp wraparound:
round_a = T4 - T1
round_b = T6 - T3
delay_a = T5 - T4
delay_b = T3 - T2
tof = (round_a * round_b - delay_a * delay_b) \
/ (round_a + round_b + delay_a + delay_b)
distance = abs(tof * DWT_TIME_UNITS * 299702547)
DWT_TIME_UNITS is 1 / (499200000 * 128) seconds. The Anchor prints the
calculated distance locally. This simplified protocol does not send the
distance back to the Tag.
UiFlow2 Example
Simple DS-TWR Anchor
Open the stampc5_uwb_simple_anchor.m5f2 project in UiFlow2.
The anchor receives Poll and Final frames, calculates the distance locally, and prints the non-negative ranging result.
UiFlow2 Code Block:
Simple DS-TWR Tag
Open the stampc5_uwb_simple_tag.m5f2 project in UiFlow2.
The tag sends Poll, waits for Response, and sends a delayed Final frame. This minimal protocol does not return the calculated distance to the tag.
UiFlow2 Code Block:
MicroPython Example
Simple DS-TWR Anchor
The anchor receives Poll and Final frames, calculates the distance locally, and prints the non-negative ranging result.
MicroPython Code Block:
1# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD 2# 3# SPDX-License-Identifier: MIT 4 5import os, sys, io 6import M5 7from M5 import * 8import uwb 9from stamp import StampUWB 10import time 11import struct 12import math 13 14 15stamp_uwb = None 16status = None 17sequence = None 18poll = None 19poll_rx = None 20response_frame = None 21final_frame = None 22response_tx = None 23final_rx = None 24round_a = None 25round_b = None 26delay_a = None 27response_rx = None 28poll_tx = None 29delay_b = None 30denominator = None 31final_tx = None 32tof_dtu = None 33distance = None 34display_distance = None 35 36 37def setup(): 38 global \ 39 stamp_uwb, \ 40 status, \ 41 sequence, \ 42 poll, \ 43 poll_rx, \ 44 response_frame, \ 45 final_frame, \ 46 response_tx, \ 47 final_rx, \ 48 round_a, \ 49 round_b, \ 50 delay_a, \ 51 response_rx, \ 52 poll_tx, \ 53 delay_b, \ 54 denominator, \ 55 final_tx, \ 56 tof_dtu, \ 57 distance, \ 58 display_distance 59 60 M5.begin() 61 stamp_uwb = StampUWB() 62 stamp_uwb.configure( 63 preamble_length=128, 64 pac=8, 65 tx_code=9, 66 rx_code=9, 67 sfd_type=uwb.SFD_DW_8, 68 data_rate=uwb.BR_6M8, 69 phr_mode=uwb.PHR_STD, 70 phr_rate=uwb.PHR_RATE_STD, 71 sfd_timeout=129, 72 ) 73 stamp_uwb.configure_tx_rf(pg_delay=0x34, tx_power=0xFEFEFEFE, pg_count=0) 74 stamp_uwb.set_antenna_delay(tx=16385, rx=16385) 75 stamp_uwb.set_lna_pa(lna=True, pa=True) 76 stamp_uwb.set_rx_timeout(30000) 77 sequence = 0 78 79 80def loop(): 81 global \ 82 stamp_uwb, \ 83 status, \ 84 sequence, \ 85 poll, \ 86 poll_rx, \ 87 response_frame, \ 88 final_frame, \ 89 response_tx, \ 90 final_rx, \ 91 round_a, \ 92 round_b, \ 93 delay_a, \ 94 response_rx, \ 95 poll_tx, \ 96 delay_b, \ 97 denominator, \ 98 final_tx, \ 99 tof_dtu, \ 100 distance, \ 101 display_distance 102 M5.update() 103 try: 104 stamp_uwb.force_trx_off() 105 stamp_uwb.clear_status(uwb.STATUS_TX_DONE | uwb.STATUS_RX_ALL) 106 stamp_uwb.rx_enable(uwb.RX_IMMEDIATE) 107 status = stamp_uwb.wait_status(uwb.STATUS_RX_ALL, 50) 108 if status & uwb.STATUS_RX_GOOD: 109 poll = stamp_uwb.read_rx_frame() 110 if len(poll) == 2 and poll[0] == 1: 111 sequence = poll[1] 112 poll_rx = stamp_uwb.rx_timestamp() & 0xFFFFFFFF 113 stamp_uwb.clear_status(uwb.STATUS_TX_DONE | uwb.STATUS_RX_ALL) 114 stamp_uwb.set_rx_after_tx_delay(0) 115 response_frame = bytearray(2) 116 response_frame[0] = 2 117 response_frame[1] = sequence 118 stamp_uwb.write_tx_frame(response_frame, True) 119 stamp_uwb.start_tx(uwb.TX_IMMEDIATE | uwb.RESPONSE_EXPECTED) 120 status = stamp_uwb.wait_status(uwb.STATUS_RX_ALL, 50) 121 if status & uwb.STATUS_RX_GOOD: 122 final_frame = stamp_uwb.read_rx_frame() 123 if ( 124 len(final_frame) == 14 125 and final_frame[0] == 3 126 and final_frame[1] == sequence 127 ): 128 response_tx = stamp_uwb.tx_timestamp() & 0xFFFFFFFF 129 final_rx = stamp_uwb.rx_timestamp() & 0xFFFFFFFF 130 poll_tx, response_rx, final_tx = struct.unpack_from("<III", final_frame, 2) 131 round_a = response_rx - poll_tx & 0xFFFFFFFF 132 round_b = final_rx - response_tx & 0xFFFFFFFF 133 delay_a = final_tx - response_rx & 0xFFFFFFFF 134 delay_b = response_tx - poll_rx & 0xFFFFFFFF 135 denominator = (round_a + round_b) + (delay_a + delay_b) 136 if denominator != 0: 137 tof_dtu = (round_a * round_b - delay_a * delay_b) / denominator 138 distance = math.fabs((tof_dtu * (1 / (499200000 * 128))) * 299702547) 139 display_distance = round(distance * 100) / 100 140 if distance >= 0 and distance <= 100: 141 print( 142 ( 143 str( 144 ( 145 str( 146 ( 147 str((str("sequence=") + str(sequence))) 148 + str(" distance=") 149 ) 150 ) 151 + str(display_distance) 152 ) 153 ) 154 + str(" m") 155 ) 156 ) 157 else: 158 print((str("invalid distance: ") + str(distance))) 159 else: 160 print("invalid final") 161 else: 162 print("final timeout") 163 except: 164 print("ranging retry") 165 stamp_uwb.force_trx_off() 166 stamp_uwb.clear_status(uwb.STATUS_TX_DONE | uwb.STATUS_RX_ALL) 167 168 time.sleep_ms(10) 169 170 171if __name__ == "__main__": 172 try: 173 setup() 174 while True: 175 loop() 176 except (Exception, KeyboardInterrupt) as e: 177 try: 178 from utility import print_error_msg 179 180 print_error_msg(e) 181 except ImportError: 182 print("please update to latest firmware")
Simple DS-TWR Tag
The tag sends Poll, waits for Response, and sends a delayed Final frame. This minimal protocol does not return the calculated distance to the tag.
MicroPython Code Block:
1# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD 2# 3# SPDX-License-Identifier: MIT 4 5import os, sys, io 6import M5 7from M5 import * 8import uwb 9from stamp import StampUWB 10import time 11import struct 12 13 14stamp_uwb = None 15poll_frame = None 16sequence = None 17status = None 18response = None 19expected_response = None 20poll_tx = None 21response_rx = None 22delayed_time = None 23delayed_time_even = None 24final_tx = None 25final_frame = None 26 27 28def setup(): 29 global \ 30 stamp_uwb, \ 31 poll_frame, \ 32 sequence, \ 33 status, \ 34 response, \ 35 expected_response, \ 36 poll_tx, \ 37 response_rx, \ 38 delayed_time, \ 39 delayed_time_even, \ 40 final_tx, \ 41 final_frame 42 43 M5.begin() 44 stamp_uwb = StampUWB() 45 stamp_uwb.configure( 46 preamble_length=128, 47 pac=8, 48 tx_code=9, 49 rx_code=9, 50 sfd_type=uwb.SFD_DW_8, 51 data_rate=uwb.BR_6M8, 52 phr_mode=uwb.PHR_STD, 53 phr_rate=uwb.PHR_RATE_STD, 54 sfd_timeout=129, 55 ) 56 stamp_uwb.configure_tx_rf(pg_delay=0x34, tx_power=0xFEFEFEFE, pg_count=0) 57 stamp_uwb.set_antenna_delay(tx=16385, rx=16385) 58 stamp_uwb.set_lna_pa(lna=True, pa=True) 59 stamp_uwb.set_rx_after_tx_delay(0) 60 stamp_uwb.set_rx_timeout(30000) 61 sequence = 0 62 63 64def loop(): 65 global \ 66 stamp_uwb, \ 67 poll_frame, \ 68 sequence, \ 69 status, \ 70 response, \ 71 expected_response, \ 72 poll_tx, \ 73 response_rx, \ 74 delayed_time, \ 75 delayed_time_even, \ 76 final_tx, \ 77 final_frame 78 M5.update() 79 try: 80 stamp_uwb.force_trx_off() 81 stamp_uwb.clear_status(uwb.STATUS_TX_DONE | uwb.STATUS_RX_ALL) 82 poll_frame = bytearray(2) 83 poll_frame[0] = 1 84 poll_frame[1] = sequence 85 stamp_uwb.write_tx_frame(poll_frame, True) 86 stamp_uwb.start_tx(uwb.TX_IMMEDIATE | uwb.RESPONSE_EXPECTED) 87 status = stamp_uwb.wait_status(uwb.STATUS_RX_ALL, 50) 88 if not (status & uwb.STATUS_RX_GOOD): 89 print("response timeout") 90 time.sleep_ms(200) 91 else: 92 response = stamp_uwb.read_rx_frame() 93 expected_response = bytearray(2) 94 expected_response[0] = 2 95 expected_response[1] = sequence 96 if response != expected_response: 97 print("invalid response") 98 time.sleep_ms(200) 99 else: 100 poll_tx = stamp_uwb.tx_timestamp() 101 response_rx = stamp_uwb.rx_timestamp() 102 delayed_time = response_rx + 10000 * 63898 >> 8 103 delayed_time_even = delayed_time & 0xFFFFFFFE 104 final_tx = (delayed_time_even << 8) + 16385 105 final_frame = struct.pack( 106 "<BBIII", 107 3, 108 sequence, 109 poll_tx & 0xFFFFFFFF, 110 response_rx & 0xFFFFFFFF, 111 final_tx & 0xFFFFFFFF, 112 ) 113 stamp_uwb.clear_status(uwb.STATUS_TX_DONE | uwb.STATUS_RX_ALL) 114 stamp_uwb.set_delayed_trx_time(delayed_time) 115 stamp_uwb.write_tx_frame(final_frame, True) 116 stamp_uwb.start_tx(uwb.TX_DELAYED) 117 status = stamp_uwb.wait_status(uwb.STATUS_TX_DONE, 30) 118 stamp_uwb.clear_status(uwb.STATUS_TX_DONE) 119 print((str("final sent, sequence=") + str(sequence))) 120 sequence = sequence + 1 & 0xFF 121 except: 122 print("ranging retry") 123 stamp_uwb.force_trx_off() 124 stamp_uwb.clear_status(uwb.STATUS_TX_DONE | uwb.STATUS_RX_ALL) 125 126 time.sleep_ms(200) 127 128 129if __name__ == "__main__": 130 try: 131 setup() 132 while True: 133 loop() 134 except (Exception, KeyboardInterrupt) as e: 135 try: 136 from utility import print_error_msg 137 138 print_error_msg(e) 139 except ImportError: 140 print("please update to latest firmware")
API
class StampUWB
Constructors
- class StampUWB
Create a Stamp UWB object using the pin mapping of the current Stamp host. Only one active instance is supported.
- Raises:
OSError – If another instance is active, SPI initialization fails, or the UWB device cannot be probed or initialized.
UiFlow2 Code Block:

MicroPython Code Block:
from stamp import StampUWB stamp_uwb_0 = StampUWB()
Constants
The option block supplies the PHY, TX/RX mode, and status-mask constants used by the methods below.
UiFlow2 Code Block:
- uwb.SFD_DW_8
Decawave 8-symbol SFD used by
StampUWB.configure().
- uwb.BR_6M8
6.8 Mbit/s PHY data rate used by
StampUWB.configure().
- uwb.PHR_STD
Standard PHY header mode used by
StampUWB.configure().
- uwb.PHR_RATE_STD
Standard PHY header rate used by
StampUWB.configure().
- uwb.TX_IMMEDIATE
Start transmission immediately.
- uwb.TX_DELAYED
Start transmission at the time set by
StampUWB.set_delayed_trx_time().
- uwb.RESPONSE_EXPECTED
Automatically enable the receiver after transmission. Combine this flag with a TX start mode.
- uwb.RX_IMMEDIATE
Enable the receiver immediately.
- uwb.RX_DELAYED
Enable the receiver at the configured delayed RX time.
- uwb.IDLE_ON_DELAY_ERROR
Return to idle if a delayed RX operation is already too late.
- uwb.STATUS_TX_DONE
Transmission-complete status bit.
- uwb.STATUS_RX_GOOD
Good-frame-received status bit.
- uwb.STATUS_RX_TIMEOUT
Combined receive-timeout status mask.
- uwb.STATUS_RX_ERROR
Combined receive-error status mask.
- uwb.STATUS_RX_ALL
Combined mask containing good-frame, receive-timeout, and receive-error status bits.
Methods
- StampUWB.configure(preamble_length=128, pac=8, tx_code=9, rx_code=9, sfd_type=uwb.SFD_DW_8, data_rate=uwb.BR_6M8, phr_mode=uwb.PHR_STD, phr_rate=uwb.PHR_RATE_STD, sfd_timeout=129)
Configure the channel 9 UWB PHY.
- Parameters:
preamble_length (int) – Preamble length in symbols. Allowed values are
32,64,72,128,256,512,1024,1536,2048, and4096. Default is128.pac (int) – Preamble acquisition chunk size. Allowed values are
4,8,16, and32. Default is8.tx_code (int) – TX preamble code, range
9to12. Default is9.rx_code (int) – RX preamble code, range
9to12. Default is9.sfd_type (int) – SFD type. Use
uwb.SFD_DW_8.data_rate (int) – PHY data rate. Use
uwb.BR_6M8.phr_mode (int) – PHR mode. Use
uwb.PHR_STD.phr_rate (int) – PHR rate. Use
uwb.PHR_RATE_STD.sfd_timeout (int) – SFD timeout in symbols, range
0to65535. Default is129.
UiFlow2 Code Block:

MicroPython Code Block:
import uwb stamp_uwb_0.configure( preamble_length=128, pac=8, tx_code=9, rx_code=9, sfd_type=uwb.SFD_DW_8, data_rate=uwb.BR_6M8, phr_mode=uwb.PHR_STD, phr_rate=uwb.PHR_RATE_STD, sfd_timeout=129, )
- StampUWB.configure_tx_rf(pg_delay=0x34, tx_power=0xFEFEFEFE, pg_count=0)
Configure the channel 9 transmitter RF settings.
- Parameters:
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.configure_tx_rf(0x34, 0xFEFEFEFE, 0)
- StampUWB.set_antenna_delay(tx=16385, rx=16385)
Set the TX and RX antenna delays.
- Parameters:
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.set_antenna_delay(tx=16385, rx=16385)
- StampUWB.set_lna_pa(lna=True, pa=True)
Enable or disable the low-noise amplifier and power amplifier controls.
- Parameters:
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.set_lna_pa(lna=True, pa=True)
- StampUWB.set_rx_after_tx_delay(delay_uus=0)
Set the delay from TX completion to automatic RX enable.
- Parameters:
delay_uus (int) – Delay in UWB microseconds, range
0to4294967295. Default is0.
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.set_rx_after_tx_delay(0)
- StampUWB.set_rx_timeout(timeout_uus=30000)
Set the RX frame timeout. A value of
0disables the timeout.- Parameters:
timeout_uus (int) – Timeout in UWB microseconds, range
0to4294967295. Default is30000.
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.set_rx_timeout(30000)
- StampUWB.set_preamble_timeout(timeout=0)
Set the preamble detection timeout. A value of
0disables the timeout.- Parameters:
timeout (int) – Timeout in PAC units, range
0to65535. Default is0.
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.set_preamble_timeout(0)
- StampUWB.write_tx_frame(data, ranging=True)
Write a payload to the TX buffer. Do not include the two-byte FCS.
- Parameters:
data – Bytes-like payload, range
0to125bytes.ranging (bool) – Set the ranging bit in TX frame control. Default is
True.
- Raises:
ValueError – If the payload exceeds 125 bytes.
OSError – If the payload cannot be written to the TX buffer.
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.write_tx_frame(b"hello", ranging=True)
- StampUWB.set_delayed_trx_time(device_time)
Set the delayed TX/RX device time.
- Parameters:
device_time (int) – Low 32 bits of the 40-bit device timestamp shifted right by 8, range
0to4294967295.
UiFlow2 Code Block:

MicroPython Code Block:
delayed_time = (stamp_uwb_0.rx_timestamp() + 4500 * 63898) >> 8 stamp_uwb_0.set_delayed_trx_time(delayed_time)
- StampUWB.start_tx(mode)
Start an immediate or delayed transmission.
- Parameters:
mode (int) – TX mode composed from
uwb.TX_IMMEDIATEoruwb.TX_DELAYEDand optionaluwb.RESPONSE_EXPECTED.- Raises:
OSError – If a delayed transmission time has already passed.
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.start_tx(uwb.TX_IMMEDIATE | uwb.RESPONSE_EXPECTED)
- StampUWB.rx_enable(mode=uwb.RX_IMMEDIATE)
Enable the receiver.
- Parameters:
mode (int) – Use
uwb.RX_IMMEDIATEoruwb.RX_DELAYED. Delayed RX can be combined withuwb.IDLE_ON_DELAY_ERROR. Default isuwb.RX_IMMEDIATE.- Raises:
OSError – If the receiver cannot be enabled.
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.rx_enable(uwb.RX_IMMEDIATE)
- StampUWB.wait_status(mask, timeout_ms=-1)
Wait until any requested system status bit is set.
- Parameters:
- Returns:
Raw 32-bit system status value.
- Return type:
- Raises:
OSError –
ETIMEDOUTif no requested status bit is set before the software timeout.
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.wait_status(uwb.STATUS_RX_ALL, 50)
- StampUWB.read_status()
Read the low 32 bits of the system status register.
- Returns:
Raw 32-bit system status value.
- Return type:
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.read_status()
- StampUWB.clear_status(mask)
Clear selected system status bits.
- Parameters:
mask (int) – Status mask, range
0x00000000to0xFFFFFFFF.
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.clear_status(uwb.STATUS_TX_DONE)
- StampUWB.force_trx_off()
Force the transmitter and receiver to the idle state.
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.force_trx_off()
- StampUWB.frame_length()
Get the last received frame length including the two-byte FCS.
- Returns:
Received frame length in bytes, range
2to127.- Return type:
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.frame_length()
- StampUWB.read_rx_frame()
Read the last received payload without the two-byte FCS.
- Returns:
Received payload, range
0to125bytes.- Return type:
- Raises:
OSError – If the received frame length is outside the valid range.
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.read_rx_frame()
- StampUWB.tx_timestamp()
Read the last TX timestamp.
- Returns:
Full 40-bit TX timestamp in device time units.
- Return type:
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.tx_timestamp()
- StampUWB.rx_timestamp()
Read the last RX timestamp.
- Returns:
Full 40-bit RX timestamp in device time units.
- Return type:
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.rx_timestamp()
- StampUWB.system_timestamp()
Read the current UWB system timestamp.
- Returns:
Full 40-bit system timestamp in device time units.
- Return type:
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.system_timestamp()
- StampUWB.reset()
Reset, probe, and reinitialise the UWB device. Configure the PHY and RF settings again after reset.
- Raises:
OSError – If the device cannot be probed or initialized after reset.
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.reset() stamp_uwb_0.configure( preamble_length=128, pac=8, tx_code=9, rx_code=9, sfd_type=uwb.SFD_DW_8, data_rate=uwb.BR_6M8, phr_mode=uwb.PHR_STD, phr_rate=uwb.PHR_RATE_STD, sfd_timeout=129, ) stamp_uwb_0.configure_tx_rf( pg_delay=0x34, tx_power=0xFEFEFEFE, pg_count=0 )
- StampUWB.wakeup()
Pulse the WAKEUP pin to wake the UWB device.
UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.wakeup()
- StampUWB.deinit()
Stop TX/RX and release the SPI and GPIO resources. This method is idempotent. After deinitialization, other methods raise
OSError(ENODEV).UiFlow2 Code Block:

MicroPython Code Block:
stamp_uwb_0.deinit()



