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.

    1. Is the MISO line connected?
    2. Does the read function work if you place everything in main.c of the SPIM example?

    "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." 
    - yeah we know, I think it will use the new file once you submit the post though. 

    Also, spi_config.ss_pin = SPI_SS_PIN != NRF_DRV_SPI_PIN_NOT_USED ? SPI_SS_PIN : NRF_DRV_SPI_PIN_NOT_USED; //this if statement does nothing, you might as well just use spi_config.ss_pin = SPI_SS_PIN; 

  • Thank you for your fast reply.

    The if-statement on the slave select pin indeed does nothing. I implemented it when I used a gpio for slave select and still have that code in case I am going to need it in future. That is also why I haven't removed that condition yet. As I see it it should not cause any problems though.

    1. It has to be properly connected. This is proven by the debug terminal which outputs the 0x19 every time I write a configuration.

    <info> app:  Received:
    <info> app:  19                     |.  

    I can also see the spi_xfer_done being toggled using a watch in the debugger.

    2.1. I have loaded the spim example (\examples\peripheral\spi\) onto the board and edited m_tx_buf to {136}. Scope shows: 

    MOSI [10001000, 11111111]    (0x88, 0xFF)

    MISO [00011001, 00000000]    (0x19, 0x00)

    The debug terminal doesn't show anything, but I can see m_rx_buf containing [0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]. The code also does not get stuck, opposed to my own project.

    2.2. I have copied peripheral.h and peripheral.c almost completely (excluding some irrelevant code) into main.c of the example. Added my configuration of the internal SPI peripheral and initialization of the external peripheral, and substituted the content of the while-loop to let it read a register (1000 ms delay). This is running correctly and I can see the m_rx_buf reflect the data.

    One thing I have noticed here is that it hits a certain breakpoint (nrf_drv_spi_transfer()) 4 times before it continues. On the third time it actually sends and receives as I can see on the scope and rx_buffer watch. I suspect this has something to do with timings. I have not observed this at any point before.

    I'm not sure what to conclude from this. To me it seems the only difference is the code being in 3 files or only main.c to get it to work

    Edit: Thinking about it, my projectis advertising as per examples\ble_peripheral\ble_app_template. I have merged these 2 examples. Maybe this could be of an influence?

    Edit2: To expand on my previous thought. I have added the read to trigger continuously in main() with a 500 ms delay instead of at the press of a button. This works for a few times until I get a "SOFTDEVICE: ASSERTION FAILED" error.

    The button I'm using triggers a timer to execute the reading a few times.

  • I have tried finding what causes spi_event_handler not to get called in my project. I have set breakpoints at

    - nrf_drv_spi.c line 94

    - nrfx_spi.c line 234, 416 and 423

    None of these breakpoints are getting triggered in my own project when reading registers. They do however trigger when writing registers.

  • You really should use the SPIM peripheral and not the SPI peripheral, as this will play a lot better with the SoftDevice and the rest of your application. 

  • I have been hesitant to do this since SPI seemed more straight forward without having to worry about easyDMA. I will try to change it to SPIM.

    I am using a timer to start the SPI transfer, I also have 2 other timers running not related to SPI. I have not found anything indicating it, but could this potentially be of influence?

Related