DS Two Way Ranging example code not communicating

I’m not sure the timestamp is being reported properly. What is the timestamp exactly? Is it nothing more than a snapshot of elapsed time from some reference point using UWB units? I fixed a formatting error in the clock ratio printing and now it is more stable. Here are the latest clock ratio and range numbers.

DIST: -2721.78 m
cr: 1.440756e-06
DIST: -3618.04 m
cr: 1.808628e-06
DIST: -4302.51 m
cr: 2.153218e-06
DIST: -4816.23 m
cr: 2.012588e-06
DIST: -5223.41 m
cr: 2.467073e-06
DIST: -5602.04 m
cr: 2.454035e-06
DIST: -5951.69 m
cr: 2.582557e-06
DIST: -6231.92 m
cr: 2.461486e-06
DIST: -6450.90 m
cr: 2.563931e-06
DIST: -6672.99 m
cr: 2.585351e-06
DIST: -6886.10 m
cr: 2.604909e-06
DIST: -7046.84 m
cr: 2.846122e-06
DIST: -7206.77 m
cr: 2.929941e-06
DIST: -7343.56 m
cr: 2.976507e-06
DIST: -7456.04 m
cr: 2.807938e-06
DIST: -7581.44 m
cr: 2.981164e-06
DIST: -7658.24 m
cr: 3.003515e-06
DIST: -7724.29 m
cr: 3.069639e-06
DIST: -7819.13 m
cr: 2.947636e-06
DIST: -7870.31 m
cr: 3.108755e-06
DIST: -7933.09 m
cr: 3.102236e-06
DIST: -7980.82 m
cr: 3.258698e-06
DIST: -8036.68 m
cr: 3.233552e-06
DIST: -8064.24 m
cr: 3.253110e-06
DIST: -8100.77 m
cr: 3.120862e-06
DIST: -8127.04 m
cr: 3.304332e-06
DIST: -8152.95 m
cr: 3.131106e-06
DIST: -8139.07 m
cr: 3.150664e-06
DIST: -8131.50 m
cr: 3.363006e-06
DIST: -8160.60 m
cr: 3.501773e-06
DIST: -8168.92 m
cr: 3.158115e-06
DIST: -8167.05 m
cr: 3.279187e-06
DIST: -8182.51 m
cr: 3.556721e-06
DIST: -8209.82 m
cr: 3.129244e-06
DIST: -8234.97 m
cr: 3.160909e-06
DIST: -8226.78 m
cr: 3.061257e-06
DIST: -8218.71 m
cr: 3.242865e-06
DIST: -8225.43 m
cr: 3.447756e-06
DIST: -8233.56 m
cr: 3.232621e-06
DIST: -8270.89 m
cr: 3.195368e-06
DIST: -8304.38 m
cr: 3.353693e-06
DIST: -8298.62 m
cr: 3.221445e-06
DIST: -8318.44 m
cr: 3.186054e-06
DIST: -8331.40 m
cr: 3.298745e-06
DIST: -8344.84 m

A couple of observations: the distance numbers keep getting larger over time and they are negative. Here are what I’ve captured in the timestamps, these being the poll reception timestamps in the responder role. The reporting interval is approximately 19s.

poll_rx_ts: 638A9EA89E
poll_rx_ts: 7DEC425A60
poll_rx_ts: 985159E24F
poll_rx_ts: B2BA102E37
poll_rx_ts: CD1AE38B00
poll_rx_ts: E77F4BA61C
poll_rx_ts: 1E38F6783
poll_rx_ts: 1C414B557D
poll_rx_ts: 36A66CD757
poll_rx_ts: 51DA40AE5
poll_rx_ts: 6B6E1D74F4
poll_rx_ts: 85D423C61A
poll_rx_ts: A033F69BFA
poll_rx_ts: BA9D81591B
poll_rx_ts: D4FF07BEF6
poll_rx_ts: EF5F121E42
poll_rx_ts: 9BEAF6570
poll_rx_ts: 24202DECA1
poll_rx_ts: 3E847123A9
poll_rx_ts: 58E7C89461
poll_rx_ts: 7343E8C379
poll_rx_ts: 8DA7472373
poll_rx_ts: A88BC7836
poll_rx_ts: C265528DF0
poll_rx_ts: DCC4AE6283
poll_rx_ts: F7250111BD
poll_rx_ts: 118105A502
poll_rx_ts: 2BE10598F2
poll_rx_ts: 46442AEAEE
poll_rx_ts: 60AB89AED5
poll_rx_ts: 7BE546ADF
poll_rx_ts: 956F7C26F3
poll_rx_ts: AFD6CF9858
poll_rx_ts: CA36061F37
poll_rx_ts: E49B2755FC
poll_rx_ts: FF4A4875
poll_rx_ts: 196109B4CC
poll_rx_ts: 33BDD9A50A
poll_rx_ts: 4E210E2AD7
poll_rx_ts: 6886DFD733
poll_rx_ts: 82ED23DD81
poll_rx_ts: 9D4BBADE65
poll_rx_ts: B7AC0E0D78
poll_rx_ts: D212E4A8A7
poll_rx_ts: EC7283102D
poll_rx_ts: 6D410FA6F
poll_rx_ts: 2136FA8349

Do these look “normal”? I still have to disable the RX timeout to get the initiator to see the RXFCG bit set, otherwise it just always times out and reports an SFD timeout error, even though the responder completes the handshake. Thinking that this timeout error might be affecting the reported timestamp, I added a receive reset at the top of each cycle, but this didn’t appear to change anything. This is getting extremely frustrating.

Something is very wrong with your setup, though I couldn’t tell you what it is. You shouldn’t need to disable the SFD timeout (you say “RX timeout” but there are multiple timeouts, I ASSUME you mean the SFD timeout specifically, as opposed to the more general preamble detection timeout), and you shouldn’t be getting such wild results on timing.

I’ve never run that example code, but here’s my own debug printout from my own single sided ranging code (which yes, gets around 10cm accuracy):

Sending PING...
DW3K TransmitWait    (status=000000000f00 state=000a0000)...
DW3K TransmitActive  (status=000000000f00 state=000d0008)...
DW3K TransmitDone    (status=000000000f00 state=00030000)...
DW3K Ready           (status=000000000f00 state=00030000)...
DW3K counters: FCG=898 TXFS=132

Waiting for PONG...
DW3K ReceiveListen   (status=000000000000 state=01170500)...
DW3K ReceiveDone     (status=000000000f00 state=00030000)...

PING  4.467135521162s -->  3.788673935343s    3.513ppm offset
 dt   0.001775991477s  -   0.001776000883s = -0.000000009406s (raw)
 dt   0.001775991477s  -   0.001775994617s = -0.000000003140s (adjusted)
PONG  4.468911512639s <--  3.790449936226s   -3.543ppm offset

DW3K Ready           (status=000000000f00 state=00030000)...
DW3K counters: FCG=899 TXFS=132

Note, the clock offset is nicely symmetrical (each side sees about the same offset from the other, just negative), the time delay is small and stable (slightly negative in this case, but that’s just the lack of antenna calibration, if I move the boards apart it goes positive and tracks with distance), etc…

The data sheet indicates that 3.0V should be in-spec for supply voltage, so that’s probably not the problem.

Can you fetch (with dwt_read32bitreg) and print (in hex) the values for these registers? We can try to sanity check them.

SYS_CFG
TX_FCTRL
CHAN_CTRL
DGC_CFG
DTUNE0
DTUNE3
RF_TX_CTRL1
RF_TX_CTRL2
RX_CAL_RESI
RX_CAL_RESQ

By the way, my experience was that it’s relatively easy to screw things up such that things work very badly, by skipping various important calibration steps. Once you get things dialed in, suddenly performance is WAY better, in terms of basic packet transmission and also ranging measurements.

Thanks much egnor. Yes, referring to the SFD timeout. I don’t suppose you’d be willing to share your SS ranging code? The code I’m using is from the SS example as part of the Decawave API, located here: “\dw1000_api_rev2p14_stsw\dw1000_api_rev2p14_stsw\Src\examples\ex_06a_ss_twr_init” and ex_06b_ss_twr_resp, so you can see exactly what I’m doing. These are the register values as best I can tell from your brief descriptions:

    regval = dwt_read32bitreg(SYS_CFG_ID);
    regval = dwt_read32bitreg(TX_FCTRL_ID);
    regval = dwt_read32bitreg(CHAN_CTRL_ID);
    regval = dwt_read32bitreg(DRX_CONF_ID+DRX_TUNE0b_OFFSET);
    regval = dwt_read32bitreg(DRX_CONF_ID+DRX_TUNE1a_OFFSET);
    regval = dwt_read32bitreg(DRX_CONF_ID+DRX_TUNE1b_OFFSET);
    regval = dwt_read32bitreg(DRX_CONF_ID+DRX_TUNE2_OFFSET);
    regval = dwt_read32bitreg(RF_CONF_ID);
    regval = dwt_read32bitreg(DRX_CONF_ID+RF_TXCTRL_OFFSET);

    //RX_CAL_RESI not found
    //RX_CAL_RESQ not found

The ID’s are pulled directly from deca_regs.h, I could not find any register matching the last two descriptors, if you can give me their ID’s (register address) I can get you those as well. Here are the values in order:
0x00001200
0x00164000
0x4a480022
0xdeaddead
0xe7550448
0x00000000
0xdeadde00
0x00000000
0xdeaddead

I certainly believe that given the insane number of registers and settings involved, but what I don’t understand is why the reference code from Decawave doesn’t seem to work, building the code from scratch not knowing the intricacies of the chip seems untenable and unnecessary. Do you work for Decawave? If not, they haven’t contributed to this thread at all. Thanks very much for your help.

Well, I’m at my wits end. I implemented the reference code verbatim from this github link (Single Sided Two Way Ranging - #11 by kashgarim) with no other logic and am getting the same, if not worse, range results. I’m making sure the reset line is not being pulled high. The handshake is being completed on both sides with no extra delays. Clock ratios are good at less than 2ppm, but range numbers are all over the place:
Reception # : 1
cr: 1.150255e-06
Distance : 577037.312500
Reception # : 2
cr: 1.414464e-06
Distance : 1055935.125000
Reception # : 3
cr: 1.622507e-06
Distance : 732009.187500
Reception # : 4
cr: 1.887862e-06
Distance : 885728.687500

I have found that rtd_init and rtd_response are not at all symmetrical with rtd_init being about 30million times as large as rtd_response. Looking deeper, the high order byte of the read tx timestamp is consistently numerically one larger than the read rx timestamp in the initiator code. This goes against note 5 that suggests the high byte can be ignored when subtracting these two numbers. Thoughts?

It seems the timestamps are just not being calculated properly, including the system time. To test this, I print out the system time before and after (dwt_readsystime) a timer delay that is enforced by the master MCU. With a 1ms delay you could expect the systime counter to increment by 3.7e6, but what I’m seeing is not that at all, and sometimes the before systime is actually greater than the after systime. Why is the systime and timestamp reporting not working? Is there a clock setting I’m missing?

There shouldn’t be anything you need to do to get the clock to work correctly. The PLL locked flags in the status register are set?

Ideally you should heck the 40 bit values but with a 1ms period you shouldn’t be hitting that very often even if you only look at the low 32 bits.

You’re using a module so the clock itself should be good.

Issues with the power supply or reset could potentially cause issues. You aren’t driving reset high are you?

The only other thing I can think of that would impact the timekeeping would be the sync in pin but you’d need to specifically set things up for that that to have any impact.

Status register value when the response flag is set is 0x00806FF2. so yes the PLL locked flag is set, although I do clear this flag at initialization time. I’m checking the 40-bit values and as I mention above when I check the poll_tx_ts against the resp_rx_ts, oftentimes the upper bits of the timestamp (dwt_readrxtimestamphi32) are different, which is of course not right. I’m running the module at 3.0V, is that a problem? Reset line is being driven by a GPIO from a Nordic BTLE module that is set to normal drive low but no drive high and no pullup, although I do see the reset line raise to the high rail when released. I’ve also tried reprogramming the antenna delays at the start of the handshake and setting the AON_WCFG_ONW_LLDE bit to re-load the LDE algorithm. with no effect. One other clue is that when I re-run the initialisation at the end of the handshake the timestamps really go haywire. Why would that be?

Running at 3.3V makes no difference.

Does it matter where in the sequence the timestamp is read? In other words, is the TX timestamp recorded when the transmit is complete, and then as long as there is no longer any frames transmitted the timestamp persists in the register?

Here’s some evidence about what I believe is the root of the issue. The following data was taken from consecutive SS handshake cycles. Note that the rtd_init always stays about the same even though the poll_tx_ts and resp_rx_ts vary greatly. This applies no matter how physically far apart the initiator and responder are from each other. Note that when an exclamation point is in front of the “cr” this indicates that the upper 8 bits of these two 40-bit numbers differ by 1. The “before” and “after” numbers are system time stamps taken 1ms apart from each other. What seems to be happening is that the poll_tx_ts timestamp and the resp_rx_ts timestamp numbers reflect about the same time difference from each other, but the absolute values of the timestamps vary. This is what you would expect from two stationary participants in the handshake, but the problem is that the time difference suggests that the two are thousands of km apart, and that no matter how far apart they actually are, this difference remains approximately the same. So I’m begging you, can somebody please, please tell me what the hell is going on?? I’ve tried switching up the clocks, reloading the LDE code from ROM to RAM (this actually appears to be working and the difference is not that great), changing the SPI frequency, resetting the PMSC registers back to default, altering the timeout and response delay, changing the antenna delay, disconnecting the reset line, changing the supply voltage, and probably a dozen other things, but nothing works. HELP!!

Reception # : 3
before: 0C4B07315
after : 04A667F15
poll_tx_ts: DD679AC
resp_rx_ts: 4F485290
cr: -5.450387e-07
rtd_init : 4171D8E4
Reception # : 4
before: 0864AED1E
after : 0E8FEF81E
poll_tx_ts: 7F9A85AC
resp_rx_ts: C5D4AA99
cr: -5.077857e-07
rtd_init : 463A24ED
Reception # : 5
before: 024415F28
after : 0 2F76A28
poll_tx_ts: F95FBBAC
resp_rx_ts: 3C64041A
!cr: -5.639516e-07
rtd_init : 4304486E
Reception # : 6
before: 05EAAD931
after : 03A61E531
poll_tx_ts: 6BE5CFAC
resp_rx_ts: B3B23F1F
cr: -2.326874e-07
rtd_init : 47CC6F73
Reception # : 7
before: 09A 14C3B
after : 0DCB7573B
poll_tx_ts: E61349AC
resp_rx_ts: 2AEF878D
!cr: -5.100782e-07
rtd_init : 44DC3DE1
Reception # : 8
before: 0E47CBE44
after : 0F831CA44
poll_tx_ts: 5899E9AC
resp_rx_ts: 999FFFDB
cr: -3.513235e-07
rtd_init : 4106162F
Reception # : 9
before: 09C84304E
after : 0E6373C4E
poll_tx_ts: CAA48BAC
resp_rx_ts: 1064D24B
!cr: -2.722327e-07
rtd_init : 45C0469F
Reception # : 10
before: 0C2F2A257
after : 036A9AE57
poll_tx_ts: 3D080FAC
resp_rx_ts: 7F238063
cr: -2.705134e-07
rtd_init : 421B70B7
Reception # : 11
before: 0D0 C1661
after : 03CC42161
poll_tx_ts: B01B09AC
resp_rx_ts: F5AA436C
cr: -3.026082e-07
rtd_init : 458F39C0
Reception # : 12
before: 0AC89896A
after : 0B640956A
poll_tx_ts: 239D85AC
resp_rx_ts: 64E569D9
cr: -4.745447e-07
rtd_init : 4147E42D
Reception # : 13
before: 01E43FC73
after : 0B8F8 774
poll_tx_ts: 9660CBAC
resp_rx_ts: DB38872D
cr: -3.816990e-07
rtd_init : 44D7BB81
Reception # : 14
before: 0D8FF6E7D
after : 010B77A7D
poll_tx_ts: 90877AC
resp_rx_ts: 49F32C09
cr: -2.458692e-07
rtd_init : 40EAB45D
Reception # : 15
before: 036C5E186
after : 06A7BED86
poll_tx_ts: 7BD4B1AC
resp_rx_ts: BF9E960D
cr: -3.432998e-07
rtd_init : 43C9E461
Reception # : 16
before: 0D03D5C90
after : 0E2F36790
poll_tx_ts: EE72C7AC
resp_rx_ts: 3583B622
!cr: -1.673515e-07
rtd_init : 4710EE76
Reception # : 17
before: 0FAA0CE99
after : 0A856DA99
poll_tx_ts: 68BD57AC
resp_rx_ts: AB975150
cr: -3.060469e-07
rtd_init : 42D9F9A4
Reception # : 18
before: 0FE3249A3
after : 0B2E854A3
poll_tx_ts: DB6863AC
resp_rx_ts: 21E7E08D
!cr: -3.960270e-07
rtd_init : 467F7CE1
Reception # : 19
before: 05C97BCAC
after : 0C84EC8AC
poll_tx_ts: 56A753AC
resp_rx_ts: 99A5351C
cr: -1.203555e-07
rtd_init : 42FDE170
Reception # : 20
before: 03C7437B6
after : 0A42943B6
poll_tx_ts: C9B281AC
resp_rx_ts: 1178A3E4
!cr: -1.960076e-07
rtd_init : 47C62238
Reception # : 21
before: 0A4 1ABBF
after : 092B9B6BF
poll_tx_ts: 450933AC
resp_rx_ts: 88AE8A0C
cr: -1.507310e-07
rtd_init : 43A55660

Got it to work, it was a hardware problem, perhaps a bad module, and very subtle.

Well, it wasn’t a hardware problem after all (long story), but I did get it working. In the interest of making sure nobody else has to go through what I did, here are some lessons learned I’d like to share:

The example code is very poorly written from the typing standpoint. It intermixes different data types improperly and this can cause a myriad of issues depending upon your particular compiler. So even though the example code is a good place to start, make sure you go through it and use good coding practices to fix the typing mistakes.

Take their notes with a grain of salt. For example, they stipulate that the high-order byte of the timestamps can be discarded, which is plainly not true, and I have lots of data to demonstrate that.

To optimize the timeouts (critical in getting the TWR working in a power and time-effective way) you can guesstimate based upon the system timestamps. For example, when determining the optimal timeout for the SS TWR poll reception to response transmit delay, it must be long enough so that the transmit is executed just a bit beyond the system time when the code gets there. Otherwise, the system needs to wait until the system counter rolls over, which is far too long. But it shouldn’t be too long, because this adversely affects accuracy.

If your initiator poll to response time is drifting, you need to make sure your clock offset compensation is working properly.

Hopefully this will help somebody in the future. I’m afraid that I must say that Qorvo support on this forum is dismal to nonexistent so I wouldn’t rely on them for any help. Don’t hesitate to reach out to me on this forum for help with any deploying SS TWR on a DWM1000 with a Nordic MCU as SPI master, glad to help.

Ooh glad you got it working. Sorry I wasn’t around to offer moral support at least!

What turned out to be the specific bug in your case? Something around the timestamp high byte that you mentioned?

Yes, that was part of it, but there were other things as I mentioned before, such as data type issues in the reference code and just understanding the timeouts and how they affect the functioning of the handshake.

1 Like

I have read your post with great interest, much appreciated. I have a requirement to use the DW3000 with ds_twr_initiator and ds_twr_responder.

I have managed to get the example code of ss_twr_initiator and ss_twr_responder to work well with NRF Connect, however the DW3000 and DS ranging is just beyond my ability, even the cat can’t help and he is normally reall good at this stuff…

I’m using a PCA10056 with the DW3000EVB, just curious if you have had any experiance with this set of hardware and would love some help, because I have no idea how to set the timeout values and am guessing.

Thanks in advance.

Hi Beau, sorry about not seeing this sooner, I started working on some other projects but am back to this now. Just a question - why DS as opposed to the simpler SS? Do you still need help? I’d like to learn more about your application. Cheers

Hi! I am using double side two way ranging using interrupts and getting distance value of 2906m. And in single side two way ranging the distance value I have got above 39000m that was not correct. I am using Deca-driver library for ranging measurement. Can you help me for accurate ranging measurements. I am using dwm1000 module for this purpose.

This is my initiator code with interrupts

/*
 * double_sided_interrupt_initator.c
 *
 *  Created on: Jan 13, 2024
 *      Author: Admin
 */

#include "double_sided_interrupt_initator.h"

/* Inter-ranging delay period, in milliseconds. */
#define RNG_DELAY_MS 1000


__IO uint8_t rec_ok = 0;
__IO uint8_t rec_err = 0;
uint32_t status1 = 0;

/* Default communication configuration. We use here EVK1000's default mode (mode 3). */
static DWT_ConfigTypeDef dwtConfig = {
		  .LoadCode          = DW_LOAD_UCODE,
		  .Channel           = DW_CHANNEL_2,
		  .PulseRepFreq      = DW_PRF_64M,
		  .TxPreambLen       = DW_TX_PLEN_1024,
		  .PreambleAcqChunk  = DW_PAC_32,
		  .TxCode            = 9,
		  .RxCode            = 9,
		  .NonStandardSFD    = ENABLE,
		  .DataRate          = DW_DATARATE_110K,
		  .PhrMode           = DW_PHR_MODE_STD,
		  .SFDTimeout        = (1025 + 64 - 32) // TxPreambLen + 1 + SFD length - PAC
};


/* Default antenna delay values for 64 MHz PRF. See NOTE 1 below. */
#define TX_ANT_DLY 16436
#define RX_ANT_DLY 16436

/* Frames used in the ranging process. See NOTE 2 below. */
static uint8 tx_poll_msg[] = {0x41, 0x88, 0, 0xCA, 0xDE, 'W', 'A', 'V', 'E', 0x21, 0, 0};
static uint8 rx_resp_msg[] = {0x41, 0x88, 0, 0xCA, 0xDE, 'V', 'E', 'W', 'A', 0x10, 0x02, 0, 0, 0, 0};
static uint8 tx_final_msg[] = {0x41, 0x88, 0, 0xCA, 0xDE, 'W', 'A', 'V', 'E', 0x23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
/* Length of the common part of the message (up to and including the function code, see NOTE 2 below). */
#define ALL_MSG_COMMON_LEN 10
/* Indexes to access some of the fields in the frames defined above. */
#define ALL_MSG_SN_IDX 2
#define FINAL_MSG_POLL_TX_TS_IDX 10
#define FINAL_MSG_RESP_RX_TS_IDX 14
#define FINAL_MSG_FINAL_TX_TS_IDX 18
#define FINAL_MSG_TS_LEN 4
/* Frame sequence number, incremented after each transmission. */
static uint8 frame_seq_nb = 0;

/* Buffer to store received response message.
 * Its size is adjusted to longest frame that this example code is supposed to handle. */
#define RX_BUF_LEN 20
static uint8 rx_buffer[RX_BUF_LEN];



/* UWB microsecond (uus) to device time unit (dtu, around 15.65 ps) conversion factor.
 * 1 uus = 512 / 499.2 �s and 1 �s = 499.2 * 128 dtu. */
#define UUS_TO_DWT_TIME 63898

/* Delay between frames, in UWB microseconds. See NOTE 4 below. */
/* This is the delay from the end of the frame transmission to the enable of the receiver, as programmed for the DW1000's wait for response feature. */
#define POLL_TX_TO_RESP_RX_DLY_UUS 300
/* This is the delay from Frame RX timestamp to TX reply timestamp used for calculating/setting the DW1000's delayed TX function. This includes the
 * frame length of approximately 2.66 ms with above configuration. */
#define RESP_RX_TO_FINAL_TX_DLY_UUS 3100
/* Receive response timeout. See NOTE 5 below. */
#define RESP_RX_TIMEOUT_UUS 2700
/* Preamble timeout, in multiple of PAC size. See NOTE 6 below. */
#define PRE_TIMEOUT 8

/* Time-stamps of frames transmission/reception, expressed in device time units.
 * As they are 40-bit wide, we need to define a 64-bit int type to handle them. */
static uint64 poll_tx_ts;
static uint64 resp_rx_ts;
static uint64 final_tx_ts;

/* Declaration of static functions. */
static uint64 get_tx_timestamp_u64(void);
static uint64 get_rx_timestamp_u64(void);
static void final_msg_set_ts(uint8 *ts_field, uint64 ts);



pFunc DW1000_ExtiCallback = NULL;

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin == DW1000_IRQn_Pin)
	{
		DW1000_ExtiCallback();
	}
}

static void rx_ok_cb( const dwt_cb_data_t *cb_data )
{
    /* Clear good RX frame event in the DW1000 status register. */
    DWT_SetSysStatus(SYS_STATUS_RXFCG);

	  for (int i = 0 ; i < RX_BUF_LEN; i++ )
	    {
		  rx_buffer[i] = 0;
	    }


  if (cb_data->datalength <= RX_BUF_LEN) {
    DWT_ReadRxData(rx_buffer, cb_data->datalength, 0);
    rec_ok = 1;
  }

}

static void rx_err_cb( const dwt_cb_data_t *cb_data )
{
	status1 = DWT_GetSysStatus();
	rec_err = 1;
	DWT_RxReset();
}



/*! ------------------------------------------------------------------------------------------------------------------
 * @fn main()
 *
 * @brief Application entry point.
 *
 * @param  none
 *
 * @return none
 */
int double_sided_interrupt_initator(void)
{
	  DWT_SetSpeedLow();
	  DWT_Initialise(&dwtConfig);
	  DWT_SetSpeedHigh();

	  DWT_Configure(&dwtConfig);

	  DWT_SetCallbacks(NULL, &rx_ok_cb, NULL, &rx_err_cb);
	  DWT_SetInterrupt(DWT_INT_RFCG| DWT_INT_RFTO | DWT_INT_RXPTO | DWT_INT_RPHE | DWT_INT_RFCE | DWT_INT_RFSL | DWT_INT_SFDT, 1);

	  DWT_SetRxAntennaDelay(RX_ANT_DLY);
	  DWT_SetTxAntennaDelay(TX_ANT_DLY);

      /* Write frame data to DW1000 and prepare transmission. See NOTE 8 below. */
      tx_poll_msg[ALL_MSG_SN_IDX] = frame_seq_nb;
      DWT_WriteTxData(sizeof(tx_poll_msg), tx_poll_msg, 0); /* Zero offset in TX buffer. */
      DWT_WriteTxFCtrl(sizeof(tx_poll_msg), 0, 1); /* Zero offset in TX buffer, ranging. */

      /* Start transmission, indicating that a response is expected so that reception is enabled automatically after the frame is sent and the delay
       * set by dwt_setrxaftertxdelay() has elapsed. */
      DWT_StartTx(DWT_TX_IMMEDIATE);

      while(!(DWT_GetSysStatus() & SYS_STATUS_TXFRS));
      /* Activate reception immediately. */
      DWT_SetSysStatus(SYS_STATUS_TXFRS);
      /* Increment frame sequence number after transmission of the poll message (modulo 256). */
      frame_seq_nb++;
	  DWT_RxEnable(DWT_RX_IMMEDIATE);
	  while (1) {

            if(rec_err)
            {
                /* Write frame data to DW1000 and prepare transmission. See NOTE 8 below. */
                tx_poll_msg[ALL_MSG_SN_IDX] = frame_seq_nb;
                DWT_WriteTxData(sizeof(tx_poll_msg), tx_poll_msg, 0); /* Zero offset in TX buffer. */
                DWT_WriteTxFCtrl(sizeof(tx_poll_msg), 0, 1); /* Zero offset in TX buffer, ranging. */

                /* Start transmission, indicating that a response is expected so that reception is enabled automatically after the frame is sent and the delay
                 * set by dwt_setrxaftertxdelay() has elapsed. */
                DWT_StartTx(DWT_TX_IMMEDIATE);

                while(!(DWT_GetSysStatus() & SYS_STATUS_TXFRS));
                /* Activate reception immediately. */
                DWT_SetSysStatus(SYS_STATUS_TXFRS);
                /* Increment frame sequence number after transmission of the poll message (modulo 256). */
                frame_seq_nb++;
          	    DWT_RxEnable(DWT_RX_IMMEDIATE);
          	    rec_err = 0;
            }

			if(rec_ok)
			{
				rec_ok = 0;
				rx_buffer[ALL_MSG_SN_IDX] = 0;
		        if (memcmp(rx_buffer, rx_resp_msg, ALL_MSG_COMMON_LEN) == 0) {
	                uint32_t final_tx_time;
	                int ret;

	                /* Retrieve poll transmission and response reception timestamp. */

	                poll_tx_ts = get_tx_timestamp_u64();
	                resp_rx_ts = get_rx_timestamp_u64();


	                /* Compute final message transmission time. See NOTE 10 below. */
	                final_tx_time = (resp_rx_ts + (RESP_RX_TO_FINAL_TX_DLY_UUS * UUS_TO_DWT_TIME)) >> 8;
	                DWT_SetRxAfterTxDelay(final_tx_time);

	                /* Final TX timestamp is the transmission time we programmed plus the TX antenna delay. */
	                final_tx_ts = (((uint64)(final_tx_time & 0xFFFFFFFEUL)) << 8) + TX_ANT_DLY;

	                /* Write all timestamps in the final message. See NOTE 11 below. */
	                final_msg_set_ts(&tx_final_msg[FINAL_MSG_POLL_TX_TS_IDX], poll_tx_ts);
	                final_msg_set_ts(&tx_final_msg[FINAL_MSG_RESP_RX_TS_IDX], resp_rx_ts);
	                final_msg_set_ts(&tx_final_msg[FINAL_MSG_FINAL_TX_TS_IDX], final_tx_ts);

	                /* Write and send final message. See NOTE 8 below. */
	                tx_final_msg[ALL_MSG_SN_IDX] = frame_seq_nb;
	                DWT_WriteTxData(sizeof(tx_final_msg), tx_final_msg, 0); /* Zero offset in TX buffer. */
	                DWT_WriteTxFCtrl(sizeof(tx_final_msg), 0, 1); /* Zero offset in TX buffer, ranging. */
	                ret = DWT_StartTx(DWT_TX_IMMEDIATE);

	                while(!(DWT_GetSysStatus() & SYS_STATUS_TXFRS));
	                /* Activate reception immediately. */
	                DWT_SetSysStatus(SYS_STATUS_TXFRS);


	    	        HAL_Delay(1000);

	    	        frame_seq_nb++;
	    	        /* Write frame data to DW1000 and prepare transmission. See NOTE 8 below. */
	    	        tx_poll_msg[ALL_MSG_SN_IDX] = frame_seq_nb;
	    	        DWT_WriteTxData(sizeof(tx_poll_msg), tx_poll_msg, 0); /* Zero offset in TX buffer. */
	    	        DWT_WriteTxFCtrl(sizeof(tx_poll_msg), 0, 1); /* Zero offset in TX buffer, ranging. */

	    	        /* Start transmission, indicating that a response is expected so that reception is enabled automatically after the frame is sent and the delay
	    	         * set by dwt_setrxaftertxdelay() has elapsed. */
	    	        DWT_StartTx(DWT_TX_IMMEDIATE);

	    	        while(!(DWT_GetSysStatus() & SYS_STATUS_TXFRS));
	    	        /* Activate reception immediately. */
	    	        DWT_SetSysStatus(SYS_STATUS_TXFRS);
	    	        DWT_RxEnable(DWT_RX_IMMEDIATE);
		        }
			}

		}


	}

/*! ------------------------------------------------------------------------------------------------------------------
 * @fn get_tx_timestamp_u64()
 *
 * @brief Get the TX time-stamp in a 64-bit variable.
 *        /!\ This function assumes that length of time-stamps is 40 bits, for both TX and RX!
 *
 * @param  none
 *
 * @return  64-bit value of the read time-stamp.
 */
static uint64 get_tx_timestamp_u64(void)
{
    uint8_t ts_tab[5];
    uint64 ts = 0;
    int i;
    DWT_ReadTxTimestamp(ts_tab);
    for (i = 4; i >= 0; i--)
    {
        ts <<= 8;
        ts |= ts_tab[i];
    }
    return ts;
}

/*! ------------------------------------------------------------------------------------------------------------------
 * @fn get_rx_timestamp_u64()
 *
 * @brief Get the RX time-stamp in a 64-bit variable.
 *        /!\ This function assumes that length of time-stamps is 40 bits, for both TX and RX!
 *
 * @param  none
 *
 * @return  64-bit value of the read time-stamp.
 */
static uint64 get_rx_timestamp_u64(void)
{
    uint8_t ts_tab[5];
    uint64 ts = 0;
    int i;
    DWT_ReadRxTimestamp(ts_tab);
    for (i = 4; i >= 0; i--)
    {
        ts <<= 8;
        ts |= ts_tab[i];
    }
    return ts;
}

/*! ------------------------------------------------------------------------------------------------------------------
 * @fn final_msg_set_ts()
 *
 * @brief Fill a given timestamp field in the final message with the given value. In the timestamp fields of the final
 *        message, the least significant byte is at the lower address.
 *
 * @param  ts_field  pointer on the first byte of the timestamp field to fill
 *         ts  timestamp value
 *
 * @return none
 */
static void final_msg_set_ts(uint8 *ts_field, uint64 ts)
{
    int i;
    for (i = 0; i < FINAL_MSG_TS_LEN; i++)
    {
        ts_field[i] = (uint8_t) ts;
        ts >>= 8;
    }
}

And this is my responder side code

/*
 * double_sided_interrupt_responder.c
 *
 *  Created on: Jan 13, 2024
 *      Author: Admin
 */

#include "double_sided_interrupt_responder.h"


__IO uint8_t rec_ok = 0;
__IO uint8_t rec_err = 0;
uint32_t status1 = 0;


/* Inter-ranging delay period, in milliseconds. */
#define RNG_DELAY_MS 1000

static DWT_ConfigTypeDef dwtConfig = {
		  .LoadCode          = DW_LOAD_UCODE,
		  .Channel           = DW_CHANNEL_2,
		  .PulseRepFreq      = DW_PRF_64M,
		  .TxPreambLen       = DW_TX_PLEN_1024,
		  .PreambleAcqChunk  = DW_PAC_32,
		  .TxCode            = 9,
		  .RxCode            = 9,
		  .NonStandardSFD    = ENABLE,
		  .DataRate          = DW_DATARATE_110K,
		  .PhrMode           = DW_PHR_MODE_STD,
		  .SFDTimeout        = (1024 + 64 - 32) // TxPreambLen + 1 + SFD length - PAC
};

/* Default antenna delay values for 64 MHz PRF. See NOTE 1 below. */
#define TX_ANT_DLY 16436
#define RX_ANT_DLY 16436

/* Frames used in the ranging process. See NOTE 2 below. */
static uint8 rx_poll_msg[] = {0x41, 0x88, 0, 0xCA, 0xDE, 'W', 'A', 'V', 'E', 0x21, 0, 0};
static uint8 tx_resp_msg[] = {0x41, 0x88, 0, 0xCA, 0xDE, 'V', 'E', 'W', 'A', 0x10, 0x02, 0, 0, 0, 0};
static uint8 rx_final_msg[] = {0x41, 0x88, 0, 0xCA, 0xDE, 'W', 'A', 'V', 'E', 0x23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
/* Length of the common part of the message (up to and including the function code, see NOTE 2 below). */
#define ALL_MSG_COMMON_LEN 10
/* Index to access some of the fields in the frames involved in the process. */
#define ALL_MSG_SN_IDX 2
#define FINAL_MSG_POLL_TX_TS_IDX 10
#define FINAL_MSG_RESP_RX_TS_IDX 14
#define FINAL_MSG_FINAL_TX_TS_IDX 18
#define FINAL_MSG_TS_LEN 4
/* Frame sequence number, incremented after each transmission. */
static uint8 frame_seq_nb = 0;

/* Buffer to store received messages.
 * Its size is adjusted to longest frame that this example code is supposed to handle. */
#define RX_BUF_LEN 24
static uint8 rx_buffer[RX_BUF_LEN];


/* UWB microsecond (uus) to device time unit (dtu, around 15.65 ps) conversion factor.
 * 1 uus = 512 / 499.2 �s and 1 �s = 499.2 * 128 dtu. */
#define UUS_TO_DWT_TIME 63898

/* Delay between frames, in UWB microseconds. See NOTE 4 below. */
/* This is the delay from Frame RX timestamp to TX reply timestamp used for calculating/setting the DW1000's delayed TX function. This includes the
 * frame length of approximately 2.46 ms with above configuration. */
#define POLL_RX_TO_RESP_TX_DLY_UUS 2750
/* This is the delay from the end of the frame transmission to the enable of the receiver, as programmed for the DW1000's wait for response feature. */
#define RESP_TX_TO_FINAL_RX_DLY_UUS 500
/* Receive final timeout. See NOTE 5 below. */
#define FINAL_RX_TIMEOUT_UUS 3300
/* Preamble timeout, in multiple of PAC size. See NOTE 6 below. */
#define PRE_TIMEOUT 8

/* Timestamps of frames transmission/reception.
 * As they are 40-bit wide, we need to define a 64-bit int type to handle them. */
static uint64 poll_rx_ts;
static uint64 resp_tx_ts;
static uint64 final_rx_ts;

/* Speed of light in air, in metres per second. */
#define SPEED_OF_LIGHT 299702547

/* Hold copies of computed time of flight and distance here for reference so that it can be examined at a debug breakpoint. */
static double tof;
static double distance;

/* String used to display measured distance on LCD screen (16 characters maximum). */
char dist_str[16] = {0};

/* Declaration of static functions. */
static uint64 get_tx_timestamp_u64(void);
static uint64 get_rx_timestamp_u64(void);
static void final_msg_get_ts(const uint8 *ts_field, uint32 *ts);



pFunc DW1000_ExtiCallback = NULL;

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin == DW1000_IRQn_Pin)
	{
		DW1000_ExtiCallback();
	}
}

static void rx_ok_cb( const dwt_cb_data_t *cb_data )
{
	  for (int i = 0 ; i < RX_BUF_LEN; i++ )
		    {
		      rx_buffer[i] = 0;
		    }
  if (cb_data->datalength <= RX_BUF_LEN) {
    DWT_ReadRxData(rx_buffer, cb_data->datalength, 0);
    rec_ok = 1;
  }
}

static void rx_err_cb( const dwt_cb_data_t *cb_data )
{
	 status1 = DWT_GetSysStatus();
	 rec_err = 1;
	 DWT_RxReset();


}


int double_sided_interrupt_responder(void)
{
	DWT_SetSpeedLow();
	DWT_Initialise(&dwtConfig);
	DWT_SetSpeedHigh();

	DWT_Configure(&dwtConfig);

	DWT_SetCallbacks(NULL, &rx_ok_cb, NULL, &rx_err_cb);
	DWT_SetInterrupt(DWT_INT_RFCG| DWT_INT_RFTO | DWT_INT_RXPTO | DWT_INT_RPHE | DWT_INT_RFCE | DWT_INT_RFSL | DWT_INT_SFDT, 1);

	DWT_SetRxAntennaDelay(RX_ANT_DLY);
    DWT_SetTxAntennaDelay(TX_ANT_DLY);
 	DWT_RxEnable(DWT_RX_IMMEDIATE);
	  while (1) {

		  if(rec_err)
		  {
			  rec_err = 0;
			  DWT_RxEnable(DWT_RX_IMMEDIATE);
		  }

		  if(rec_ok)
			{
				rec_ok = 0;
	            rx_buffer[ALL_MSG_SN_IDX] = 0;
			      if (memcmp(rx_buffer, rx_poll_msg, ALL_MSG_COMMON_LEN) == 0) {
			        uint32_t resp_tx_time;
	                int ret;

	                /* Retrieve poll reception timestamp. */
	                poll_rx_ts = get_rx_timestamp_u64();

	                /* Set send time for response. See NOTE 9 below. */
	                resp_tx_time = (poll_rx_ts + (POLL_RX_TO_RESP_TX_DLY_UUS * UUS_TO_DWT_TIME)) >> 8;
                    DWT_SetDelayedTxRxTime(resp_tx_time);

	                /* Set expected delay and timeout for final message reception. See NOTE 4 and 5 below. */
	                DWT_SetRxAfterTxDelay(RESP_TX_TO_FINAL_RX_DLY_UUS);
//	                DWT_SetRxTimeout(FINAL_RX_TIMEOUT_UUS);

	                /* Write and send the response message. See NOTE 10 below.*/
	                tx_resp_msg[ALL_MSG_SN_IDX] = frame_seq_nb;
	                DWT_WriteTxData(sizeof(tx_resp_msg), tx_resp_msg, 0); /* Zero offset in TX buffer. */
	                DWT_WriteTxFCtrl(sizeof(tx_resp_msg), 0, 1); /* Zero offset in TX buffer, ranging. */
	                ret = DWT_StartTx(DWT_RX_IMMEDIATE);

	                /* If dwt_starttx() returns an error, abandon this ranging exchange and proceed to the next one. See NOTE 11 below. */
	                if (ret == DWT_ERROR)
	                {
	                    continue;
	                }

	                while(!(DWT_GetSysStatus() & SYS_STATUS_TXFRS));
	                DWT_SetSysStatus(SYS_STATUS_TXFRS);

	                DWT_RxEnable(DWT_RX_IMMEDIATE);

			        }

			      while(!(rec_ok | rec_err))
			      {
			      };

					rec_ok = 0;
					rec_err = 0;

                  /* Check that the frame is a final message sent by "DS TWR initiator" example.
                   * As the sequence number field of the frame is not used in this example, it can be zeroed to ease the validation of the frame. */
                  rx_buffer[ALL_MSG_SN_IDX] = 0;
                  if (memcmp(rx_buffer, rx_final_msg, ALL_MSG_COMMON_LEN) == 0)
                  {
                      uint32 poll_tx_ts, resp_rx_ts, final_tx_ts;
                      uint32 poll_rx_ts_32, resp_tx_ts_32, final_rx_ts_32;
                      double Ra, Rb, Da, Db;
                      int64 tof_dtu;

                      /* Retrieve response transmission and final reception timestamps. */
                      resp_tx_ts = get_tx_timestamp_u64();
                      final_rx_ts = get_rx_timestamp_u64();

                      /* Get timestamps embedded in the final message. */
                      final_msg_get_ts(&rx_buffer[FINAL_MSG_POLL_TX_TS_IDX], &poll_tx_ts);
                      final_msg_get_ts(&rx_buffer[FINAL_MSG_RESP_RX_TS_IDX], &resp_rx_ts);
                      final_msg_get_ts(&rx_buffer[FINAL_MSG_FINAL_TX_TS_IDX], &final_tx_ts);
//
                      /* Compute time of flight. 32-bit subtractions give correct answers even if clock has wrapped. See NOTE 12 below. */
                      poll_rx_ts_32 = (uint32)poll_rx_ts;
                      resp_tx_ts_32 = (uint32)resp_tx_ts;
                      final_rx_ts_32 = (uint32)final_rx_ts;
                      Ra = (double)(resp_rx_ts - poll_tx_ts);
                      Rb = (double)(final_rx_ts_32 - resp_tx_ts_32);
                      Da = (double)(final_tx_ts - resp_rx_ts);
                      Db = (double)(resp_tx_ts_32 - poll_rx_ts_32);
                      tof_dtu = (int64)((Ra * Rb - Da * Db) / (Ra + Rb + Da + Db));

                      tof = tof_dtu * DWT_TIME_UNITS;
                      distance = tof * SPEED_OF_LIGHT;
                   	  DWT_RxEnable(DWT_RX_IMMEDIATE);
                    }

			      }

//	        HAL_Delay(RNG_DELAY_MS);
	   }
}


/*! ------------------------------------------------------------------------------------------------------------------
 * @fn get_tx_timestamp_u64()
 *
 * @brief Get the TX time-stamp in a 64-bit variable.
 *        /!\ This function assumes that length of time-stamps is 40 bits, for both TX and RX!
 *
 * @param  none
 *
 * @return  64-bit value of the read time-stamp.
 */
static uint64 get_tx_timestamp_u64(void)
{
    uint8 ts_tab[5];
    uint64 ts = 0;
    int i;
    DWT_ReadTxTimestamp(ts_tab);
    for (i = 4; i >= 0; i--)
    {
        ts <<= 8;
        ts |= ts_tab[i];
    }
    return ts;
}

/*! ------------------------------------------------------------------------------------------------------------------
 * @fn get_rx_timestamp_u64()
 *
 * @brief Get the RX time-stamp in a 64-bit variable.
 *        /!\ This function assumes that length of time-stamps is 40 bits, for both TX and RX!
 *
 * @param  none
 *
 * @return  64-bit value of the read time-stamp.
 */
static uint64 get_rx_timestamp_u64(void)
{
    uint8 ts_tab[5];
    uint64 ts = 0;
    int i;
    DWT_ReadRxTimestamp(ts_tab);
    for (i = 4; i >= 0; i--)
    {
        ts <<= 8;
        ts |= ts_tab[i];
    }
    return ts;
}

/*! ------------------------------------------------------------------------------------------------------------------
 * @fn final_msg_get_ts()
 *
 * @brief Read a given timestamp value from the final message. In the timestamp fields of the final message, the least
 *        significant byte is at the lower address.
 *
 * @param  ts_field  pointer on the first byte of the timestamp field to read
 *         ts  timestamp value
 *
 * @return none
 */
static void final_msg_get_ts(const uint8 *ts_field, uint32 *ts)
{
    int i;
    *ts = 0;
    for (i = 0; i < FINAL_MSG_TS_LEN; i++)
    {
        *ts += ts_field[i] << (i * 8);
    }
}

A couple of suggestions based upon my experience: 1) Be very careful about using #define constants, casting and proper formatting when printing to the debug console when you’re doing floating point operations to calculate the distance from the timestamps. Different compilers do different things. 2) Make sure that the received timestamps match the transmitted time stamps. 3) Review your code and make sure that the sequence of xmit/recv cycles match the Qorvo two-sided TWR diagram, and 4) Take some time to really understand the delay and timeout settings (POLL_TX_TO_RESP_RX_DLY_UUS, RESP_RX_TIMEOUT_UUS on the initiator side and POLL_RX_TO_RESP_TX_DLY_UUS on the responder side (these defines are for SS TWR so the actual defines may be different for DS TWR)) and make sure that these numbers are set to give the other side as much time as it needs to receive the timestamps. For example, if you set the Rx to Tx delay of the responder too short the transmitter will have to wait for the sys clock tick to “roll over” before transmitting, in which case the other side may never see the packet, or you’ll get a bogus timestamp. If you set it too long, the other side may give up listening and the packet will never be seen. In real-time range finding, you also need to worry about the fact that the two sides of the transaction are running completely asynchronously and so you need the timer interval and size of receive window for each side to overlap often enough to meet your latency requirements without draining the battery quickly. Hope this helps.

Oh, and one more thing. Don’t rely on Qorvo tech support for timely answers, one FAE at CES admitted that their tech support group is woefully understaffed and has trouble keeping up with their teir1 accounts, never mind the startups. Not only that but oftentimes their answers are too vague and generalized to be any real use. So post on this forum certainly, but carry on your investigation elsewhere (Reddit and so forth), take their documentation with a grain of salt (I’ve found many details to be missing), and pay attention to the minutia (their example code is far from cut and paste). Good luck!