I like to share with you my C implementation for estimating the DecaWave transmission time because it has been created with help from the community and thus should be shared with it.

The DecaWave DW1000 is a versatile ultra-wideband transceiver that enables long range and fast communication. Due to its wide spectral width it is able to occupy a very narrow temporal with. Precise time-stamping then allows for estimating the time-of-flight which in turn can be used for determining the distance between two nodes.

When implementing this with a lot of nodes, strict timing becomes important. It is favorable to know the transmission time of a frame in advance such that the process of planning and sending a frame takes as little of time as possible.

After discussing some details with fellow DecaWave users I wrote a small C-function that is able to estimate the TX-time from the DecaWave configuration struct as used in their C API software. I would like to share this function here for others to use. If you notice any room for improvements, please let me know.

/**
 * Estimate the transmission time of a frame in seconds
 * dwt_config_t   dwt_config  Configuration struct of the DW1000
 * uint16_t       framelength Framelength including the 2-Byte CRC
 * bool           only_rmarker Option to compute only the time to the RMARKER (SHR time)
 */
float dwt_estimate_tx_time( dwt_config_t dwt_config, uint16_t framelength, bool only_rmarker )
{

  int32_t tx_time;
  size_t sym_timing_ind;
  uint16_t shr_len;

  const uint16_t DATA_BLOCK_SIZE  = 330;
  const uint16_t REED_SOLOM_BITS  = 48;

  // Symbol timing LUT
  const size_t SYM_TIM_16MHZ = 0;
  const size_t SYM_TIM_64MHZ = 9;
  const size_t SYM_TIM_110K  = 0;
  const size_t SYM_TIM_850K  = 3;
  const size_t SYM_TIM_6M8   = 6;
  const size_t SYM_TIM_SHR   = 0;
  const size_t SYM_TIM_PHR   = 1;
  const size_t SYM_TIM_DAT   = 2;

  const static uint16_t SYM_TIM_LUT[] = {
    // 16 Mhz PRF
    994, 8206, 8206,  // 0.11 Mbps
    994, 1026, 1026,  // 0.85 Mbps
    994, 1026, 129,   // 6.81 Mbps
    // 64 Mhz PRF
    1018, 8206, 8206, // 0.11 Mbps
    1018, 1026, 1026, // 0.85 Mbps
    1018, 1026, 129   // 6.81 Mbps
  };

  // Find the PHR
  switch( dwt_config.prf ) {
    case DWT_PRF_16M:  sym_timing_ind = SYM_TIM_16MHZ; break;
    case DWT_PRF_64M:  sym_timing_ind = SYM_TIM_64MHZ; break;
  }

  // Find the preamble length
  switch( dwt_config.txPreambLength ) {
    case DWT_PLEN_64:    shr_len = 64;    break;
    case DWT_PLEN_128:  shr_len = 128;  break;
    case DWT_PLEN_256:  shr_len = 256;  break;
    case DWT_PLEN_512:  shr_len = 512;  break;
    case DWT_PLEN_1024: shr_len = 1024;  break;
    case DWT_PLEN_1536: shr_len = 1536;  break;
    case DWT_PLEN_2048: shr_len = 2048;  break;
    case DWT_PLEN_4096: shr_len = 4096;  break;
  }

  // Find the datarate
  switch( dwt_config.dataRate ) {
    case DWT_BR_110K:
      sym_timing_ind  += SYM_TIM_110K;
      shr_len         += 64;  // SFD 64 symbols
      break;
    case DWT_BR_850K:
      sym_timing_ind  += SYM_TIM_850K;
      shr_len         += 8;   // SFD 8 symbols
      break;
    case DWT_BR_6M8:
      sym_timing_ind  += SYM_TIM_6M8;
      shr_len         += 8;   // SFD 8 symbols
      break;
  }

  // Add the SHR time
  tx_time   = shr_len * SYM_TIM_LUT[ sym_timing_ind + SYM_TIM_SHR ];

  // If not only RMARKER, calculate PHR and data
  if( !only_rmarker ) {
    
    // Add the PHR time (21 bits)
    tx_time  += 21 * SYM_TIM_LUT[ sym_timing_ind + SYM_TIM_PHR ];

    // Bytes to bits
    framelength *= 8;

    // Add Reed-Solomon parity bits
    framelength += REED_SOLOM_BITS * ( framelength + DATA_BLOCK_SIZE - 1 ) / DATA_BLOCK_SIZE;

    // Add the DAT time
    tx_time += framelength * SYM_TIM_LUT[ sym_timing_ind + SYM_TIM_DAT ];
  
  }

  // Return float seconds
  return (1.0e-9f) * tx_time;

}

A typical frame consists of a preamble, a start-frame-delimiter (SFD), a PHY header (PHR) and data. The preamble and SFD together form the synchronization header (SHR). The time the first symbol launches from the antenna is the RMARKER. This is the event used for timestamping and scheduling. The function allows for computing only the time to the RMARKER such that a transmission with planned TX-time can be send as soon as possible. The SHR is followed by the PHR containing details about the remaining frame, it is 19 bits in length and embeds a 6 bit SECDED parity check. The PHR is followed by the data. Every block of 330 (or less) databits is accompanied by a block of 48 Reed-Solomon parity bits. If the frame adheres to the IEEE 802.15.4 MAC layer, the data part is started with a MAC header and ended with two CRC bytes.