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

SPI MISO buffer doesn't fill

Hello,

I am using an nRF52840PDK (master) to use an SPI peripheral, where my code is based on examples\peripheral\spi. My IDE is Segger Embedded Studio and I am using SDK 15.0.0. I am using an oscilloscope to visualize the SPI bus activity.

I start of by configuring the SPI peripheral hardware on the nRF52840. Afterwards I write my configurations into the external peripheral registers over SPI. All of this works as expected.

Following is my problem:

I am trying to read from one of the registers. To do this I write my command byte and want to read 2 bytes (1 status & 1 data). I can see both MOSI and MISO lines reflect the correct data with the oscilloscope. The data does however not seem to be getting loaded into the receive buffer.

Using the debugger I can see being stuck in an endless loop (peripheral.c, register_read(), thus the spi_event_handler is not called to set spi_transfer_done = true. My first suspicion was my event_handler pointer. This works during writing so should also work during reading. Using a watch on the rx_buffer shows me that it is not being filled.

Below this is are scope images and my relevant code. At the very bottom is the concrete question.

Both of the devices are in SPI mode 0.

Yellow = MOSI - blue = MISO - grey line = SCLK 2 periods (external trigger edited in manually)      ----      The first MISO byte is a status byte and discarded, second byte is data

Image writing register 17:

 MOSI [command,  data] - MISO [status]

Image reading register 17:

 MOSI[command] - MISO[status, data]

main.c:

static const nrf_drv_spi_t peripheral = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE);       /**< SPI instance */
const nrf_drv_spi_t *p_spi_instance = &peripheral;
static volatile bool spi_xfer_done;                                             /**< Flag used to indicate that SPI instance completed the transfer */

extern void *p_spi_event_handler;
void spi_config(void)
{
  nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
  spi_config.sck_pin  = SPI_SCK_PIN;
  spi_config.miso_pin = SPI_MISO_PIN;
  spi_config.mosi_pin = SPI_MOSI_PIN;
  spi_config.ss_pin = SPI_SS_PIN != NRF_DRV_SPI_PIN_NOT_USED ? SPI_SS_PIN : NRF_DRV_SPI_PIN_NOT_USED;
  APP_ERROR_CHECK(nrf_drv_spi_init(p_spi_instance, &spi_config, p_spi_event_handler, NULL));
}

int main(void)
{
    //SPI configuration
    spi_config();
    //Peripheral initialization
    peripheral_init(&spi_xfer_done);

    // Enter main loop.
    for (;;)
    {
        idle_state_handle();
    }
}

peripheral.h:

#ifndef PERIPHERAL_H__
#define PERIPHERAL_H__

#include "nrf_drv_spi.h"

extern const nrf_drv_spi_t *p_spi_instance;                                      /**< Pointer to SPI instance */

#endif  //PERIPHERAL_H__

peripheral.c:

#include "peripheral.h"
#include "nrf_gpio.h"
#include "nrf_log.h"
#include "nrf_drv_spi.h"
#include <stdint.h>

static volatile bool *p_spi_xfer_done;                                           /**< Pointer to SPI_xfer_done flag */
static uint8_t m_tx_buf[2] = {};                                                /**< TX buffer */
static uint8_t m_rx_buf[sizeof(m_tx_buf) + 1];                                  /**< RX buffer */
static const int8_t tx_length = sizeof(m_tx_buf);                               /**< Transfer length */
static const int8_t rx_length = sizeof(m_rx_buf);                               /**< Transfer length */

/**@brief Reads data from a register
 */
static void register_read(peripheral_registers_t register_to_access, int rx_length)
{
  peripheral_result_codes_t result;
  uint8_t command;

  memset(m_tx_buf, 0, tx_length);
  memset(m_rx_buf, 0, rx_length);
  *p_spi_xfer_done = false;

  m_tx_buf[0] = command_byte_set(register_to_access, read);    //Left shift 3 & set 2nd bit 0
  APP_ERROR_CHECK(nrf_drv_spi_transfer(p_spi_instance, m_tx_buf, 1, m_rx_buf, rx_length + 1));      //p_spi_instance = 0x0003be04

  while (!*p_spi_xfer_done)     //I get into an endless loop here
  {
    __WFE();
  }
}

/**@brief Writes data to a register
 */
static void register_write(peripheral_registers_t register_to_access, uint8_t data, int tx_length)
{
  peripheral_result_codes_t result;
  uint8_t command;

  memset(m_tx_buf, 0, tx_length);
  memset(m_rx_buf, 0, rx_length);
  *p_spi_xfer_done = false;

  m_tx_buf[0] = command_byte_set(register_to_access, write);    //Left shift 3 & set 2nd bit 1
  m_tx_buf[1] = data;         //append data to buffer

  APP_ERROR_CHECK(nrf_drv_spi_transfer(p_spi_instance, m_tx_buf, tx_length + 1, m_rx_buf, 1));

  while (!*p_spi_xfer_done)
  {
    __WFE();
  }
}

void spi_event_handler(nrf_drv_spi_evt_t const * p_event,
                       void *                    p_context)
{
    *p_spi_xfer_done = true;
    NRF_LOG_INFO("Transfer completed.");
    if (m_rx_buf[0] != 0)
    {
        NRF_LOG_INFO(" Received:");
        NRF_LOG_HEXDUMP_INFO(m_rx_buf, strlen((const char *)m_rx_buf));
    }
}
void (*p_spi_event_handler) = &spi_event_handler;       //Function pointer declared in main.c

/**@brief Initialize peripheral at startup
 */
void peripheral_init(volatile bool *spi_xfer_done)
{
  p_spi_xfer_done = spi_xfer_done;

  register_init(17, 0, 0, 0, 1, 1, 0, 1, 0);        //creates byte with configuration value and calls register_write
}

static void register_init(peripheral_registers_t register_to_access,
                     uint8_t bit_value7,
                     uint8_t bit_value6,
                     uint8_t bit_value5,
                     uint8_t bit_value4,
                     uint8_t bit_value3,
                     uint8_t bit_value2,
                     uint8_t bit_value1,
                     uint8_t bit_value0)
{
  uint8_t register_value = 0;
  uint8_t bit_value_array[8] = {bit_value0, bit_value1, bit_value2, bit_value3, bit_value4, bit_value5, bit_value6, bit_value7};   //Array and binary MSB/LSB are reverse in order!

  for(int8_t bit_shift = 7; bit_shift >= 0; bit_shift--)
  {
    //Shift configuration bit into the right place of the register value
    register_value |= shift_bit(register_value, bit_value_array[bit_shift], bit_shift);
  }
  register_write(register_to_access, register_value, 1);
}

/**@brief Read register 17 on button press
 */
void peripheral_read_17(void)
{
  uint8_t result;
  register_read(17, 1);
}

Regarding the SPI mode: the nRF52840 is in mode0 (SCK active high, sample on leading edge of clock) while the documentation of my peripheral reads:

"[...] These signals are represented in the form (CPOL, CPHA). An
interface that expects both positive edge SCKS and the MOSI data to be available before the first
positive clock edge, can operate in modes (0,0) and (1,1) without alteration. […]"

My question is: Why am I getting stuck in this endless loop and how can I fix it?

Thank you in advance (this is quite long),

Kevin

Edit: Corrected image discription

P.s.: I think there is a bug where I can't upload, remove, then reupload a different image with the same name. It will then show the first image again in the editor.

Parents Reply Children
  • Hello again, sorry for the delay as I've had a week off.

    I have looked into using the SPIM peripheral and the examples. Specifically the SPI Master example as I have been using this from the start. It seems to me as if this uses the SPI peripheral making use of the older nrf driver. Is this correct?

    It seems to me as to use SPIM I should also make sure I use the nrfx driver. To ensure this I have been commenting the legacy layer SPI  configurations in sdk_config.h out. Is this the right way to go?

    Lastly, should I create the SPI instance as in the SPI master example (NRF_DRV_SPI_INSTANCE() macro) or the nrfx version? I guess this ties into my other questions.

    Is there anything I should specifically watch for to transition to SPIM?

    Edit: I think the best question would be: Can you link the api reference from the infocenter of the SPIM master driver I should be using (e.g. this)?

  • Use nrfx_spim_init and nrfx_spim_xfer

    "To ensure this I have been commenting the legacy layer SPI  configurations in sdk_config.h out. Is this the right way to go?"

    -No, not really. You should leave it as is, and enable the SPI instance in the legacy layer SPI of sdk_config.h, I believe the nrfx driver has an #ifdef or two that requires it. 

    "Lastly, should I create the SPI instance as in the SPI master example (NRF_DRV_SPI_INSTANCE() macro) or the nrfx version?"
    - Yes. 


    I suggest you try this out and verify the implementation by reading the SPIM registers before and after you've called nrfx_spim_xfer (debug session). That way you can see if the SPIM peripheral is set up correctly, and what might be missing, like enabled interrupts, clk frequency, CPOL/CPHA config, RXD/TXD pointers, etc. 

  • Thank you for your clear response, but I have not managed to solve it completely yet. The received data now properly reflect in the receive buffer. Despite this I'm still stuck in the infinite loop in register_read().

    I have changed everything SPI related to use the SPIM driver. With this I have had nrf_drv_spi enabled and also disabled (and commented out). I do not see any difference in behaviour between these two. I have also used regular SPI, which yields the same result.

    This infinite loop only occurs when I trigger a transfer by pressing a button. Any transfer started in main(), whether for configuration or repeatedly reading a register, works perfectly normal. I don't have any timers running anymore to eliminate possibly disruptive sources, so the only difference I can think of is having the button or not. Note that the softdevice is running in every scenario.

    Is there anything else I could try, or is it not possible to have an SPI transaction being triggered by a button?

  • "Is there anything else I could try, or is it not possible to have an SPI transaction being triggered by a button?"
    - It is certainly possible.

    Do you mind sharing your updated code and show us where you're stuck in a loop?

  • Ok, so I have created some pre-processor conditions so I can easily swap between SPI and SPIM. I will show both in case I've made a mistake.

    Near the bottom of main() is a for(;;) loop to test continuous reading which works fine. Commenting this out and triggering the same read with a button enters an infinite while-loop in register_read() (peripheral.c). The buffer reflects the correct data, the event handler is simply not being called

    main.c

    //Button
    #define USBBUTTON_BUTTON                BSP_EVENT_KEY_1                         /**< Button to start search for USB device */
    //SPI
    #define SPIM_INSTANCE                   0                                       /**< SPI instance index */
    #if SPIM_DRIVER
    #define SPIM_FREQUENCY                  NRF_SPIM_FREQ_4M                        /**< SPIM frequency */
    //#define SPIM_HW_SS                      true                                    /**< SPIM use hardware slave select - only with SPIM3 */
    #else
    #define SPI_FREQUENCY                   NRF_DRV_SPI_FREQ_4M                     /**< SPI frequency */
    #endif
    #define SPIM_SCK_PIN                    3                                       /**< SPI_SCK_PIN - pin number */
    #define SPIM_MOSI_PIN                   4                                       /**< SPI_SCK_PIN - pin number */
    #define SPIM_MISO_PIN                   28                                      /**< SPI_SCK_PIN - pin number */
    #define SPIM_SS_PIN                     29                                      /**< SPI_SCK_PIN - pin number */
    //#define CUSTOM_SS_PIN                   29                                      /**< CUSTOM_SS_PIN - pin number */
    
    #if SPIM_DRIVER
    static const nrfx_spim_t peripheral = NRFX_SPIM_INSTANCE(SPIM_INSTANCE);       /**< SPIM instance */
    const nrfx_spim_t *p_spim_instance = &peripheral;
    #else
    static const nrf_drv_spi_t peripheral = NRF_DRV_SPI_INSTANCE(SPIM_INSTANCE);       /**< SPI instance */
    const nrf_drv_spi_t *p_spi_instance = &peripheral;
    #endif
    static volatile bool spi_xfer_done;                                             /**< Flag used to indicate that SPI instance completed the transfer */
    
    
    void usb_timers_start(void)     //Triggered on button press
    {
      peripheral_read_17();
    }
    
    extern void *p_spi_event_handler;
    void spi_config(void)
    {
    #if SPIM_DRIVER
      nrfx_spim_config_t spim_config = NRFX_SPIM_DEFAULT_CONFIG;
      spim_config.sck_pin = SPIM_SCK_PIN;
      spim_config.miso_pin = SPIM_MISO_PIN;
      spim_config.mosi_pin = SPIM_MOSI_PIN;
      spim_config.ss_pin = SPIM_SS_PIN != NRF_DRV_SPI_PIN_NOT_USED ? SPIM_SS_PIN : NRF_DRV_SPI_PIN_NOT_USED;
      spim_config.frequency = SPIM_FREQUENCY;
    //  spim_config.use_hw_ss = SPIM_HW_SS;
      APP_ERROR_CHECK(nrfx_spim_init(p_spim_instance, &spim_config, p_spi_event_handler, NULL));
    #else
      nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
      spi_config.sck_pin  = SPIM_SCK_PIN;
      spi_config.miso_pin = SPIM_MISO_PIN;
      spi_config.mosi_pin = SPIM_MOSI_PIN;
      spi_config.ss_pin = SPIM_SS_PIN != NRF_DRV_SPI_PIN_NOT_USED ? SPIM_SS_PIN : NRF_DRV_SPI_PIN_NOT_USED;
      spi_config.frequency = SPI_FREQUENCY;
      APP_ERROR_CHECK(nrf_drv_spi_init(p_spi_instance, &spi_config, p_spi_event_handler, NULL));
    #endif      //SPIM_DRIVER
      /*Configure GPIO as slave select for SPI*/
      //nrf_gpio_cfg_output(CUSTOM_SS_PIN);
      //nrf_gpio_pin_set(CUSTOM_SS_PIN);
    }
    
    static void bsp_event_handler(bsp_event_t event)
    {
        ret_code_t err_code;
    
        switch (event)
        {
            //default cases from example
            //..
            case USBBUTTON_BUTTON:
              usb_timers_start();
              break;
            default:
                break;
        }
    }
    
    int main(void)
    {
        bool erase_bonds;
    
        // Initialize.
        log_init();
        timers_init();
        buttons_leds_init(&erase_bonds);
        power_management_init();
        ble_stack_init();
        gap_params_init();
        gatt_init();
        advertising_init();
        services_init();
        conn_params_init();
        peer_manager_init();
    
        //SPI configuration and initialization
        spi_config();
        //peripheral initialization
        peripheral_init(/*peripheral, */&spi_xfer_done);
    
        // Start execution.
        NRF_LOG_INFO("Template example started.");
        //application_timers_start();
        advertising_start(erase_bonds);
    
    //For testing - Comment this out for button to trigger peripheral_read_17();
    for(;;)      //This works normally
    {
    nrf_delay_ms(500);
      peripheral_read_17();
    }
    
    
        // Enter main loop.
        for (;;)
        {
            idle_state_handle();
        }
    }
    

    peripheral.h

    #ifndef PERIPHERAL_H__
    #define PERIPHERAL_H__
    
    #include "nrf_drv_spi.h"
    
    
    #define SPIM_DRIVER       1         /**< SPI driver = 0 - SPIM driver = 1 */
    
    #if SPIM_DRIVER
    extern const nrfx_spim_t *p_spim_instance;
    #else
    extern const nrf_drv_spi_t *p_spi_instance;                                      /**< Pointer to SPI instance */
    #endif      //SPIM_DRIVER
    
    #endif  //PERIPHERAL_H__

    peripheral.c

    #include "peripheral.h"
    #include "nrf_gpio.h"
    #include "nrf_log.h"
    #include "nrf_drv_spi.h"
    #include <stdint.h>
    
    static volatile bool *p_spi_xfer_done;                                           /**< Pointer to SPI_xfer_done flag */
    static uint8_t m_tx_buf[2] = {};                                                /**< TX buffer */
    static uint8_t m_rx_buf[sizeof(m_tx_buf) + 1];                                  /**< RX buffer */
    static const int8_t tx_buf_length = sizeof(m_tx_buf);                               /**< Transfer length */
    static const int8_t rx_buf_length = sizeof(m_rx_buf);                               /**< Transfer length */
    
    static void register_read(peripheral_host_registers_t register_to_access, int rx_length)
    {
      peripheral_host_result_codes_t result;
      uint8_t command;
    
      memset(m_tx_buf, 0, tx_buf_length);
      memset(m_rx_buf, 0, rx_buf_length);
      *p_spi_xfer_done = false;
    
      m_tx_buf[0] = command_byte_set(register_to_access, read);
    #if SPIM_DRIVER
      nrfx_spim_xfer_desc_t transfer_descriptor;                                      //SPIM
      transfer_descriptor.p_tx_buffer = m_tx_buf;
      transfer_descriptor.tx_length = 1;
      transfer_descriptor.p_rx_buffer = m_rx_buf;
      transfer_descriptor.rx_length = rx_length + 1;
      APP_ERROR_CHECK(nrfx_spim_xfer(p_spim_instance, &transfer_descriptor, 0));        //SPIM
    #else
      APP_ERROR_CHECK(nrf_drv_spi_transfer(p_spi_instance, m_tx_buf, 1, m_rx_buf, rx_length + 1));      //SPI
    #endif      //SPIM_DRIVER
    
      while (!*p_spi_xfer_done)                ////This is an infinite loop
      {
        __WFE();
      }
    }
    
    static void register_write(peripheral_host_registers_t register_to_access, uint8_t data, int tx_length)
    {
      peripheral_host_result_codes_t result;
      uint8_t command;
    
      memset(m_tx_buf, 0, tx_buf_length);
      memset(m_rx_buf, 0, rx_buf_length);
      *p_spi_xfer_done = false;
    
      m_tx_buf[0] = command_byte_set(register_to_access, write);
      m_tx_buf[1] = data;         //append data to buffer
    
    #if SPIM_DRIVER
      nrfx_spim_xfer_desc_t transfer_descriptor;                                      //SPIM
      transfer_descriptor.p_tx_buffer = m_tx_buf;
      transfer_descriptor.tx_length = tx_length + 1;
      transfer_descriptor.p_rx_buffer = m_rx_buf;
      transfer_descriptor.rx_length = 1;
      APP_ERROR_CHECK(nrfx_spim_xfer(p_spim_instance, &transfer_descriptor, 0));        //SPIM
    #else
      APP_ERROR_CHECK(nrf_drv_spi_transfer(p_spi_instance, m_tx_buf, tx_length + 1, m_rx_buf, 1));        //SPI
    #endif      //SPIM_DRIVER
    
      while (!*p_spi_xfer_done)
      {
        __WFE();
      }
    }
    
    void spi_event_handler(nrf_drv_spi_evt_t const * p_event,
                           void *                    p_context)
    {
        *p_spi_xfer_done = true;
        NRF_LOG_INFO("Transfer completed.");
        if (m_rx_buf[0] != 0)
        {
            NRF_LOG_INFO(" Received:");
            NRF_LOG_HEXDUMP_INFO(m_rx_buf, strlen((const char *)m_rx_buf));
        }
    }
    void (*p_spi_event_handler) = &spi_event_handler;                   //Function pointer declared in main.c
    
    ////Below this is the same as before
    
    void peripheral_init(volatile bool *spi_xfer_done)
    {
      p_spi_xfer_done = spi_xfer_done;
    
      register_init(17, 0, 0, 0, 1, 1, 0, 1, 0);        //creates byte with configuration value (0x1a) and calls register_write
    }
    
    static void register_init(peripheral_registers_t register_to_access,
                         uint8_t bit_value7,
                         uint8_t bit_value6,
                         uint8_t bit_value5,
                         uint8_t bit_value4,
                         uint8_t bit_value3,
                         uint8_t bit_value2,
                         uint8_t bit_value1,
                         uint8_t bit_value0)
    {
      uint8_t register_value = 0;
      uint8_t bit_value_array[8] = {bit_value0, bit_value1, bit_value2, bit_value3, bit_value4, bit_value5, bit_value6, bit_value7};   //Array and binary MSB/LSB are reverse in order!
    
      for(int8_t bit_shift = 7; bit_shift >= 0; bit_shift--)
      {
        //Shift configuration bit into the right place of the register value
        register_value |= shift_bit(register_value, bit_value_array[bit_shift], bit_shift);
      }
      register_write(register_to_access, register_value, 1);
    }
    
    /**@brief Read register 17 on button press
     * Expected result is 0x1a
     */
    void peripheral_read_17(void)
    {
      uint8_t result;
      register_read(17, 1);
    }

    When I follow the call to nrfx_spim_xfer (reading) I get up to nrfx_spim.c line 540, this seems to return NRFX_SUCCESS. After this I either enter the infinite while-loop or immediately get an error (softdevice assertion failed & spi_xfer_done symbol not found in watch). I assume getting the error is due to timing not working out after being stuck in the loop for a bit or stepping through the code.

    All of this is with NRFX_SPIM_ENABLED, NRFX_SPIM0_ENABLED, SPI_ENABLED, SPI0_ENABLED, SPI0_USE_EASY_DMA being enabled. The interrupt priorities of these are set to 7.

    Let me know if there is anything else you need to know

Related