Hi everyone! I am working on a ESP32 project that uses the DW1000 UWB library. I need some assistance achieving the same range in my own prototype as what is achievable with the example sketches.
What I want to achieve
I purchased two ESP32 UWB MakerFab dev boards. I want to use the UWB ranging function to determine the range between the two boards and I also want to send and receive text strings between the two devices.
These boards have an advertised ranging distance of 200 meters. Using the DW1000 libary and example code provided by MakerFab I was able to achieve ranging out to +-103m which is acceptable for my purposes. This result was achieved using the following example sketches.
uwb_tab_display & anchor_display -103m range
Where I'm at
From the DW1000 library, I tested the following example sketches on the ESP32 boards and measured the distance achieved before the connection was lost.
-
Ranging
- D1000Ranging_ANCHOR & DW1000Ranging_TAG - 28m
- RangingAnchor & RangingTag - 9.5m
-
Messaging
- BasicSender & BasicReceiver - Send and receive messages to 8.6m
My Prototype
I then cobbled together a prototype using the above examples from the DW1000 library. This was done to achieve both ranging and messaging.
Ranging and messaging features work but only out to a minimal range of around 9.2m which isn't nearly as far as the 103m I achieved the with MakerFab example code. See below my code.
- CombinedTag_V4
- RangingTag + Both BasicSender & BasicReceiver
My code for the tag
#include <SPI.h>
#include <DW1000.h>
// connection pins
const uint8_t PIN_RST = 27; // reset pin
const uint8_t PIN_IRQ = 34; // irq pin
const uint8_t PIN_SS = 21; // spi select pin
// messages used in the ranging protocol
// TODO replace by enum
#define POLL 0
#define POLL_ACK 1
#define RANGE 2
#define RANGE_REPORT 3
#define RANGE_FAILED 255
// message flow state
volatile byte expectedMsgId = POLL_ACK;
// message sent/received state
volatile boolean sentAck = false;
volatile boolean receivedAck = false;
volatile boolean error = false;
String uwbReceive;
// timestamps to remember
DW1000Time timePollSent;
DW1000Time timePollAckReceived;
DW1000Time timeRangeSent;
// data buffer
#define LEN_DATA 16
byte data[LEN_DATA];
// watchdog and reset period
uint32_t lastActivity;
uint32_t resetPeriod = 250;
// reply times (same on both sides for symm. ranging)
uint16_t replyDelayTimeUS = 3000;
void setup() {
// DEBUG monitoring
Serial.begin(115200);
// initialize the driver
DW1000.begin(PIN_IRQ, PIN_RST);
DW1000.select(PIN_SS);
// general configuration
DW1000.newConfiguration();
DW1000.setDefaults();
DW1000.setDeviceAddress(2);
DW1000.setNetworkId(10);
DW1000.enableMode(DW1000.MODE_LONGDATA_RANGE_LOWPOWER);
DW1000.commitConfiguration();
Serial.println(F("Committed configuration ..."));
char uwbSend;
// attach callback for (successfully) sent and received messages
DW1000.attachSentHandler(handleSent);
DW1000.attachReceivedHandler(handleReceived);
DW1000.attachReceiveFailedHandler(handleError);
DW1000.attachErrorHandler(handleError);
// anchor starts by transmitting a POLL message
receiver();
transmitPoll();
noteActivity();
}
void noteActivity() {
// update activity timestamp, so that we do not reach "resetPeriod"
lastActivity = millis();
}
void resetInactive() {
// tag sends POLL and listens for POLL_ACK
expectedMsgId = POLL_ACK;
transmitPoll();
noteActivity();
}
void handleSent() {
// status change on sent success
sentAck = true;
}
void handleReceived() {
// status change on received success
receivedAck = true;
}
void transmitPoll() {
DW1000.newTransmit();
DW1000.setDefaults();
data[0] = POLL;
DW1000.setData(data, LEN_DATA);
DW1000.startTransmit();
}
void transmitRange() {
DW1000.newTransmit();
DW1000.setDefaults();
data[0] = RANGE;
// delay sending the message and remember expected future sent timestamp
DW1000Time deltaTime = DW1000Time(replyDelayTimeUS, DW1000Time::MICROSECONDS);
timeRangeSent = DW1000.setDelay(deltaTime);
timePollSent.getTimestamp(data + 1);
timePollAckReceived.getTimestamp(data + 6);
timeRangeSent.getTimestamp(data + 11);
DW1000.setData(data, LEN_DATA);
DW1000.startTransmit();
//Serial.print("Expect RANGE to be sent @ "); Serial.println(timeRangeSent.getAsFloat());
}
void handleError() {
error = true;
}
void receiver() {
DW1000.newReceive();
DW1000.setDefaults();
// so we don't need to restart the receiver manually
DW1000.receivePermanently(true);
DW1000.startReceive();
}
void transmitMessage() {
// transmit some data
DW1000.newTransmit();
DW1000.setDefaults();
String uwbSend = "1";
DW1000.setData(uwbSend);
// delay sending the message for the given amount
DW1000Time deltaTime = DW1000Time(10, DW1000Time::MILLISECONDS);
DW1000.setDelay(deltaTime);
DW1000.startTransmit();
}
void loop() {
//MESSAGING CODE
if (receivedAck) {
// get data as string
DW1000.getData(uwbReceive);
//Serial.println(uwbReceive);
if (uwbReceive == "1") {
Serial.println("Message Received");
transmitMessage();
Serial.print("FP power is [dBm] ... "); Serial.println(DW1000.getFirstPathPower());
Serial.print("RX power is [dBm] ... "); Serial.println(DW1000.getReceivePower());
Serial.print("Signal quality is ... "); Serial.println(DW1000.getReceiveQuality());
receivedAck = false; //NOTE - I COULD PROBABLY REMOVE THIS LINE
}
}
//RANGING CODE
if (!sentAck && !receivedAck) {
// check if inactive
if (millis() - lastActivity > resetPeriod) {
resetInactive();
}
return;
}
// continue on any success confirmation
if (sentAck) {
sentAck = false;
byte msgId = data[0];
if (msgId == POLL) {
DW1000.getTransmitTimestamp(timePollSent);
//Serial.print("Sent POLL @ "); Serial.println(timePollSent.getAsFloat());
} else if (msgId == RANGE) {
DW1000.getTransmitTimestamp(timeRangeSent);
noteActivity();
}
}
if (receivedAck) {
receivedAck = false;
// get message and parse
DW1000.getData(data, LEN_DATA);
byte msgId = data[0];
if (msgId != expectedMsgId) {
// unexpected message, start over again
//Serial.print("Received wrong message # "); Serial.println(msgId);
expectedMsgId = POLL_ACK;
transmitPoll();
return;
}
if (msgId == POLL_ACK) {
DW1000.getReceiveTimestamp(timePollAckReceived);
expectedMsgId = RANGE_REPORT;
transmitRange();
noteActivity();
} else if (msgId == RANGE_REPORT) {
expectedMsgId = POLL_ACK;
float curRange;
memcpy(&curRange, data + 1, 4);
transmitPoll();
noteActivity();
}
}
}
- CombinedAnchor_V3
- RangingAnchor + Both BasicSender & BasicReceiver
My code for the anchor
#include <SPI.h>
#include <DW1000.h>
// connection pins
const uint8_t PIN_RST = 27; // reset pin
const uint8_t PIN_IRQ = 34; // irq pin
const uint8_t PIN_SS = 21; // spi select pin
// messages used in the ranging protocol
// TODO replace by enum
#define POLL 0
#define POLL_ACK 1
#define RANGE 2
#define RANGE_REPORT 3
#define RANGE_FAILED 255
// message flow state
volatile byte expectedMsgId = POLL;
// message sent/received state
volatile boolean sentAck = false;
volatile boolean receivedAck = false;
// DEBUG packet received status
volatile boolean error = false;
String uwbReceive;
// protocol error state
boolean protocolFailed = false;
// timestamps to remember
DW1000Time timePollSent;
DW1000Time timePollReceived;
DW1000Time timePollAckSent;
DW1000Time timePollAckReceived;
DW1000Time timeRangeSent;
DW1000Time timeRangeReceived;
// last computed range/time
DW1000Time timeComputedRange;
// data buffer
#define LEN_DATA 16
byte data[LEN_DATA];
// watchdog and reset period
uint32_t lastActivity;
uint32_t resetPeriod = 250;
// reply times (same on both sides for symm. ranging)
uint16_t replyDelayTimeUS = 3000;
// ranging counter (per second)
uint16_t successRangingCount = 0;
uint32_t rangingCountPeriod = 0;
float samplingRate = 0;
//Button Variables
int ButtonState;
int ButtonPin = 12;
bool ButtonReleased = true;
//LED Variables
int LedPin = 13;
//Timer Variables
unsigned long CurrentMillis = 0;
unsigned long ButtonPressMillis = 0;
unsigned long ReleasedMillis = 0;
unsigned long PreviousMillis = 0;
unsigned long PeriodMillis = 500;
//Ranging Variable
float distance;
void setup() {
// DEBUG monitoring
Serial.begin(115200);
delay(1000);
Serial.println(F("### DW1000-arduino-ranging-anchor ###"));
// initialize the driver
DW1000.begin(PIN_IRQ, PIN_RST);
DW1000.select(PIN_SS);
Serial.println(F("DW1000 initialized ..."));
// general configuration
DW1000.newConfiguration();
DW1000.setDefaults();
DW1000.setDeviceAddress(1);
DW1000.setNetworkId(10);
DW1000.enableMode(DW1000.MODE_LONGDATA_RANGE_LOWPOWER);
DW1000.commitConfiguration();
Serial.println(F("Committed configuration ..."));
// DEBUG chip info and registers pretty printed
char msg[128];
DW1000.getPrintableDeviceIdentifier(msg);
Serial.print("Device ID: "); Serial.println(msg);
DW1000.getPrintableExtendedUniqueIdentifier(msg);
Serial.print("Unique ID: "); Serial.println(msg);
DW1000.getPrintableNetworkIdAndShortAddress(msg);
Serial.print("Network ID & Device Address: "); Serial.println(msg);
DW1000.getPrintableDeviceMode(msg);
Serial.print("Device mode: "); Serial.println(msg);
// attach callback for (successfully) sent and received messages
DW1000.attachSentHandler(handleSent);
DW1000.attachReceivedHandler(handleReceived);
DW1000.attachReceiveFailedHandler(handleError);
DW1000.attachErrorHandler(handleError);
// anchor starts in receiving mode, awaiting a ranging poll message
receiver();
noteActivity();
// for first time ranging frequency computation
rangingCountPeriod = millis();
pinMode(LedPin, OUTPUT);
pinMode(ButtonPin, INPUT_PULLDOWN);
}
void noteActivity() {
// update activity timestamp, so that we do not reach "resetPeriod"
lastActivity = millis();
}
void resetInactive() {
// anchor listens for POLL
expectedMsgId = POLL;
receiver();
noteActivity();
}
void handleSent() {
// status change on sent success
sentAck = true;
}
void handleReceived() {
// status change on received success
receivedAck = true;
}
void handleError() {
error = true;
}
void transmitPollAck() {
DW1000.newTransmit();
DW1000.setDefaults();
data[0] = POLL_ACK;
// delay the same amount as ranging tag
DW1000Time deltaTime = DW1000Time(replyDelayTimeUS, DW1000Time::MICROSECONDS);
DW1000.setDelay(deltaTime);
DW1000.setData(data, LEN_DATA);
DW1000.startTransmit();
}
void transmitRangeReport(float curRange) {
DW1000.newTransmit();
DW1000.setDefaults();
data[0] = RANGE_REPORT;
// write final ranging result
memcpy(data + 1, &curRange, 4);
DW1000.setData(data, LEN_DATA);
DW1000.startTransmit();
}
void transmitRangeFailed() {
DW1000.newTransmit();
DW1000.setDefaults();
data[0] = RANGE_FAILED;
DW1000.setData(data, LEN_DATA);
DW1000.startTransmit();
}
void receiver() {
DW1000.newReceive();
DW1000.setDefaults();
// so we don't need to restart the receiver manually
DW1000.receivePermanently(true);
DW1000.startReceive();
}
/*
* RANGING ALGORITHMS
* ------------------
* Either of the below functions can be used for range computation (see line "CHOSEN
* RANGING ALGORITHM" in the code).
* - Asymmetric is more computation intense but least error prone
* - Symmetric is less computation intense but more error prone to clock drifts
*
* The anchors and tags of this reference example use the same reply delay times, hence
* are capable of symmetric ranging (and of asymmetric ranging anyway).
*/
void computeRangeAsymmetric() {
// asymmetric two-way ranging (more computation intense, less error prone)
DW1000Time round1 = (timePollAckReceived - timePollSent).wrap();
DW1000Time reply1 = (timePollAckSent - timePollReceived).wrap();
DW1000Time round2 = (timeRangeReceived - timePollAckSent).wrap();
DW1000Time reply2 = (timeRangeSent - timePollAckReceived).wrap();
DW1000Time tof = (round1 * round2 - reply1 * reply2) / (round1 + round2 + reply1 + reply2);
// set tof timestamp
timeComputedRange.setTimestamp(tof);
}
void computeRangeSymmetric() {
// symmetric two-way ranging (less computation intense, more error prone on clock drift)
DW1000Time tof = ((timePollAckReceived - timePollSent) - (timePollAckSent - timePollReceived) +
(timeRangeReceived - timePollAckSent) - (timeRangeSent - timePollAckReceived)) * 0.25f;
// set tof timestamp
timeComputedRange.setTimestamp(tof);
}
/*
* END RANGING ALGORITHMS
* ----------------------
*/
void transmitter() {
// transmit some data
DW1000.newTransmit();
DW1000.setDefaults();
String uwbSend = "1";
DW1000.setData(uwbSend);
//Confirm send
Serial.print("Message Sent - ");
// delay sending the message for the given amount
DW1000Time deltaTime = DW1000Time(10, DW1000Time::MILLISECONDS);
DW1000.setDelay(deltaTime);
DW1000.startTransmit();
}
void NotificationLight() {
if (ButtonState == HIGH) {
digitalWrite(LedPin, HIGH);
} else {
digitalWrite(LedPin, LOW);
}
}
void loop() {
int32_t curMillis = millis();
ButtonState = digitalRead(ButtonPin);
CurrentMillis = millis();
//MESSAGING CODE
if((CurrentMillis-PreviousMillis)>=PeriodMillis){
//Serial.println("Transmitted");
//transmitter();
//Serial.print(distance);Serial.println(" m");
PreviousMillis=CurrentMillis;
}
//Code for receiving UWB message
if (receivedAck) {
// get data as string
DW1000.getData(uwbReceive);
if (uwbReceive == "1") {
Serial.print(uwbReceive);Serial.print(" - ");
Serial.println("Message Received");
}
//receivedAck = false;
}
//On button press
if (ButtonState == HIGH && ButtonReleased == true) {
Serial.println("MANUAL ACTIVATE");
transmitter();
Serial.print(distance);
Serial.println("");
Serial.println("MANUAL ACTIVATE");
ButtonReleased = !ButtonReleased;
//delay(200); //Debounce
}
//Reset button state on release
if (ButtonState == LOW && ButtonReleased == false) {
ButtonReleased = !ButtonReleased;
}
NotificationLight();
//RANGING CODE
if (!sentAck && !receivedAck) {
// check if inactive
if (curMillis - lastActivity > resetPeriod) {
resetInactive();
}
return;
}
// continue on any success confirmation
if (sentAck) {
sentAck = false;
byte msgId = data[0];
if (msgId == POLL_ACK) {
DW1000.getTransmitTimestamp(timePollAckSent);
noteActivity();
}
}
if (receivedAck) {
receivedAck = false;
// get message and parse
DW1000.getData(data, LEN_DATA);
byte msgId = data[0];
if (msgId != expectedMsgId) {
// unexpected message, start over again (except if already POLL)
protocolFailed = true;
}
if (msgId == POLL) {
// on POLL we (re-)start, so no protocol failure
protocolFailed = false;
DW1000.getReceiveTimestamp(timePollReceived);
expectedMsgId = RANGE;
transmitPollAck();
noteActivity();
}
else if (msgId == RANGE) {
DW1000.getReceiveTimestamp(timeRangeReceived);
expectedMsgId = POLL;
if (!protocolFailed) {
timePollSent.setTimestamp(data + 1);
timePollAckReceived.setTimestamp(data + 6);
timeRangeSent.setTimestamp(data + 11);
// (re-)compute range as two-way ranging is done
computeRangeAsymmetric(); // CHOSEN RANGING ALGORITHM
transmitRangeReport(timeComputedRange.getAsMicroSeconds());
distance = timeComputedRange.getAsMeters();
Serial.print("Range: "); Serial.print(distance); Serial.print(" m");
Serial.print("\t RX power: "); Serial.print(DW1000.getReceivePower()); Serial.print(" dBm");
Serial.print("\t Sampling: "); Serial.print(samplingRate); Serial.println(" Hz");
//Serial.print("FP power is [dBm]: "); Serial.print(DW1000.getFirstPathPower());
//Serial.print("RX power is [dBm]: "); Serial.println(DW1000.getReceivePower());
//Serial.print("Receive quality: "); Serial.println(DW1000.getReceiveQuality());
// update sampling rate (each second)
successRangingCount++;
if (curMillis - rangingCountPeriod > 1000) {
samplingRate = (1000.0f * successRangingCount) / (curMillis - rangingCountPeriod);
rangingCountPeriod = curMillis;
successRangingCount = 0;
}
}
else {
transmitRangeFailed();
}
noteActivity();
}
}
}
Please help me to resolve the following
How can I achieve the same range in the prototype as I did with the MakerFab example code? What do I need to change in my code to achieve this?