System Hardfault when calling dwt_probe()

Hi all,

I am integrating our Cortex-M33 MCU board with DM3000 EVB (DWM3000EVB - Decawave) in IAR IDE project

Our MCU signal is 1.8V while DM3000EVB uses 3.3V signal, so I add volt level shifter in between.
Currently, I got system hard fault when it calls dwt_probe().

Here is my software setup:

1- I added this to the IAR linker script to place dwt_driver APIs in flash:

define exported symbol __dw_drivers_start = 0x08001180;
define exported symbol __dw_drivers_end. = 0x08008FFFF; //512KB
define region TEXT_DW_region = mem:[from __dw_drivers_start to __dw_drivers_end];
define block DW_DRIVERS with alignment = 4 { section *.dw_drivers};
place in TEXT_DW_region {block DW_DRIVERS};

2- I link the project with libdwt_uwb_driver-m33-hfp-6.0.7.a library

3- I run read_dev_id example

4- I probe 3V3 SPI signal on the DM3000EVB side and see it outputs DEV_ID correctly when dwt_probe() calls my platform-specific readspi() function. Then I see it does not exit dwt_probe() but jumps to hardfault handler forever.

I read DW3XXX_Software_API_Guide_2p2.pdf document to see it mentions about populating “struct dwt_driver_s dw3000_driver” but I do not see such structure defined in the example code for STM32F429 Nucleo EVK or nRF52840 EVK

Should I need to populate “struct dwt_drivers”? Can you provide a example about populating that structure?

Best regards,

Can you write your own code to test your SPI layer, at least as far as reading DEV_ID? I just want to make sure that you’ve got things like the SPI mode set up right. Specifically, if you push five 0x00 bytes, you should get 0xDECA0302 back for the last four.

Spi most likely.

The second problem is that you specify the address of the end of the __dwt_driver_end symbol.

The way dwt_probe() is organized is that there is an iterator of N driver structures, so you read chip id via spi, then compare to the driver struct, if matches - use that driver.

One problem is that if spi does not read correct chip id.

Second problem is that say if we reach the final N driver struct, the dwt_probe() will keep iterate and that obviously would end to an error.

I would recommend you dump the downloaded code and check memory where drivers structs placed.

I confirmed that dwt_probe() calls our platform-specific spiread() implementation and I also probed SPI lines to see that DM3000 returns correct CHIP_ID which is 0xDECA0302.

The second problem is that you specify the address of the end of the __dwt_driver_end symbol.
Second problem is that say if we reach the final N driver struct, the dwt_probe() will keep iterate and that obviously would end to an error.
I do not understand what “driver struct” here. Do you mean “struct dwt_driver_s dw3000_driver”?
Should I need to implement such structure in the code? I do not see any reference implementation for that structure in file that I downloaded on Qorvo website.

Can you explain more about driver struct and how to support it in new MCU platform?

Best regards,

Hi all,

I corrected the value for __dw_drivers_end to 0x08008FFF.
Is that the issue mentioned by Alliv by this comment?

The second problem is that you specify the address of the end of the __dwt_driver_end symbol.


Hi all,

I can resolve the issue now.
I need to place this section at the address in flash specified by __dw_drivers_start in IAR linker script:
define exported symbol __dw_drivers_start = 0x08001180;
define exported symbol __dw_drivers_end. = 0x0800119F;
define region TEXT_DW_region = mem:[from __dw_drivers_start to __dw_drivers_end];
define block DW_DRIVERS with alignment = 4 { section .dw_drivers};
place in TEXT_DW_region { block DW_DRIVERS};

Now I can some examples fine.
Thanks for the support. I will post questions when I evaluate more DM3000 features.

Best regards,

Hi @BenFossil, thanks for the description of how-to link the ABI drivers’ library in IAR. That also shows that the EABI is a working mechanism, which is great.

A contribution to people who use STM32CubeIDE (I don’t know way Qorvo does not explain this in the API guide !!! )

  1. Add in the linker script what is told in the API guide
    /* The program code and other data into “FLASH” Rom type memory /
    .text :
    . = ALIGN(4);
    __dw_drivers_start = .;
    __dw_drivers_end = .;
  2. Do not include the static library in the regular way, IT WILL NOT WORK! Do this instead (in my project I am using M4 core so I picked the suitable API library)

So you leave Libraries (-I) empty BUT in MCU G++ Linker Miscellaneous you add in “other flags” what is shown in the picture (remember to use a suitable API library for your core)

That´s it, easy if somebody tells you. Qorvo, why don’t you?

still have no idea with Keil. Can anyone share his tips on the linker script using Keil?

Keil can use scatter files for custom memory layouts. The ones discussed here are GNU linker scripts. There is a “use memory layout from dialog” option that needs to be unchecked and it’ll use a scatter file that you can provide. It’s been a long time since I used Keil, so that might have changed. But it should definitely be possible.

In general I don’t get why it was implemented this way. You have to call an initialize function anyway, which would have made this way more portable than using a custom memory layout for config data.

hi, GeeF
Yes, you’re right. I unlock the “use memory layout from dialog” and change the sct file as followed:

LR_IROM1 0x00027000 0x000D9000 { ; load region size_region
ER_IROM1 0x00027000 0x000D9000 { ; load address = execution address
*.o (RESET, +First)
.ANY (+RO)
.ANY (+XO)

DECA 0x20002AE8 0x400 {
.ANY (.dw_drivers)

RW_IRAM1 0x20002EE8 0x0003D118 { ; RW data
.ANY (+RW +ZI)

Additionally, in main.c file, i add these sentences:

typedef struct dwt_driver_s DWT_DRIVER;
extern uint32_t Image$$DECA$$Base;
extern uint32_t Image$$DECA$$Limit;
DWT_DRIVER *__dw_drivers_start = (DWT_DRIVER *)&Image$$DECA$$Base;
DWT_DRIVER *__dw_drivers_end = (DWT_DRIVER *)&Image$$DECA$$Limit;

then, after adding libdwt_uwb_driver-m4-sfp-6.0.7.a into the project as a library file, the project compile and run successfully. However, i faced the same issue mentioned here The read_id example successfully dwt_probe() but hardfault at next dwt_readdevid(). I also take a look at the SPI line, and DECA0312 is transfered but no more.
The PCB Board is ok, as it can run all the example supplied by qrovo using SES platform. So i doubt there’s still having problem in the linker scripts. For example, in the .map file, nothing is stored in the .dw_drivers section:

Hi Bruno,

Otherwise, after doing your steps i get the 0xDECA0302 but the dwt_probe is return -1. Do you have any idea for this problem too ?

Here is a function to do simple_tx (no interrupts so you make sure it works, disable DWM3000 interrupts before!!!). It works, and we used some functions built by ourselves that I attached at the end of the simple_tx() function. Try it !

int simple_tx(void)
uint32_t dev_id;

/* Configure SPI rate, DW3000 supports up to 36 MHz */

wakeup_device_with_io(); //without this it hardfaults

/* Reset DW IC */
reset_DWIC(); /* Target specific drive of RSTn line into DW IC low for a period. */

Sleep(2); // Time needed for DW3000 to start up (transition from INIT_RC to IDLE_RC, or could wait for SPIRDY event)

/* Probe for the correct device driver. */
dwt_probe((struct dwt_probe_s *)&dw3000_probe_interf);  // if no hard fault it is ok

dev_id = dwt_readdevid();
// dev_id = my_dwm3000_read32bitoffsetreg_packeted(0,0); //reads the devID directly. CHECKING THAT myRead32bit works, the code should work normally.
while (!dwt_checkidlerc()) /* Need to make sure DW IC is in IDLE_RC before proceeding */ { };

if (dwt_initialise(DWT_DW_INIT) == DWT_ERROR)
    while (1) { }; // you can retry but in this case just a forever loop

uint32_t CHAN_CTRL_reg = my_dwm3000_read32bitoffsetreg_packeted(0x01,0x14);
uint32_t RX_FINFO_reg = my_dwm3000_read32bitoffsetreg_packeted(0x00,0x4C); //we want to read RXPRF

/* if the dwt_configure returns DWT_ERROR either the PLL or RX calibration has failed the host should reset the device */
if (dwt_configure(&config2))
    while (1) { }; // you can retry but in this case just a forever loop

CHAN_CTRL_reg = my_dwm3000_read32bitoffsetreg_packeted(0x01,0x14);
RX_FINFO_reg = my_dwm3000_read32bitoffsetreg_packeted(0x00,0x4C); //we want to read RXPRF

 * Setup the txconfig options. these ones are in an extern in the examples, we don't know if its necessary to do it
 * or if it has preloaded default values. let's put them in for good measure.
dwt_txconfig_t txconfig_options2 = {
    0x34,       /* PG delay. */
	0xfdfdfdfd, /* TX power. */ //alternate, 0x3e3e3e3e
    0x0         /*PG count*/
/* Configure the TX spectrum parameters (power PG delay and PG Count) */

/* Loop forever sending frames periodically. */
while (1)
	dwt_error_e dw_error = DWT_ERROR; //read them using breakpoints and debugging
	/* Write frame data to DW IC and prepare transmission. See NOTE 3 below.*/
	dw_error = dwt_writetxdata(FRAME_LENGTH - FCS_LEN, tx_msg, 0); /* Zero offset in TX buffer. */
    /* In this example since the length of the transmitted frame does not change,
     * nor the other parameters of the dwt_writetxfctrl function, the
     * dwt_writetxfctrl call could be outside the main while(1) loop.
	dwt_writetxfctrl(FRAME_LENGTH, 0, 0); /* Zero offset in TX buffer, no ranging. */
    uint32_t TX_FCTRL_low_reg = my_dwm3000_read32bitoffsetreg_packeted(0x00,0x24); //we want TXBR

    /* Start transmission. */
    dw_error = dwt_starttx(DWT_START_TX_IMMEDIATE);

    /* Hold copy of status register state here for reference so that it can be examined at a debug breakpoint. */
	uint32_t status_reg;

    /* Poll DW IC until TX frame sent event set. See NOTE 4 below.
     * STATUS register is 4 bytes long but, as the event we are looking
     * at is in the first byte of the register, we can use this simplest
     * API function to access it.*/
    waitforsysstatus(&status_reg, NULL, DWT_INT_TXFRS_BIT_MASK, 0);

    status_reg =  dwt_readsysstatuslo();

    //uint32_t status_reg_byHand = my_dwm3000_read32bitoffsetreg_packeted(0x00,0x44);

    uint32_t DWT_INT_TXFRS_BIT_flag   = !(!(status_reg & DWT_INT_TXFRS_BIT_MASK   ) ) ; //turns from "zero or non-zero value" to logic 1 or 0 so the debugger puts nicer numbers
	uint32_t DWT_INT_TXPHS_BIT_flag   = !(!(status_reg & DWT_INT_TXPHS_BIT_MASK   ) ) ;
	uint32_t DWT_INT_TXPRS_BIT_flag   = !(!(status_reg & DWT_INT_TXPRS_BIT_MASK   ) ) ;
	uint32_t DWT_INT_TXFRB_BIT_flag   = !(!(status_reg & DWT_INT_TXFRB_BIT_MASK   ) ) ;
	uint32_t DWT_INT_CP_LOCK_BIT_flag = !(!(status_reg & DWT_INT_CP_LOCK_BIT_MASK ) ) ;

	if(status_reg & (DWT_INT_TXFRS_BIT_MASK | DWT_INT_CP_LOCK_BIT_MASK) ){ //we want to make sure both flags are on to describe a "txOK".
		HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    /* Clear TX frame sent event. */

// status_reg = dwt_readsysstatuslo(); //commented this out because we already know it works.
// DWT_INT_TXFRS_BIT_flag = !(!(status_reg & DWT_INT_TXFRS_BIT_MASK ) ) ;
// DWT_INT_TXPHS_BIT_flag = !(!(status_reg & DWT_INT_TXPHS_BIT_MASK ) ) ;
// DWT_INT_TXPRS_BIT_flag = !(!(status_reg & DWT_INT_TXPRS_BIT_MASK ) ) ;
// DWT_INT_TXFRB_BIT_flag = !(!(status_reg & DWT_INT_TXFRB_BIT_MASK ) ) ;
// DWT_INT_CP_LOCK_BIT_flag = !(!(status_reg & DWT_INT_CP_LOCK_BIT_MASK ) ) ;

    /* Execute a delay between transmissions. */


    /* Increment the blink frame sequence number (modulo 256). */


// auxiliaryFunctions

uint32_t usTime_to_dwTime(uint32_t usTime){
return usTime * 5120 / 4992;

void my_dwt_goSleep(void){
dwt_configuresleep(DWT_CONFIG, 0x20 | 0x08 | 0x02 | 0x01 ); //not real thought behind the flags, just something that should make it sleep without a wakeuptimer

void my_dwm3000_wakeup(void){
HAL_GPIO_WritePin(DW_CS_GPIO_Port, DW_CS_Pin, 0);
HAL_Delay(1); //documentation states 500us, we may lower this whenever we have the time to do so.
HAL_GPIO_WritePin(DW_CS_GPIO_Port, DW_CS_Pin, 1);

void my_dwm3000_disableFF(void){
dwt_configureframefilter(DWT_FF_DISABLE, 0);

void my_dwt_setDeviceID(uint8_t* our_deviceID){

uint32_t my_dwm3000_read32bitoffsetreg_packeted(uint8_t regfile, uint8_t offset){

//Create the header

uint16_t tempHeader = ( (regfile << 9) | 0x4000 ) | (offset<<2);

uint8_t myBuffer[4] = {0xff,0xff,0xff,0xff};
uint8_t* checkBuffer = myBuffer;
uint8_t myHeader[2] = { (uint8_t)(tempHeader >> 8) , (uint8_t) tempHeader};
readfromspi(2, myHeader, 4, myBuffer); //we try to read SYS_STATUS directly.
uint32_t myBufferPacketed = 0;

for(int i = 3; i >= 0 ; i--){
	myBufferPacketed |= myBuffer[i];
	if (i > 0){
		myBufferPacketed = myBufferPacketed << 8;

return myBufferPacketed;


void my_dwm3000_readRXBuffer(uint8_t regfile, uint8_t offset, uint8_t* buffer, uint8_t len){

//Create the header

uint16_t tempHeader = ( (regfile << 9) | 0x4000 ) | (offset<<2);

uint8_t myBuffer[4] = {0};
uint8_t myHeader[2] = { (uint8_t)(tempHeader >> 8) , (uint8_t) tempHeader};
readfromspi(2, myHeader, len, buffer); //we try to read SYS_STATUS directly.


1 Like

I’m blocked at the dwt_readdevid(), might be the linker script that is not functioning ? Here’s is a portion of my linker script. When this function is called i’m receiving 0x0203CADE like the dwt_probe(), is it ok?
FLASH (rx) : ORIGIN = 0x8006000, LENGTH = 0xb8000
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x10000



.text :
linker_vectors_begin = .;
linker_vectors_end = .;

__Vectors_End = .;
__Vectors_Size = __Vectors_End - __Vectors;

linker_code_begin = .;
linker_code_end = .;


. = ALIGN(4);
__dw_drivers_start = .;
__dw_drivers_end = .;

/* .ctors */
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)

/* .dtors */
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)



Edit : This function is giving me the device ID but not the typical (dwt_readdevid)
dev_id = my_dwm3000_read32bitoffsetreg_packeted(0x00,0x00);


I am stuck with the same problem using an STM32 MCU. Configured Linker script according to API docs. SPI reads the correct device ID but dwt_probe returns -1. Have you found a solution in the meantime?

Hello, may I ask if your IAR project can be shared? I haven’t been able to connect

Hey I am facing the same issue. Were you able to fix it.

I found that IAR 9.32 did not pull in the necessary drivers once I added the above. My icf file modifications looks like, which did not add the proper drivers.

define exported symbol __dw_drivers_start = ICFEDIT_intvec_start + 1024;
define exported symbol __dw_drivers_end = __dw_drivers_start + 128;
define region TEXT_DW_region = mem:[from __dw_drivers_start to __dw_drivers_end];
define block DW_DRIVERS with alignment = 4 { section *.dw_drivers};
place in TEXT_DW_region { block DW_DRIVERS };

I had to add “dw3000_driver” to the Keep section in Options->Linker->Input: Eventually, I’ll get all of this into a separate icf file as UWB is optional for my projects.

IAR map file section:

STM IDE map file section:

For the life of me I can’t figure out why Qorvo requires a start & end section. I’m working on making the section float as opposed to being forced to a memory region.

You only need symbols from the linker such the driver “probe” would know where the array of struct describing driver starts and ends.

defining of the section(but you do not have to make it on a fixed address) is one approach;

Another approach would be to define array in your main code which has these start end symbols defined. You also can reduce the code size by attaching only 1 variant of driver to your code.

I think the code example was on this forum…