The DW3000 is an exciting part, available as a convenient Arduino-shield eval board with good distribution. HOWEVER, this is NOT a "maker friendly" part with SparkFun or Adafruit type tutorials and examples! It is a sophisticated radio that can be the heart of a positioning system, but you have to do quite a lot of heavy lifting to get there.
For basic use, the older-but-still-good DW1000 may be a better choice; interface libraries are available for Arduino and Raspberry Pi. Or look into packaged location-system vendors, like Estimote, Pozyx, Ubitrack and many others.
The DW3000 user manual is actually pretty decent. Expect to cuddle up with this tome. However, it IS incomplete; some important notes are missing, and other parts refer you to the reference driver implementation for details (e.g. accessing OTP memory).
Qorvo would very much like you to use that reference driver library ("API Software and API Guide" here) to access the part. This reference implementation encapsulates lots of undocumented wisdom. HOWEVER, the current version (v1.2) is only available as a binary blob, packaged for the ST NUCLEO-F429ZI or Nordic nRF52840-DK dev boards. You MIGHT be able to use these blobs on another Cortex-M4 or Cortex-M33 based system, writing your own hardware access layer (for SPI and interrupts).
For the moment, source code for an older Qorvo driver version (v1.1) is still available for download; look for the nested DW3000_API_C0_rev4p0.zip
. You can compile this for your platform, or just use it as reference. There is even some Raspberry Pi support! Of course, it will be buggier and less polished than newer versions. (I have confirmed with Qorvo's distributor Symmetry Electronics that source code for the current version of the dwt_uwb_driver
core is not available to customers, even under NDA. However, support for more micros is in the works.)
As an alternative, you can use the Makerfabs driver for the ESP32 or Emin Eminof's driver for the Arduino, both of which seem to be based on Qorvo's source code.
If you want to write your own driver (or debug an existing one), you'll be running into hidden gotchas. This document is intended to help.
(This is a very incomplete work in progress!)
The user manual describes the layout of OTP (One Time Programmable) memory but refers to driver functions for reading and writing those values. The Qorvo driver uses these steps to read an OTP register:
- set
OTP_MAN
(bit 0) inOTP_CFG
- write the OTP register address (7 bits) to
OTP_ADDR
- clear
OTP_MAN
(bit 0) and setOTP_READ
(bit 1) inOTP_CFG
- Read the value (32 bits) from
OTP_RDATA
- clear
OTP_READ
(bit 1) inOTP_CFG
Writing OTP memory is more involved (and more dangerous!) but not typically necessary.
The OTP memory contains calibration/tuning values which should be loaded into operating registers on startup (and possibly wake from sleep?). There are "KICK" bits in the OTP_SF
register to do this, but apparently they don't do a complete job.
From dwt_initialise()
in deca_device.c
(before PLL setup)
- set
LDO_KICK
inOTP_CFG
- set
BIAS_KICK
(undocumented bit 8!) inOTP_CFG
- copy bits 16-20 (5 bits) from OTP
BIASTUNE_CAL
into bits 0-5 ofBIAS_CTRL
- copy bits 0-5 (6 bits) from OTP
XTAL_Trim
intoXTAL
From dwt_configure()
in deca_device.c
- set
DGC_KICK
and (if ch9)DGC_SEL
inOTP_CFG
- set
OPS_KICK
and appropriateOPS_SEL
bits inOTP_CFG
The Qorvo driver checks if the OTP values are 0, and uses hardcoded values instead of setting KICK bits if so. On DWM (Arduino-shield) boards, these OTP values should all be programmed.
Receiver calibration (aka "PGF calibration") must run successfully at startup (and after wakeup or 20°C temperature change) for decent performance. The manual describes how to start calibration with RX_CAL
and check results in RX_CAL_RESI
and RX_CAL_RESQ
but misses some details:
- Before running calibration, bits 0 (
VDDMS1
), 2 (VDDMS3
), and 8 (VDDIF2
) must be set inLDO_CTRL
- Before reading
RX_CAL_RESI
/RESQ
, bit 16 inRX_CAL_CFG
(the low bit ofCOMP_DLY
) must be set - (After calibration, the previous value of
LDO_CTRL
should be restored to save power.)
Without these steps, calibration will fail (missing LDOs) and the failure won't be noticed (result values not being read properly), but the radio will perform very badly.
The manual says to always change the default 0xAF5F584C
to 0xAF5F35CC
. However, the Qorvo driver keeps the default (...584C
) in most cases, only using the replacement (...35CC
) when sending packets with no data.
The Qorvo driver sets this undocumented register at 07:10
to 0x08B5A833
for ch9 (it is left alone for ch5), but this value is the default anyway so you don't have to worry about it.
The User Manual says this about detecting too-late submission of a delayed TX request (section 9.4.1):
Due to an errata in the DW3000, there is a case when neither the HPDWARN event gets set nor does the packet get transmitted. ... The host can check for this issue by reading the PMSC_STATE. When this bug occurs, the PMSC_STATE will be “TX” but TX_STATE will be “IDLE”, the TXFRS event will never be set, see state descriptions in 8.2.14.19 Sub-register 0x0F:30 – System state. The host should abort the transmission in this case. This check and recovery is implemented in the published DW3000 dwt-starttx() [sic] API.
However, the PMSC_STATE
(aka TSE_STATE
) bitfield is inconsistently described, and there are several "TX" states. The Qorvo driver reports an error if SYS_STATE == 0x000D0000
(which looks for a specific "TX" substate).
According to the API documentation, preamble codes 25-29 are associated with "SCP", as distinct from 16MHz or 64MHz PRF codes. A header comment describes SCP as "UWB PRF ~100MHz", and the Qorvo driver uses different parameter sets when SCP mode is selected. The chip user manual doesn't mention any of this.
The significance and purpose of "SCP" mode remains a mystery.
These registers are named in deca_regs.h
but not in the user manual:
02:34 LCSS_MARGIN
- unused in Qorvo driver03:1C DGC_CFG
(0
,1
) - hardcoded if OTP DGC data is missing03:38 DGC_LUT_
(0
-6
)_CFG
- hardcoded if OTP DGC data is missing07:10 RF_RX_CTRL_HI
- loaded with magic value0x08B5A833
for ch90E:1E PGF_DELAY_COMP_
(LO
,HI
) - unused in Qorvo driver11:10 PWR_UP_TIMES_LO
-TXFSEQ
, but at11:10
instead of11:12
(??)
There are also numerous undocumented fields in otherwise-documented registers (e.g. BIAS_KICK
in OTP_CFG
).
- If using a 16MHz PRF (PCODE 3 or 4), set
RX_TUNE_EN
inDGC_CFG
- Always change
THR_64
inDGC_CFG
to0x32
- Always clear
DT0B4
inDTUNE0
- Always change
COMP_DLY
inRX_CAL
to0x2
- Always change
LDO_RLOAD
to0x14
- Always change
RF_TX_CTRL_1
to0x0E
- Always change
RF_TX_CTRL_2
to0x1C071134
(ch5) or0x1C010034
(ch9) - Always change
PLL_CFG
to0x1F3C
(ch5) or0x0F3C
(ch9) - Always change
PLL_CFG_LD
inPLL_CAL
to0x8
(documented as0x81
but that's the whole register) - For accurate ranging you need to calibrate antenna delay (see APS014)
- DW3000 user manual
- v1.2 DW3000 API, binary blob
- v1.1 DW3000 API, with source
- DW1000 user manual (for the older chip, but more complete-- good to cross check for insight)
- NConcepts/Makerfabs driver for the ESP32
- Emin Eminof's driver for ATMega328p Arduinos
- Port of the v1.1 Qorvo driver to the Zephyr RTOS
- Rust driver for the DW3000
- Raspberry Pi/Python access to the DW1000 (for the older chip, but good reading)
- "Sadly we only distribute this release package under NDA" (forum post)
- Missing and ambiguous information in DW3000 user manual (forum post)
- "Missing manual" for register-level access to DW3000 (discussion of this doc!)