Seeking Guidance on ESP32 DW3000 Antenna Delay Calibration Process (Autocalibration)

Hello everyone,

I have just received the ESP32-WROOM UWB DW3000 device from Makerfabs, and I am currently attempting to perform auto-calibration for this device. Previously, I have done calibration by roughly estimating the values of “TX_ANT_DLY” and “RX_ANT_DLY” that approximate their actual values, using a ruler/caliper. However, I realize that this approach may not be accurate and efficient.

I need an understanding of a more precise and efficient auto-calibration process for the ESP32 DW3000 device, similar to what exists for the DW1000. Does anyone have experience with this device or can you provide a step-by-step guide on how to do it correctly?

I appreciate any advice, insights, or responses you can provide. I truly need your assistance in comprehending and successfully executing this auto-calibration. Thank you in advance for your contributions and support!

Thank you very much,
Theophilus Ezra

Hi Theophilus,

This app note APS014 shows the procedure for antenna delay calibration. It is written for DW1000 but the steps will be the same for DW3000. There is a section on reference device generation. If you already have manually calibrated one device, you can use that as reference to calibrate others.

Best regards,
Matthias

Hi Matthias,

Thank you so much for your reply, i have seen at APS014 and how to calibrate it. However, is it possible to use the DW3000 library? Honestly, I am still confused about starting to make autocalibration like those used by DW1000.

Source:

Autocalibration using DW1000:
Getting Started with ESP32 UWB (Ultra Wideband) Module (how2electronics.com)

Library DW3000:
Makerfabs/Makerfabs-ESP32-UWB-DW3000 (github.com)

Hi Theophilus,

i’m not familiar with the makerfabs libraries; but if you can set the antenna delay and measure distance, the same calibration procedure should work for the DW3000 library.

Best regards,
Matthias

Hi Matthias,

Alright, thank you for the advice!

Hi Matthias,

I want to ask again, there is a condition where I try to test ranging with a single sided two way ranging program and there is inaccuracies or very besrous errors. The program is as follows:

The Program of Tag (initiator) Single Sided Two Way Ranging:

#include “dw3000.h”

#define APP_NAME “SS TWR INIT v1.0”

// connection pins
const uint8_t PIN_RST = 27; // reset pin
const uint8_t PIN_IRQ = 34; // irq pin
const uint8_t PIN_SS = 4; // spi select pin

/* Default communication configuration. We use default non-STS DW mode. /
static dwt_config_t config = {
5, /
Channel number. /
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. /
1, /
0 to use standard 8 symbol SFD, 1 to use non-standard 8 symbol, 2 for non-standard 16 symbol SFD and 3 for 4z 8 symbol SDF type /
DWT_BR_6M8, /
Data rate. /
DWT_PHRMODE_STD, /
PHY header mode. /
DWT_PHRRATE_STD, /
PHY header rate. /
(129 + 8 - 8), /
SFD timeout (preamble length + 1 + SFD length - PAC size). Used in RX only. /
DWT_STS_MODE_OFF, /
STS disabled /
DWT_STS_LEN_64,/
STS length see allowed values in Enum dwt_sts_lengths_e /
DWT_PDOA_M0 /
PDOA mode off */
};

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

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

/* Frames used in the ranging process. See NOTE 3 below. /
static uint8_t tx_poll_msg[] = {0x41, 0x88, 0, 0xCA, 0xDE, ‘W’, ‘A’, ‘V’, ‘E’, 0xE0, 0, 0};
static uint8_t rx_resp_msg[] = {0x41, 0x88, 0, 0xCA, 0xDE, ‘V’, ‘E’, ‘W’, ‘A’, 0xE1, 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 3 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 RESP_MSG_POLL_RX_TS_IDX 10
#define RESP_MSG_RESP_TX_TS_IDX 14
#define RESP_MSG_TS_LEN 4
/
Frame sequence number, incremented after each transmission. */
static uint8_t 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_t 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_t status_reg = 0;

/* Delay between frames, in UWB microseconds. See NOTE 1 below. /
#ifdef RPI_BUILD
#define POLL_TX_TO_RESP_RX_DLY_UUS 240
#endif //RPI_BUILD
#ifdef STM32F429xx
#define POLL_TX_TO_RESP_RX_DLY_UUS 240
#endif //STM32F429xx
#ifdef NRF52840_XXAA
#define POLL_TX_TO_RESP_RX_DLY_UUS 240
#endif //NRF52840_XXAA
/
Receive response timeout. See NOTE 5 below. */
#ifdef RPI_BUILD
#define RESP_RX_TIMEOUT_UUS 270
#endif //RPI_BUILD
#ifdef STM32F429xx
#define RESP_RX_TIMEOUT_UUS 210
#endif //STM32F429xx
#ifdef NRF52840_XXAA
#define RESP_RX_TIMEOUT_UUS 400
#endif //NRF52840_XXAA

#define POLL_TX_TO_RESP_RX_DLY_UUS 240
#define RESP_RX_TIMEOUT_UUS 400

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

/* Values for the PG_DELAY and TX_POWER registers reflect the bandwidth and power of the spectrum at the current

  • temperature. These values can be calibrated prior to taking reference measurements. See NOTE 2 below. */
    extern dwt_txconfig_t txconfig_options;

void setup() {
UART_init();
test_run_info((unsigned char *)APP_NAME);

/* Configure SPI rate, DW3000 supports up to 38 MHz /
/
Reset DW IC */
spiBegin(PIN_IRQ, PIN_RST);
spiSelect(PIN_SS);

delay(2); // Time needed for DW3000 to start up (transition from INIT_RC to IDLE_RC, or could wait for SPIRDY event)

while (!dwt_checkidlerc()) // Need to make sure DW IC is in IDLE_RC before proceeding
{
UART_puts(“IDLE FAILED\r\n”);
while (1) ;
}

if (dwt_initialise(DWT_DW_INIT) == DWT_ERROR)
{
UART_puts(“INIT FAILED\r\n”);
while (1) ;
}

// Enabling LEDs here for debug so that for each TX the D1 LED will flash on DW3000 red eval-shield boards.
dwt_setleds(DWT_LEDS_ENABLE | DWT_LEDS_INIT_BLINK);

/* Configure DW IC. See NOTE 6 below. */
if(dwt_configure(&config)) // if the dwt_configure returns DWT_ERROR either the PLL or RX calibration has failed the host should reset the device
{
UART_puts(“CONFIG FAILED\r\n”);
while (1) ;
}

/* Configure the TX spectrum parameters (power, PG delay and PG count) */
dwt_configuretxrf(&txconfig_options);

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

/* Set expected response's delay and timeout. See NOTE 1 and 5 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);

/* Next can enable TX/RX states output on GPIOs 5 and 6 to help debug, and also TX/RX LEDs
 * Note, in real low power applications the LEDs should not be used. */
dwt_setlnapamode(DWT_LNA_ENABLE | DWT_PA_ENABLE);

}

void loop() {
/* Write frame data to DW IC 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_BIT_MASK);
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 8 below. */
    while (!((status_reg = dwt_read32bitreg(SYS_STATUS_ID)) & (SYS_STATUS_RXFCG_BIT_MASK | SYS_STATUS_ALL_RX_TO | SYS_STATUS_ALL_RX_ERR)))
    { };

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

    if (status_reg & SYS_STATUS_RXFCG_BIT_MASK)
    {
        uint32_t frame_len;

        /* Clear good RX frame event in the DW IC status register. */
        dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG_BIT_MASK);

        /* A frame has been received, read it into the local buffer. */
        frame_len = dwt_read32bitreg(RX_FINFO_ID) & RXFLEN_MASK;
        if (frame_len <= sizeof(rx_buffer))
        {
            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_t poll_tx_ts, resp_rx_ts, poll_rx_ts, resp_tx_ts;
                int32_t 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 = ((float)dwt_readclockoffset()) / (uint32_t)(1<<26);

                /* 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 LCD. */
                snprintf(dist_str, sizeof(dist_str), "DIST: %3.2f m", distance);
                test_run_info((unsigned char *)dist_str);
            }
        }
    }
    else
    {
        /* Clear RX error/timeout events in the DW IC status register. */
        dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_ALL_RX_TO | SYS_STATUS_ALL_RX_ERR);
    }

    /* Execute a delay between ranging exchanges. */
    Sleep(RNG_DELAY_MS);

}

The Program of Anchor (Responden) Single Sided Two Way Ranging:

#include “dw3000.h”

#define APP_NAME “SS TWR RESP v1.0”

// connection pins
const uint8_t PIN_RST = 27; // reset pin
const uint8_t PIN_IRQ = 34; // irq pin
const uint8_t PIN_SS = 4; // spi select pin

/* Default communication configuration. We use default non-STS DW mode. /
static dwt_config_t config = {
5, /
Channel number. /
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. /
1, /
0 to use standard 8 symbol SFD, 1 to use non-standard 8 symbol, 2 for non-standard 16 symbol SFD and 3 for 4z 8 symbol SDF type /
DWT_BR_6M8, /
Data rate. /
DWT_PHRMODE_STD, /
PHY header mode. /
DWT_PHRRATE_STD, /
PHY header rate. /
(129 + 8 - 8), /
SFD timeout (preamble length + 1 + SFD length - PAC size). Used in RX only. /
DWT_STS_MODE_OFF, /
STS disabled /
DWT_STS_LEN_64,/
STS length see allowed values in Enum dwt_sts_lengths_e /
DWT_PDOA_M0 /
PDOA mode off */
};

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

/* Frames used in the ranging process. See NOTE 3 below. /
static uint8_t rx_poll_msg[] = {0x41, 0x88, 0, 0xCA, 0xDE, ‘W’, ‘A’, ‘V’, ‘E’, 0xE0, 0, 0};
static uint8_t tx_resp_msg[] = {0x41, 0x88, 0, 0xCA, 0xDE, ‘V’, ‘E’, ‘W’, ‘A’, 0xE1, 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 3 below). /
#define ALL_MSG_COMMON_LEN 10
/
Index to access some of the fields in the frames involved in the process. /
#define ALL_MSG_SN_IDX 2
#define RESP_MSG_POLL_RX_TS_IDX 10
#define RESP_MSG_RESP_TX_TS_IDX 14
#define RESP_MSG_TS_LEN 4
/
Frame sequence number, incremented after each transmission. */
static uint8_t frame_seq_nb = 0;

/* Buffer to store received messages.

  • Its size is adjusted to longest frame that this example code is supposed to handle. */
    #define RX_BUF_LEN 12//Must be less than FRAME_LEN_MAX_EX
    static uint8_t 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_t status_reg = 0;

/* Delay between frames, in UWB microseconds. See NOTE 1 below. */
#ifdef RPI_BUILD
#define POLL_RX_TO_RESP_TX_DLY_UUS 550
#endif //RPI_BUILD
#ifdef STM32F429xx
#define POLL_RX_TO_RESP_TX_DLY_UUS 450
#endif //STM32F429xx
#ifdef NRF52840_XXAA
#define POLL_RX_TO_RESP_TX_DLY_UUS 650
#endif //NRF52840_XXAA

#define POLL_RX_TO_RESP_TX_DLY_UUS 450

/* Timestamps of frames transmission/reception. */
static uint64_t poll_rx_ts;
static uint64_t resp_tx_ts;

/* Values for the PG_DELAY and TX_POWER registers reflect the bandwidth and power of the spectrum at the current

  • temperature. These values can be calibrated prior to taking reference measurements. See NOTE 5 below. */
    extern dwt_txconfig_t txconfig_options;

void setup() {
UART_init();
test_run_info((unsigned char *)APP_NAME);

/* Configure SPI rate, DW3000 supports up to 38 MHz /
/
Reset DW IC */
spiBegin(PIN_IRQ, PIN_RST);
spiSelect(PIN_SS);

delay(2); // Time needed for DW3000 to start up (transition from INIT_RC to IDLE_RC, or could wait for SPIRDY event)

while (!dwt_checkidlerc()) // Need to make sure DW IC is in IDLE_RC before proceeding
{
UART_puts(“IDLE FAILED\r\n”);
while (1) ;
}

if (dwt_initialise(DWT_DW_INIT) == DWT_ERROR)
{
UART_puts(“INIT FAILED\r\n”);
while (1) ;
}

// Enabling LEDs here for debug so that for each TX the D1 LED will flash on DW3000 red eval-shield boards.
dwt_setleds(DWT_LEDS_ENABLE | DWT_LEDS_INIT_BLINK);

/* Configure DW IC. See NOTE 6 below. */
if(dwt_configure(&config)) // if the dwt_configure returns DWT_ERROR either the PLL or RX calibration has failed the host should reset the device
{
UART_puts(“CONFIG FAILED\r\n”);
while (1) ;
}

/* Configure the TX spectrum parameters (power, PG delay and PG count) */
dwt_configuretxrf(&txconfig_options);

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

/* Next can enable TX/RX states output on GPIOs 5 and 6 to help debug, and also TX/RX LEDs
 * Note, in real low power applications the LEDs should not be used. */
dwt_setlnapamode(DWT_LNA_ENABLE | DWT_PA_ENABLE); 

}

void loop() {
/* Activate reception immediately. */
dwt_rxenable(DWT_START_RX_IMMEDIATE);

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

    if (status_reg & SYS_STATUS_RXFCG_BIT_MASK)
    {
        uint32_t frame_len;

        /* Clear good RX frame event in the DW IC status register. */
        dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG_BIT_MASK);

        /* A frame has been received, read it into the local buffer. */
        frame_len = dwt_read32bitreg(RX_FINFO_ID) & RXFLEN_MASK;
        if (frame_len <= sizeof(rx_buffer))
        {
            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_t resp_tx_time;
                int ret;

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

                /* Compute response 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_t)(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 DW IC until TX frame sent event set. See NOTE 6 below. */
                    while (!(dwt_read32bitreg(SYS_STATUS_ID) & SYS_STATUS_TXFRS_BIT_MASK))
                    { };

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

                    /* Increment frame sequence number after transmission of the poll message (modulo 256). */
                    frame_seq_nb++;
                }
            }
        }
    }
    else
    {
        /* Clear RX error events in the DW IC status register. */
        dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_ALL_RX_ERR);
    }

}

Explanation:
I am using modified programs for my experiments. When I run the program, there are moments when the antenna is aligned correctly, it produces fairly accurate distances, but this accuracy only lasts for a short while. After that, the distances become random again. For example, I placed the anchor and tag at a distance of 32.5 cm. Then, when I align the antennas parallel to each other, the distance shows a result of 32 cm, which means it is quite accurate. However, this value does not stay consistent and becomes random, even going up to 50 cm or down to 14 cm, without me changing their positions at all. The same thing happens when I change the orientation of the devices from being side by side to facing each other.

Is this condition normal? My goal is to maximize accurate results in ranging because I will need it for trilateration in creating a highly accurate Real-Time Indoor Location System.

Actually, I am still confused about the calibration process because there are times when the distances produced by the DW3000 device are accurate, and other times they are not.

Thank you for your assistance. It means a lot to me and the progress of this project.

Sincerely,
Theophilus Ezra

Hi Matthias,

Actually, I’m confused about how to calculate the antenna delay and Poll_TX_RX delay. Is there a specific formula or guide that I can learn about regarding this (Except the APS014)?

Sincerely,
Theophilus Ezra

Hey,

could you discripe your problem with the calibartion process a little bit more. I trying to implemt the simple algorithmen as well. I posted some queastions as well maybe you will finde some answers here:

DWM3000 ASP014 Calibartion

At the end I posted a link to a github repo where I implemended the algo from the example. Maybe you can use it for your own problem.

I can tell you my experience with the DWM3001cdk and DWM1000 dev board. Here it is normal the devices need a little bit with the exmple algorithmen to get “warm”. What I like to say is, that after a while the (1-2 minutes or a few 100 measurements) the accuracy get better and better and stablelise, not doing the other way around.

Probibly there is a mistake with your delays (waiting for response time or delayed sending time) or you facing more and more side effects like multipath. I think you should have a look at the diagnostics of your data. You can a finde a example Code provided by Qorovo ex_02c_rx_diagnostics (https://www.qorvo.com/products/p/DWM3001CDK#documents and than under software) or a discrption in the DW3000 API Guide.

Hi Schven04,

I agree with you regarding the delay and incorrect timing settings. After conducting research and manual testing (meaning trial and error related to antenna delay values), the accuracy improved slightly, but it didn’t last long. I experimented by placing obstacles like metal objects or gadgets between the two devices (anchor and tag) that I was ranging, and the error results became significantly large. Here is the program:

Anchor’ Program:

#include “dw3000.h”

#define APP_NAME “SS TWR RESP v1.0”

// connection pins
const uint8_t PIN_RST = 27; // reset pin
const uint8_t PIN_IRQ = 34; // irq pin
const uint8_t PIN_SS = 4; // spi select pin

/* Default communication configuration. We use default non-STS DW mode. /
static dwt_config_t config = {
5, /
Channel number. /
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. /
1, /
0 to use standard 8 symbol SFD, 1 to use non-standard 8 symbol, 2 for non-standard 16 symbol SFD and 3 for 4z 8 symbol SDF type /
DWT_BR_6M8, /
Data rate. /
DWT_PHRMODE_STD, /
PHY header mode. /
DWT_PHRRATE_STD, /
PHY header rate. /
(128 + 1 + 8 - 8), /
SFD timeout (preamble length + 1 + SFD length - PAC size). Used in RX only. /
DWT_STS_MODE_OFF, /
STS disabled /
DWT_STS_LEN_128,/
STS length see allowed values in Enum dwt_sts_lengths_e /
DWT_PDOA_M0 /
PDOA mode off */
};

/* Default antenna delay values for 64 MHz PRF. See NOTE 2 below. */
#define TX_ANT_DLY 16387
#define RX_ANT_DLY 16387 //16385

/* Frames used in the ranging process. See NOTE 3 below. */
static uint8_t rx_poll_msg = {0x41, 0x88, 0, 0xCA, 0xDE, ‘W’, ‘A’, ‘V’, ‘E’, 0xE0, 0, 0};
static uint8_t tx_resp_msg = {0x41, 0x88, 0, 0xCA, 0xDE, ‘V’, ‘E’, ‘W’, ‘A’, 0xE1, 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 3 below). */
#define ALL_MSG_COMMON_LEN 10

/* Index to access some of the fields in the frames involved in the process. */
#define ALL_MSG_SN_IDX 2
#define RESP_MSG_POLL_RX_TS_IDX 10
#define RESP_MSG_RESP_TX_TS_IDX 14
#define RESP_MSG_TS_LEN 4

/* Frame sequence number, incremented after each transmission. */
static uint8_t frame_seq_nb = 0;

/* Buffer to store received messages.
Its size is adjusted to longest frame that this example code is supposed to handle. */
#define RX_BUF_LEN 12//Must be less than FRAME_LEN_MAX_EX
static uint8_t 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_t status_reg = 0;

/* Delay between frames, in UWB microseconds. See NOTE 1 below. */
#define POLL_RX_TO_RESP_TX_DLY_UUS 2000

/* Timestamps of frames transmission/reception. */
static uint64_t poll_rx_ts;
static uint64_t resp_tx_ts;

/* Values for the PG_DELAY and TX_POWER registers reflect the bandwidth and power of the spectrum at the current
temperature. These values can be calibrated prior to taking reference measurements. See NOTE 5 below. */
extern dwt_txconfig_t txconfig_options;

void setup() {
UART_init();
test_run_info((unsigned char *)APP_NAME);

/* Configure SPI rate, DW3000 supports up to 38 MHz /
/
Reset DW IC */
spiBegin(PIN_IRQ, PIN_RST);
spiSelect(PIN_SS);

delay(2); // Time needed for DW3000 to start up (transition from INIT_RC to IDLE_RC, or could wait for SPIRDY event)

while (!dwt_checkidlerc()) // Need to make sure DW IC is in IDLE_RC before proceeding
{
UART_puts(“IDLE FAILED\r\n”);
while (1) ;
}

if (dwt_initialise(DWT_DW_INIT) == DWT_ERROR)
{
UART_puts(“INIT FAILED\r\n”);
while (1) ;
}

// Enabling LEDs here for debug so that for each TX the D1 LED will flash on DW3000 red eval-shield boards.
dwt_setleds(DWT_LEDS_ENABLE | DWT_LEDS_INIT_BLINK);

/* Configure DW IC. See NOTE 6 below. */
if (dwt_configure(&config)) // if the dwt_configure returns DWT_ERROR either the PLL or RX calibration has failed the host should reset the device
{
UART_puts(“CONFIG FAILED\r\n”);
while (1) ;
}

/* Configure the TX spectrum parameters (power, PG delay and PG count) */
dwt_configuretxrf(&txconfig_options);

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

/* Next can enable TX/RX states output on GPIOs 5 and 6 to help debug, and also TX/RX LEDs
Note, in real low power applications the LEDs should not be used. */
dwt_setlnapamode(DWT_LNA_ENABLE | DWT_PA_ENABLE);
}

void loop() {
/* Activate reception immediately. */
dwt_rxenable(DWT_START_RX_IMMEDIATE);

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

if (status_reg & SYS_STATUS_RXFCG_BIT_MASK)
{
uint32_t frame_len;

/* Clear good RX frame event in the DW IC status register. */
dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG_BIT_MASK);

/* A frame has been received, read it into the local buffer. */
frame_len = dwt_read32bitreg(RX_FINFO_ID) & RXFLEN_MASK;
if (frame_len <= sizeof(rx_buffer))
{
  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_t resp_tx_time;
    int ret;

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

    /* Compute response 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_t)(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 DW IC until TX frame sent event set. See NOTE 6 below. */
      while (!(dwt_read32bitreg(SYS_STATUS_ID) & SYS_STATUS_TXFRS_BIT_MASK))
      { };

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

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

}
else
{
/* Clear RX error events in the DW IC status register. */
dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_ALL_RX_ERR);
}
}

Tag’ Program:

/Library Used/

#include “dw3000.h”
#include <Wire.h>
#define APP_NAME “SS TWR INIT v1.0”

/connection pins/
const uint8_t PIN_RST = 27; /reset pin/
const uint8_t PIN_IRQ = 34; /irq pin/
const uint8_t PIN_SS = 4; /spi select pin/

static double tof;
static double distance;

/* Default communication configuration. We use default non-STS DW mode. /
static dwt_config_t config = {
5, /
Channel number. /
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. /
1, /
0 to use standard 8 symbol SFD, 1 to use non-standard 8 symbol, 2 for non-standard 16 symbol SFD and 3 for 4z 8 symbol SDF type /
DWT_BR_6M8, /
Data rate. /
DWT_PHRMODE_STD, /
PHY header mode. /
DWT_PHRRATE_STD, /
PHY header rate. /
(128 + 1 + 8 - 8), /
SFD timeout (preamble length + 1 + SFD length - PAC size). Used in RX only. /
DWT_STS_MODE_OFF, /
STS disabled /
DWT_STS_LEN_128,/
STS length see allowed values in Enum dwt_sts_lengths_e /
DWT_PDOA_M0 /
PDOA mode off */
};

#define RNG_DELAY_MS 100

/* Default antenna delay values for 64 MHz PRF. See NOTE 2 below. */

#define TX_ANT_DLY 16387

#define RX_ANT_DLY 16387 //16385

/* Frames used in the ranging process. See NOTE 3 below. */

static uint8_t tx_poll_msg = {0x41, 0x88, 0, 0xCA, 0xDE, ‘W’, ‘A’, ‘V’, ‘E’, 0xE0, 0, 0};

static uint8_t rx_resp_msg = {0x41, 0x88, 0, 0xCA, 0xDE, ‘V’, ‘E’, ‘W’, ‘A’, 0xE1, 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 3 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 RESP_MSG_POLL_RX_TS_IDX 10

#define RESP_MSG_RESP_TX_TS_IDX 14

#define RESP_MSG_TS_LEN 4

/* Frame sequence number, incremented after each transmission. */

static uint8_t 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_t 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_t status_reg = 0;

#define POLL_TX_TO_RESP_RX_DLY_UUS 1720
#define RESP_RX_TIMEOUT_UUS 250

extern dwt_txconfig_t txconfig_options;

void setup()
{
UART_init();
test_run_info((unsigned char *)APP_NAME);

Wire.begin();
Serial.begin(9600);
Serial.print(“Setup DW3000…”);

/* Configure SPI rate, DW3000 supports up to 38 MHz /
/
Reset DW IC */
spiBegin(PIN_IRQ, PIN_RST);
spiSelect(PIN_SS);

delay(2); // Time needed for DW3000 to start up (transition from INIT_RC to IDLE_RC, or could wait for SPIRDY event)

while (!dwt_checkidlerc()) // Need to make sure DW IC is in IDLE_RC before proceeding
{
UART_puts(“IDLE FAILED\r\n”);
while (1) ;
}

if (dwt_initialise(DWT_DW_INIT) == DWT_ERROR)
{
UART_puts(“INIT FAILED\r\n”);
while (1) ;
}

// Enabling LEDs here for debug so that for each TX the D1 LED will flash on DW3000 red eval-shield boards.
dwt_setleds(DWT_LEDS_ENABLE | DWT_LEDS_INIT_BLINK);

/* Configure DW IC. See NOTE 6 below. */
if (dwt_configure(&config)) // if the dwt_configure returns DWT_ERROR either the PLL or RX calibration has failed the host should reset the device
{
UART_puts(“CONFIG FAILED\r\n”);
while (1) ;
}

/* Configure the TX spectrum parameters (power, PG delay and PG count) */
dwt_configuretxrf(&txconfig_options);

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

/* Set expected response’s delay and timeout. See NOTE 1 and 5 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);

/* Next can enable TX/RX states output on GPIOs 5 and 6 to help debug, and also TX/RX LEDs
Note, in real low power applications the LEDs should not be used. */
dwt_setlnapamode(DWT_LNA_ENABLE | DWT_PA_ENABLE);
}

void loop()
{
/* Write frame data to DW IC 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_BIT_MASK);
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 8 below. */
while (!((status_reg = dwt_read32bitreg(SYS_STATUS_ID)) & (SYS_STATUS_RXFCG_BIT_MASK | SYS_STATUS_ALL_RX_TO | SYS_STATUS_ALL_RX_ERR)))
{ };

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

if (status_reg & SYS_STATUS_RXFCG_BIT_MASK)
{
uint32_t frame_len;

/* Clear good RX frame event in the DW IC status register. */
dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG_BIT_MASK);

/* A frame has been received, read it into the local buffer. */
frame_len = dwt_read32bitreg(RX_FINFO_ID) & RXFLEN_MASK;
if (frame_len <= sizeof(rx_buffer))
{
  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_t poll_tx_ts, resp_rx_ts, poll_rx_ts, resp_tx_ts;
    int32_t 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 = ((float)dwt_readclockoffset()) / (uint32_t)(1 << 26);

    /* 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 LCD. */
    snprintf(dist_str, sizeof(dist_str), "DIST: %3.2f m", distance);
    test_run_info((unsigned char *)dist_str);
  }
}

}
else
{
/* Clear RX error/timeout events in the DW IC status register. */
dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_ALL_RX_TO | SYS_STATUS_ALL_RX_ERR);
}

/* Execute a delay between ranging exchanges. */
Sleep(RNG_DELAY_MS);
}

The way I performed manual calibration was by inputting the anchor program into one device and the tag program into one of the other two devices. Then, I conducted experiments to change the antenna delay values one by one and searched for the most accurate one. Certainly, after looking at some references like APS014, this isn’t an effective and wise approach.

In your opinion, how can we address this multipath issue? Also, I’m very grateful for the GitHub repository you provided to help me with this problem.

I have a few questions about your program when implemented in the Arduino IDE. Could you correct my understanding of your program?

My understanding is that your program uses iteration to find candidate antenna delay values (candidates are potential antenna delay values) to be used. These antenna delays are iterated within a range from A to B, and then a comparison is made with similar values from actual measurements and experiments. Is this understanding correct?

Furthermore, in your calibration, you’re using the same anchor and tag communication (single-sided ranging communication system), correct? How many devices are needed to perform the calibration? Is it sufficient to have just two devices as anchor and tag?

I apologize as I’m still a beginner in this, to be honest, I’m having difficulty understanding the APS014 part. I’m also confused about how to calculate the delay for each of them, such as in the values “POLL_TX_TO_RESP_RX_DLY_UUS” and “RESP_RX_TIMEOUT_UUS,” as well as “TX_ANT_DLY” and “RX_ANT_DLY.”

Thank you for your assistance!

Hi Schven04,

I have one more question, does the calibration process require looping ranging (TWR) or does it just use the iteration program? Because I still don’t understand the calibration, how the device to be calibrated is run.

Sincerely,
Theophilus Ezra

Hi,

Which multipath issue did you mean here?

Yes of course, I can if you have any questions.

Yes basicly this is an simple evolutionary algorithm. The comparison is made with a laser measurement for a precise measurement of the range between the devices.

Which kind of measurement you are unsing isn’t really relevant I would say. The script I provided you is the implementation from the example. I am at the point where I like to test what kind measurement is the best for the calibatrion process. At the end you are compare to distances. The APS014 sayes they are using TWR for the ranging measurement. This is I think more a symetric double-sided two-way ranging and not a SS-TWR.

Because in a simple SS-TWR you migth be dealing with clock-drift errors and in DS-TWR the clock drift isn’t that relative anymore. But this is the point where I am now and trying to understand what happenend.

So for the antenna calibration process, which is described in ASP014, you need 3 or more devices.The device should be placed in a LOS direction with a known range (for exmaple a triangle). Measure the distance with an laser or an other highly precise distance measurement unit. After that you are setting the antenna delay to 0 for all devices. Than you doing an range of measurements (for example 200) from and to each device (1-2,1-3, 2-1,2-3,3-1,3-2). With the measurements done, you calcuate the average distance for that.
Thats basicly the ranging process, after that you should use the script I am provided.

Cheers,
Sven

Hi Schven,

Thank you so much for your responses, it is really help me for this project. I understand now, that the calibration process have to use the Double Sided TWR method. Could you help me to review my code about double-sided TWR? I have made a post for it. Thank you!

The post:

Sincerely,
Theophilus Ezra

Hi,

please a have look at the answers from Andy under your post, he gave you a frist hint for you, that I am totaly in.

And another hint: reading the code here is really really hard. Maybe it is easier when you upload your code to github or something simliar and start there a conservation about your code, maybe in the issue. Here maybe you can post the link, I think this will be better handle.

1 Like

Thank you so much for the advice! I am working with it