DS Two Way Ranging example code not communicating

We have a DS TWR application that we’re trying to get running on a Cortex M4 in a Nordic nRF82540 that talks to the DWM1000 via SPI. I’ve ported the DS code (initiator and responder) found in the examples of the API. My strategy is to use a general purpose timer to poll for a ranging packet with the role switching every timer interval. I have two devices running the same code but neither one of them ever sees the SYS_STATUS_RXFCG bit set. Is this a reasonable strategy and what’s the most expedient way to debug the handshake? Do I implement one unit in “sniffer” mode? I also have a Trek1000 dev kit if that helps. Please advise. Code below (timer ISR and init code).
Timer ISR:
static void twr_poll_timer_isr(void * p_context)
{

app_timer_stop(m_twr_timer_id);
//toggle initiator role each timer interval
static bool initiator;
if (initiator == true) {
initiator = false;
}
else {
initiator = true;
}
if (initiator)
{
static unsigned long status_reg = 0;
/* 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_START_TX_IMMEDIATE | DWT_RESPONSE_EXPECTED);

/* We assume that the transmission is achieved correctly, poll for reception of a frame or error/timeout. See NOTE 9 below. */
status_reg = dwt_read32bitreg(SYS_STATUS_ID);

/* Increment frame sequence number after transmission of the poll message (modulo 256). */
frame_seq_nb++;

if (status_reg & SYS_STATUS_RXFCG)
{
    uint32 frame_len;

    /* Clear good RX frame event and TX frame sent in the DW1000 status register. */
    dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG | SYS_STATUS_TXFRS);

    /* 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 "DS 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 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_setdelayedtrxtime(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_START_TX_DELAYED);

        /* If dwt_starttx() returns an error, abandon this ranging exchange and proceed to the next one. See NOTE 12 below. */
        if (ret == DWT_SUCCESS)
        {
            /* Poll DW1000 until TX frame sent event set. See NOTE 9 below. */
            while (!(dwt_read32bitreg(SYS_STATUS_ID) & SYS_STATUS_TXFRS))
            { };

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

            /* Increment frame sequence number after transmission of the final message (modulo 256). */
            frame_seq_nb++;
        }
      }
    }
    else //no frame received
    {
        /* 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. */
        dwt_rxreset();
    }
  } //initiator role
  else { //responder role
    /* Clear reception timeout to start next ranging process. */
    dwt_setrxtimeout(0);

    /* Activate reception immediately. */
    dwt_rxenable(DWT_START_RX_IMMEDIATE);

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

    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 "DS 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();

            /* 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_setdelayedtrxtime(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_START_TX_DELAYED | DWT_RESPONSE_EXPECTED);

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

            /* Poll for reception of expected "final" frame or error/timeout. See NOTE 8 below. */
            while (!((status_reg = dwt_read32bitreg(SYS_STATUS_ID)) & (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_TO | SYS_STATUS_ALL_RX_ERR)))
            { };

            /* Increment frame sequence number after transmission of the response message (modulo 256). */
            frame_seq_nb++;

            if (status_reg & SYS_STATUS_RXFCG)
            {
                /* Clear good RX frame event and TX frame sent in the DW1000 status register. */
                dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG | SYS_STATUS_TXFRS);

                /* 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 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;

                    /* Display computed distance on LCD. */
                    sprintf(dist_str, "DIST: %3.2f m", distance);
                    SEGGER_RTT_printf(0,"%s\r\n", dist_str);
                    //lcd_display_str(dist_str);
                }
            }
            else
            {
                /* 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. */
                dwt_rxreset();
            }
        }
    }
    else
    {
        /* 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. */
        dwt_rxreset();
    }
} 
app_timer_start(m_twr_timer_id, TWR_POLL_INTERVAL, NULL);

}

Init routine:
void twr_init(void) {

/* Default communication configuration. We use here EVK1000’s default mode (mode 3). /
static dwt_config_t config = {
2, /
Channel number. /
DWT_PRF_64M, /
Pulse repetition frequency. /
DWT_PLEN_1024, /
Preamble length. Used in TX only. /
DWT_PAC32, /
Preamble acquisition chunk size. Used in RX only. /
9, /
TX preamble code. Used in TX only. /
9, /
RX preamble code. Used in RX only. /
1, /
0 to use standard SFD, 1 to use non-standard SFD. /
DWT_BR_110K, /
Data rate. /
DWT_PHRMODE_STD, /
PHY header mode. /
(1025 + 64 - 32) /
SFD timeout (preamble length + 1 + SFD length - PAC size). Used in RX only. */
};

// The DW1000 needs a reset before the SPI interface behaves
nrf_gpio_pin_write(UWB_WAKEUP_PIN, 0);
nrf_delay_ms(2);
nrf_gpio_pin_write(UWB_WAKEUP_PIN, 1);
nrf_delay_ms(2);

dwt_softreset();
nrf_delay_ms(2);

//port_set_dw1000_slowrate();
int dwt_error = dwt_initialise(DWT_LOADUCODE);

if (dwt_error == 0) {
SEGGER_RTT_printf(0,“UWB module interface go! \r\n”);
}

/*test write and read register
//GPIO mode register
dwt_write32bitoffsetreg(GPIO_CTRL_ID, GPIO_MODE_OFFSET, 0x00100000);
nrf_delay_ms(1);
//GPIO direction register (1=input, 0=output)
dwt_write32bitoffsetreg(GPIO_CTRL_ID, GPIO_DIR_OFFSET, 0xF0F0F0F0); //F’s are for the mask bits
nrf_delay_ms(1);
//Set actual bits
//Note GPIO0 output value is set by bit 24 of this
//dwt_write32bitoffsetreg(GPIO_CTRL_ID, GPIO_DOUT_OFFSET, 0xFFFFFFFF); //set GOM’s to 1
//dwt_write32bitoffsetreg(EXT_SYNC_ID, EC_CTRL_OFFSET, 0x44);
dwt_write32bitoffsetreg(GPIO_CTRL_ID, 0x08, 0x0011F9FA);
nrf_delay_ms(1);
dwt_write32bitoffsetreg(GPIO_CTRL_ID, 0x0C, 0x0011F7F8);
nrf_delay_ms(1);
dwt_write32bitoffsetreg(GPIO_CTRL_ID, 0x24, 0x00000154);
nrf_delay_ms(1);
//read back
//unsigned long gpio_test = dwt_read32bitoffsetreg(GPIO_CTRL_ID, GPIO_DOUT_OFFSET);
//unsigned long gpio_test = dwt_read32bitoffsetreg(EXT_SYNC_ID, EC_CTRL_OFFSET);
//unsigned long gpio_test = dwt_read32bitoffsetreg(GPIO_CTRL_ID, GPIO_IRQE_OFFSET);
unsigned long reg32test1 = dwt_read32bitoffsetreg(GPIO_CTRL_ID, 0x08);
unsigned long reg32test2 = dwt_read32bitoffsetreg(GPIO_CTRL_ID, 0x0C);
unsigned long reg32test3 = dwt_read32bitoffsetreg(GPIO_CTRL_ID, 0x24);
// uint16_t expected = (reg32test1>>24) + (reg32test2<<8);
uint16_t reg16test = dwt_read16bitoffsetreg(GPIO_CTRL_ID, 0x23); //should return the last 16 bits of
// the system status register, bits 24-31 of offset zero in the least sig byte and bits
// 0-7 of offset 3 in the most sig byte with the bit ordering exactly the same as the register
// description in the DW1000 user manual.
uint8_t reg8test = dwt_read8bitoffsetreg(GPIO_CTRL_ID, 0x25);
dwt_write8bitoffsetreg(GPIO_CTRL_ID, 0x0D, 0xF5);
nrf_delay_ms(1);
reg8test = dwt_read8bitoffsetreg(GPIO_CTRL_ID, 0x0D);
*/

//port_set_dw1000_fastrate();

/* Configure DW1000. See NOTE 7 below. */
dwt_configure(&config);

/* Apply default antenna delay value. See NOTE 1 below. */
dwt_setrxantennadelay(RX_ANT_DLY);
dwt_settxantennadelay(TX_ANT_DLY);

/* 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. */
    dwt_setrxaftertxdelay(POLL_TX_TO_RESP_RX_DLY_UUS);
    dwt_setrxtimeout(RESP_RX_TIMEOUT_UUS);
    dwt_setpreambledetecttimeout(PRE_TIMEOUT);
    }

I think you may want to re-visit your strategy.

When in receiver mode you disable the Rx timeout and then sit in an infinite loop waiting for the either an Rx error or an Rx OK. Since this all taking place within an interrupt it’s going to block other interrupts including your next timer interrupt.

Also how do you ensure that your two devices are switching state at the same times? Or do you just assume that one will already be in Rx mode when the other enters Tx mode and initiates a range? This will be true for one direction so it should sort of work assuming things haven’t gotten caught in an infinite polling loop.

Is there any reason why you are polling the status register rather than using the DW1000 interrupt line to tell you when something has happened? If you do want to use a polling based system then any polling loops should be done in the background loop not in the interrupt handler.

Hi Andy, thanks for that, I had taken out the loop for the Tx but had neglected to do it for the Rx. So aside from the polling vs interrupt subject, I now am running the initiator and responder code just as in the examples, one device permanently an initiator and one a responder, but still no setting the RXCFG bit. For the sys_status register on the responder side I’m getting 0x8B2003FE and on the initiator side getting 0x780201FE. It’s unclear the complete meaning and ramifications of the bits in that register, but it appears the receive side is getting a preamble timeout and the transmit side a receive frame wait timeout among other things. How do I get to the bottom of this? Code below.

Handshake loop:
static void twr_poll_timer_isr(void * p_context)
{

app_timer_stop(m_twr_timer_id);
//toggle initiator role each timer interval
#if defined(INITIATOR_ONLY)
static bool initiator = true;
#endif
#if defined(RESPONDER_ONLY)
static bool initiator = false;
#endif
#if defined(BOTH_ROLES)
static bool initiator;
if (initiator == true) {
initiator = false;
}
else {
initiator = true;
}
#endif
#if defined(INITIATOR_ONLY)
while(1)
#endif
#if defined(RESPONDER_ONLY)
if (0)
#endif
#if defined(BOTH_ROLES)
if (initiator)
#endif
{
static unsigned long status_reg = 0;
/* 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_START_TX_IMMEDIATE | DWT_RESPONSE_EXPECTED);

/* We assume that the transmission is achieved correctly, poll for reception of a frame or error/timeout. See NOTE 9 below. */

#if defined(INITIATOR_ONLY)
while (!((status_reg = dwt_read32bitreg(SYS_STATUS_ID)) & (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_TO | SYS_STATUS_ALL_RX_ERR)))
{ };
#endif
#if defined(BOTH_ROLES)
status_reg = dwt_read32bitreg(SYS_STATUS_ID);
#endif

/* Increment frame sequence number after transmission of the poll message (modulo 256). */
frame_seq_nb++;

if (status_reg & SYS_STATUS_RXFCG)
{
    uint32 frame_len;

    /* Clear good RX frame event and TX frame sent in the DW1000 status register. */
    dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG | SYS_STATUS_TXFRS);

    /* 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 "DS 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 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_setdelayedtrxtime(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_START_TX_DELAYED);

        /* If dwt_starttx() returns an error, abandon this ranging exchange and proceed to the next one. See NOTE 12 below. */
        if (ret == DWT_SUCCESS)
        {
            /* Poll DW1000 until TX frame sent event set. See NOTE 9 below. */
            while (!(dwt_read32bitreg(SYS_STATUS_ID) & SYS_STATUS_TXFRS))
            { };

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

            /* Increment frame sequence number after transmission of the final message (modulo 256). */
            frame_seq_nb++;
        }
      }
    }
    else //no frame received
    {
        /* 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. */
        dwt_rxreset();
    }
  } //initiator role

#if defined(RESPONDER_ONLY)
while(1)
#endif
#if defined(INITIATOR_ONLY)
if (0)
#endif
#if defined(BOTH_ROLES)
else //responder role
#endif
{
/* Clear reception timeout to start next ranging process. */
dwt_setrxtimeout(0);

    /* Activate reception immediately. */
    dwt_rxenable(DWT_START_RX_IMMEDIATE);

    /* Poll for reception of a frame or error/timeout. See NOTE 8 below. */

#if defined(RESPONDER_ONLY)
while (!((status_reg = dwt_read32bitreg(SYS_STATUS_ID)) & (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_ERR)))
{
nrf_delay_ms(1);
}
#endif
#if defined(BOTH_ROLES)
status_reg = dwt_read32bitreg(SYS_STATUS_ID);
#endif

    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 "DS 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();

            /* 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_setdelayedtrxtime(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_START_TX_DELAYED | DWT_RESPONSE_EXPECTED);

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

            /* Poll for reception of expected "final" frame or error/timeout. See NOTE 8 below. */
            status_reg = dwt_read32bitreg(SYS_STATUS_ID);

            /* Increment frame sequence number after transmission of the response message (modulo 256). */
            frame_seq_nb++;

            if (status_reg & SYS_STATUS_RXFCG)
            {
                /* Clear good RX frame event and TX frame sent in the DW1000 status register. */
                dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG | SYS_STATUS_TXFRS);

                /* 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 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;

                    /* Display computed distance on LCD. */
                    sprintf(dist_str, "DIST: %3.2f m", distance);
                    SEGGER_RTT_printf(0,"%s\r\n", dist_str);
                    //lcd_display_str(dist_str);
                }
            }
            else
            {
                /* 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. */
                dwt_rxreset();
            }
        }
    }
    else
    {
        /* 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. */
        dwt_rxreset();
    }
} 

#if defined(BOTH_ROLES)
app_timer_start(m_twr_timer_id, TWR_POLL_INTERVAL, NULL);
#endif
}

Defines & helper functoins:
#define TWR_POLL_INTERVAL APP_TIMER_TICKS((rand() % 400) + 100)
/* 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};
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
/
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];

/* Hold copy of status register state here for reference so that it can be examined at a debug breakpoint. */
static uint32 status_reg = 0;

/* 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 65536

/* 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
    /
    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
    /
    Speed of light in air, in metres per second. */
    #define SPEED_OF_LIGHT 299702547

/* 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. /
    typedef unsigned long long uint64;
    typedef signed long long int64;
    /
    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};

static uint64 poll_tx_ts;
static uint64 resp_rx_ts;
static uint64 final_tx_ts;
static uint64 poll_rx_ts;
static uint64 resp_tx_ts;
static uint64 final_rx_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);

/*! ------------------------------------------------------------------------------------------------------------------

  • 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_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) ts;
    ts >>= 8;
    }
    }

/*! ------------------------------------------------------------------------------------------------------------------

  • 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);
    }
    }

Initializatoin:

void twr_init(void) {

/* Default communication configuration. We use here EVK1000’s default mode (mode 3). /
static dwt_config_t config = {
2, /
Channel number. /
DWT_PRF_64M, /
Pulse repetition frequency. /
DWT_PLEN_1024, /
Preamble length. Used in TX only. /
DWT_PAC32, /
Preamble acquisition chunk size. Used in RX only. /
9, /
TX preamble code. Used in TX only. /
9, /
RX preamble code. Used in RX only. /
1, /
0 to use standard SFD, 1 to use non-standard SFD. /
DWT_BR_110K, /
Data rate. /
DWT_PHRMODE_STD, /
PHY header mode. /
(1025 + 64 - 32) /
SFD timeout (preamble length + 1 + SFD length - PAC size). Used in RX only. */
};

// The DW1000 needs a reset before the SPI interface behaves
nrf_gpio_pin_write(UWB_WAKEUP_PIN, 0);
nrf_delay_ms(2);
nrf_gpio_pin_write(UWB_WAKEUP_PIN, 1);
nrf_delay_ms(2);

dwt_softreset();
nrf_delay_ms(2);

//port_set_dw1000_slowrate();
int dwt_error = dwt_initialise(DWT_LOADUCODE);

if (dwt_error == 0) {
SEGGER_RTT_printf(0,“UWB module interface go! \r\n”);
}

//port_set_dw1000_fastrate();

/* Configure DW1000. See NOTE 7 below. */
dwt_configure(&config);

/* Apply default antenna delay value. See NOTE 1 below. */
dwt_setrxantennadelay(RX_ANT_DLY);
dwt_settxantennadelay(TX_ANT_DLY);

/* 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. */
    dwt_setrxaftertxdelay(POLL_TX_TO_RESP_RX_DLY_UUS);
    dwt_setrxtimeout(RESP_RX_TIMEOUT_UUS);
    dwt_setpreambledetecttimeout(PRE_TIMEOUT);
    }

Receive timeout means that the receiver isn’t seeing a packet within the set time period. The simple solution to this for now is to disable receive timeouts in the system configuration register.

This feature is handy for interrupt driven or power critical systems since it gives a way to shut the receiver off and generate an interrupt if nothing is happening. But for getting things up and running to start with this is less critical, if needed you can generate timeouts in your polling code for now which gives you more direct control over when it gives up looking.

Hi Andy, it turns out I’m having some issues with the SPI interface which might be root cause. I’m corresponding with Nordic to get this resolved, but let’s keep this ticket open until I’m sure this actually is root cause. Thanks.

OK, I got the issues with SPI resolved as far as I know, but I’m still never seeing the RXFCG bit set in the status register for either the initiator or responder. Now the system status constantly reads 0x00800002. Shouldn’t the bits 4-7 change when the various packets are sent in response to the dwt_starttx command? Could you look at the code to see if I’m doing something wrong? The initiate_twr() routine is run in an infinite loop following initialization, which is in the second code block.

void initiate_twr()
{
    static unsigned long status_reg = 0;
    /* 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_START_TX_IMMEDIATE | DWT_RESPONSE_EXPECTED);

    /* We assume that the transmission is achieved correctly, poll for reception of a frame or error/timeout. See NOTE 9 below. */
    status_reg = dwt_read32bitreg(SYS_STATUS_ID);
    while (!(status_reg & (SYS_STATUS_RXFCG | SYS_STATUS_ALL_RX_TO | SYS_STATUS_ALL_RX_ERR)))
    {
      status_reg = dwt_read32bitreg(SYS_STATUS_ID);
    }

    /* Increment frame sequence number after transmission of the poll message (modulo 256). */
    frame_seq_nb++;

    if (status_reg & SYS_STATUS_RXFCG)
    {
        uint32 frame_len;

        /* Clear good RX frame event and TX frame sent in the DW1000 status register. */
        dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG | SYS_STATUS_TXFRS);

        /* 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 "DS 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 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_setdelayedtrxtime(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_START_TX_DELAYED);

            /* If dwt_starttx() returns an error, abandon this ranging exchange and proceed to the next one. See NOTE 12 below. */
            if (ret == DWT_SUCCESS)
            {
                /* Poll DW1000 until TX frame sent event set. See NOTE 9 below. */
                while (!(dwt_read32bitreg(SYS_STATUS_ID) & SYS_STATUS_TXFRS))
                { };

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

                /* Increment frame sequence number after transmission of the final message (modulo 256). */
                frame_seq_nb++;
            }
          }
        }
        else //no frame received
        {
            /* 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. */
            dwt_rxreset();
        }
      } //initiator role}

void twr_init(void) {

/* Default communication configuration. We use here EVK1000’s default mode (mode 3). /
static dwt_config_t config = {
2, /
Channel number. /
DWT_PRF_64M, /
Pulse repetition frequency. /
DWT_PLEN_1024, /
Preamble length. Used in TX only. /
DWT_PAC32, /
Preamble acquisition chunk size. Used in RX only. /
9, /
TX preamble code. Used in TX only. /
9, /
RX preamble code. Used in RX only. /
1, /
0 to use standard SFD, 1 to use non-standard SFD. /
DWT_BR_110K, /
Data rate. /
DWT_PHRMODE_STD, /
PHY header mode. /
(1025 + 64 - 32) /
SFD timeout (preamble length + 1 + SFD length - PAC size). Used in RX only. */
};

dwt_softreset();
nrf_delay_ms(2);

//port_set_dw1000_slowrate();
int dwt_error = dwt_initialise(DWT_LOADUCODE);

if (dwt_error == 0) {
SEGGER_RTT_printf(0,“UWB module interface go! \r\n”);
}

//port_set_dw1000_fastrate();

/* Configure DW1000. See NOTE 7 below. */
dwt_configure(&config);

/* Apply default antenna delay value. See NOTE 1 below. */
dwt_setrxantennadelay(RX_ANT_DLY);
dwt_settxantennadelay(TX_ANT_DLY);

/* 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. */
    dwt_setrxaftertxdelay(POLL_TX_TO_RESP_RX_DLY_UUS);
    dwt_setrxtimeout(RESP_RX_TIMEOUT_UUS);
    dwt_setpreambledetecttimeout(PRE_TIMEOUT);
    }

Another note on the SPI read. I’ve done some testing on the read/write of various registers and have focused on the GPIO ones as they are RW and have a sub-address. However, when I write 0x0011FBFC to the GPIO direction register (0x26:0x08) and read it back I get 0xdead01BC when the expected result would be 0x00010B0C since the mask bits always read back as zero. But when I write 0x0011F7F8 to the GPIO output register (0x26, 0x0C) and then read it back I get 0xde010708, which is almost what’s expected. Please let me know if this is correct.

When running two devices, one as initiator and one as responder, the system status register is as follows: initiator 0x00800002, responder 0x00800002.

Trying the SS example code as well, same status reg reading for the initiator, responder status is 0x00802B02.

Hello? Anybody? The code I’m running is exactly as it is from the examples.

So I’ve tried setting up GPIO5 and 6 with their secondary function to see if the receiver is even turning on and I get no activity on these lines at all, they just stay high. So I wanted to make sure that my configuration code is working and so I tested to see if I could toggle any of the GPIO pins. What I’ve discovered is that even though I can write and read back the GPIO registers, I cannot change the state of any of the GPIO pins. Here is the code that I’m using to toggle the GPIO bits:

dwt_write32bitoffsetreg(GPIO_CTRL_ID, GPIO_MODE_OFFSET, 0x00100000);
  dwt_write32bitoffsetreg(GPIO_CTRL_ID, GPIO_DIR_OFFSET, 0x0080F0F0);
  dwt_write32bitoffsetreg(GPIO_CTRL_ID, GPIO_DOUT_OFFSET, 0x0080F0F0);
  temp1 = dwt_read32bitoffsetreg(GPIO_CTRL_ID, GPIO_DOUT_OFFSET);
  dwt_write32bitoffsetreg(GPIO_CTRL_ID, GPIO_DOUT_OFFSET, 0x000000FF);
  temp2 = dwt_read32bitoffsetreg(GPIO_CTRL_ID, GPIO_DOUT_OFFSET);

temp1 is 0xDE00000 and temp2 is 0xDE00000F but the GPIO pins don’t change state. When I pull them up with an external 5K resistor, they go to VCC but in this case when I try to pull the pin low, the pin seemingly can only sink 20uA instead of the 4mA specified in the datasheet. I’m not going to get very far with debugging if I can’t even figure out why I can’t toggle a GPIO bit. It’s not quite clear in the datasheet if these GPIO pins are push-pull or open collector or what. Please advise.

I can now successfully toggle the GPIO bits - the method used by the Nordic SPI SDK to determine the end of the SPI transaction doesn’t work for the DW1000, a delay needed to be set to allow the completion. Now the GPIO bits work as expected as GPIO’s. I also implemented the SPI interface test as described in the debugging guide and that works now as well.
Moving on, I have implemented the simple TX test code and judging from polling the SYS_STATUS register it seems the frame is indeed being sent but the simple receiver only detects the preamble and times out on the SFD, even when the SFD timeout is disabled.

I can now receive frames using the simple example, but I cannot get the LED functions of GPIO0-3 to work. Code:

 //apdobaj_twr set GPIO pins to show TX and RX states per Debugging TWR1000 document
  dwt_setlnapamode(0x11); //GPIO5 (goes high when transmitting) and 6 (goes high when receiver is active)
  // set GPIO0-3 to secondary monitoring functions
  dwt_write32bitoffsetreg(GPIO_CTRL_ID, GPIO_MODE_OFFSET, 0x00101540);
  dwt_write32bitoffsetreg(GPIO_CTRL_ID, GPIO_DIR_OFFSET, 0x0080F0F0);
  //set the blink time for the LED's
  temp3 = (((uint32_t)PMSC_LEDC_BLINK_TIM_MASK & (uint32_t)LED_DURATION_14MS) | (uint32_t)PMSC_LEDC_BLNKEN);
  dwt_write32bitoffsetreg(PMSC_ID, PMSC_LEDC_OFFSET, temp3);
  // configure LED blink counter timer
  temp1 = dwt_read32bitoffsetreg(PMSC_ID, PMSC_CTRL1_OFFSET);
  temp2 = ((~((uint32_t)PMSC_CTRL1_KHZCLKDIV_MASK)) & temp1) | (uint32_t)KHZCLKDIV_320KHZ;
  dwt_write32bitoffsetreg(PMSC_ID, PMSC_CTRL1_OFFSET, temp2);
  // enable the KHZ clock
  temp1 = dwt_read32bitoffsetreg(PMSC_ID, PMSC_CTRL0_OFFSET);
  temp2 = (uint32_t)PMSC_CTRL0_KHZCLEN | temp1;
  dwt_write32bitoffsetreg(PMSC_ID, PMSC_CTRL0_OFFSET, temp2);
  // test by blinking now
  temp1 = temp3 | 0x000F0000;
  dwt_write32bitoffsetreg(PMSC_ID, PMSC_LEDC_OFFSET, temp1);

The LED’s never come on. What am I missing?

Good grief, this forum is totally useless. Does anybody from qorvo actually look?

They do occasionally. But most of the support ends up being from the “community”.

I would help but right now I don’t have the time to try to debug code using an API I don’t use on hardware I don’t have. :frowning:

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. */
    dwt_setrxaftertxdelay(POLL_TX_TO_RESP_RX_DLY_UUS);
    //dwt_setrxtimeout(RESP_RX_TIMEOUT_UUS);
    dwt_setrxtimeout(0);

    /* 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. */
        dwt_starttx(DWT_START_TX_IMMEDIATE | DWT_RESPONSE_EXPECTED);
        //dwt_starttx(DWT_START_TX_IMMEDIATE);

        /* 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. 
        nrf_delay_ms(1000);

        // Increment the blink frame sequence number (modulo 256). 
        tx_msg[BLINK_FRAME_SN_IDX]++;
        //end simple tx test */

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

        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_delay_ms(750);
                nrf_gpio_pin_write(LED_PIN, 1);
            }
        }
        else
        {
            // 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. 
            dwt_rxreset();
        }

        // Execute a delay between ranging exchanges. 
        //Sleep(RNG_DELAY_MS);
        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. */
        dwt_rxenable(DWT_START_RX_IMMEDIATE);

        /* 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);
        }
        else
        {
            // 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;
                dwt_setdelayedtrxtime(resp_tx_time);

                // 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_delay_ms(750);
                    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). 
                    frame_seq_nb++;
                }
            }
        }
        else
        {
            // 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. 
            dwt_rxreset();
        }
    }// 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!