Help with ESP32-IDF TWR Code for ESP32-DW3000 Modules

Hello everyone,

I am a student working on a positioning project using two ESP32-DW3000 modules. I have reviewed the TWR distance measurement example code from the following link: GitHub - br101/libdeca: UWB Library for Decawave/Qorvo DW3000. However, when I built the main program according to the instructions, I noticed that the system’s distance measurement does not follow the TWR method.

For example, as shown in the diagram below, the anchor device should be the one that emits the poll pulses, the tag device receives the poll pulses and sends back a response message, and the tag device will calculate the ToF as (Tloop - Treply) / 2.

However, when running the program built from the above library, I noticed that the anchor device only runs the twr_start function, and the tag device is the one that calculates and prints out the measurement results using the twr_set_observer(twr_done_cb) function. The file main.c is here:
/* SPI Master Half Duplex EEPROM example.

This example code is in the Public Domain (or CC0 licensed, at your option.)

Unless required by applicable law or agreed to in writing, this
software is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include “freertos/FreeRTOS.h”
#include “freertos/task.h”
#include “driver/spi_master.h”
#include “driver/gpio.h”

#include “sdkconfig.h”
#include “esp_log.h”
#include “dw3000_hw.h”
#include “dwhw.h”
#include “dwmac.h”
#include “dwphy.h”
#include “dwproto.h”
#include “ranging.h”
/*
This code demonstrates how to use the SPI master half duplex mode to read/write a AT932C46D EEPROM (8-bit mode).
*/

#define IS_ANCHOR 0

static const char TAG = “main”;

static void twr_done_cb(uint64_t src, uint64_t dst, uint16_t dist,
uint16_t num)
{
ESP_LOGI(TAG,“TWR Done %04X: %d cm”, (uint16_t)dst, dist);
}

void test_twr(void)
{
// decadriver init
dw3000_hw_init();
dw3000_hw_reset();
dw3000_hw_init_interrupt();

// libdeca init
dwhw_init();
dwphy_config();
dwphy_set_antenna_delay(DWPHY_ANTENNA_DELAY);
uint16_t PANID = 0x1234; // Example PANID

#if IS_ANCHOR == 1
uint16_t MAC16 = 0x5678; // Example MAC16
#else
uint16_t MAC16 = 0x5677; // Example MAC16
#endif
dwmac_init(PANID, MAC16, dwprot_rx_handler, NULL, NULL);
dwmac_set_frame_filter();
twr_init(10000 /TWR_PROCESSING_DELAY/, false);
twr_set_observer(twr_done_cb);
// two way ranging to 0x0001

#if IS_ANCHOR == 1
dwmac_set_rx_reenable(true);
dwt_forcetrxoff();
dwt_rxenable(DWT_START_RX_IMMEDIATE);
#else
twr_start(0x5678);
#endif
}

void app_main(void)
{

test_twr();
while (1) {
    // Add your main loop handling code here.
    vTaskDelay(100/portTICK_PERIOD_MS);

#if IS_ANCHOR == 0
twr_start(0x5678);
#endif
}
}
I’m not sure where I went wrong. Could you please help me check the code again? I would like to write a program for the two devices, ANCHOR and TAG, to properly follow the TWR method, with the ANCHOR sending the poll pulse, the TAG sending back the response, and the ANCHOR calculating and printing the result.

Thank you for your help!

I also tried to rewrite 2 programs for anchor and tag as follows, but when flashing the module, the measurement result is still on the responder (Tag) side, I don’t understand where I went wrong.
ANCHOR:
#include “dw3000_hw.h”
#include “dwhw.h”
#include “dwmac.h”
#include “dwphy.h”
#include “dwproto.h”
#include “freertos/FreeRTOS.h”
#include “freertos/task.h”
#include “esp_log.h”

static const char TAG = “initiator”;

// Callback để xử lý khi quá trình TWR hoàn tất
static void twr_done_cb(uint64_t src, uint64_t dst, uint16_t dist, uint16_t num)
{
ESP_LOGI(TAG, “TWR Done %04X: %d cm”, (uint16_t)dst, dist);
}

// Hàm khởi tạo và thực hiện đo TWR
void test_twr(void)
{
// Khởi tạo phần cứng
dw3000_hw_init();
dw3000_hw_reset();
dw3000_hw_init_interrupt();

// Khởi tạo thư viện phần mềm
dwhw_init();
dwphy_config();
dwphy_set_antenna_delay(DWPHY_ANTENNA_DELAY);
uint16_t PANID = 0x1234;  // PAN ID mẫu
uint16_t MAC16 = 0x5677;  // MAC16 cho initiator

// Khởi tạo giao thức MAC
dwmac_init(PANID, MAC16, dwprot_rx_handler, NULL, NULL);
dwmac_set_frame_filter();

// Khởi tạo TWR
twr_init(10000, false);  // Thời gian xử lý TWR là 10000 (TWR_PROCESSING_DELAY)
twr_set_observer(twr_done_cb);

// Bắt đầu quá trình TWR, gửi Poll tới responder có MAC16 = 0x5678
twr_start(0x5678);

// Đảm bảo rằng initiator đang ở chế độ nhận sau khi gửi gói Poll
dwmac_set_rx_reenable(true);
dwt_forcetrxoff();
dwt_rxenable(DWT_START_RX_IMMEDIATE);  // Kích hoạt chế độ nhận ngay lập tức

}

// Hàm main
void app_main(void)
{
test_twr();
while (1) {
vTaskDelay(100 / portTICK_PERIOD_MS); // Thực thi vòng lặp chính mỗi 100 ms
twr_start(0x5678); // Tiếp tục đo khoảng cách sau mỗi khoảng thời gian
}
}
TAG:
#include “dw3000_hw.h”
#include “dwhw.h”
#include “dwmac.h”
#include “dwphy.h”
#include “dwproto.h”
#include “freertos/FreeRTOS.h”
#include “freertos/task.h”
#include “esp_log.h”

static const char TAG = “responder”;

// Callback để xử lý khi quá trình TWR hoàn tất
static void twr_done_cb(uint64_t src, uint64_t dst, uint16_t dist, uint16_t num)
{
ESP_LOGI(TAG, “TWR Done %04X: %d cm”, (uint16_t)dst, dist);
}

// Hàm khởi tạo và thực hiện đo TWR ở phía responder
void test_twr(void)
{
// Khởi tạo phần cứng
dw3000_hw_init();
dw3000_hw_reset();
dw3000_hw_init_interrupt();

// Khởi tạo thư viện phần mềm
dwhw_init();
dwphy_config();
dwphy_set_antenna_delay(DWPHY_ANTENNA_DELAY);
uint16_t PANID = 0x1234;  // PAN ID mẫu
uint16_t MAC16 = 0x5678;  // MAC16 cho responder

// Khởi tạo giao thức MAC
dwmac_init(PANID, MAC16, dwprot_rx_handler, NULL, NULL);
dwmac_set_frame_filter();

// Khởi tạo TWR
twr_init(10000, false);  // Thời gian xử lý TWR là 10000 (TWR_PROCESSING_DELAY)
twr_set_observer(twr_done_cb);

// Bật chế độ nhận để sẵn sàng nhận gói Poll từ initiator
dwmac_set_rx_reenable(true);
dwt_forcetrxoff();
dwt_rxenable(DWT_START_RX_IMMEDIATE);

}

// Hàm main
void app_main(void)
{
test_twr();
while (1) {
vTaskDelay(100 / portTICK_PERIOD_MS); // Thực thi vòng lặp chính mỗi 100 ms
}
}

From the header file of the library you are using:

/** Start DS-TWR (Double Sided - Two Way Ranging) bsequence to ancor */
bool twr_start(uint64_t dst);
/** Start SS-TWR (Single Sided - Two Way Ranging) sequence to ancor */
bool twr_start_ss(uint64_t dst);

You are using the twr_start() function which performs double sided two way ranging. The diagram you showed for how you expect it to act is for single sided two way ranging.

This explains the difference in the behaviour you are seeing. Double sided adds an additional message from the initiator to the responder which means that unless you add something extra on the end of the ranging the responder is the one that calculates the position.
Double sided takes an extra message and so is slower but has the advantage that it automatically compensates for the differences in clock speeds between the two devices.