Environment Setup: Refer to the Arduino IDE Getting Started Guide to complete the IDE installation, and install the board manager and drivers for the Stamp C5.
Libraries Used:
Hardware Products Used:




The pin connections between Stamp C5 and Stamp UWB F are as follows:
| Stamp UWB F Pin | Stamp C5 GPIO |
|---|---|
| GP7 | G23 |
| IRQ | G0 |
| WAKEUP | G24 |
| RST | G25 |
| MISO | G26 |
| MOSI | G27 |
| CS | G11 |
| SCK | G12 |
#include <Arduino.h>
#include <M5Stamp_UWB.h>
static constexpr uint32_t RANGE_INTERVAL_MS = 200;
static constexpr uint32_t LOG_INTERVAL = 10;
M5Stamp_UWB uwb;
M5Stamp_UWBDSRangeConfig rangeConfig;
bool uwbReady = false;
uint32_t lastRangeMs = 0;
uint32_t rangeCount = 0;
uint32_t okCount = 0;
static bool initUwb()
{
// Stamp-C5 connections to the onboard QM33120 UWB transceiver.
M5Stamp_UWBConfig config;
config.pin_gp7 = 23;
config.pin_irq = 0;
config.pin_wakeup = 24;
config.pin_rst = 25;
config.pin_miso = 26;
config.pin_mosi = 27;
config.pin_cs = 11;
config.pin_sck = 12;
M5Stamp_UWBPHYConfig phy;
phy.channel = M5Stamp_UWBChannel::Channel5;
// Both devices must use the same network addresses and DS-TWR timing.
rangeConfig.panId = 0xDECA;
rangeConfig.initiatorAddress = 0x0001;
rangeConfig.responderAddress = 0x0002;
rangeConfig.responseRxAfterTxDelayUus = 1500;
rangeConfig.responseTxDelayUus = 3000;
rangeConfig.finalTxDelayUus = 1800;
rangeConfig.finalRxAfterResponseTxDelayUus = 500;
rangeConfig.resultRxAfterFinalTxDelayUus = 500;
rangeConfig.rxTimeoutUus = 3000;
rangeConfig.hostTimeoutMs = 100;
rangeConfig.resultRepeatCount = 3;
rangeConfig.resultRepeatGapMs = 3;
if (!uwb.begin(config, phy)) {
Serial.printf("UWB_BEGIN,result=FAIL,error=%s\n", uwb.lastErrorName());
Serial.printf("UWB_RAW_ID,dev_id=0x%08lX\n", static_cast<unsigned long>(uwb.readRawDeviceId()));
return false;
}
const uint32_t devId = uwb.deviceId();
Serial.printf("UWB_ID,dev_id=0x%08lX,chip=%s\n", static_cast<unsigned long>(devId), uwb.chipName());
if (devId != M5STAMP_UWB_QM33120_DEVICE_ID) {
Serial.printf("UWB_ID,result=FAIL,expected=0xDECA0314\n");
return false;
}
Serial.printf("UWB_CONFIG,result=OK,ch=%u,plen=%u,rate=6M8,tx_power=0x%08lX\n", static_cast<unsigned>(phy.channel),
static_cast<unsigned>(phy.preambleLength), static_cast<unsigned long>(phy.txPower));
return true;
}
static void runRanging()
{
// Start one DS-TWR exchange at the configured interval.
if (millis() - lastRangeMs < RANGE_INTERVAL_MS) return;
lastRangeMs = millis();
const M5Stamp_UWBDSRangeResult result = uwb.requestDSRange(rangeConfig);
rangeCount++;
okCount += result.success;
// Print accumulated statistics every LOG_INTERVAL attempts.
if (rangeCount % LOG_INTERVAL) return;
const uint32_t failCount = rangeCount - okCount;
if (result.success) {
Serial.printf(
"DS_RANGE_STAT,count=%lu,ok=%lu,fail=%lu,last=OK,seq=%u,distance_mm=%ld,distance_m=%.3f,elapsed_ms=%lu\n",
static_cast<unsigned long>(rangeCount), static_cast<unsigned long>(okCount),
static_cast<unsigned long>(failCount), result.sequence, static_cast<long>(result.distanceMm),
result.distanceM, static_cast<unsigned long>(result.elapsedMs));
} else {
Serial.printf("DS_RANGE_STAT,count=%lu,ok=%lu,fail=%lu,last=FAIL,seq=%u,error=%s\n",
static_cast<unsigned long>(rangeCount), static_cast<unsigned long>(okCount),
static_cast<unsigned long>(failCount), result.sequence, uwb.lastErrorName());
}
}
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.printf("M5Stamp UWB DS-TWR TAG\n");
Serial.printf("ROLE,mode=TAG\n");
Serial.printf("TWR_MODE,mode=DS-TWR\n");
uwbReady = initUwb();
Serial.printf("TEST_START,result=%s\n", uwbReady ? "OK" : "FAIL");
}
void loop()
{
if (uwbReady) runRanging();
else delay(1000);
}Serial output example:
M5Stamp UWB DS-TWR TAG
ROLE,mode=TAG
TWR_MODE,mode=DS-TWR
UWB_ID,dev_id=0xDECA0314,chip=QM33120/DW3720
UWB_CONFIG,result=OK,ch=5,plen=128,rate=6M8,tx_power=0xFEFEFEFE
TEST_START,result=OK
DS_RANGE_STAT,count=10,ok=10,fail=0,last=OK,seq=10,distance_mm=164,distance_m=0.164,elapsed_ms=7
DS_RANGE_STAT,count=20,ok=20,fail=0,last=OK,seq=20,distance_mm=166,distance_m=0.166,elapsed_ms=7
DS_RANGE_STAT,count=30,ok=30,fail=0,last=OK,seq=30,distance_mm=171,distance_m=0.171,elapsed_ms=7
DS_RANGE_STAT,count=40,ok=40,fail=0,last=OK,seq=40,distance_mm=168,distance_m=0.168,elapsed_ms=7
DS_RANGE_STAT,count=50,ok=50,fail=0,last=OK,seq=50,distance_mm=153,distance_m=0.153,elapsed_ms=7
DS_RANGE_STAT,count=60,ok=60,fail=0,last=OK,seq=60,distance_mm=196,distance_m=0.196,elapsed_ms=7
DS_RANGE_STAT,count=70,ok=70,fail=0,last=OK,seq=70,distance_mm=172,distance_m=0.172,elapsed_ms=7
DS_RANGE_STAT,count=80,ok=80,fail=0,last=OK,seq=80,distance_mm=182,distance_m=0.182,elapsed_ms=7
DS_RANGE_STAT,count=90,ok=90,fail=0,last=OK,seq=90,distance_mm=160,distance_m=0.160,elapsed_ms=7
DS_RANGE_STAT,count=100,ok=100,fail=0,last=OK,seq=100,distance_mm=157,distance_m=0.157,elapsed_ms=7
DS_RANGE_STAT,count=110,ok=110,fail=0,last=OK,seq=110,distance_mm=210,distance_m=0.210,elapsed_ms=7
DS_RANGE_STAT,count=120,ok=120,fail=0,last=OK,seq=120,distance_mm=164,distance_m=0.164,elapsed_ms=7
DS_RANGE_STAT,count=130,ok=130,fail=0,last=OK,seq=130,distance_mm=174,distance_m=0.174,elapsed_ms=7 #include <Arduino.h>
#include <M5Stamp_UWB.h>
static constexpr uint32_t LOG_INTERVAL = 20;
M5Stamp_UWB uwb;
M5Stamp_UWBDSRangeConfig rangeConfig;
bool uwbReady = false;
uint32_t responseCount = 0;
uint32_t failCount = 0;
static bool initUwb()
{
// Stamp-C5 connections to the onboard QM33120 UWB transceiver.
M5Stamp_UWBConfig config;
config.pin_gp7 = 23;
config.pin_irq = 0;
config.pin_wakeup = 24;
config.pin_rst = 25;
config.pin_miso = 26;
config.pin_mosi = 27;
config.pin_cs = 11;
config.pin_sck = 12;
M5Stamp_UWBPHYConfig phy;
phy.channel = M5Stamp_UWBChannel::Channel5;
// Both devices must use the same network addresses and DS-TWR timing.
rangeConfig.panId = 0xDECA;
rangeConfig.initiatorAddress = 0x0001;
rangeConfig.responderAddress = 0x0002;
rangeConfig.responseRxAfterTxDelayUus = 1500;
rangeConfig.responseTxDelayUus = 3000;
rangeConfig.finalTxDelayUus = 1800;
rangeConfig.finalRxAfterResponseTxDelayUus = 500;
rangeConfig.resultRxAfterFinalTxDelayUus = 500;
rangeConfig.rxTimeoutUus = 3000;
rangeConfig.hostTimeoutMs = 100;
rangeConfig.resultRepeatCount = 3;
rangeConfig.resultRepeatGapMs = 3;
if (!uwb.begin(config, phy)) {
Serial.printf("UWB_BEGIN,result=FAIL,error=%s\n", uwb.lastErrorName());
Serial.printf("UWB_RAW_ID,dev_id=0x%08lX\n", static_cast<unsigned long>(uwb.readRawDeviceId()));
return false;
}
const uint32_t devId = uwb.deviceId();
Serial.printf("UWB_ID,dev_id=0x%08lX,chip=%s\n", static_cast<unsigned long>(devId), uwb.chipName());
if (devId != M5STAMP_UWB_QM33120_DEVICE_ID) {
Serial.printf("UWB_ID,result=FAIL,expected=0xDECA0314\n");
return false;
}
Serial.printf("UWB_CONFIG,result=OK,ch=%u,plen=%u,rate=6M8,tx_power=0x%08lX\n", static_cast<unsigned>(phy.channel),
static_cast<unsigned>(phy.preambleLength), static_cast<unsigned long>(phy.txPower));
return true;
}
static void runResponder()
{
// Wait for a Poll/Final exchange and return the DS-TWR distance result.
const M5Stamp_UWBDSResponderResult result = uwb.respondDSRange(rangeConfig);
if (!result.success) {
// Idle receive timeouts are expected and are not counted as failures.
if (result.error == M5Stamp_UWBError::RxTimeout) return;
if (++failCount % LOG_INTERVAL == 0) {
Serial.printf("DS_RESP_STAT,count=%lu,fail=%lu,last=FAIL,error=%s\n",
static_cast<unsigned long>(responseCount), static_cast<unsigned long>(failCount),
uwb.lastErrorName());
}
return;
}
// Print accumulated statistics every LOG_INTERVAL responses.
if (++responseCount % LOG_INTERVAL == 0) {
Serial.printf(
"DS_RESP_STAT,count=%lu,fail=%lu,last=OK,seq=%u,requester=0x%X,distance_mm=%ld,distance_m=%.3f,elapsed_ms=%lu\n",
static_cast<unsigned long>(responseCount), static_cast<unsigned long>(failCount), result.sequence,
result.requester, static_cast<long>(result.distanceMm), result.distanceM,
static_cast<unsigned long>(result.elapsedMs));
}
}
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.printf("M5Stamp UWB DS-TWR ANCHOR\n");
Serial.printf("ROLE,mode=ANCHOR\n");
Serial.printf("TWR_MODE,mode=DS-TWR\n");
uwbReady = initUwb();
Serial.printf("TEST_START,result=%s\n", uwbReady ? "OK" : "FAIL");
}
void loop()
{
if (uwbReady) runResponder();
else delay(1000);
}Serial output example:
M5Stamp UWB DS-TWR ANCHOR
ROLE,mode=ANCHOR
TWR_MODE,mode=DS-TWR
UWB_ID,dev_id=0xDECA0314,chip=QM33120/DW3720
UWB_CONFIG,result=OK,ch=5,plen=128,rate=6M8,tx_power=0xFEFEFEFE
TEST_START,result=OK
DS_RESP_STAT,count=9500,fail=0,last=OK,seq=7,requester=0x1,distance_mm=162,distance_m=0.162,elapsed_ms=98
DS_RESP_STAT,count=9520,fail=0,last=OK,seq=27,requester=0x1,distance_mm=185,distance_m=0.185,elapsed_ms=93
DS_RESP_STAT,count=9540,fail=0,last=OK,seq=47,requester=0x1,distance_mm=181,distance_m=0.181,elapsed_ms=98
DS_RESP_STAT,count=9560,fail=0,last=OK,seq=67,requester=0x1,distance_mm=167,distance_m=0.167,elapsed_ms=98
DS_RESP_STAT,count=9580,fail=0,last=OK,seq=87,requester=0x1,distance_mm=193,distance_m=0.193,elapsed_ms=98
DS_RESP_STAT,count=9600,fail=0,last=OK,seq=107,requester=0x1,distance_mm=166,distance_m=0.166,elapsed_ms=98
DS_RESP_STAT,count=9620,fail=0,last=OK,seq=127,requester=0x1,distance_mm=164,distance_m=0.164,elapsed_ms=98 #include <Arduino.h>
#include <M5Stamp_UWB.h>
static constexpr uint32_t RANGE_INTERVAL_MS = 200;
static constexpr uint32_t LOG_INTERVAL = 10;
M5Stamp_UWB uwb;
M5Stamp_UWBRangeConfig rangeConfig;
bool uwbReady = false;
uint32_t lastRangeMs = 0;
uint32_t rangeCount = 0;
uint32_t okCount = 0;
static bool initUwb()
{
// Stamp-C5 connections to the onboard QM33120 UWB transceiver.
M5Stamp_UWBConfig config;
config.pin_gp7 = 23;
config.pin_irq = 0;
config.pin_wakeup = 24;
config.pin_rst = 25;
config.pin_miso = 26;
config.pin_mosi = 27;
config.pin_cs = 11;
config.pin_sck = 12;
M5Stamp_UWBPHYConfig phy;
phy.channel = M5Stamp_UWBChannel::Channel5;
// Both devices must use the same PAN ID, addresses, and response timing.
rangeConfig.panId = 0xDECA;
rangeConfig.initiatorAddress = 0x0001;
rangeConfig.responderAddress = 0x0002;
rangeConfig.responseRxAfterTxDelayUus = 500;
rangeConfig.responseTxDelayUus = 3000;
rangeConfig.rxTimeoutUus = 6000;
rangeConfig.hostTimeoutMs = 100;
if (!uwb.begin(config, phy)) {
Serial.printf("UWB_BEGIN,result=FAIL,error=%s\n", uwb.lastErrorName());
Serial.printf("UWB_RAW_ID,dev_id=0x%08lX\n", static_cast<unsigned long>(uwb.readRawDeviceId()));
return false;
}
const uint32_t devId = uwb.deviceId();
Serial.printf("UWB_ID,dev_id=0x%08lX,chip=%s\n", static_cast<unsigned long>(devId), uwb.chipName());
if (devId != M5STAMP_UWB_QM33120_DEVICE_ID) {
Serial.printf("UWB_ID,result=FAIL,expected=0xDECA0314\n");
return false;
}
Serial.printf("UWB_CONFIG,result=OK,ch=%u,plen=%u,rate=6M8,tx_power=0x%08lX\n", static_cast<unsigned>(phy.channel),
static_cast<unsigned>(phy.preambleLength), static_cast<unsigned long>(phy.txPower));
return true;
}
static void runRanging()
{
// Start one SS-TWR exchange at the configured interval.
if (millis() - lastRangeMs < RANGE_INTERVAL_MS) return;
lastRangeMs = millis();
const M5Stamp_UWBRangeResult result = uwb.requestRange(rangeConfig);
rangeCount++;
okCount += result.success;
// Print accumulated statistics every LOG_INTERVAL attempts.
if (rangeCount % LOG_INTERVAL) return;
const uint32_t failCount = rangeCount - okCount;
if (result.success) {
Serial.printf(
"SS_RANGE_STAT,count=%lu,ok=%lu,fail=%lu,last=OK,seq=%u,distance_mm=%ld,distance_m=%.3f,elapsed_ms=%lu\n",
static_cast<unsigned long>(rangeCount), static_cast<unsigned long>(okCount),
static_cast<unsigned long>(failCount), result.sequence, static_cast<long>(result.distanceMm),
result.distanceM, static_cast<unsigned long>(result.elapsedMs));
} else {
Serial.printf("SS_RANGE_STAT,count=%lu,ok=%lu,fail=%lu,last=FAIL,seq=%u,error=%s\n",
static_cast<unsigned long>(rangeCount), static_cast<unsigned long>(okCount),
static_cast<unsigned long>(failCount), result.sequence, uwb.lastErrorName());
}
}
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.printf("M5Stamp UWB SS-TWR TAG\n");
Serial.printf("ROLE,mode=TAG\n");
Serial.printf("TWR_MODE,mode=SS-TWR\n");
uwbReady = initUwb();
Serial.printf("TEST_START,result=%s\n", uwbReady ? "OK" : "FAIL");
}
void loop()
{
if (uwbReady) runRanging();
else delay(1000);
}Serial output example:
M5Stamp UWB SS-TWR TAG
ROLE,mode=TAG
TWR_MODE,mode=SS-TWR
UWB_ID,dev_id=0xDECA0314,chip=QM33120/DW3720
UWB_CONFIG,result=OK,ch=5,plen=128,rate=6M8,tx_power=0xFEFEFEFE
TEST_START,result=OK
SS_RANGE_STAT,count=10,ok=10,fail=0,last=OK,seq=10,distance_mm=2270,distance_m=2.270,elapsed_ms=4
SS_RANGE_STAT,count=20,ok=20,fail=0,last=OK,seq=20,distance_mm=2282,distance_m=2.282,elapsed_ms=4
SS_RANGE_STAT,count=30,ok=30,fail=0,last=OK,seq=30,distance_mm=2275,distance_m=2.275,elapsed_ms=4
SS_RANGE_STAT,count=40,ok=40,fail=0,last=OK,seq=40,distance_mm=2251,distance_m=2.251,elapsed_ms=4
SS_RANGE_STAT,count=50,ok=50,fail=0,last=OK,seq=50,distance_mm=2265,distance_m=2.265,elapsed_ms=4
SS_RANGE_STAT,count=60,ok=60,fail=0,last=OK,seq=60,distance_mm=2301,distance_m=2.301,elapsed_ms=4
SS_RANGE_STAT,count=70,ok=70,fail=0,last=OK,seq=70,distance_mm=2301,distance_m=2.301,elapsed_ms=4
SS_RANGE_STAT,count=80,ok=80,fail=0,last=OK,seq=80,distance_mm=2291,distance_m=2.291,elapsed_ms=4
SS_RANGE_STAT,count=90,ok=90,fail=0,last=OK,seq=90,distance_mm=2282,distance_m=2.282,elapsed_ms=4
SS_RANGE_STAT,count=100,ok=100,fail=0,last=OK,seq=100,distance_mm=2312,distance_m=2.312,elapsed_ms=4 #include <Arduino.h>
#include <M5Stamp_UWB.h>
static constexpr uint32_t LOG_INTERVAL = 20;
M5Stamp_UWB uwb;
M5Stamp_UWBRangeConfig rangeConfig;
bool uwbReady = false;
uint32_t responseCount = 0;
uint32_t failCount = 0;
static bool initUwb()
{
// Stamp-C5 connections to the onboard QM33120 UWB transceiver.
M5Stamp_UWBConfig config;
config.pin_gp7 = 23;
config.pin_irq = 0;
config.pin_wakeup = 24;
config.pin_rst = 25;
config.pin_miso = 26;
config.pin_mosi = 27;
config.pin_cs = 11;
config.pin_sck = 12;
M5Stamp_UWBPHYConfig phy;
phy.channel = M5Stamp_UWBChannel::Channel5;
// Both devices must use the same PAN ID, addresses, and response timing.
rangeConfig.panId = 0xDECA;
rangeConfig.initiatorAddress = 0x0001;
rangeConfig.responderAddress = 0x0002;
rangeConfig.responseRxAfterTxDelayUus = 500;
rangeConfig.responseTxDelayUus = 3000;
rangeConfig.rxTimeoutUus = 6000;
rangeConfig.hostTimeoutMs = 100;
if (!uwb.begin(config, phy)) {
Serial.printf("UWB_BEGIN,result=FAIL,error=%s\n", uwb.lastErrorName());
Serial.printf("UWB_RAW_ID,dev_id=0x%08lX\n", static_cast<unsigned long>(uwb.readRawDeviceId()));
return false;
}
const uint32_t devId = uwb.deviceId();
Serial.printf("UWB_ID,dev_id=0x%08lX,chip=%s\n", static_cast<unsigned long>(devId), uwb.chipName());
if (devId != M5STAMP_UWB_QM33120_DEVICE_ID) {
Serial.printf("UWB_ID,result=FAIL,expected=0xDECA0314\n");
return false;
}
Serial.printf("UWB_CONFIG,result=OK,ch=%u,plen=%u,rate=6M8,tx_power=0x%08lX\n", static_cast<unsigned>(phy.channel),
static_cast<unsigned>(phy.preambleLength), static_cast<unsigned long>(phy.txPower));
return true;
}
static void runResponder()
{
// Wait for a Poll frame and send the delayed SS-TWR Response frame.
const M5Stamp_UWBResponderResult result = uwb.respondRange(rangeConfig);
if (!result.success) {
// Idle receive timeouts are expected and are not counted as failures.
if (result.error == M5Stamp_UWBError::RxTimeout) return;
if (++failCount % LOG_INTERVAL == 0) {
Serial.printf("SS_RESP_STAT,count=%lu,fail=%lu,last=FAIL,error=%s\n",
static_cast<unsigned long>(responseCount), static_cast<unsigned long>(failCount),
uwb.lastErrorName());
}
return;
}
// Print accumulated statistics every LOG_INTERVAL responses.
if (++responseCount % LOG_INTERVAL == 0) {
Serial.printf("SS_RESP_STAT,count=%lu,fail=%lu,last=OK,seq=%u,requester=0x%X,elapsed_ms=%lu\n",
static_cast<unsigned long>(responseCount), static_cast<unsigned long>(failCount), result.sequence,
result.requester, static_cast<unsigned long>(result.elapsedMs));
}
}
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.printf("M5Stamp UWB SS-TWR ANCHOR\n");
Serial.printf("ROLE,mode=ANCHOR\n");
Serial.printf("TWR_MODE,mode=SS-TWR\n");
uwbReady = initUwb();
Serial.printf("TEST_START,result=%s\n", uwbReady ? "OK" : "FAIL");
}
void loop()
{
if (uwbReady) runResponder();
else delay(1000);
}Serial output example:
M5Stamp UWB SS-TWR ANCHOR
ROLE,mode=ANCHOR
TWR_MODE,mode=SS-TWR
UWB_ID,dev_id=0xDECA0314,chip=QM33120/DW3720
UWB_CONFIG,result=OK,ch=5,plen=128,rate=6M8,tx_power=0xFEFEFEFE
TEST_START,result=OK
SS_RESP_STAT,count=1260,fail=0,last=OK,seq=12,requester=0x1,elapsed_ms=98
SS_RESP_STAT,count=1280,fail=0,last=OK,seq=32,requester=0x1,elapsed_ms=98
SS_RESP_STAT,count=1300,fail=0,last=OK,seq=52,requester=0x1,elapsed_ms=93
SS_RESP_STAT,count=1320,fail=0,last=OK,seq=72,requester=0x1,elapsed_ms=98
SS_RESP_STAT,count=1340,fail=0,last=OK,seq=92,requester=0x1,elapsed_ms=98 The following example is intended for ranging between one Tag and multiple Anchors. The Tag initiates DS-TWR ranging with each address in ANCHOR_SHORT_ADDRS in sequence, while each Anchor responds only to requests addressed to its own ANCHOR_SHORT_ADDR. The Tag ultimately outputs the distance to each Anchor and does not calculate 2D or 3D coordinates. Assign a unique short address to every Anchor and add all Anchor addresses to the address list on the Tag. The Tag and all Anchors must use the same PAN ID, UWB channel, Tag address, and DS-TWR timing parameters. The Tag waits briefly before switching Anchors and retries the current Anchor when ranging fails to reduce the impact of stale frames or frame collisions. To perform 2D or 3D positioning, you must first determine the coordinates of each Anchor and add a multilateration positioning algorithm to the upper-layer program.
#include <Arduino.h>
#include <M5Stamp_UWB.h>
static constexpr uint32_t RANGE_INTERVAL_MS = 1000;
static constexpr uint32_t ANCHOR_SWITCH_GAP_MS = 30;
static constexpr uint8_t ANCHOR_RETRY_COUNT = 3;
static constexpr uint16_t PAN_ID = 0xDECA;
static constexpr uint16_t TAG_SHORT_ADDR = 0x0001;
static constexpr M5Stamp_UWBChannel UWB_CHANNEL = M5Stamp_UWBChannel::Channel5;
// Add or remove Anchor addresses as needed. Every Anchor must be unique.
static constexpr uint16_t ANCHOR_SHORT_ADDRS[] = {0x0002, 0x0003, 0x0004};
static constexpr size_t ANCHOR_COUNT = sizeof(ANCHOR_SHORT_ADDRS) / sizeof(ANCHOR_SHORT_ADDRS[0]);
M5Stamp_UWB uwb;
M5Stamp_UWBDSRangeConfig rangeConfig;
bool uwbReady = false;
uint32_t lastRangeMs = 0;
uint32_t rangeCount = 0;
uint32_t okCount = 0;
static void setRangeConfig(uint16_t anchorAddress)
{
// Both devices must use the same network addresses and DS-TWR timing.
rangeConfig.panId = PAN_ID;
rangeConfig.initiatorAddress = TAG_SHORT_ADDR;
rangeConfig.responderAddress = anchorAddress;
rangeConfig.responseRxAfterTxDelayUus = 1500;
rangeConfig.responseTxDelayUus = 3000;
rangeConfig.finalTxDelayUus = 1800;
rangeConfig.finalRxAfterResponseTxDelayUus = 500;
rangeConfig.resultRxAfterFinalTxDelayUus = 500;
rangeConfig.rxTimeoutUus = 3000;
rangeConfig.hostTimeoutMs = 100;
rangeConfig.resultRepeatCount = 3;
rangeConfig.resultRepeatGapMs = 3;
}
static bool initUwb()
{
M5Stamp_UWBConfig config;
config.pin_gp7 = 23;
config.pin_irq = 0;
config.pin_wakeup = 24;
config.pin_rst = 25;
config.pin_miso = 26;
config.pin_mosi = 27;
config.pin_cs = 11;
config.pin_sck = 12;
M5Stamp_UWBPHYConfig phy;
phy.channel = UWB_CHANNEL;
if (!uwb.begin(config, phy)) {
Serial.printf("UWB_BEGIN,result=FAIL,error=%s\n", uwb.lastErrorName());
Serial.printf("UWB_RAW_ID,dev_id=0x%08lX\n",
static_cast<unsigned long>(uwb.readRawDeviceId()));
return false;
}
const uint32_t devId = uwb.deviceId();
Serial.printf("UWB_ID,dev_id=0x%08lX,chip=%s\n",
static_cast<unsigned long>(devId), uwb.chipName());
if (devId != M5STAMP_UWB_QM33120_DEVICE_ID) {
Serial.printf("UWB_ID,result=FAIL,expected=0xDECA0314\n");
return false;
}
Serial.printf("UWB_CONFIG,result=OK,ch=%u,plen=%u,rate=6M8,tx_power=0x%08lX\n",
static_cast<unsigned>(phy.channel),
static_cast<unsigned>(phy.preambleLength),
static_cast<unsigned long>(phy.txPower));
return true;
}
static M5Stamp_UWBDSRangeResult rangeAnchor(uint16_t anchorAddress)
{
M5Stamp_UWBDSRangeResult result;
for (uint8_t attempt = 0; attempt < ANCHOR_RETRY_COUNT; ++attempt) {
setRangeConfig(anchorAddress);
result = uwb.requestDSRange(rangeConfig);
if (result.success) return result;
if (attempt + 1 < ANCHOR_RETRY_COUNT) {
// Clear stale frames before retrying the same Anchor.
delay(ANCHOR_SWITCH_GAP_MS);
}
}
return result;
}
static void runRanging()
{
if (millis() - lastRangeMs < RANGE_INTERVAL_MS) return;
lastRangeMs = millis();
// Range each Anchor in sequence so their responses do not collide.
for (size_t i = 0; i < ANCHOR_COUNT; ++i) {
const uint16_t anchorAddress = ANCHOR_SHORT_ADDRS[i];
const M5Stamp_UWBDSRangeResult result = rangeAnchor(anchorAddress);
rangeCount++;
okCount += result.success;
const uint32_t failCount = rangeCount - okCount;
if (result.success) {
Serial.printf(
"MULTI_ANCHOR_RANGE,count=%lu,ok=%lu,fail=%lu,anchor=0x%04X,last=OK,seq=%u,distance_mm=%ld,distance_m=%.3f,elapsed_ms=%lu\n",
static_cast<unsigned long>(rangeCount),
static_cast<unsigned long>(okCount),
static_cast<unsigned long>(failCount),
static_cast<unsigned>(anchorAddress),
result.sequence,
static_cast<long>(result.distanceMm),
result.distanceM,
static_cast<unsigned long>(result.elapsedMs));
} else {
Serial.printf(
"MULTI_ANCHOR_RANGE,count=%lu,ok=%lu,fail=%lu,anchor=0x%04X,last=FAIL,seq=%u,error=%s\n",
static_cast<unsigned long>(rangeCount),
static_cast<unsigned long>(okCount),
static_cast<unsigned long>(failCount),
static_cast<unsigned>(anchorAddress),
result.sequence,
uwb.lastErrorName());
}
if (i + 1 < ANCHOR_COUNT) {
// Wait for the current Anchor to finish repeated Result frames.
delay(ANCHOR_SWITCH_GAP_MS);
}
}
}
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.printf("M5Stamp UWB DS-TWR MULTI-ANCHOR TAG\n");
Serial.printf("ROLE,mode=TAG\n");
Serial.printf("TWR_MODE,mode=DS-TWR\n");
uwbReady = initUwb();
Serial.printf("TEST_START,result=%s,anchors=%u\n",
uwbReady ? "OK" : "FAIL",
static_cast<unsigned>(ANCHOR_COUNT));
}
void loop()
{
if (uwbReady) runRanging();
else delay(1000);
}Serial output example:
M5Stamp UWB DS-TWR MULTI-ANCHOR TAG
ROLE,mode=TAG
TWR_MODE,mode=DS-TWR
UWB_ID,dev_id=0xDECA0314,chip=QM33120/DW3720
UWB_CONFIG,result=OK,ch=5,plen=128,rate=6M8,tx_power=0xFEFEFEFE
TEST_START,result=OK,anchors=3
MULTI_ANCHOR_RANGE,count=1,ok=1,fail=0,anchor=0x0002,last=OK,seq=1,distance_mm=1014,distance_m=1.014,elapsed_ms=7
MULTI_ANCHOR_RANGE,count=2,ok=2,fail=0,anchor=0x0003,last=OK,seq=2,distance_mm=478,distance_m=0.478,elapsed_ms=7
MULTI_ANCHOR_RANGE,count=3,ok=3,fail=0,anchor=0x0004,last=OK,seq=3,distance_mm=703,distance_m=0.703,elapsed_ms=7
MULTI_ANCHOR_RANGE,count=4,ok=4,fail=0,anchor=0x0002,last=OK,seq=4,distance_mm=977,distance_m=0.977,elapsed_ms=7
MULTI_ANCHOR_RANGE,count=5,ok=5,fail=0,anchor=0x0003,last=OK,seq=5,distance_mm=233,distance_m=0.233,elapsed_ms=7
MULTI_ANCHOR_RANGE,count=6,ok=6,fail=0,anchor=0x0004,last=OK,seq=6,distance_mm=758,distance_m=0.758,elapsed_ms=7
MULTI_ANCHOR_RANGE,count=7,ok=7,fail=0,anchor=0x0002,last=OK,seq=7,distance_mm=1009,distance_m=1.009,elapsed_ms=7
MULTI_ANCHOR_RANGE,count=8,ok=8,fail=0,anchor=0x0003,last=OK,seq=8,distance_mm=55,distance_m=0.055,elapsed_ms=7
MULTI_ANCHOR_RANGE,count=9,ok=9,fail=0,anchor=0x0004,last=OK,seq=9,distance_mm=679,distance_m=0.679,elapsed_ms=7 #include <Arduino.h>
#include <M5Stamp_UWB.h>
static constexpr uint32_t LOG_INTERVAL = 20;
static constexpr uint16_t PAN_ID = 0xDECA;
static constexpr uint16_t TAG_SHORT_ADDR = 0x0001;
// Change this value on every Anchor. Do not reuse an address.
static constexpr uint16_t ANCHOR_SHORT_ADDR = 0x0002;
static constexpr M5Stamp_UWBChannel UWB_CHANNEL = M5Stamp_UWBChannel::Channel5;
M5Stamp_UWB uwb;
M5Stamp_UWBDSRangeConfig rangeConfig;
bool uwbReady = false;
uint32_t responseCount = 0;
uint32_t failCount = 0;
static void setRangeConfig()
{
// Both devices must use the same network addresses and DS-TWR timing.
rangeConfig.panId = PAN_ID;
rangeConfig.initiatorAddress = TAG_SHORT_ADDR;
rangeConfig.responderAddress = ANCHOR_SHORT_ADDR;
rangeConfig.responseRxAfterTxDelayUus = 1500;
rangeConfig.responseTxDelayUus = 3000;
rangeConfig.finalTxDelayUus = 1800;
rangeConfig.finalRxAfterResponseTxDelayUus = 500;
rangeConfig.resultRxAfterFinalTxDelayUus = 500;
rangeConfig.rxTimeoutUus = 3000;
rangeConfig.hostTimeoutMs = 100;
rangeConfig.resultRepeatCount = 3;
rangeConfig.resultRepeatGapMs = 3;
}
static bool initUwb()
{
M5Stamp_UWBConfig config;
config.pin_gp7 = 23;
config.pin_irq = 0;
config.pin_wakeup = 24;
config.pin_rst = 25;
config.pin_miso = 26;
config.pin_mosi = 27;
config.pin_cs = 11;
config.pin_sck = 12;
M5Stamp_UWBPHYConfig phy;
phy.channel = UWB_CHANNEL;
if (!uwb.begin(config, phy)) {
Serial.printf("UWB_BEGIN,result=FAIL,error=%s\n", uwb.lastErrorName());
Serial.printf("UWB_RAW_ID,dev_id=0x%08lX\n",
static_cast<unsigned long>(uwb.readRawDeviceId()));
return false;
}
const uint32_t devId = uwb.deviceId();
Serial.printf("UWB_ID,dev_id=0x%08lX,chip=%s\n",
static_cast<unsigned long>(devId), uwb.chipName());
if (devId != M5STAMP_UWB_QM33120_DEVICE_ID) {
Serial.printf("UWB_ID,result=FAIL,expected=0xDECA0314\n");
return false;
}
Serial.printf("UWB_CONFIG,result=OK,ch=%u,plen=%u,rate=6M8,tx_power=0x%08lX\n",
static_cast<unsigned>(phy.channel),
static_cast<unsigned>(phy.preambleLength),
static_cast<unsigned long>(phy.txPower));
return true;
}
static void runResponder()
{
setRangeConfig();
const M5Stamp_UWBDSResponderResult result = uwb.respondDSRange(rangeConfig);
if (!result.success) {
// Other Anchors also hear Polls addressed to them; ignore those frames.
if (result.error == M5Stamp_UWBError::RxTimeout ||
result.error == M5Stamp_UWBError::RangeFrameMismatch) {
return;
}
if (++failCount % LOG_INTERVAL == 0) {
Serial.printf(
"MULTI_ANCHOR_RESP,count=%lu,fail=%lu,last=FAIL,anchor=0x%X,error=%s\n",
static_cast<unsigned long>(responseCount),
static_cast<unsigned long>(failCount),
static_cast<unsigned>(ANCHOR_SHORT_ADDR),
uwb.lastErrorName());
}
return;
}
if (++responseCount % LOG_INTERVAL == 0) {
Serial.printf(
"MULTI_ANCHOR_RESP,count=%lu,fail=%lu,last=OK,anchor=0x%X,seq=%u,requester=0x%X,distance_mm=%ld,distance_m=%.3f,elapsed_ms=%lu\n",
static_cast<unsigned long>(responseCount),
static_cast<unsigned long>(failCount),
static_cast<unsigned>(ANCHOR_SHORT_ADDR),
result.sequence,
result.requester,
static_cast<long>(result.distanceMm),
result.distanceM,
static_cast<unsigned long>(result.elapsedMs));
}
}
void setup()
{
Serial.begin(115200);
delay(1000);
Serial.printf("M5Stamp UWB DS-TWR ANCHOR\n");
Serial.printf("ROLE,mode=ANCHOR\n");
Serial.printf("TWR_MODE,mode=DS-TWR\n");
Serial.printf("ANCHOR_ADDR,addr=0x%04X\n",
static_cast<unsigned>(ANCHOR_SHORT_ADDR));
uwbReady = initUwb();
Serial.printf("TEST_START,result=%s\n",
uwbReady ? "OK" : "FAIL");
}
void loop()
{
if (uwbReady) runResponder();
else delay(1000);
}Serial output example:
M5Stamp UWB DS-TWR ANCHOR
ROLE,mode=ANCHOR
TWR_MODE,mode=DS-TWR
ANCHOR_ADDR,addr=0x0002
UWB_ID,dev_id=0xDECA0314,chip=QM33120/DW3720
UWB_CONFIG,result=OK,ch=5,plen=128,rate=6M8,tx_power=0xFEFEFEFE
TEST_START,result=OK 