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!)
I have decompiled and analyzed the newest driver available and found the following differences:
Architecture Differences
The driver no longer ships as a single binary for a single chip but instead is made up of:
deca_compat
deca_interface
dw3000_device
dw3700_device
dw3720_device
According to the forums,
dw37XX
is a transitional chip that never went to market.1deca_compat
now uses anioctl
to send commands to the*_device
and dynamically loads a driver indwt_probe()
. Most of the commands are relayed that way to the specific device driver.Speculations
SCP
While SCP is not documented at all, it seems that SCP mode is in the range of
25..=29
and, if enabled, will enable theCIA
(channel impulse analyzer).This means:
0x0B:08
:OPS_KICK
of reserved (01
)0x0E:0C
:IP_NTM = 6
,IP_PMULT = 0
,1
into bit9
,IP_RTM = 0
0x0E:0E
:IP_RTM = 0
,STS_NTM = 0
,STS_PMULT = 0
0x0E:12
:STS_NTM = 0xA
(instead of0xC
), bits13 = 0
,14 = 1
,STS_MNTH = 12
0x0E:16
:RES_B0
to0x9D
, instead of the suggested0x9B
from the user manualAn interesting part is how
0x0E:0E
partially overwrites what was set in0x0E:0C
and is overwritten partially by the0x0E:12
, and instead of using thedwt_write16bitoffsetreg
, thedwt_write32bitoffsetreg
was chosen instead. This seems odd (given how all the other code is written) and suggests that SCP mode may be a relic of the past or not tested(?).What is SCP?
The FiRa Consortium is a non-profit tasked with standardizing UWB. Qorvo is a member, and I found a whitepaper from qorvo2, which includes
SCP
. It turns out thatSCP
is the Secure Channel Protocol3, which is primarily used on smart cards.Changes between versions
The new version is essentially the same, with some minor differences.
dwt_initialise()
XTAL
is now set via the first6
bits (via0x3F
mask) instead of the first7
(via0x7F
mask). (_dwt_otpread(XTRIM_ADDRESS) & 0x3f
vs_dwt_otpread(XTRIM_ADDRESS) & 0x7f
)This now also loads
0x35
(PLL_LOCK_CODE
) of the OTP into the0x09:04
(PLL coarse code) register, but only if that value is!= 0
dwt_configure()
I do not know if this were compiler optimizations at play here, but the equation for the
ststhreshold
is no longer(int16_t)((((uint32_t)sts_len) * 8) * STSQUAL_THRESH_64);
, but instead(uint16_t)(sts_len * 0x26668 >> 0xf)
.If SCP is not enabled after
MNTH
has been calculated and written into0x0E:12
, regardless ifSTS
is enabled, the driver will now set in0x0E:16
(0xBFFFFF00:0x94
):RES_B0 = 0x94
Note: This deviates from the default described in the user manualSTS_SS_EN = 0
DTUNE0
now setsDT0B4
to0
only ifPDoA
mode 1, and otherwise always sets it to1
, previouslyDT0B4
was never touched.The code for another default of
DTUNE3
ifSTS_ND
was selected was removed,0xAF5F35CC
is now set regardless ofSTS
mode.At the end a new undocumented register is written to, dubbed
DTUNE4
, with the following code:👂 Open Questions
The default timeout is now:
I am unsure if this is a decompilation thing or an actual field is reset, further investigation is required 🔍
dwt_setdwstate
DWT_DW_IDLE
Before setting
AINIT2IDLE
, this now setsCAL_EN = 1
andUSE_OLD = 1
in the0x09:08
register.dwt_pgf_cal
Before
dwt_run_pgfcal
, the chip will sleep fordeca_usleep(20)
.It is also odd that in
dwt_run_pgfcal
, (in v04 and v06), before reading the calibration results,COMP_DLY
is set to0x3
. This is undocumented, and the user manual states no other value than0x02
should be used.dwt_enable_rf_tx
This function no longer distinguishes between channels 5 and 7 and instead writes:
0x2003c00
toRF_ENABLE_ID
.dwt_enable_rftx_blocks
This function no longer distinguishes between channels 5 and 7 and writes:
0x2003c00
toRF_CTRL_MASK_ID
.dwt_getframelength
AFAIK this is a new function
dwt_rxenable
On
DWT_START_RX_DLY_RS
andDWT_START_RX_DLY_TS
respectively, a new function is called_dwt_adjust_delaytime
with0
as arg forDWT_START_RX_DLY_RS
, otherwise1
. The source code of_dwt_adjust_delaytime
is roughly:It seems like it now accounts for the delay of each antenna (tho if
dx_time < *_antenna_delay
then a underflow would occur)Other Discoveries
It seems that you can read and write from the Scratch RAM (located at
0x16
), but you are unable to use masked write transactions on that particular memory region.I will update my notes with more information once available
Footnotes
https://forum.qorvo.com/t/what-is-dw3700/12533 ↩
https://www.firaconsortium.org/sites/default/files/2022-08/FIRA-Whitepaper-UWB-Secure-Ranging-August-2022.pdf ↩
https://globalplatform.org/wp-content/uploads/2017/09/GPC_2_3_F_SCP11_v1.2_PublicRelease.pdf ↩