Using DW3xxx SDK on upstream Zephyr (not NCS) — official path?

I’m building a peer-to-peer UWB product (consumer wearable, AoA ranging, no phone or anchor infrastructure) and I’m currently using upstream Zephyr v4.2.0 with a DWM3001CDK for development, planning to move to a custom PCB based on the DW3110 later.

For the driver layer I’m using the open-source br101/zephyr-dw3000-decadriver module, which works fine for low-level chip access. But I’d prefer to use Qorvo’s official SDK (DW3xxx & QM3xxx SDK v1.1.1) for the higher-level functionality — particularly the FiRa MAC stack, ranging session management, and power management — since reimplementing those well is a significant amount of work that I’d ideally not duplicate.

The SDK targets nRF Connect SDK rather than upstream Zephyr. A few questions on this:

  1. Is there an officially supported way to use the Qorvo SDK on upstream Zephyr, or is NCS strictly required? I understand NCS is downstream Zephyr, but the SDK’s Zephyr integration appears to assume NCS-specific paths and APIs.
  2. If the answer is “use NCS,” what’s the minimum NCS footprint needed? In other words, can I use NCS just for the UWB layer while keeping the rest of my application on upstream Zephyr, or does the SDK need to be the build environment for the entire application?
  3. For the binary FiRa MAC library specifically — is it tied to NCS at the build level, or just at the integration/sample level? My understanding is the library itself is fairly self-contained and the NCS dependency might be in the platform glue rather than the MAC stack.
  4. For a product that doesn’t need FiRa interoperability (closed peer-to-peer ecosystem, no phones/cars/locks), is there a recommended subset of the SDK that gives me the power management and scheduling benefits without the full FiRa MAC stack? Or is the PM logic baked into the MAC layer?
  5. Are there partners or third-party stacks (similar to how SynchronicIT provides the Omlox tag stack) that offer upstream-Zephyr-compatible alternatives to the Qorvo SDK?

For context on the application: dense-environment ranging is important (crowded venues with potentially many devices ranging concurrently), and battery life is a key product constraint. These are the two areas where I expect the SDK’s mature implementation matters most versus a from-scratch approach.

Any guidance would be appreciated. I’d rather know definitively than guess.

Thanks.

I can’t answer most of your questions.
I’m just another customer using the Qorvo sdk

No info on the zephyr sdk has been announced, but Nordic has moved to zephyr, so in the long run most customers will need to move too. I’ve uncovered a couple new sdk enhancements I’d like to use.

There were quite a few changes in 1.1.1 vs 1.0.2 to move to qxxx based functions and services, away from FreeRTOS specific functions

This tells me that one could see them replacing the uwb_stack qxxx libs with zephyr ported versions

The ncs build is pretty complex. I’ve updated the vs tasks.json to give me more config choices for my project., and updated the python script to pass the config along as defines to the generated make tree.

I dont have any experience w the zephyr based sdk yet.
This is my only Nordic based project

I’m using the Fira stack, no idea if there are usable sub components.

Thanks for the reply.

I’ve started looking into the source code of the os abstraction layer of the SDK and there is actually a Zephyr Version there. It exclusively uses core Zephyr functions, no NCS functions so it does seem compatible. The Cmakelists file is subdirectory compatible so should be good integration too. However the OD abstraction is built for Zephyr 3.6. I’ve now started poring it to 4.2 and so far it’s been going pretty smoothly. Once I’ve Goten everything to work cleanly I’ll integrate it as a Zephyr Module, upload the fork to GitHub and send the link in here for you or anyone else stumbling upon this.

Wish me luck :slight_smile:

Wow. I’ve got to look there more.
Be careful, I think we agreed not to publish as part of the agreement to get access

I’ll make sure before publishing anything but yes definitely dig deeper if it’s of interest to you

I’m interested. I have a big extension to the Qorvo 1.1.1 sdk that I’ve just finished, but moving to zephyr now would be better. Still need dfu, battery mgt, gpio integration too

What kind of extension?

I sent you a message

Hi @acaserta I am interested in this for sure. Thank you for taking a stab at 4.2. Did you end up uploading the work to GitHub and would you be able to provide a link?

I got it fully building and compiling but I’m currently still fixing some runtime issues (Also I want to note I’m not a professional embedded dev so while I am giving it my best I feel it’s important to point this out regarding robustness). It’s a lot of work for sure, the SDK was never meant to work on zephyr. Even though there is a zephyr branch even calling it a stub would be generous. Some files are fully stubbed out while others say that they work under Zephyr but have heavy issues in the code making it non-functional. The SDK in general is at times a little messy. A lot of hot fixes, at times straight up duplicate code that must have been pushed by accident and never gotten to “Todo” comments. I’ve had to implement a lot of stuff myself from scratch like thread management, power management etc.

The repo is currently still private, I’ve checked the license though and it seems I’m free to distribute it open source as soon as it’s functional to the point where im satisfied with it I’lle make the repo public and let you know! I need to get it to work either way as I need it for my own project :slight_smile:

Here’s the current readme to give you an idea of the scope though keep in mind it’s a WIP so this is subject to change:

qorvo-sdk-zephyr

A Zephyr module providing the Qorvo DW3 QM33 SDK with FiRa MAC support. This is a fork of Qorvo’s SDK 1.1.1 with patches making it actually buildable under vanilla Zephyr 4.2.

Status

Working: builds and links against vanilla Zephyr 4.2 for nRF52833 (DWM3001CDK).

Tested chips: DW3110 (via the DWM3001CDK module).

Tested host MCUs: nRF52833 only. The fork has Nordic-specific assumptions (see Limitations) and will not build on other SoC families without additional work.

Tested Zephyr version: 4.2.0. Earlier versions fail for unrelated API differences; later versions are untested.

Quick start

1. Add this fork to your west workspace

In your west.yml:

manifest:
  projects:
    - name: qorvo-sdk-zephyr
      url: <your fork URL>
      revision: main
      path: modules/lib/qorvo

Then west update.

2. Describe the UWB chip in your device tree

This SDK reads the UWB chip’s wiring from device tree. Add a node with compatible = "decawave,dw3000" to the SPI bus the chip is wired to, with these properties:

  • reg — chip select line index on the SPI bus (usually <0>)
  • spi-max-frequency — SPI clock rate in Hz; 8 MHz is conservative
  • rstn-gpios — chip reset line (active low)
  • irq-gpios — chip interrupt line

The node label is yours to choose; the SDK finds the chip by compatible string, not by label.

Example: DWM3001CDK

The upstream Zephyr board file for the DWM3001CDK declares the SPI3 bus but does not declare the on-module DW3110 chip itself. Applications must provide an overlay. Save as boards/decawave_dwm3001cdk.overlay in your application:

&spi3 {
    dw3110_uwb: dw3110@0 {
        compatible = "decawave,dw3000";
        status = "okay";
        reg = <0>;
        spi-max-frequency = <8000000>;
        rstn-gpios = <&gpio0 25 GPIO_ACTIVE_LOW>;
        irq-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;
    };
};

Pin assignments are per the DWM3001C module schematic (Qorvo FOR-001324). The board file’s existing cs-gpios = <&gpio1 6> on &spi3 is correct and should not be overridden.

For other boards

Add an analogous node describing your hardware’s wiring. The SDK’s chip-finding logic is DT_COMPAT_GET_ANY_STATUS_OKAY(decawave_dw3000) — any node with that compatible and the required properties will be found.

3. Enable in prj.conf

CONFIG_QORVO_UWB=y
CONFIG_QORVO_UWB_DELIVERY_FIRA=y

# Required by the bundled mbedTLS-backed mcps_crypto implementation

# (Not needed if you disable CONFIG_QORVO_CRYPTO_MBEDTLS to provide

# your own crypto backend.)

CONFIG_MBEDTLS=y
CONFIG_MBEDTLS_BUILTIN=y
CONFIG_MBEDTLS_CMAC=y
CONFIG_MBEDTLS_CIPHER_CCM_ENABLED=y

That’s the minimum. Optional Kconfig values for tuning are listed below.

4. Build

west build -b decawave_dwm3001cdk

Configuration reference

All options are under the QORVO_UWB Kconfig menu. Most have sensible defaults; you only need to set them if your hardware differs from typical assumptions.

Required

Option Description
CONFIG_QORVO_UWB Enables this SDK

MAC delivery (pick one)

Option Description
CONFIG_QORVO_UWB_DELIVERY_FIRA (default) FiRa-only MAC binary (smaller)
CONFIG_QORVO_UWB_DELIVERY_FULL Full MAC (FiRa + Apple NI + automotive)

SPI configuration

Option Default Description
CONFIG_UWB_SPI_FRAME_SIZE 8 SPI frame width in bits; do not change
CONFIG_UWB_SPI_INSTANCE 1 SPI peripheral instance number
CONFIG_SPI_UWB_IRQ_PRIORITY 2 Interrupt priority for UWB IRQ
CONFIG_SPI_UWB_FAST_RATE_FREQ 8 MHz SPI clock after init (max 38 MHz per datasheet)
CONFIG_SPI_UWB_SLOW_RATE_FREQ 4 MHz SPI clock during cold-start (max 7 MHz per datasheet)

Platform implementation

Option Default Description
CONFIG_QHAL_IMPL_ZEPHYR y OS abstraction uses Zephyr APIs
CONFIG_QPLATFORM_IMPL_NRF y on Nordic SoCs Nordic-specific hardware defaults

Crypto

Option Default Description
CONFIG_QORVO_CRYPTO_MBEDTLS y if MBEDTLS Provides mcpscrypto* via mbedTLS
CONFIG_HEAP_MEM_POOL_ADD_SIZE_QORVO_CRYPTO 8192 Heap added for crypto contexts

Disable QORVO_CRYPTO_MBEDTLS if you want to provide the mcpscrypto_ symbols from your own implementation (e.g. a hardware crypto backend). When disabled, you must supply implementations for: mcps_crypto_init, mcps_crypto_deinit, mcps_crypto_get_random, mcps_crypto_cmac_aes_128_digest, mcps_crypto_cmac_aes_256_digest, the mcps*crypto_aead_aes_ccm_star_128*_family (5 functions), and themcps*crypto_aes_ecb_128*\* family (4 functions).

Limitations

Nordic-only platform support

The qplatform layer’s idle timer values (RTC instance, frequency, counter width) are hardcoded inside #ifdef CONFIG_QPLATFORM_IMPL_NRF. Running on a non-Nordic SoC requires either:

  • Extending the #ifdef chain in qm33_qhal_common/src/qplatform.c with values for your platform, or
  • Refactoring those values into Kconfig options with per-SoC defaults (planned, not yet done).

Until then, building on STM32, NXP, ESP32, etc. will fail with #error "Please provide idle timer configuration for selected platform."

Single chip family supported

Currently only decawave,dw3000 is wired up end-to-end. The SDK source code references decawave,dw3720 (used by the QM33 family with integrated host MCU), but this fork does not yet:

  • Ship a binding for decawave,dw3720
  • Auto-select the correct driver based on DT compatible

Adding a second chip family requires creating the binding, adding it to the SDK’s chip-detection paths, and ideally driving the USE_DRV_DW3xxx build flags from DT presence (dt_has_compat(...)) so users only declare the chip once in DT rather than syncing it with CMake values.

Stubbed chip-internal persistence

persistent_config.h and persistent_time.h are stubbed. The MAC’s internal calibration data (antenna delays, etc.) does not survive reboots. Affects ranging accuracy after reset; a re-calibration is needed each boot. The proper fix is implementing these against Zephyr’s settings subsystem.

Upstream Zephyr DWM3001CDK board file is incomplete

The upstream Zephyr board file for the DWM3001CDK does not declare the on-module DW3110 chip. Until that’s fixed upstream, applications targeting this board must include the DT overlay shown above. Once the upstream board file gains the chip description, the application overlay can be removed.

MAC binary is precompiled

The FiRa MAC layer ships as a precompiled .a archive. You cannot inspect its source, modify its behavior, or fully audit it. Updates require Qorvo releasing a new SDK.

CCM vs CCM* in the bundled mbedTLS implementation

mbedTLS provides standard AES-CCM, while FiRa specifies CCM*. The two differ only in whether mac_len=0 (encryption-only mode) is permitted: CCM rejects it, CCM* allows it. The bundled wrapper returns QERR_ENOTSUP and logs a warning if called with mac_len=0. In practice FiRa always authenticates frames, so this branch should not be exercised; if you see the warning at runtime, you’ve found a case that needs proper CCM* handling.

Crypto heap sizing is a starting estimate

CONFIG_HEAP_MEM_POOL_ADD_SIZE_QORVO_CRYPTO is set to 8KB by default — enough for several concurrent CCM and ECB contexts. If you run more sessions than this can support, allocations fail at runtime and the MAC silently misbehaves. Increase CONFIG_HEAP_MEM_POOL_SIZE directly if your application needs more crypto context memory than the default sum provides.

qspi async mode is sync-with-callback

The Zephyr qspi.c implementation invokes the user handler synchronously
after spi_transceive() returns rather than from interrupt context. Grep
confirms no code in the SDK currently calls qspi_irq_set_callback with a
non-NULL handler, so this is harmless today. If a future SDK update or
custom code passes a non-NULL handler expecting interrupt-context
delivery, behavior will be wrong.

qspi_close does not release the Zephyr SPI device

Zephyr’s SPI API has no explicit release primitive. If you share the UWB
SPI controller with another device, add SPI_LOCK_ON to operation flags
so transfers serialize correctly.

Architecture notes

The SDK has three layers, organized by what they abstract:

  • OS abstraction (qosal/, qhal/src/zephyr/) — wraps OS APIs (threads, GPIO, SPI, timers). Zephyr-specific implementations live under zephyr/ subdirectories.
  • Chip abstraction (qhal/src/qm33/, qplatform/qm33_qhal_zephyr/) — wraps DW3xxx-family chip operations. Builds on top of the OS abstraction.
  • MAC (uwbstack_libs/) — precompiled FiRa MAC binary. Calls into the chip abstraction.

The qm33 directory naming refers to a Qorvo product family that includes DW3110, DW3120, DW3572, etc. — not exclusively the DW3572. Code in qhal/src/qm33/ works for any DW3xxx-family chip that fits the abstraction.

Wow! That’s a lot of detail and work. I am using the Murata Type2ab which uses the Qorvo QM33120W, so I think that means another board definition file