ESP32 DW1000 SS TWR Example

Hello there,
I implemented a custom SS TWR algorithm using Makerfabs ESP32 DW1000 board and Makerfabs DW1000 library (GitHub - Makerfabs/Makerfabs-ESP32-UWB). I noticed, it doesn’t have the CarrierOffset API call and there are other smaller difference, but as for the SPI calls I figured, they are the same compared to the library used in dwm1001-examples/examples/ss_twr_init at master · Decawave/dwm1001-examples · GitHub.

So except for the library and the CarrierOffset, I tried to copy as much as possible from the official example. The code works, but I got offsets of about 7m, between tags. Even negative distances, what means, that the reply time is larger than the round trip time, what is unphysical. I tried to tweak the antenna delays, but without any access. It is like there is a constant bias in the system I cannot get rid of.

With the DWM1001 Dev Boards I hadn’t have any similar issues. What could be the culprit for this bias?

Thanks,
Christian

A constant bias in the range measurement is almost always an antenna delay error.
You can either use the API to set the antenna delays (assuming it has a suitable call, I think there is one) or use the raw register read/write commands to manually write to the antenna delay registers directly and change the value that way.

Personally I created a change antenna delay function. You give it the measured range error in meters, I know antenna delay is a time measurement but I find it easier to treat it as distance in my code as much as possible. The function converts that distance into DW clock cycles, reads the current delay value from the device, increases/decreases it by the appropriate amount and writes the new value back.

Hi Andy,
thanks for your reply. Good point. I noticed the Arduino DW1000 library is merely adding a timestamp to the time measurements. I don’t see SPI calls, like in the Decawave example.

I’ll try to port these Decawave API calls to the Arduino lib.

void dwt_setrxantennadelay(uint16 rxDelay)
{
    // Set the RX antenna delay for auto TX timestamp adjustment
    dwt_write16bitoffsetreg(LDE_IF_ID, LDE_RXANTD_OFFSET, rxDelay);
}


void dwt_settxantennadelay(uint16 txDelay)
{
    // Set the TX antenna delay for auto TX timestamp adjustment
    dwt_write16bitoffsetreg(TX_ANTD_ID, TX_ANTD_OFFSET, txDelay);
}

I’ve not used the arduino library but I did take a look at it in the past and it looked to be the decawave drivers with the low level SPI calls updated for the new hardware and a wrapper added over the top.
Which means all of those dwt_ function calls should still be in there but you may need to modify the driver library slightly to expose them.

To be honest I don’t think the Arduino library does the SPI calls for the delay. At least not with the custom values specified. I don’t see it in the code, or I’m blind. I only saw the addition to the time stamp. Do you know if the DW1000 has a default value in case no SPI calls are made?

In any case thank you very much. That helps a lot.

Update: oh my I’m blind!! I found it a similar call here

I gotta check if I have the same lib. It seems there exists different versions of the Arduino DW1000 which all look similar, but sometimes have slight differences…

void DW1000Class::commitConfiguration() {
// write all configurations back to device
writeNetworkIdAndDeviceAddress();
writeSystemConfigurationRegister();
writeChannelControlRegister();
writeTransmitFrameControlRegister();
writeSystemEventMaskRegister();
// tune according to configuration
tune();
// TODO check not larger two bytes integer
byte antennaDelayBytes[DW1000Time::LENGTH_TIMESTAMP];
if( _antennaDelay.getTimestamp() == 0 && _antennaCalibrated == false) {
_antennaDelay.setTimestamp(16384);
_antennaCalibrated = true;
} // Compatibility with old versions.
_antennaDelay.getTimestamp(antennaDelayBytes);

writeBytes(TX_ANTD, NO_SUB, antennaDelayBytes, LEN_TX_ANTD);
writeBytes(LDE_IF, LDE_RXANTD_SUB, antennaDelayBytes, LEN_LDE_RXANTD);
}

I don’t think there is a default value as such but start-up code could be reading the value stored in the OTP memory and loading that.
With an antenna delay of 0 I see range errors of over 50 meters so an error of a few meters probably means that there is some non-zero value in there.

To give you an idea of how I handle this my code has a hard coded average error to assume, I think from memory it’s around 55 meters. We then in production measure it for each unit and store the unit specific error (typically under 50 cm after allowing for the default) in eeprom (we don’t use the OTP). That tweaked value is what gets used for all the range measurements.

When installing a system we measure the range errors once installed at the final locations/temperatures etc…
This will typically give error numbers in the 3-4 cm region. We add/subtract those numbers from measured values when calculating the position. This way the anchors don’t need to know about the on site calibration values, only the tags need that information, which makes setup simpler.

1 Like

That’s a neat trick. Thanks for sharing. Allow me that last question: My use case is a peer mesh. So each UWB device acts alternating as anchor and then as tag. I use fixed time slots to change from tag to anchor and apply some randomness to avoid clashes between tags. It is not super efficient, but robust as I don’t need to sync between the tags. Robustness is paramount in my use case. Do you think a similar calibration like you described could also be applied here? The max number of UWB devices is 6 in my case.

It should be possible. With two devices delay calibration is easy, you measure the error and assign it 50/50 to each device. It may not be technically correct but it works.
With 3 or more it gets trickier since you need to know the actual delay for a given unit. This is easy to measure if you have a calibrated unit but that means you need a calibrated unit to create a calibrated unit.

There are app notes and various posts here on different ways to get out of this situation. My solution was to put a number of units (6 to 8) in a big circle. Each unit then measures the range to each other unit 1000 times, throws away any outliers and then calculates the average.
I then measure the actual locations (I have some optical survey equipment handy which makes this quick and easy to mm accuracies) and calculate the correct ranges.
I then run a least squares optimisation to find the delay value to add to each unit that minimises the range errors. This is then the delay calibration number (in meters) for that unit. I have in the past posted the python script I initially used for this before moving to c#, if you want a copy and can’t find it then let me know and I’ll see if I can dig it out again.
For production line calibration we use a known calibrated unit at a fixed distance but the final on site calibration still uses a variation on this process.

For sync between tags I have a fixed order that tags should transmit in, each tag knows it is tag n of m. Each tags transmit includes its location in the sequence and how far through it’s set of measurements it is. When not measuring tags are listening. This means that as soon as any tag hears another tag it can work out exactly when its turn to transmit should start. As long as everything is configured correctly this works really well with close to peak efficiency. The trade off is in flexibility, any change to the tags being used requires every tag to be reconfigured.

1 Like

Cool, I can’t emphasize how helpful that is. I also ended up with solving an optimization problem by turning range error to DWT time units with the help of the speed of light and correcting it for a known formation of tags. My script still has issues, that’s why I’d be super interested in your script. I think something is off with my problem formulation.

The script is linked to here:

The results are in meters but you could easily add a constant scale factor at the end to convert to time units if needed.

1 Like

Thanks a lot! That is much appreciated.

Hi @AndyA,
I have another question regarding the combination ESP32 and DW1000. The provided Arduino DW1000 library seems to miss some features like the carrierOffset computation.

I recently acquired some ESP32 DW3000 boards and saw the library suddenly looks way closer to the original Decawave DW1000M library.

Since I still struggle with my ranging errors for the ESP32 DW1000 for SS-TWR, even with calibration, I wonder if it could also be some missing features in the Arduino library? What is your oppinion on this?

When I switch to DS-TWR it works perfectly and the offsets are suddenly just a few cms.

It certainly sounds like a clock compensation issue, if you aren’t allowing for that the single sided results aren’t going to be any good. Does the library have the ability to read the carrier recovery integrator register of the DW1000 (Sub-Register 0x27:28 – DRX_CAR_INT)?
If so that generally gives the best correction factor.

Keep in mind that the provided libraries are something hacked together or copied from some other source by Makerfabs because you can’t sell boards without claiming to have a library for them. They aren’t officially supported by anyone and the quality isn’t going to be particularly consistent.

Hm, yes that explains a lot the ambiguity in code out there. I came across this:



int32_t DW1000Class::readCarrierIntegrator() {
	uint32_t  regval = 0 ;
	int     j ;
	uint8_t   buffer[DRX_CARRIER_INT_LEN] ;

	readBytes(DRX_CONF_ID, DRX_CARRIER_INT_OFFSET, buffer, DRX_CARRIER_INT_LEN);
	//readBytes(byte cmd, uint16_t offset, byte data[], uint16_t n)

	for (j = 2 ; j >= 0 ; j --)  // arrange the three bytes into an unsigned integer value
    {
        regval = (regval << 8) + buffer[j] ;
    }

	if (regval & B20_SIGN_EXTEND_TEST) regval |= B20_SIGN_EXTEND_MASK ; // sign extend bit #20 to whole word
    else regval &= DRX_CARRIER_INT_MASK ;                               // make sure upper bits are clear if not sign extending

    return (int32_t) regval ; // cast unsigned value to signed quantity.

DRX_CONF_ID is 0x27 and DRX_CARRIER_INT_OFFSET is 0x28. Looks like there’s something. I think in the original Decawave library it returns a float tho. I think I need to check what this function is doing…

That function looks fairly sensible, it’s reading the signed 21 bit number from the device and converting it to a signed 32 bit number that the compiler can deal with normally.

You would need to then divide that by the correct scale factor (should be in the user manual) to get the difference in clock rates it may be positive or negative depending on which is running faster.
You the calculate a clock scale factor of (1 - scaled carrier integrator value). (Or maybe 1+ that, depending on which way around things are being done). If you multiply the measured time difference by the clock scale factor then you should calculate the correct ranges.

Hmm right, interestingly I keep getting regval of 0 no matter what I do. I call the function, similar to the example ss_twr_init example right after the reception of a message. The registers and the way how they are called, seems consistent with the Decawave example. Could it be some other modification of Makerfab? Unfortunately, Makerfab didn’t do a great job here with their documentation of things… as you said.

It could be timing related.
You have to read the register after receiving a packet and before re-initialising the receive or transmit logic.

Hello guys,
I also doing a project about 4 anchors with a Tag, I encountered a very strange problem.
I use raspberry pi pico and DW1000 as anchor and Tag and use arduino IDE program them with arduino dw1000 library. When testing the distance to the tag using each single anchor, the results obtained are normal.

for example,
The situation of the strange problem is I set the distance between anchor 1-4 and tag is 1m, and test each anchor to tag is 1m, when I turn on the anchor 1, the distance of anchor 1 to tag is 1m,
turn on anchor 2, the the distance of anchor 1 to tag become 0.9m, anchor 2 to tag is 0.88m,
turn on anchor 3, the the distance of anchor 1 to tag become 0.8m, anchor 2 to tag is 0.6m, anchor 3 to tag is 0.8m,
turn on anchor 4, the the distance of anchor 1 to tag become 0.8m, anchor 2 to tag is 0.4m, anchor 3 to tag is 0.8m, anchor 4 to tag is 0.2m,

Do you know why multiple anchors affect each other when they are tested at the same time?

I have try to recalculate the antenna delay for the anchor, but I change a anchor, the tset disdance of other anchor will also be affected.

Have you ever encountered this problem?

Thank you for attention.

  1. When posting a new question please start a new topic.

  2. How are you controlling which tag replies when? Have you added logic to a) identify which device a given message is from and b) ensure no two devices are ever transmitting at the same time? If not this is a very common issue people hit when moving to multi-tag systems. Please search the forum, this topic has comes up frequently and has already been answered many times.
    If you have already done this then please include details since it’s impossible to help further without them.