DS Two Way Ranging example code not communicating

Thanks Andy, I appreciate the response. Any advice as to how to get ahold a somebody at Qorvo that can actually help with technical issues? BTW, I finally got the initiator and responder to get through a complete SS ranging exchange, but to do that I had to take some liberties with the timeouts and the distance numbers I’m getting are bogus. I am using the Qorvo API and the examples for SS ranging and a DWM1000 module, so as long as the SPI interface is working (which I’m pretty sure is) then the MCU I’m running the code on shouldn’t matter. I’m running the initiator code on one unit and responder code on another. Here’s the initiator code:

// Single sided example from Decawave
void initiate_ss_twr()
        //apdobaj added clearing the PLL losing lock bit
    dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_CLKPLL_LL);
    /* Set expected response's delay and timeout. See NOTE 4, 5 and 6 below.
    * As this example only handles one incoming frame with always the same delay and timeout, those values can be set here once for all. */

    /* Loop forever initiating ranging exchanges. */
    while (1)
        /* Write frame data to DW1000 and prepare transmission. See NOTE 7 below. */
        tx_poll_msg[ALL_MSG_SN_IDX] = frame_seq_nb;
        dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_TXFRS);
        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. */
        //test using simple tx example
        //dwt_writetxdata(sizeof(tx_msg), tx_msg, 0); /* Zero offset in TX buffer. */
        //dwt_writetxfctrl(sizeof(tx_msg), 0, 0); /* Zero offset in TX buffer, no 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. */

        /* We assume that the transmission is achieved correctly, poll for reception of a frame or error/timeout. See NOTE 8 below. */
        status_reg = dwt_read32bitreg(SYS_STATUS_ID);
        //while (!(status_reg & (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_TO | SYS_STATUS_ALL_RX_ERR)))
        while (!(status_reg & (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_TO)))
        //simple tx test
        //while (!(status_reg & SYS_STATUS_TXFRS))
          status_reg = dwt_read32bitreg(SYS_STATUS_ID);
          //if (status_reg & 0x02000000) {
          //  SEGGER_RTT_printf(0, "*");

        /*simple tx test
        // Clear TX frame sent event. 
        dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_TXFRS);

        // Execute a delay between transmissions. 

        // Increment the blink frame sequence number (modulo 256). 
        //end simple tx test */

        // start ss initiate
        // Increment frame sequence number after transmission of the poll message (modulo 256). 

        if (status_reg & SYS_STATUS_RXFCG)
            uint32 frame_len;

            // Clear good RX frame event in the DW1000 status register. 
            dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG);

            // A frame has been received, read it into the local buffer. 
            frame_len = dwt_read32bitreg(RX_FINFO_ID) & RX_FINFO_RXFLEN_MASK;
            if (frame_len <= RX_BUF_LEN)
                dwt_readrxdata(rx_buffer, frame_len, 0);

            // Check that the frame is the expected response from the companion "SS TWR responder" example.
            // As the sequence number field of the frame is not relevant, it is cleared to simplify the validation of the frame. 
            rx_buffer[ALL_MSG_SN_IDX] = 0;
            if (memcmp(rx_buffer, rx_resp_msg, ALL_MSG_COMMON_LEN) == 0)
                uint32 poll_tx_ts, resp_rx_ts, poll_rx_ts, resp_tx_ts;
                int32 rtd_init, rtd_resp;
                float clockOffsetRatio ;

                // Retrieve poll transmission and response reception timestamps. See NOTE 9 below. 
                poll_tx_ts = dwt_readtxtimestamplo32();
                resp_rx_ts = dwt_readrxtimestamplo32();

                // Read carrier integrator value and calculate clock offset ratio. See NOTE 11 below. 
                clockOffsetRatio = dwt_readcarrierintegrator() * (FREQ_OFFSET_MULTIPLIER * HERTZ_TO_PPM_MULTIPLIER_CHAN_2 / 1.0e6) ;

                // Get timestamps embedded in response message. 
                resp_msg_get_ts(&rx_buffer[RESP_MSG_POLL_RX_TS_IDX], &poll_rx_ts);
                resp_msg_get_ts(&rx_buffer[RESP_MSG_RESP_TX_TS_IDX], &resp_tx_ts);

                // Compute time of flight and distance, using clock offset ratio to correct for differing local and remote clock rates 
                rtd_init = resp_rx_ts - poll_tx_ts;
                rtd_resp = resp_tx_ts - poll_rx_ts;

                tof = ((rtd_init - rtd_resp * (1 - clockOffsetRatio)) / 2.0) * DWT_TIME_UNITS;
                distance = tof * SPEED_OF_LIGHT;
                // Display computed distance on console. 
                sprintf(dist_str, "DIST: %3.2f m", distance);
                SEGGER_RTT_printf(0, "%s\r\n", dist_str);
                //apdobaj-twr add LED indication of distance calculated
                nrf_gpio_pin_write(LED_PIN, 0);
                nrf_gpio_pin_write(LED_PIN, 1);
            // Clear RX error/timeout events in the DW1000 status register. 
            dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_ALL_RX_TO | SYS_STATUS_ALL_RX_ERR);

            // Reset RX to properly reinitialise LDE operation. 

        // Execute a delay between ranging exchanges. 
        nrf_delay_ms(1000); // end ss initiate//

And the responder code:

void respond_ss_twr()

        //apdobaj added clearing the PLL losing lock bit
    dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_CLKPLL_LL);
    //apdobaj simple test set SFD detection timeout to never (this will drain the battery)
    //dwt_write16bitoffsetreg(0x27, 0x20, 0);
    unsigned long sys_state_curr, sys_state_prev;
    sys_state_curr = 0;
    /* Loop forever responding to ranging requests. */
    while (1)
        //int i;
        /* Clear local RX buffer to avoid having leftovers from previous receptions  This is not necessary but is included here to aid reading
         * the RX buffer.
         * This is a good place to put a breakpoint. Here (after first time through the loop) the local status register will be set for last event
         * and if a good receive has happened the data buffer will have the data in it, and frame_len will be set to the length of the RX frame. */
        //for (i = 0 ; i < FRAME_LEN_MAX; i++ )
        //    rx_buffer[i] = 0;

        /* Activate reception immediately. */

        /* Poll for reception of a frame or error/timeout. See NOTE 6 below. */
        status_reg = dwt_read32bitreg(SYS_STATUS_ID);
        while (!(status_reg & (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_ERR)))
          status_reg = dwt_read32bitreg(SYS_STATUS_ID);

        /*apdobaj simple receive
        //check SYS_STATE register
        sys_state_prev = sys_state_curr;
        sys_state_curr = dwt_read32bitoffsetreg(0x19, 0x00);
        if (sys_state_curr != sys_state_prev) {
          SEGGER_RTT_printf(0, "state is %lu\r\n", sys_state_curr);
        if (status_reg & SYS_STATUS_RXFCG)
            // A frame has been received, copy it to our local buffer. 
            frame_len = dwt_read32bitreg(RX_FINFO_ID) & RX_FINFO_RXFL_MASK_1023;
            if (frame_len <= FRAME_LEN_MAX)
                dwt_readrxdata(rx_buffer, frame_len, 0);

            // Clear good RX frame event in the DW1000 status register. 
            dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG);
            // Clear RX error events in the DW1000 status register. 
            dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_ALL_RX_ERR);
        //apdobaj end simple receive */

        //apdobaj start ss respond
        if (status_reg & SYS_STATUS_RXFCG)
            uint32 frame_len;

            // Clear good RX frame event in the DW1000 status register. 
            dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG);

            // A frame has been received, read it into the local buffer. 
            frame_len = dwt_read32bitreg(RX_FINFO_ID) & RX_FINFO_RXFL_MASK_1023;
            if (frame_len <= RX_BUFFER_LEN)
                dwt_readrxdata(rx_buffer, frame_len, 0);

            // Check that the frame is a poll sent by "SS TWR initiator" example.
            // As the sequence number field of the frame is not relevant, it is cleared to simplify the validation of the frame. 
            rx_buffer[ALL_MSG_SN_IDX] = 0;
            if (memcmp(rx_buffer, rx_poll_msg, ALL_MSG_COMMON_LEN) == 0)
                uint32 resp_tx_time;
                int ret;

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

                // Compute final message transmission time. See NOTE 7 below. 
                resp_tx_time = (poll_rx_ts + (POLL_RX_TO_RESP_TX_DLY_UUS * UUS_TO_DWT_TIME)) >> 8;

                // Response TX timestamp is the transmission time we programmed plus the antenna delay. 
                resp_tx_ts = (((uint64)(resp_tx_time & 0xFFFFFFFEUL)) << 8) + TX_ANT_DLY;

                // Write all timestamps in the final message. See NOTE 8 below. 
                resp_msg_set_ts(&tx_resp_msg[RESP_MSG_POLL_RX_TS_IDX], poll_rx_ts);
                resp_msg_set_ts(&tx_resp_msg[RESP_MSG_RESP_TX_TS_IDX], resp_tx_ts);

                // Write and send the response message. See NOTE 9 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_START_TX_DELAYED);

                // If dwt_starttx() returns an error, abandon this ranging exchange and proceed to the next one. See NOTE 10 below. 
                if (ret == DWT_SUCCESS)
                    // Poll DW1000 until TX frame sent event set. See NOTE 6 below. 
                    while (!(dwt_read32bitreg(SYS_STATUS_ID) & SYS_STATUS_TXFRS))
                    { };
                    //apdobaj-twr add LED indication of frame sent
                    nrf_gpio_pin_write(LED_PIN, 0);
                    nrf_gpio_pin_write(LED_PIN, 1);

                    // Clear TXFRS event. 
                    dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_TXFRS);

                    // Increment frame sequence number after transmission of the poll message (modulo 256). 
            // Clear RX error events in the DW1000 status register. 
            dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_ALL_RX_ERR);

            // Reset RX to properly reinitialise LDE operation. 
    }// ss respond //

As you can see I needed to set the initiator RX timeout to zero, otherwise it would always timeout no matter what value I set for the timeout (RESP_RX_TIMEOUT_UUS). I also had to increase the SFD timeout in the config structure:

static dwt_config_t config = {
    2,               /* Channel number. */
    DWT_PRF_64M,     /* Pulse repetition frequency. */
    DWT_PLEN_128,    /* Preamble length. Used in TX only. */
    DWT_PAC8,        /* Preamble acquisition chunk size. Used in RX only. */
    9,               /* TX preamble code. Used in TX only. */
    9,               /* RX preamble code. Used in RX only. */
    0,               /* 0 to use standard SFD, 1 to use non-standard SFD. */
    DWT_BR_6M8,      /* Data rate. */
    DWT_PHRMODE_STD, /* PHY header mode. */
    //(129 + 8 - 8)    /* SFD timeout (preamble length + 1 + SFD length - PAC size). Used in RX only. */
    //apdobaj-twr increase sfd timeout
    (1025 + 64 - 32)

The response packet from the responder appears to be received correctly, but again the reported distance is all over the place. Any ideas you might have would be greatly appreciated.

How much noise are you getting in the range?
Delay calibration issues would cause a constant offset but not noise in the numbers.

Maybe print out the clockOffsetRatio values you are getting and see if noise in those explain the range noise.
I’ve always been on the hunt for the best possible accuracy so use double sided TWR to avoid that issue.

Typical numbers for reported distance (actually 30cm) and clock ratio.
DIST: 7424.98 m
cr: 1.982441e-06 m
DIST: 4095.24 m
cr: 1.111369e-06 m
DIST: 2031.73 m
cr: 1.591960e-06 m
DIST: 577.16 m
DIST: 7120.38 m
cr: 2.342885e-06 m
DIST: 3853.11 m
cr: 3.093809e-06 m
DIST: 1839.73 m
cr: 1.742145e-06 m
DIST: 420.39 m
cr: 1.968294e-03 m
DIST: -693.47 m
cr: 1.968174e-03 m
DIST: -1572.49 m
cr: 1.967813e-03 m
DIST: -2309.69 m
cr: 1.501849e-07 m

I’ll give the DS method a try. What sort of accuracy using SS should I expect without calibrating the antennas?

You should be able to get into the 10cm region using single sided ranging, certainly not km of variability.
I found antenna delays tended to add around 70m per device but that depends on the antenna and channel settings. So with no delay calibrations expect ranges to be around 100-150 meters too long.

It’s odd that your clock ratio is moving around that much, the relative clock differences between the two should be reasonably constant unless temperatures are changing.

OK interesting. I’m using the DWM1000 so I wouldn’t expect there to be any issues with the crystal or clock routing. I am running the module at 3.0V, not 3.3. Could that make a difference? I’ll go back and recheck the clock ratio determination to make sure I’m doing that properly. Just so I’m clear, is this the clock ratio to which you refer?

clockOffsetRatio = dwt_readcarrierintegrator() * (FREQ_OFFSET_MULTIPLIER * HERTZ_TO_PPM_MULTIPLIER_CHAN_2 / 1.0e6) ;

Also, I’m using the DS method now as well and I keep getting a receiver PHY header error on the responder side, if that’s a clue. Thanks for the help!

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.


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:

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