DW1000 Single-Sided Two-Way Ranging Frame Reception Issues

Hello! I am still fairly new to UWB communication and have been trying to change a double-sided two-way ranging system into a single-sided system. I am running into some issues accounting properly for delays and complete reception of each frame. The defined timeouts and delays in the following snippets have not been changed from the double-sided system.

This code snippet is common to both my transmitter and responder:

// Here is the data included in the poll message and the response message:
static uint8 rx_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, 0, 0, 0, 0, 0, 0};

// This is where the timestamps begin in the response message:
#define RESP_MSG_POLL_RX_TS_IDX 13
#define RESP_MSG_RESP_TX_TS_IDX 17

// This is how we account for conversion between microseconds and device time units
// 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

// This is how the speed of light is defined
#define SPEED_OF_LIGHT 299702547

This code snippet is on the transmitter side as I prepare and send the poll message:

/* Delay between frames, in UWB microseconds./
/
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 100

/* 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 */
#define RESP_RX_TIMEOUT_UUS 2700

/* Preamble timeout, in multiple of PAC size */
#define PRE_TIMEOUT 0 (this is 0 so the preamble timeout isn’t enabled)

/* Write frame data to DW1000 and prepare transmission. /
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. /
ret1 = dwt_starttx(DWT_START_TX_IMMEDIATE | DWT_RESPONSE_EXPECTED);
/
If dwt_starttx() returns an error, abandon this ranging exchange and proceed to the next one. See NOTE 12 below. /
if (ret1 == 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 poll message (modulo 256). /
frame_seq_nb++;
GreLED_on();
osDelay(50);
GreLED_off();
}
/
Increment frame sequence number after transmission of the poll message (modulo 256). */
frame_seq_nb++;
ret2 = dwt_rxenable(DWT_START_RX_IMMEDIATE);

This code snippet is on the receiver side as I intercept the poll message, check that it is correct, and prepare and send the response message:

/* Delay between frames, in UWB microseconds. /
/
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. */
#define FINAL_RX_TIMEOUT_UUS 3300

/* Preamble timeout, in multiple of PAC size.*/
#define PRE_TIMEOUT 0

// The response message contains the poll rx timestamp (recorded at the detection of the preamble code) // and the response transmission timestamp based off of the following calculation:
resp_tx_time = (poll_rx_ts + (POLL_RX_TO_RESP_TX_DLY_UUS * UUS_TO_DWT_TIME)) >> 8;
resp_tx_ts = resp_tx_time + poll_rx_ts;

// This is how I am preparing to respond and how I am sending the response message:
// First, I 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 = 0;;
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);
// Compute resp_tx_ts
resp_tx_ts = resp_tx_time + poll_rx_ts;
// Set timestamp in resp msg
resp_msg_set_ts(&rx_buffer[RESP_MSG_POLL_RX_TS_IDX], poll_rx_ts);
resp_msg_set_ts(&rx_buffer[RESP_MSG_RESP_TX_TS_IDX], resp_tx_ts);
/
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);
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 poll message (modulo 256). /
frame_seq_nb++;
GreLED_on();
osDelay(50);
GreLED_off();
}
/
If dwt_starttx() returns an error, abandon this ranging exchange and proceed to the next one. */
if (ret == DWT_ERROR)
{
RedLED_on();
osDelay(50);
RedLED_off();
}
}

Now, this code snippet is back on the transmitter side as I receive the response message, check that it is correct, and compute the final ToF. The end behavior I’d like is the initiator to turn on the green LED and it to be in a wait state until I reset the module:

/* 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 poll_rx_ts = 0;
uint32 resp_tx_ts = 0;
uint32 poll_rx_ts_32 = 0;
uint32 resp_tx_ts_32 = 0;
double t_reply = 0;
int64 tof_dtu = 0;
double tof = 0;
double distance = 0;
// 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);
/
Retrieve poll transmission and response reception timestamp. */
poll_tx_ts = get_tx_timestamp_u64();
resp_rx_ts = get_rx_timestamp_u64();
// Perform 32 bit subtractions to compute correct timestamps if clock has wrapped
poll_rx_ts_32 = (uint32)poll_rx_ts;
resp_tx_ts_32 = (uint32)resp_tx_ts;
// Compute t_reply by subtracting resp_tx_ts from poll_rx_ts
t_reply = (double)(resp_tx_ts - poll_rx_ts);
// Compute ToF in device time units
tof_dtu = (uint64)((resp_rx_ts - poll_tx_ts - t_reply)/2);
// Convert ToF into ordinary time
tof = tof_dtu * DWT_TIME_UNITS;
// Multiply ToF by speed of light to compute the distance
distance = tof * SPEED_OF_LIGHT;
// Turn on green led and turn off red LED to indicate that distance has been computed and ranging is done
GreLED_on();
while (1)
{
osDelay(100);
}
}

I am getting the transmitter to properly send the poll message, the receiver will intercept that message, and properly send the response message back to the transmitter. However, I have yet to detect the response message on the transmitter side. I have double-checked that the response message is being fully sent and that the transmitter goes into receive mode after the coded delay of 100 microseconds before listening for the response message.

I have determined that I must be running into a timing issue and that is why my ranging exchange seems to be failing. I’d like to mathematically determine how much delay I need and what the timestamp values should be given the length of the message being sent. How may I do this and ensure that I am doing the right things to achieve single-sided two-way ranging?

Thank you so much for any suggestions :slight_smile:

P.S. My responder doesn’t always succeed in the transmission of the response message. I think this may be because my POLL_RX_TO_RESP_TX_DLY_UUS may not be long enough. Any insight on this issue? Thanks again!

Please use the preformatted text option when posting code. Without it the indentation gets changed and some characters get incorrectly interpreted as being formatting codes and aren’t displayed. The end result is something that’s painful to read.

Have you tried increasing all the delays? Double the Rx to Tx delay and tripple all the timeouts? Not ideal in terms of throughput or power efficiency but if it is a timing issue it will get things working. Once it’s working you can read the times and work out how much you can safely reduce things by. e.g. After scheduling a transmit read the current time from the device, compare the current time to the scheduled time and see how much margin you had, that tells you how much faster you can run things.

Also some of your defined delays are different in the two bits of code. Single sided only works if the initiator knows how much delay the responder is adding. If that number is defined differently in the two the range is going to be off by a huge amount.
If possible I would recommend putting all the time constants used in a single header file and then include the same header file in both versions of the code, that way it’s impossible (or at least very hard) for them to be different in the two versions.

Gotcha, thank you for the response. I will try increasing the delays and making sure I account for any changes in both sides of the communication. My bad about the text format, I will be better in the future about that.

Would it be possible to send a burst of poll messages from the transmitter to the receiver, and then wait for a response after the burst is sent out? I only ask since my receiver is working in its current state and if the transmitter is set to never timeout, that might be an easier way to confirm that the transmitter will for sure be in receive mode when the receiver has sent the response message. Just a thought I had!

The system only works if you know the time that the initiator sends the message so that you can subtract it from the time you receive the reply. If you send multiple messages then how do you know which one the reply is for?

Personally I set the transmitter into receive mode as soon as transmission is complete and don’t set a timeout for reception on either side. Obviously you need to handle never getting a reply in some way but my system is all interrupt based. Ranges get started at a fixed rate based on a timer whether the previous one has finished or not, so in effect that acts a my timeout on reception. Not power efficient but that wasn’t an issue for me.
If power does matter I’d still set it something like that at first, once you have it working then you can dial in the timeouts and delays before arming receive based on the timings you are actually seeing.