"Missing manual" for register-level access to DW3000 (without using API)

(Reposting this after requesting deletion of my previous post-- thanks!-- since its content was no longer editable after a couple days.) Note, I am NOT affiliated with Qorvo at all, just a community member.

I’ve been able to get the DW3000 to work reasonably (within the DWM3000EVB) with register-only access (I’m not using one of the micros supported by the official API library). It was a bit of a journey of discovery, so here are my notes in case they’re helpful for anyone else:

Corrections/clarifications quite welcome, I’ll keep it updated as best I can.


Hi Egnor,

I too am exploring the DWM3000 at the register level and outside the API.
Just wanted you to know that I found your notes to be very helpful, so thanks
for taking the time to write them up and post them.


Thanks! :grin: Let me know if anything is unclear or incorrect or missing. I’ve gotten basic ranging to work with decent accuracy but haven’t tried anything “advanced” like scrambled timestamps or power management or whatnot.

I also haven’t tried double buffering and the manual wording on that topic isn’t 100% clear to me (though I have some hypothesis for when I get around to that).

Anyway the good news is once the various magical voodoo parameters are initialized, actual rx/tx mostly works as described.


Two things I think you could add to your writeup:

  1. The User Manual for the DWM1000 covers some topics in more detail than the UM for the 3000,
    so I find it useful to cross-reference topics to see if there are any tips or tricks to be had.
    e.g. If I remember correctly the 1000 manual explains the steps for dealing with OTP rather than
    simply refering to non-existant/unavailable source code.

  2. I’m using a Rasberry Pi environment, as I wanted to do my project using Python rather than C
    which is my “native” language. I found the link below related to a DWM1000 ranging project extremely
    useful. Particularly the approach the author took to dealing with register definitions/operations by using
    Python ctypes which is easily transportable to the 3000.

Anyway, the above may be of interest to you or those who follow us.


Oh that reminds me I hadn’t described the process for reading OTP! I added a section about that, and added the references you described. Thanks!

Hi Dan,

After tooling around a little with reading and writing registers I’ve arrived
at the point where it’s time to initialize/configure the part.

Looking at the User Manual and your notes, it seems that the place to start is
by setting LDO_KICK and BIAS_KICK in the OTP_CFG reg at 0x0B:0x08,
and then to supplement that basic step with a few manual “fixes”. However,
after writing back LDO_KICK and BIAS_KICK I do not see any part of the
values from OTP (BIASTUNE_CAL / LDOTUNE_CAL) appearing in their
respective operating registers BIAS_CTRL / LDO_TUNE after the kick. These
2 registers were zeroed before the kick and remain zero after the kick. So the kick appears to do nothing. Is this what you see?



I guess I never looked at the values! I’ll take a look next time I get a chance (might be a couple days, remind me as needed). For all I know the KICK is doing nothing and the manual “fixes” do all the important work. Or maybe I’m running without calibration! (It’s hard to know how important any one of these things is…)

You do see nonzero values in the OTP, though, right?

Hi Dan,

Yes, there are values in OTP that can be read for LDOTUNE_CAL and
BIASTUNE_CAL, but these values do not get transfered to their respective
runtime registers after the KICKs. I plan on pushing ahead and implementing
the “fixes”, etc and see if I can get things to where I can execute a TX / RX.


I may also try directly copying the OTP vals into the respective runtime


Here’s what I get with my logic (based on my understanding of the older Qorvo driver source), after doing the KICKs:


LDO_TUNE_HI 07068788
LDO_TUNE_LO 88888788
BIAS_TUNE   00100013


LDO_TUNE    0000000088888788
LDO_CTRL    00000000
BIAS_CTRL   00001070

So, I concur – there’s no evidence of copying as a result of the KICK bit being set. Maybe we can get someone who can conveniently run the official driver to dump the registers and see what they look like?


Not sure that it matters but some of my values differ from yours. See lines w/ ??
Note that I’m doing KICK only with no “supplemental” copying as per driver code and/or your notes.
i.e. I’m wanting to understand what the chip is doing on its own (which appears to be nothing as
far as BIAS_KICK nad LDO_KICK).

CHIP_ID     8e6212a8
LOT_ID      00000000
REVISION    00000000

I don’t know how to interpret this, since neither of our chip ID values matches the documented layout…


It is my intention to explore the DWM3000 at the register level as well as outside of the API. Thank you for your notes; I found them very helpful.

1 Like

Hi Dan,

Not sure if you’re still tooling around with this but I am. After much gnashing of teeth, false starts and corrections, I just got my first TX / RX to work yesterday.
This is not using the API but instead directly reading/writing the reg set using Python on a Raspberry Pi.

As to your doc on this, the User Manual specifies two other default val
scenarios that I don’t think you listed.

  1. Set PLL_CFG to 0x1F3c for ch5 or 0x0F3c for ch9.

  2. Set LDO_RLOAD to 0x14

I’ve still got more work to do on simple TX / RX but I then plan to
look into antenna calibration and then, hopefully ranging.

I will say this: the chip seems to be quite the engineering miracle.
I’m hoping that with time the powers that be will allow source code
access vs API but we’ll see. Meanwhile, I’m hoping I can get the chip
to deliver on the advertised 10 cm accuracy for ranging.

The other thing I plan to do is start using interrupts… Have you done
that yet? If Y, any advice?


For the project’s on hold pending higher priority things, but I do plan to get back to it.

I added your register notes to my gist, thanks!!

I have not tried to use interrupts, I’m a polling kinda guy :slight_smile: (and my application is not trying for super low current consumption). Good luck and if you find any gotchas let me know!