Facts checked on 16 October 2026

Short answer

Upstream Zephyr has a radio driver only for the legacy DW1000. The DW3000, QM33 and QM35 have no upstream driver or binding, and the DWM3001CDK board file does not describe its UWB radio. Qorvo’s DW3_QM33 SDK is built on FreeRTOS for Nordic nRF52 hosts; for the QM35825, Qorvo ships a Zephyr host example.

On a custom DW3000 or QM33 board you integrate Qorvo’s driver yourself, then bring it up in a fixed order: power and reset, SPI at 7 MHz or less, device ID, interrupts, wake-up, calibration, and only then ranging.

What Zephyr and Qorvo provide today

ComponentStatus (October 2026)Notes
DW1000 driver in ZephyrUpstreamDevicetree compatible decawave,dw1000, Kconfig IEEE802154_DW1000, required int-gpios and reset-gpios; exposed as an IEEE 802.15.4 radio
DWM1001-DEV boardUpstream, marked “not actively maintained”Board decawave_dwm1001_dev (nRF52832); its devicetree includes the DW1000 node on SPI at 8 MHz
DWM3001CDK boardUpstream, marked “not actively maintained”Board decawave_dwm3001cdk (nRF52833); supports peripherals such as GPIO, USB, Bluetooth and OpenThread, but has no UWB radio node; its 802.15.4 interface is the nRF52833’s own radio
DW3000 or QM33 driver in ZephyrNot upstreamQorvo’s driver and stack ship in the DW3_QM33 SDK; community ports to Zephyr exist but are not supported by Qorvo
Community module br101/zephyr-dw3000-decadriverPublic, not supported by QorvoQorvo’s DW3XXX driver 08.02.02 from DW3_QM33 SDK 1.0.2; binding decawave,dw3000; Kconfig DW3000
DW3_QM33 SDKQorvo, version 1.1.1FreeRTOS on Nordic nRF52; boards DWM3001CDK, QM33120WDK1 and Murata Type2AB EVK; the FiRa stack ships as a pre-compiled library
QM35 SDK Zephyr exampleQorvo, SDK 3.0.0nRF Connect SDK v2.6.0; devicetree binding qorvo,hsspi; overlays for nRF52840 DK and nRF54L15 PDK

On 16 October 2026 the Zephyr bindings index still listed decawave,dw1000 as the only Decawave or Qorvo compatible. The community module is the most complete public port: Qorvo’s driver source plus Zephyr glue for SPI, GPIO and devicetree, under Qorvo’s licence and ISC respectively.

Choose your integration path

Path A: use the DW3_QM33 SDK on FreeRTOS as it is. Qorvo’s SDK targets its own kits on Nordic nRF52 hosts. If your board is close to the DWM3001CDK or the QM33120WDK1, this is the fastest route to FiRa ranging. You stay on FreeRTOS and on Qorvo’s application structure.

Path B: integrate Qorvo’s driver and stack into Zephyr. Choose this when the rest of your product lives in Zephyr: Bluetooth LE, MCUboot, OTA updates, sensors, power management. You map the SDK’s operating-system abstraction to Zephyr primitives and describe the radio in devicetree. Check early that the pre-compiled FiRa library matches your CPU, floating-point ABI and toolchain; library names in the SDK indicate builds for Arm Cortex-M4 with hardware floating point. The community module carries only the low-level driver, which is enough for Qorvo’s two-way ranging examples but not for FiRa.

Path C: a QM35825 with a Zephyr host. The QM35825 runs Qorvo’s firmware; your Zephyr MCU drives it over UCI. Start from the QM35 SDK’s Zephyr example and its qorvo,hsspi binding.

Decision criteria

QuestionPoints to
Is the radio a QM35825?Path C
Do you need FiRa sessions on a DW3000 or QM33?Path A, or Path B with Qorvo’s FiRa library on a Cortex-M4 with FPU
Does the rest of the product already run Zephyr?Path B
Do you only need proprietary ranging between your own devices?Path B with the open-source driver

Know the pins before you write devicetree

Connections between a host MCU and the Qorvo DW3000: SPI with chip select, IRQ, RSTn, WAKEUP and EXTON, plus the supply rails.

The DW3000 datasheet defines the host interface. The signals you will describe are:

SignalFunction (DW3000 datasheet)Bring-up note
SPI (SCK, MOSI, MISO) and chip selectHost interface; the datasheet quotes up to 38 MHz on its front page, 36 MHz in the SPI section, and 20 MHz in CRC modeStart slow and raise the clock only after the board is stable
IRQ“Interrupt request output from the DW3000 to the host processor”; active high by default; floats in SLEEP and DEEPSLEEPFit an external pull-down (100 kΩ recommended); keep the handler short
RSTnReset, active low; the datasheet warns it “must not be pulled high by external source”Drive low to reset, then release; never drive it high
WAKEUP“brings the DW3000 out of SLEEP or DEEPSLEEP states into operational mode”; tie to ground if unusedNeeded for low-power designs
EXTON“External device enable”, asserted during wake-up until the device sleepsOptional; useful to switch external regulators or an LNA
GPIO5/SPIPOL and GPIO6/SPIPHASampled at reset and wake-up to select the SPI mode; internal pull-downs select mode 0Leave unloaded for mode 0; for another mode, pull up with 10 kΩ or less

The supply rails are VDD1 1.62–3.6 V (which also powers the I/Os), VDD2a/b 2.4–3.6 V and VDD3 1.5–3.6 V; VDD2a/b and VDD3 must be stable within 10 ms of VDD1, otherwise the device needs a reset. For the board design itself, Qorvo’s reference is APH301, the hardware design guide for DW3000 and QM33100 series ICs. The SPI timing table also sets minimum gaps of 23 ns from chip select to the first clock edge, 24 ns from the last clock edge to chip-select release and 40 ns between accesses.

Describing the radio in devicetree

Because there is no upstream binding for the DW3000, define your own binding in the application (for example under dts/bindings) or reuse the community module’s decawave,dw3000, and reference it from the board overlay. Zephyr also searches dts/bindings in shields, DTS_ROOT entries and modules that declare a dts_root, as the community module does.

The community binding includes Zephyr’s spi-device.yaml, so reg and spi-max-frequency are mandatory, and adds these properties:

PropertyRequiredMeaning
reset-gpiosYesRESET pin, active low
irq-gpiosYesIRQ pin, active high
wakeup-gpiosNoWAKEUP pin, active high
spi-pol-gpios, spi-pha-gpiosNoSPI polarity and phase pins

Without wakeup-gpios, the driver wakes the chip by holding chip select low for 500 µs.

An overlay for a DWM3001CDK-style design

The upstream decawave_dwm3001cdk board file already enables spi3 as an SPIM instance with pin control and a GPIO chip select, but declares no device on it, so an overlay only adds the child node. The fragment below is illustrative: its GPIO numbers follow the overlay that the community module ships for this kit, and you must check them against your schematic.

/* Illustrative: app/boards/decawave_dwm3001cdk.overlay */
&spi3 {
    uwb0: dw3000@0 {
        compatible = "decawave,dw3000";     /* community binding */
        reg = <0>;                          /* first entry of cs-gpios */
        spi-max-frequency = <8000000>;      /* raise after bring-up */
        irq-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;
        reset-gpios = <&gpio0 25 GPIO_ACTIVE_LOW>;
        wakeup-gpios = <&gpio1 19 GPIO_ACTIVE_HIGH>;
    };
};

On a custom board you also own the SPI pins. The illustrative fragment below adds pin-control states and the chip select; the pin numbers are placeholders.

/* Illustrative: custom nRF52833 board, radio on SPIM3 */
&pinctrl {
    spi3_uwb_default: spi3_uwb_default {
        group1 {
            psels = <NRF_PSEL(SPIM_SCK, 0, 28)>,
                    <NRF_PSEL(SPIM_MOSI, 0, 31)>,
                    <NRF_PSEL(SPIM_MISO, 0, 3)>;
        };
    };
    spi3_uwb_sleep: spi3_uwb_sleep {
        group1 {
            psels = <NRF_PSEL(SPIM_SCK, 0, 28)>,
                    <NRF_PSEL(SPIM_MOSI, 0, 31)>,
                    <NRF_PSEL(SPIM_MISO, 0, 3)>;
            low-power-enable;
        };
    };
};

&spi3 {
    status = "okay";
    pinctrl-0 = <&spi3_uwb_default>;
    pinctrl-1 = <&spi3_uwb_sleep>;
    pinctrl-names = "default", "sleep";
    cs-gpios = <&gpio0 29 GPIO_ACTIVE_LOW>;
    /* add the dw3000@0 child node shown above */
};

Two patterns keep RSTn from ever being driven high: release it by reconfiguring the pin as an input after the low pulse, as the community module does, or describe it as open drain and toggle it as an output. Do not combine them, because Zephyr’s gpio_pin_configure() asserts when a pin flagged open drain is configured as an input only.

The community README suggests the legacy nordic,nrf-spi controller, without EasyDMA, for the DW3000’s many short transactions, but on the nRF52833 those instances stop at 8 MHz, while SPIM3 reaches 32 MHz. For a QM35825 host, the QM35 SDK’s own binding uses ss-irq-gpios, reset-gpios and an optional exton-gpios; copy its nRF52840 DK overlay, which runs the link at 5 MHz on spi3, rather than inventing a binding.

Kconfig for the first build

The community module adds DW3000, which depends on SPI, and a choice between DW3000_CHIP_DW3000 and DW3000_CHIP_DW3720 for QM33 parts. Its log level comes from Zephyr’s logging template. An illustrative first prj.conf:

# Illustrative prj.conf for bring-up with the community module
CONFIG_GPIO=y
CONFIG_SPI=y
CONFIG_DW3000=y
CONFIG_DW3000_CHIP_DW3000=y      # QM33 parts: CONFIG_DW3000_CHIP_DW3720=y
CONFIG_LOG=y
CONFIG_LOG_MODE_DEFERRED=y
CONFIG_LOG_BUFFER_SIZE=4096      # the value in Qorvo's QM35 Zephyr example
CONFIG_DW3000_LOG_LEVEL_INF=y    # logs the pins and SPI speed at init
CONFIG_THREAD_NAME=y
CONFIG_THREAD_ANALYZER=y
CONFIG_THREAD_ANALYZER_AUTO=y

For your own driver, copy the upstream DW1000 pattern: the option defaults to y and depends on DT_HAS_DECAWAVE_DW1000_ENABLED, so it builds only when the devicetree has an enabled node. Check the result in build/zephyr/zephyr.dts.

A bring-up sequence that finds faults in the right order

  1. Power and clocks. Measure the rails and the crystal before any software runs. A marginal supply or an untrimmed crystal produces symptoms that look like software bugs later.
  2. Reset. Drive RSTn low, release it and wait for the device to come out of reset. Confirm on a scope that nothing pulls the line high and that IRQ rises when the device is ready.
  3. SPI at low speed. Read the device ID register and compare it with the value expected by Qorvo’s driver. Nothing else is worth testing until this read is stable.
  4. Interrupts. Trigger a known event and confirm that IRQ fires, that the polarity is right and that your handler defers work to a thread or work queue.
  5. Wake-up and sleep. Put the device to sleep, wake it with WAKEUP and check that configuration survives as the driver expects.
  6. Radio configuration and calibration. Load the configuration and the calibration values: crystal trim, transmit power and antenna delay, from OTP or your own storage.
  7. Transmit and receive sanity checks. Send and receive simple frames between your board and a known-good kit before attempting ranging.
  8. First ranging. Run single-sided or double-sided two-way ranging against a reference device at a measured distance, then FiRa sessions if your product needs them.
  9. Raise the SPI clock. Increase the frequency step by step while repeating the ID read and ranging tests, and keep a margin below the datasheet limit.
  10. Bluetooth LE alongside UWB. On nRF hosts, check that radio scheduling for Bluetooth LE does not delay UWB timing-critical operations such as delayed transmissions.
  11. Automate it. Turn each step into a test that runs on hardware in CI, so the next board revision or SDK update repeats the whole sequence.

Download the bring-up checklist as a two-page PDF

Run the same application on a reference kit first: the DWM3001CDK, or an nRF52840 DK with Qorvo’s DWS3000 shield, which the community module supports at up to 16 MHz. The community port of Qorvo’s examples lists only four as run-tested.

Reset and the device ID

After RSTn is released, the device moves from INIT_RC, where SPI is limited to 7 MHz, to IDLE_RC. SPIRDY is enabled at power-up, so IRQ rises when the device is ready; without the interrupt, wait 2 ms and call dwt_checkidlerc(), because earlier SPI traffic can misconfigure the device. On a scope, every RSTn release should end with a rising IRQ.

Next, read DEV_ID at 0x00:00 with the one-octet header 0x00. Octets return least significant first; reserved registers read 0xDEADDEAD.

PartDEV_IDOctets after the header
DW3000 without PDoA0xDECA030202 03 CA DE
DW3000 with PDoA0xDECA031212 03 CA DE
QM331100xDECA030404 03 CA DE
QM331200xDECA031414 03 CA DE

The QM33 values come from Qorvo’s driver header 08.02.02. Read the ID with your own SPI code first, so that a wiring fault cannot crash the driver; the sketch below is illustrative and follows Zephyr’s main branch in October 2026.

/* Illustrative: raw DEV_ID read without the Qorvo driver */
#include <zephyr/drivers/spi.h>
#include <zephyr/sys/byteorder.h>

static const struct spi_dt_spec uwb =
        SPI_DT_SPEC_GET(DT_NODELABEL(uwb0), SPI_WORD_SET(8));
static struct spi_config slow_cfg;          /* separate low-speed config */

int uwb_read_dev_id(uint32_t *id)
{
        uint8_t tx[5] = { 0x00 };           /* read, register file 0x00 */
        uint8_t rx[5];
        const struct spi_buf txb = { .buf = tx, .len = sizeof(tx) };
        const struct spi_buf rxb = { .buf = rx, .len = sizeof(rx) };
        const struct spi_buf_set txs = { .buffers = &txb, .count = 1 };
        const struct spi_buf_set rxs = { .buffers = &rxb, .count = 1 };
        int err;

        if (!spi_is_ready_dt(&uwb)) {
                return -ENODEV;
        }
        slow_cfg = uwb.config;
        slow_cfg.frequency = 2000000;       /* INIT_RC limit is 7 MHz */
        err = spi_transceive(uwb.bus, &slow_cfg, &txs, &rxs);
        if (err == 0) {
                *id = sys_get_le32(&rx[1]); /* LSB first after the header */
        }
        return err;
}

dwt_probe() then reads the same octets and matches them with the mask 0xFFFFFF0F: the DW3000 driver accepts 0xDECA0302 and 0xDECA0312, the DW3720 driver 0xDECA0304 and 0xDECA0314. If the probe fails, stop, because later dwt_ calls use the driver pointer it did not set.

SPI clock before and after the clock locks

State or callSPI clock limitSource
INIT_RC, after reset or wake-up7 MHzDatasheet, user manual
dwt_initialise()Below 7 MHzDriver header
dwt_configuresleepcnt()Below 3 MHzDriver header
IDLE_RCFull speedUser manual
IDLE_PLL, clock PLL locked38 MHz; 36 MHz in the datasheet timing tableUser manual, datasheet
SPI CRC mode20 MHzDatasheet, user manual

Qorvo’s examples switch to the fast rate before reset and rely on the 2 ms wait; during bring-up, stay slow until dwt_initialise() returns DWT_SUCCESS. Both public Zephyr drivers keep a separate 2 MHz configuration for this. On nRF52 hosts, Zephyr’s SPIM driver uses the highest supported rate not above the request, capped at 8 MHz for SPIM0 to SPIM2 and 32 MHz for SPIM3 on the nRF52833 and nRF52840: 7 MHz becomes 4 MHz and 20 MHz becomes 16 MHz. Measure SCK, step through 2, 8, 16 and 32 MHz with the ID loop, and enable dwt_enablespicrccheck() at 16 MHz or below until SPI integrity is proven.

Interrupts in Zephyr

IRQ stays asserted while any event enabled in SYS_ENABLE is latched in SYS_STATUS; the host clears an event by writing 1 to it. An edge-triggered GPIO interrupt therefore misses events that latch while IRQ is still high, so the community module calls dwt_isr() in a loop while the pin reads active. Because SPIRDY is enabled at power-up, IRQ is already high after reset, and an edge interrupt armed later stays silent until that event is cleared. Keep SPI work in thread context: the community module submits a work item from the GPIO callback, and the upstream DW1000 driver uses its own work queue.

Sleep and wake-up

dwt_configuresleep(mode, wake) selects the wake sources (DWT_WAKE_WUP, DWT_WAKE_CSN) and what is restored on wake-up (DWT_CONFIG). Without DWT_SLEEP the device enters DEEPSLEEP, and the driver header says DWT_PGFCAL should be cleared for QM33 parts. The sketch below is illustrative and follows Qorvo’s IDLE_RC sleep example.

/* Illustrative sleep and wake-up cycle, Qorvo driver 08.02.02 API */
dwt_configuresleep(DWT_CONFIG | DWT_PGFCAL,         /* no PGFCAL on QM33 */
                   DWT_PRES_SLEEP | DWT_WAKE_WUP | DWT_WAKE_CSN | DWT_SLP_EN);

dwt_entersleep(DWT_DW_IDLE_RC);      /* stay in IDLE_RC after wake-up */
k_msleep(1000);                      /* the host does other work */

dwt_wakeup_ic();                     /* port hook: WAKEUP or CS pulse */
k_msleep(2);                         /* or wait for the SPIRDY interrupt */
while (!dwt_checkidlerc()) {         /* bound this loop in real code */
}
dwt_restoreconfig(1);                /* 08.02.02 takes full_restore */

The wake pulse lasts about 500 µs: WAKEUP high, or chip select low with MOSI held low so that no spurious write occurs. Restoring the saved configuration takes about 85 µs, so wait for SPIRDY before using SPI. If no wake source was enabled, only RSTn or a power cycle recovers the device. Older Qorvo examples call dwt_restoreconfig() without an argument, so check signatures against your driver.

Calibration values

dwt_initialise() loads LDO, bias and crystal trims from OTP and falls back to DEFAULT_XTAL_TRIM (0x2E, defined for 2 pF load capacitors) when no trim is stored. dwt_setxtaltrim() takes 0x00 to 0x3F, which the datasheet says typically covers ±20 ppm depending on crystal and PCB, and dwt_readclockoffset() shows the offset to a reference transmitter. If dwt_configure() returns DWT_ERROR, PLL or receiver calibration failed, and Qorvo’s examples reset the device. Antenna delays use device time units of about 15.65 ps.

Integrating Qorvo’s SDK into a Zephyr application

Most of the work in Path B is plumbing between two worlds:

  • Operating-system abstraction. Map the SDK’s mutexes, semaphores, timers and task creation to Zephyr’s kernel objects, and keep that mapping in one module so SDK updates touch one place.
  • Interrupt context. The SDK expects its interrupt service routine to run on IRQ. In Zephyr, hand the event to a high-priority thread or work queue rather than doing SPI transactions inside the GPIO callback.
  • Timing. UWB ranging schedules transmissions at precise device times. Logging, flash writes and Bluetooth LE events can delay the host; measure the worst-case latency of the path from IRQ to the next SPI command.
  • Binary libraries. Link pre-compiled libraries with matching compiler flags, floating-point ABI and C library. A mismatch often surfaces as a HardFault, not as a link error. On Arm, a hard-float library needs CONFIG_FPU with the default CONFIG_FP_HARDABI, not CONFIG_FP_SOFTABI.
  • Memory. Size thread stacks from measurements, not defaults, and check the watermarks again after every SDK update; Zephyr’s Thread analyzer reports them.
  • Logging. Route the SDK’s logging into Zephyr’s logging subsystem with rate limits, so diagnostics do not change timing.

The driver’s porting surface

HookCommunity module’s Zephyr implementation
readfromspi, writetospi, writetospiwithcrcspi_transceive() with header and body buffers
setslowrate, setfastrateTwo struct spi_config objects: 2 MHz and spi-max-frequency
wakeup_device_with_ioWAKEUP high for 1 ms, or chip select low for 500 µs
decamutexon, decamutexoffDisable and re-enable the IRQ pin interrupt
deca_sleep, deca_usleepk_msleep() and k_usleep()
driver_list in struct dwt_probe_sdw3000_driver or dw3720_driver, chosen in Kconfig

Build, flash and debug with west

The community module’s zephyr/module.yml sets board_root and dts_root to its own directory, which makes its binding and DWS3000 shield visible. Pass it with EXTRA_ZEPHYR_MODULES until you pin it in your west manifest. On the DWM3001CDK, west uses nrfutil by default for flashing and the on-board J-Link for debugging. Illustrative commands:

west build -p always -b decawave_dwm3001cdk app -- \
    -DEXTRA_ZEPHYR_MODULES=/path/to/zephyr-dw3000-decadriver
west build -p always -b nrf52840dk/nrf52840 --shield qorvo_dws3000 app -- \
    -DEXTRA_ZEPHYR_MODULES=/path/to/zephyr-dw3000-decadriver
west build -t menuconfig                    # D saves a minimal config
west build -- -DEXTRA_CONF_FILE=debug.conf  # merge a debug fragment
west flash -r jlink                         # choose the J-Link runner
west debug                                  # GDB session through the runner

Logging without changing the timing

Use deferred logging: immediate mode formats each message in the calling context, and Zephyr’s documentation lists 412 bytes of stack for a two-argument log call in that mode against 40 bytes deferred, on a Cortex-M3. Use LOG_*_RATELIMIT() macros for per-frame messages, and size CONFIG_LOG_BUFFER_SIZE because messages that do not fit are dropped. To measure timing, toggle a GPIO at IRQ entry and before the next SPI command instead of logging.

Common bring-up failures and how to diagnose them

SymptomLikely causeHow to confirmFix
Device ID reads as zeros or onesWrong SPI mode, chip select, power or reset heldScope SPI and RSTn; read at the lowest clockFix wiring, rail or cs-gpios
ID correct at 2 MHz onlySignal integrity or MISO samplingScope SCK and MISO; enable SPI CRCShorten traces; tune SPIM3 rx-delay
Garbage right after resetSPI access before IDLE_RCTime the first access against RSTnWait for SPIRDY
dwt_probe() fails, raw ID correctDW3000 driver on a QM33 part, or the reverseID ends in 2 (DW3000) or 4 (QM33)Select the matching DW3000_CHIP_ option
dwt_initialise() or dwt_configure() failsFast SPI during init; PLL or receiver calibrationRepeat at 2 MHz; measure supplies and crystalStay slow until init; reset and retry
IRQ storms or no interruptsPolarity or edge configuration, missing clear of the status register, IRQ high before armingCompare the GPIO flags with the datasheet; log the status registerClear events; loop dwt_isr() while IRQ is active
Spurious interrupts during sleepIRQ floats while asleepScope IRQ during sleepFit the pull-down
No wake-up from DEEPSLEEPWake source disabled or pulse too shortScope WAKEUP or CSEnable the source; pulse about 500 µs
Ranging offset of tens of centimetresAntenna delay not calibrated for the new boardRange against a reference at a known distanceCalibrate antenna delays per board
Frames received on the kit but not on your boardCrystal offset, RF layout or supply noiseMeasure carrier frequency offset; compare with APH301 layout guidanceTrim the crystal; fix layout or supply
HardFault after an SDK updateStack too small, ABI mismatch, header and library versions out of stepCheck stack watermarks, compiler flags and library versionsResize stacks; rebuild with matching flags
Bluetooth LE works, UWB ranging fails intermittentlyRadio scheduling conflicts on the shared hostTrace IRQ-to-command latency while Bluetooth LE is active; count HPDWARNLengthen response delays; raise UWB priority
Partly zeroed payloads on nRF52840 SPIM3 (user report)Frame read inside the receive callbackLogic analyser shows the full frameRead the frame in a thread

Testing after bring-up

A board that ranged once on the bench is not yet a product. Keep a hardware-in-the-loop set-up with reference devices at known positions, run ranging, sleep and wake-up, and Bluetooth LE coexistence tests on every build, and keep a UWB sniffer at hand for frames that never appear in logs.

Zephyr’s Twister can drive such a rig: west twister --generate-hardware-map map.yml records each board’s platform, runner and serial port, and west twister --device-testing --hardware-map map.yml -T tests/uwb flashes and runs the tests on every mapped board (illustrative path). The sample sizes below are our suggested starting points, not Qorvo limits.

TestMethodExample pass criterion
SPI integrity10,000 DEV_ID reads at each clock stepEvery read returns the expected ID
Reset and IRQ100 reset cyclesIRQ rises after every reset
Sleep and wake-up1,000 cycles with an ID read and a configuration read-backNo lost configuration, no interrupts during sleep
RangingAt least 100 ranges at each of three measured distancesMean error and spread within your budget
CoexistenceRanging while Bluetooth LE advertises and holds a connectionNo failed delayed transmissions, no HPDWARN

Run long tests with a watchdog that can recover the radio. One developer reported on the Qorvo Tech Forum in October 2026 that, on a DWM3001CDK with SDK v1.1.1 and a Bluetooth LE SoftDevice, delayed transmissions started too late, which the DW3000 flags with the HPDWARN status bit, twice stopped the FiRa scheduler in 8,871 ranging blocks.

How needCode helps

needCode engineers added support for new hardware platforms in Zephyr inside Qorvo’s UWB programme and brought up nine hardware platforms for Qorvo. Our teams port and maintain FreeRTOS and Zephyr on custom boards, write drivers and board support packages, contribute to upstream Zephyr and build hardware-in-the-loop test automation for UWB and Bluetooth LE.

Frequently asked questions

Does Zephyr support the Qorvo DW3000?

Not upstream. Zephyr includes a radio driver for the legacy DW1000 (decawave,dw1000) and board files for the DWM1001-DEV and the DWM3001CDK, but the DWM3001CDK board file has no UWB radio node and there is no DW3000 or QM33 driver. Teams integrate Qorvo’s driver from the DW3_QM33 SDK or use community ports.

Is Qorvo’s DW3_QM33 SDK based on Zephyr?

No. Qorvo’s tutorials and forum answers show the DW3_QM33 SDK running on FreeRTOS on Nordic nRF52 hosts, with board support for the DWM3001CDK, the QM33120WDK1 and the Murata Type2AB EVK. The latest version listed by Qorvo is 1.1.1.

Can I run the QM35 SDK host software on Zephyr?

Yes. The QM35 SDK 3.0.0 includes a Zephyr example built on nRF Connect SDK v2.6.0, with overlays for the nRF52840 DK and the nRF54L15 PDK. Qorvo notes that the nRF52840 adaptation is not provided by Qorvo and is in beta.

What SPI speed should I use for the DW3000?

Start low during bring-up, at 7 MHz or less until dwt_initialise() has run, and raise the clock once the device ID read and ranging are stable. The DW3000 datasheet quotes up to 38 MHz on its front page and 36 MHz in its SPI section, with 20 MHz in CRC mode. For the QM35825, the Linux binding states 40 MHz as the maximum, while the SDK’s examples run at 5–10 MHz.

Which pins does a DW3000 board need?

SPI with chip select, IRQ to the host, RSTn for reset and WAKEUP for low-power designs. EXTON is optional and can switch external components during wake-up. RSTn must never be driven high by an external source.

Can I use the Zephyr DWM3001CDK board as a starting point?

Yes, for the nRF52833 side: clocks, GPIO, USB, Bluetooth LE and other peripherals. You still have to add the UWB radio to devicetree and integrate the driver, because the upstream board file does not describe it.

What device ID should a DW3000 or QM33 return?

DEV_ID at 0x00:00 reads 0xDECA0302 on a DW3000 without PDoA and 0xDECA0312 with PDoA. Qorvo’s driver 08.02.02 also defines 0xDECA0304 for the QM33110 and 0xDECA0314 for the QM33120. Octets arrive least significant first.

How do I wake a DW3000 from DEEPSLEEP?

Drive WAKEUP high, or hold chip select low with MOSI low, for about 500 µs, provided that wake source was enabled with dwt_configuresleep(). Then wait for SPIRDY and call dwt_restoreconfig(). Without an enabled wake source, only RSTn or a power cycle recovers the device.

Is there a public devicetree binding for the DW3000?

Not in upstream Zephyr. The community module br101/zephyr-dw3000-decadriver defines decawave,dw3000 with required reset-gpios and irq-gpios and optional wakeup-gpios, spi-pol-gpios and spi-pha-gpios. Reuse it or define your own.

Sources

  1. Zephyr: devicetree binding decawave,dw1000 and bindings index
  2. Zephyr: decawave_dwm1001_dev board and decawave_dwm3001cdk board
  3. Zephyr source: DW1000 Kconfig and DWM3001CDK board devicetree
  4. Qorvo Tech Forum: DW3_QM33 SDK v1.0.2 release
  5. Qorvo Tech Forum: tutorial on DW3_QM33 SDK with a battery-operated DK
  6. Qorvo QM33120WDK1 product page (DW3xxx and QM3xxx SDK v1.1.1)
  7. Qorvo Tech Forum: FiRa coordinator scheduler thread (SDK v1.1.1 library names)
  8. Qorvo QM35 SDK repository on GitLab (Zephyr example, bindings and overlays)
  9. DW3000 datasheet v1.1 (distributor copy)
  10. Qorvo DW3110 product page (documents including APH301)
  11. Community Zephyr module for the DW3000: br101/zephyr-dw3000-decadriver (README, binding, Kconfig, platform code, overlays)
  12. Qorvo DW3XXX driver 08.02.02 API header deca_device_api.h, as published in the community module
  13. DW3000 User Manual v1.1, copy published in the community module
  14. Community port of Qorvo’s DW3000 examples to Zephyr: br101/zephyr-dw3000-examples
  15. Zephyr source: DW1000 driver
  16. Zephyr source: SPI device binding and SPI API header
  17. Zephyr source: nRF52833 SoC devicetree, nRF52840 SoC devicetree and nordic,nrf-spim binding
  18. Zephyr source: nRF SPIM common driver (frequency selection)
  19. Zephyr source: GPIO API header (open-drain assertion)
  20. Zephyr source: Arm core Kconfig (floating-point ABI)
  21. Zephyr docs: Building, flashing and debugging with west and Interactive Kconfig interfaces
  22. Zephyr docs: Introduction to devicetree bindings and Devicetree HOWTOs
  23. Zephyr docs: Modules and Shields
  24. Zephyr docs: Logging and logging Kconfig template
  25. Zephyr docs: Thread analyzer
  26. Zephyr docs: Twister, running tests on hardware
  27. Zephyr docs: nRF52840 DK board
  28. GitHub issue: partly zeroed receive data on nRF52840 (foldedtoad/dwm3000 #2)

About needCode

needCode is an independent embedded engineering company from Kraków, Poland, and an official UWB business partner of Qorvo. Its engineers have worked inside Qorvo’s wireless programmes for more than eight years, including on the public QM35 SDK. needCode is certified to ISO 9001:2015 and ISO/IEC 27001:2022 and is a member of the UWB Alliance. This article does not represent Qorvo or the Zephyr Project. All trademarks belong to their respective owners.