DW3000 problem with interrupt handling

Hi :slight_smile:

I’m currently working on a project for my university and I’m having trouble using interrupts with the dw3000 board with an ESP32 wrover B.

I’m using the legacy Arduino IDE. The Arduino version is 1.8.19 and the gcc version is 11.4.0.

I cloned the repository of the lib from GitHub - Makerfabs/Makerfabs-ESP32-UWB-DW3000. Added it to the Arduino library dir.

Now I wanted to use deep sleep when sending some data every x seconds to reduce power consumption in my scenario. To do this I wanted to use interrupts to complete the transmission and wake up the system.

I used the example for sending data in the repository (simple_tx) and added two callbacks to handle the interrupts.

dwt_setcallbacks(&txDoneHandler, NULL, NULL, NULL, NULL, &readyHandler);

Next I called

dwt_setinterrupt(SYS_ENABLE_LO_SPIRDY_ENABLE_BIT_MASK | SYS_ENABLE_LO_TXFRS_ENABLE_BIT_MASK, 0 , DWT_ENABLE_INT );

I then cleared the status bits and sent data, which works. But the callbacks are never called. My first problem was that when I called dwt_setinterrupt I got an Illegal Instruction Exception and the system rebooted. With the help of debugging tools I found out that the problem must be in the function decamutexon(), which disables interrupts and saves the current state. The function is declared to return a value, but it doesn’t. If I change it to return something like NULL, the crash is gone, but the interrupts still don’t work. So there seems to be something wrong with the compiler?

Ignoring this error, what am I doing wrong in handling interrupts, is there a flag that needs to be set ?

I have no experience with the makerfabs boards or drivers but those look like standard decawave driver function calls so they have probably simply ported them (badly by the sound of it).

I’ve only ever used the Tx and Rx interrupts using the standard drivers but looking at my code for that are you sure it’s supposed to be &txDoneHandler? I think you may want just txDoneHandler without the &.

Also after enabling the interrupts it’s worth writing to the status register to clear the interrupt flag, if that bit is already set for any reason then you won’t get an further interrupts.

You might be right. But still no interrupts are issued. Maybe it’s more useful if I display the code here :slight_smile:

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 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,
  DWT_STS_LEN_64, /* STS length, see allowed values in Enum dwt_sts_lengths_e */
  DWT_PDOA_M0     /* PDOA mode off */
};
void setup() {
  UART_init();
  Serial.print("Initialse... ");

  spiBegin(PIN_IRQ, PIN_RST);
  spiSelect(PIN_SS);

  delay(200);
  while (!dwt_checkidlerc())
  {
    Serial.println("IDLE FAILED 1 ... Retry");
    while (100);
  }

  dwt_softreset();
  delay(200);

  while (!dwt_checkidlerc())
  {
    Serial.println("IDLE FAILED 2 ... Retry");
    while (100);
  }

  if (dwt_initialise(DWT_DW_INIT) == DWT_ERROR)
  {
    Serial.println("INIT FAILED ... Retry");
    while (100);
  }

  dwt_setleds(DWT_LEDS_ENABLE | DWT_LEDS_INIT_BLINK);
  
  if (dwt_configure(&config))
  {
    Serial.println("Config FAILED ... Retry");
    while (100);
  }
  
  Serial.println("Install interrupthandlers");
  dwt_setcallbacks(doneTxIrq, NULL, NULL, NULL, NULL, wakeUpIrq);
  
  dwt_configuretxrf(&txconfig_options);

  Serial.println("Enable interrupts");
  dwt_setinterrupt(SYS_ENABLE_LO_SPIRDY_ENABLE_BIT_MASK | SYS_ENABLE_LO_TXFRS_ENABLE_BIT_MASK, 1 , DWT_ENABLE_INT );
  Serial.println("Configure sleep");
  dwt_configuresleepcnt(1000);
  dwt_configuresleep(DWT_CONFIG, DWT_PRES_SLEEP | 0x40 | DWT_SLP_EN);
  
  dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_TXFRS_BIT_MASK | SYS_STATUS_SPIRDY_BIT_MASK);
  Serial.println("Ready");
}

And both event handlers. I don’t assume the error to be here.

void wakeUpIrq(const dwt_cb_data_t * data){
  Serial.println("Wake up");
  while (!dwt_checkidlerc()){};
  dwt_restoreconfig();
  tx_msg.message_id++;
  dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_SPIRDY_BIT_MASK);
  txData();
}

void doneTxIrq(const dwt_cb_data_t * data){
  Serial.println("TX done");
  dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_TXFRS_BIT_MASK);
  dwt_entersleep(DWT_DW_IDLE);  
}

So the plan is that you put the DW3000 to sleep for a fixed time. It then wakes up and interrupts your processor. The processor transmits a packet and then on the Tx done interrupt puts the DW3000 back to sleep.

Are you putting the processor to sleep too? If not then the power saving of the DW3000 being asleep may not be that much. Personally I’d put the DW3000 into deep sleep, put the processor into sleep and use a CPU timer to wake the processor which then wakes the DW3000. That way the processor is always in control.

Having said that I can’t think of anything fundamentally wrong with what you’re doing. Serial prints and infinite loops are generally a very bad idea from within an interrupt, ideally wakeUp would set a flag (make sure it’s defied as volatile) and then the main background loop would check for that flag and do the actual restore transmit. But while not ideal I don’t think the way you have it would break things in this situation.

Rather than going to sleep at the end of your initialisation can you set it to instead transmit. That will let you test the transmit done interrupt. If that’s not working then fix that first before you worry about the sleep side. If you aren’t getting that then first check the physical interrupt pin is changing (this is a university project so I’d expect some sort of basic oscilloscope is available somewhere). If the firmware isn’t responding to an external signal it’s always first checking that the external signal is actually where you expect it :slight_smile:
If the Tx interrupt isn’t working then wait a short while (enough time for the Tx to complete) and then read the status register. Does it claim a transmit was completed? That should help work out where the issue is.

Once you’ve got Tx working correctly then if sleep still isn’t working you at least know it’s something specific to the sleep configuration that’s wrong.

Thanks for your reply. I’m at home at the moment and will have a look at the interrupt pin itself tomorrow. What I can say for sure is that the status register responds correctly after sending a message. The SYS_STATUS_TXFRS_BIT_MASK bit is set a few ms after the tx request is initialised. When using a simple loop i can send more than one message using the status register.

Hi @chris91, do you have any updates on this? I am starting to look into using interrupts with my DW3000 and it would be helpful to know if you managed to get this working (and what changes you had to make to the code you posted on an earlier reply)

There seems to be an error in the function decamutexon. It’s not returning a value that is requested by the function prototype. The compiler does something weird then causing an illegal instruction on my esp32 rover.

I got it working by ignoring the dw3000 library and simple used attachInterrupt. attachInterrupt() - Arduino-Referenz

See Code below:

dwt_setinterrupt( SYS_ENABLE_LO_TXFRS_ENABLE_BIT_MASK | SYS_ENABLE_LO_RXFCG_ENABLE_BIT_MASK | SYS_ENABLE_LO_RXFCE_ENABLE_BIT_MASK, 0x0 , DWT_ENABLE_INT_ONLY );
  attachInterrupt(PIN_IRQ, isr, RISING);

Hope that’s somehow helpful. Another idea could be to take a closer look at the Challenger RP2040 UWB modules. They have a github with a cleaned up dw3000 lib.

Hi @chris91 @gerhenz

I am a student and using DW3000 for research, I want to do UWB communication in interrupt mode instead of polling mode, but I am not able to implement it well!.

I want to use interrupt mode to realize simple_rx processing.

The remaining problem is that the IRQ pin does not switch between HIGH and LOW as data is received.

[Hardware Information]
・Microcontroller is XIAO ESP32 S3
・GPIO number of IRQ pin is 2

I added interrupt processing to the original program.
The program is only about 80 lines long, so could you please take a look at it a little to see if there are any problems?
I’ll show the code below.

Thank you.

#include "dw3000.h"

#define APP_NAME "INTERRUPT RX v1.2"

// connection pins
const uint8_t PIN_RST = 1; // reset pin
const uint8_t PIN_IRQ = 2; // irq pin
const uint8_t PIN_SS = 3;  // spi select pin

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 */
    DWT_PDOA_M0       /* PDOA mode off */
};

static uint8_t rx_buffer[FRAME_LEN_MAX];
uint32_t status_reg;
uint16_t frame_len;

// interrupt handler
void IRAM_ATTR dw3000_isr() {

  Serial.println("Enter dw3000_isr()"); // display that you have entered an interrupt function
  
  // processing of received data
  status_reg = dwt_read32bitreg(SYS_STATUS_ID);
  if (status_reg & SYS_STATUS_RXFCG_BIT_MASK) {
    frame_len = dwt_read32bitreg(RX_FINFO_ID) & RX_FINFO_RXFLEN_BIT_MASK;
    if (frame_len <= FRAME_LEN_MAX) {
      dwt_readrxdata(rx_buffer, frame_len - FCS_LEN, 0);
    }
    dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RXFCG_BIT_MASK);
    Serial.println("Frame Received"); // display that data was received.
  } else {
    dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_ALL_RX_ERR);
  }

  dwt_rxenable(DWT_START_RX_IMMEDIATE);
}

void setup() {
  Serial.begin(115200);
  delay(500);

  // SPI setting
  spiBegin(PIN_IRQ, PIN_RST);
  spiSelect(PIN_SS);
  delay(200);

  while (!dwt_checkidlerc()) {
    Serial.println("IDLE FAILED");
    while (1);
  }
  dwt_softreset();
  delay(200);
  if (dwt_initialise(DWT_DW_INIT) == DWT_ERROR) {
    Serial.println("INIT FAILED");
    while (1);
  }
  dwt_setleds(DWT_LEDS_ENABLE | DWT_LEDS_INIT_BLINK);
  if (dwt_configure(&config)) {
    Serial.println("CONFIG FAILED");
    while (1);
  }

  // Set interrupt pin handler
  attachInterrupt(PIN_IRQ, dw3000_isr, RISING);

  dwt_rxenable(DWT_START_RX_IMMEDIATE);

  delay(3000);
  Serial.print("SetUp Finished");
}

void loop() {
  /*
  // Intentionally switch PIN_IRQ between HIGH and LOW at 0.1 second intervals
  pinMode(PIN_IRQ, OUTPUT);
  digitalWrite(PIN_IRQ, HIGH);  // HIGH
  pinMode(PIN_IRQ, INPUT);

  delay(100);

  pinMode(PIN_IRQ, OUTPUT);
  digitalWrite(PIN_IRQ, LOW);  // LOW
  pinMode(PIN_IRQ, INPUT);
  */
}

Two things jump out at me:

  1. I can’t see you configuring the DW3000 interrupt.
    You can configure under what circumstances the device generates an interrupt and also which way up the interrupt is (active high or active low).
    As a general rule for any part you should explicitly set the conditions when interrupts are generated, the level they use to indicate an interrupt and whether they are enabled or not (there is often a master enable interrupts option flag somewhere). Once you’ve done that you should then explicitly clear all current interrupts.
    This is the sort of thing you should never trust the defaults on.

  2. printing out text over a uart inside an interrupt is generally a very bad idea. A very very very bad idea. It’s 1) slow and 2) printf (I think all the arduino print commands call printf under the covers) is not thread safe meaning it can cause crashes depending on what is going on. At most you should use putc to output a single character on function entry and any other notable conditions. Ideally you’d toggle a GPIO pin and monitor on a scope.

1 Like

@AndyA
Thank you for your advice!

finally, the code below have successed!

Changed point
・Set pull-ups : pinMode(PIN_IRQ, INPUT_PULLUP);
・Set call-bach function dwt_setcallbacks
・Use dwt_setinterrupt function
・Set the bit dwt_write32bitreg

#include "dw3000.h"

#define APP_NAME "SS TWR RESP v1.0"

// connection pins
const uint8_t PIN_RST = 1; // reset pin
const uint8_t PIN_IRQ = 2; // irq pin //割り込み DW → XIAO // 
const uint8_t PIN_SS = 3; // 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 */
};

/* Buffer to store received frame. See NOTE 1 below. */
static uint8_t rx_buffer[FRAME_LEN_MAX];

/* Hold copy of status register state here for reference so that it can be examined at a debug breakpoint. */
uint32_t status_reg;
/* Hold copy of frame length of frame received (if good) so that it can be examined at a debug breakpoint. */
uint16_t frame_len;

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(200); // 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)
      ;
  }

  dwt_softreset();
  delay(200);

  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 5 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)
      ;
  }

  pinMode(PIN_IRQ, INPUT_PULLUP);
  attachInterrupt(PIN_IRQ, dwt_isr, RISING);

  /* Register RX callback. */
  dwt_setcallbacks(NULL, rx_ok_cb, rx_err_cb, rx_err_cb, NULL, NULL);

  /* Enable wanted interrupts (RX good frames and RX errors). */
  dwt_setinterrupt(SYS_ENABLE_LO_RXFCG_ENABLE_BIT_MASK | SYS_STATUS_ALL_RX_ERR, 0, DWT_ENABLE_INT);

  /* Clearing the SPI ready interrupt */
  dwt_write32bitreg(SYS_STATUS_ID, SYS_STATUS_RCINIT_BIT_MASK | SYS_STATUS_SPIRDY_BIT_MASK);

  dwt_rxenable(DWT_START_RX_IMMEDIATE);

}

void loop()
{
  
}
/*! ------------------------------------------------------------------------------------------------------------------
 * @fn rx_ok_cb()
 *
 * @brief Callback to process RX good frame events
 *
 * @param  cb_data  callback data
 *
 * @return  none
 */
static void rx_ok_cb(const dwt_cb_data_t *cb_data)
{
    Serial.println("success"); //this should be deleted
    dwt_rxenable(DWT_START_RX_IMMEDIATE);
}

/*! ------------------------------------------------------------------------------------------------------------------
 * @fn rx_err_cb()
 *
 * @brief Callback to process RX error and timeout events
 *
 * @param  cb_data  callback data
 *
 * @return  none
 */
static void rx_err_cb(const dwt_cb_data_t *cb_data)
{
    Serial.println("error"); //this should be deleted
}