This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

SPI behaviour differs between debug and release modes

I have a custom board using NRF52832 which communicates with a BMI160 accelerometer/gyroscope chip by SPI.

I have been developing in Segger Embedded Studio (v5.10d) in debug mode, and I can configure the BMI160 and read data from the sensor without problems. 

However I have come across a strange issue: when I change from debug mode to release mode in the IDE, something goes wrong with the spi_read function.  In main() I have a simple check that the BMI160 is correctly initialised, which suddenly breaks in release mode:

  uint8_t tmpData = 0;

  uint8_t rslt = bmi160_get_regs(BMI160_CHIP_ID_ADDR, &tmpData, 1, &bmi160);
  // rslt = bmi160_get_regs(BMI160_ACCEL_RANGE_ADDR, &tmpData, 1, &bmi160);
  
  if(tmpData == BMI160_CHIP_ID)   // BMI160_CHIP_ID  BMI160_ACCEL_RANGE_4G  BMI160_ACCEL_RANGE_2G
  {
    ledOn(GREEN_LED_MASK);
  }
  else
  {
    ledOn(RED_LED_MASK);
  }

In debug mode, this works as expected (with line 4 commented out) - tmpData contains the correct BMI160_CHIP_ID value, and the green led goes on.

However in release mode, this doesn't work, and a second call to bmi160_get_regs() (i.e. line 4 uncommented) is required before tmpData == BMI160_CHIP_ID.  It doesn't matter what register the second get_regs reads - the result in tmpData after the two calls will be as it should be after the first. (In this example, if you were to add a third call of bmi160_get_regs() to any register, tmpData will hold the correct value for BMI160_ACCEL_RANGE_ADDR, and so on.)

I'm not sure whether it's a problem with my spi_read function as it seems to be fine otherwise, but here is that code:

int8_t bmi160_spi_read(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t length) 
{
  static uint8_t rx_data[SPI_BUFFER_LEN];

  ret_code_t ret = nrf_drv_spi_transfer(&spi, &reg_addr, length, rx_data, length + 1);
  
  while (!spi_xfer_done) 
  {
    __WFE();
  }

  if (ret == NRF_SUCCESS) 
  {
    for (int i = 0; i < length; i++) 
    {
      *(reg_data + i) = rx_data[i + 1];
    }
  }
 
  return (int8_t)ret;
}

Could there be some difference in spi clock speeds between debug and release modes causing this? (I haven't had a chance to put it on an oscilloscope yet but will have access to workshop tomorrow..)  Or something else I can't think of..?

Parents
  • Hi,

    Thanks for your reply - sorry, I should probably have included both points you mention in the original post.

    The bmi160_get_regs() function comes from the bmi160 driver provided by Bosch, and is as follows:

    int8_t bmi160_get_regs(uint8_t reg_addr, uint8_t *data, uint16_t len, const struct bmi160_dev *dev)
    {
        int8_t rslt = BMI160_OK;
    
        /* Variable to define temporary length */
        uint16_t temp_len = len + dev->dummy_byte;
    
        /* Variable to define temporary buffer */
        uint8_t temp_buf[temp_len];
    
        /* Variable to define loop */
        uint16_t indx = 0;
    
        /* Null-pointer check */
        if ((dev == NULL) || (dev->read == NULL))
        {
            rslt = BMI160_E_NULL_PTR;
        }
        else if (len == 0)
        {
            rslt = BMI160_READ_WRITE_LENGHT_INVALID;
        }
        else
        {
            /* Configuring reg_addr for SPI Interface */
            if (dev->interface == BMI160_SPI_INTF)
            {
                reg_addr = (reg_addr | BMI160_SPI_RD_MASK);
            }
            rslt = dev->read(dev->id, reg_addr, temp_buf, temp_len);
    
            if (rslt == BMI160_OK)
            {
                /* Read the data from the position next to dummy byte */
                while (indx < len)
                {
                    data[indx] = temp_buf[indx];
                    indx++;
                }
            }
            else
            {
                rslt = BMI160_E_COM_FAIL;
            }
        }
    
        return rslt;
    }
    

    The dev->read function pointer (line 30) in turn calls the user spi read function bmi160_spi_read() from the original post.

    In both debug and release mode, bmi160_get_regs() is always returning no error (BMI160_OK).

    As far as I can tell, tmpData should definitely be getting set correctly in bmi160_get_regs(). Using the debugger, the result of the two get_regs calls from the first code snippet in the original post are:

    Debug mode:

    bmi160_get_regs(BMI160_CHIP_ID_ADDR…)             -> data[0] = 0xD1       //  0xD1 is the correct chip id

    bmi160_get_regs(BMI160_ACCEL_RANGE_ADDR…) -> data[0] = 0x0C      //  0x0C is the correct accelerometer range

    Release mode:

    bmi160_get_regs(BMI160_CHIP_ID_ADDR…)              -> data[0] = 0x00 

    bmi160_get_regs(BMI160_ACCEL_RANGE_ADDR…) -> data[0] = 0xD1    //  0xD1 is the chip id that should have been returned last time

Reply
  • Hi,

    Thanks for your reply - sorry, I should probably have included both points you mention in the original post.

    The bmi160_get_regs() function comes from the bmi160 driver provided by Bosch, and is as follows:

    int8_t bmi160_get_regs(uint8_t reg_addr, uint8_t *data, uint16_t len, const struct bmi160_dev *dev)
    {
        int8_t rslt = BMI160_OK;
    
        /* Variable to define temporary length */
        uint16_t temp_len = len + dev->dummy_byte;
    
        /* Variable to define temporary buffer */
        uint8_t temp_buf[temp_len];
    
        /* Variable to define loop */
        uint16_t indx = 0;
    
        /* Null-pointer check */
        if ((dev == NULL) || (dev->read == NULL))
        {
            rslt = BMI160_E_NULL_PTR;
        }
        else if (len == 0)
        {
            rslt = BMI160_READ_WRITE_LENGHT_INVALID;
        }
        else
        {
            /* Configuring reg_addr for SPI Interface */
            if (dev->interface == BMI160_SPI_INTF)
            {
                reg_addr = (reg_addr | BMI160_SPI_RD_MASK);
            }
            rslt = dev->read(dev->id, reg_addr, temp_buf, temp_len);
    
            if (rslt == BMI160_OK)
            {
                /* Read the data from the position next to dummy byte */
                while (indx < len)
                {
                    data[indx] = temp_buf[indx];
                    indx++;
                }
            }
            else
            {
                rslt = BMI160_E_COM_FAIL;
            }
        }
    
        return rslt;
    }
    

    The dev->read function pointer (line 30) in turn calls the user spi read function bmi160_spi_read() from the original post.

    In both debug and release mode, bmi160_get_regs() is always returning no error (BMI160_OK).

    As far as I can tell, tmpData should definitely be getting set correctly in bmi160_get_regs(). Using the debugger, the result of the two get_regs calls from the first code snippet in the original post are:

    Debug mode:

    bmi160_get_regs(BMI160_CHIP_ID_ADDR…)             -> data[0] = 0xD1       //  0xD1 is the correct chip id

    bmi160_get_regs(BMI160_ACCEL_RANGE_ADDR…) -> data[0] = 0x0C      //  0x0C is the correct accelerometer range

    Release mode:

    bmi160_get_regs(BMI160_CHIP_ID_ADDR…)              -> data[0] = 0x00 

    bmi160_get_regs(BMI160_ACCEL_RANGE_ADDR…) -> data[0] = 0xD1    //  0xD1 is the chip id that should have been returned last time

Children
  • without knowing too much about this driver, it may look like there is an event handler that you should wait for somewhere. The reason why it is working in debug is that the release version may have some more optimization (particularly for time), so that the function that "reads the registers" (basically triggers the SPI transaction) returns, but the callback hasn't triggered yet. 

    So does the SPI have a callback? If yes, try to wait for the callback before you check the tmpData. The callback (if present) is set in the init function of your SPI. Something like this snippet from the SDK\examples\peripheral\spi example:

    APP_ERROR_CHECK(nrf_drv_spi_init(&spi, &spi_config, spi_event_handler, NULL));

    If the SPI is set up with an event handler, the SPI calls are none-blocking, meaning they will return before the SPI transactions are done. If you do not have an SPI handler the calls are blocking, meaning they will not return until the transaction is complete. 

    Depending on whether you are using the spim or spi driver this is implemented a bit differently, but looking at the SPIM implementation:

    From spim_xfer() in nrfx_spim.c:

        if (!p_cb->handler)
        {
            while (!nrf_spim_event_check(p_spim, NRF_SPIM_EVENT_END)){}
    
    #if NRFX_CHECK(NRFX_SPIM3_NRF52840_ANOMALY_198_WORKAROUND_ENABLED)
            if (p_spim == NRF_SPIM3)
            {
                anomaly_198_disable();
            }
    #endif
            if (p_cb->ss_pin != NRFX_SPIM_PIN_NOT_USED)
            {
    #if NRFX_CHECK(NRFX_SPIM_EXTENDED_ENABLED)
                if (!p_cb->use_hw_ss)
    #endif
                {
                    if (p_cb->ss_active_high)
                    {
                        nrf_gpio_pin_clear(p_cb->ss_pin);
                    }
                    else
                    {
                        nrf_gpio_pin_set(p_cb->ss_pin);
                    }
                }
            }
        }
        else
        {
            spim_int_enable(p_spim, !(flags & NRFX_SPIM_FLAG_NO_XFER_EVT_HANDLER));
        }

    So if there is a callback, p_cb->handler, the task will be triggered, and then return. 

    What you are seeing is probably that bmi160_get_regs() returns, but the callback is not yet triggered, so the data is not actually received. Then you trigger a new SPI read, but in the meantime, the callback is received, and the data is updated. Try to wait for the callback, and set spi_xfer_done = true in the callback (like in the example):

    volatile bool spi_xfer_done;
    
    void spi_event_handler(nrf_drv_spi_evt_t const * p_event,
                           void *                    p_context)
    {
        spi_xfer_done = true;
        ...
        
    }
    
    static void my_function()
    {
        uint8_t tmpData = 0;
        
        spi_xfer_done = false;
        uint8_t rslt = bmi160_get_regs(BMI160_CHIP_ID_ADDR, &tmpData, 1, &bmi160);
        while (spi_xfer_done == false)
        {
            // Wait
        }
        if (tmpData == BMI160_CHIP_ID)
        {
            ledOn(GREEN_LED_MASK);
        }
        ...
    }

    Try something like that.

    Best regards,

    Edvin

  • Hi, thanks for the response.

    The spi read function does indeed have a callback:

    void spi_event_handler(nrf_drv_spi_evt_t const * p_event,
                           void *                    p_context)
    {
       spi_xfer_done = true;
    }

    but unfortunately waiting for this to be completed before checking the register result did not change anything...

    Is there any other info I can provide that might give a clue? I believe I'm using the SPI driver (nrfx_spi_xfer) rather than SPIM.

    Thanks

Related