NRF52832 - Send and receive up to 300 bytes via SPI

Hello,

I have 2x PCA10040 dev kits connected via SPI.

Device #1 is talking to a custom board MCU by UART

Device #2 is connected by BT to my android app

The task is to relay communication between the android app and the custom board. Communication can happen at any time in any direction. Maximum payload is 300 bytes.

I set Device #1 as SPIM, and I created a test payload of 300 bytes which is to be sent every 500ms to the slave device (Device #2).

Since the maximum SPI payload is around 256, I put together some code that should send 2 transfers in case the payload is larger.

#define SPI_PACKET_SIZE 250
static uint8_t m_spi_tx_dma_buf[SPI_PACKET_SIZE + 1];
static uint8_t m_spi_rx_dma_buf[SPI_PACKET_SIZE + 1];

const nrfx_spim_t radio_spi = NRFX_SPIM_INSTANCE(0);

void radio_spi_init()
{
    nrfx_spim_config_t spi_config = NRFX_SPIM_DEFAULT_CONFIG;
    spi_config.frequency      = NRF_SPIM_FREQ_8M;
    spi_config.ss_pin         = SPIRADIO_CS;
    spi_config.miso_pin       = SPIRADIO_MISO;
    spi_config.mosi_pin       = SPIRADIO_MOSI;
    spi_config.sck_pin        = SPIRADIO_SCK;
    spi_config.bit_order = NRF_SPI_BIT_ORDER_LSB_FIRST;
    spi_config.ss_active_high = false;
    APP_ERROR_CHECK(nrfx_spim_init(&radio_spi, &spi_config, radio_spi_handler, NULL));
}

uint32_t radio_spi_write(const uint8_t *data, uint16_t len)
{
  uint32_t err_code;
  uint32_t pos = 0;
  uint8_t spi_tx_one_byte = 0;

  while (len > 0) {
      radio_spi_transfer_finished = false;
      if (len < SPI_PACKET_SIZE) {
        spi_tx_one_byte = len;
        m_spi_tx_dma_buf[0] = spi_tx_one_byte;
        memcpy(m_spi_tx_dma_buf + 1, &data[pos], len);
        len = 0;
      } else {
        spi_tx_one_byte = SPI_PACKET_SIZE;
        m_spi_tx_dma_buf[0] = spi_tx_one_byte;
        memcpy(m_spi_tx_dma_buf + 1, &data[pos], SPI_PACKET_SIZE - 1);
        pos += (SPI_PACKET_SIZE - 1);
        len -= (SPI_PACKET_SIZE - 1);
      }
              
      uint8_t rx[1];
      nrf_delay_ms(1);
      nrfx_spim_xfer_desc_t xfer_desc = NRFX_SPIM_XFER_TRX(m_spi_tx_dma_buf, spi_tx_one_byte, rx, 0);
      
      if (NRFX_SUCCESS != nrfx_spim_xfer(&radio_spi, &xfer_desc, 0)) {
        NRF_LOG_INFO("SPI WRITE FAILED!");
        goto write_exit;
      } else {
        //NRF_LOG_INFO("Sent %i bytes!",spi_tx_one_byte);
      }
      while (!radio_spi_transfer_finished)
        ;
      
    }
write_exit:
    return 0;
}


//the 300 bytes test payload

uint8_t spi_test_payload[] = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA,0xAA, 0xAA, 0xAA, 0xAA, 0xAA,0xAA, 0xAA, 0xAA, 0xAA, 0xAA,0xAA, 0xAA, 0xAA, 0xAA, 0xAA,0xAA, 0xAA, 0xAA, 0xAA, 0xAA,0xAA, 0xAA, 0xAA, 0xAA, 0xAA,0xAA, 0xAA, 0xAA, 0xAA, 0xAA,0xAA, 0xAA, 0xAA, 0xAA, 0xAA,0xAA, 0xAA, 0xAA, 0xAA, 0xAA,0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
                                0xBB, 0xBB, 0xBB, 0xBB, 0xBB,0xBB, 0xBB, 0xBB, 0xBB, 0xBB,0xBB, 0xBB, 0xBB, 0xBB, 0xBB,0xBB, 0xBB, 0xBB, 0xBB, 0xBB,0xBB, 0xBB, 0xBB, 0xBB, 0xBB,0xBB, 0xBB, 0xBB, 0xBB, 0xBB,0xBB, 0xBB, 0xBB, 0xBB, 0xBB,0xBB, 0xBB, 0xBB, 0xBB, 0xBB,0xBB, 0xBB, 0xBB, 0xBB, 0xBB,0xBB, 0xBB, 0xBB, 0xBB, 0xBB,
                                0xCC, 0xCC, 0xCC, 0xCC, 0xCC,0xCC, 0xCC, 0xCC, 0xCC, 0xCC,0xCC, 0xCC, 0xCC, 0xCC, 0xCC,0xCC, 0xCC, 0xCC, 0xCC, 0xCC,0xCC, 0xCC, 0xCC, 0xCC, 0xCC,0xCC, 0xCC, 0xCC, 0xCC, 0xCC,0xCC, 0xCC, 0xCC, 0xCC, 0xCC,0xCC, 0xCC, 0xCC, 0xCC, 0xCC,0xCC, 0xCC, 0xCC, 0xCC, 0xCC,0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
                                0xDD, 0xDD, 0xDD, 0xDD, 0xDD,0xDD, 0xDD, 0xDD, 0xDD, 0xDD,0xDD, 0xDD, 0xDD, 0xDD, 0xDD,0xDD, 0xDD, 0xDD, 0xDD, 0xDD,0xDD, 0xDD, 0xDD, 0xDD, 0xDD,0xDD, 0xDD, 0xDD, 0xDD, 0xDD,0xDD, 0xDD, 0xDD, 0xDD, 0xDD,0xDD, 0xDD, 0xDD, 0xDD, 0xDD,0xDD, 0xDD, 0xDD, 0xDD, 0xDD,0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
                                0xEE, 0xEE, 0xEE, 0xEE, 0xEE,0xEE, 0xEE, 0xEE, 0xEE, 0xEE,0xEE, 0xEE, 0xEE, 0xEE, 0xEE,0xEE, 0xEE, 0xEE, 0xEE, 0xEE,0xEE, 0xEE, 0xEE, 0xEE, 0xEE,0xEE, 0xEE, 0xEE, 0xEE, 0xEE,0xEE, 0xEE, 0xEE, 0xEE, 0xEE,0xEE, 0xEE, 0xEE, 0xEE, 0xEE,0xEE, 0xEE, 0xEE, 0xEE, 0xEE,0xEE, 0xEE, 0xEE, 0xEE, 0xEE,
                                0xFF, 0xFF, 0xFF, 0xFF, 0xFF,0xFF, 0xFF, 0xFF, 0xFF, 0xFF,0xFF, 0xFF, 0xFF, 0xFF, 0xFF,0xFF, 0xFF, 0xFF, 0xFF, 0xFF,0xFF, 0xFF, 0xFF, 0xFF, 0xFF,0xFF, 0xFF, 0xFF, 0xFF, 0xFF,0xFF, 0xFF, 0xFF, 0xFF, 0xFF,0xFF, 0xFF, 0xFF, 0xFF, 0xFF,0xFF, 0xFF, 0xFF, 0xFF, 0xFF,0xFF, 0xFF, 0xFF, 0xFF, 0xFF};


  //////////////////RADIO TEST/////////////////
  while(1){
    radio_spi_write(spi_test_payload, sizeof spi_test_payload);
    nrf_delay_ms(500);
  }

On the receiving side I have this code:

#define SPIS_INSTANCE 1                                                  /**< SPIS instance index. */
static const nrf_drv_spis_t spis = NRF_DRV_SPIS_INSTANCE(SPIS_INSTANCE); /**< SPIS instance. */

static uint8_t m_tx_buf[250];          /**< TX buffer. */
static uint8_t m_rx_buf[250]; /**< RX buffer. */
static const uint8_t m_length = sizeof(m_tx_buf); /**< Transfer length. */

static volatile bool spis_xfer_done; /**< Flag used to indicate that SPIS instance completed the transfer. */




/**
 * @brief SPIS user event handler.
 *
 * @param event
 */
void spis_event_handler(nrf_drv_spis_event_t event) {
  if (event.evt_type == NRF_DRV_SPIS_XFER_DONE) {
    spis_xfer_done = true;
    send_data_ble(m_rx_buf);
  }
}




/**
 * @brief Send data via bluetooth.
 *
 * @param payload
 */

void send_data_ble(uint8_t *payload) {
  uint32_t err_code;
  do {
    uint16_t length = sizeof(payload);
    uint32_t err_code = ble_nus_data_send(&m_nus, payload, &length, m_conn_handle);
    if ((err_code != NRF_ERROR_INVALID_STATE) &&
        (err_code != NRF_ERROR_RESOURCES) &&
        (err_code != NRF_ERROR_NOT_FOUND)) {
      APP_ERROR_CHECK(err_code);
    }
  } while (err_code == NRF_ERROR_RESOURCES);
}





int main(void) {

  nrf_gpio_pin_dir_set(CUSTOM_SPI_IRQ_PIN, NRF_GPIO_PIN_DIR_OUTPUT);
  nrf_gpio_pin_clear(CUSTOM_SPI_IRQ_PIN);

  bool erase_bonds;

  // Initialize.
  log_init();
  timers_init();
  power_management_init();
  ble_stack_init();
  gap_params_init();
  gatt_init();
  services_init();
  advertising_init();
  conn_params_init();

  // Enable the constant latency sub power mode to minimize the time it takes
  // for the SPIS peripheral to become active after the CSN line is asserted
  // (when the CPU is in sleep mode).
  // NRF_POWER->TASKS_CONSTLAT = 1;

  nrf_drv_spis_config_t spis_config = NRF_DRV_SPIS_DEFAULT_CONFIG;
  spis_config.csn_pin = APP_SPIS_CS_PIN;
  spis_config.miso_pin = APP_SPIS_MISO_PIN;
  spis_config.mosi_pin = APP_SPIS_MOSI_PIN;
  spis_config.sck_pin = APP_SPIS_SCK_PIN;
  spis_config.bit_order = 1;

  APP_ERROR_CHECK(nrf_drv_spis_init(&spis, &spis_config, spis_event_handler));

  advertising_start();

  uint32_t err_code = sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_ADV, m_advertising.adv_handle, 4); //increase power to maximum
  APP_ERROR_CHECK(err_code); 

  // Enter main loop.
  static uint8_t test_ble[] = {0xAA,0xBB,0xCC};
  while (1) {

    memset(m_rx_buf, 0, m_length);
    spis_xfer_done = false;

    APP_ERROR_CHECK(nrf_drv_spis_buffers_set(&spis, m_tx_buf, m_length, m_rx_buf, m_length));

    while (!spis_xfer_done) {
      __WFE();
      
    }
  }
}

I am just getting started in the embedded dev space and this is all new to me. My question is, how can I make my slave process those 2 transactions in a single buffer and send it via Bluetooth? Is this the right approach? Anything else I can do to my code to optimize it? Is that 1ms delay on the master side required or is the system fast enough to process everything without it?

Thanks in advance

  • Ok, the problem was that I forgot the call the nrf_drv_spis_buffers_set at the end of the spi_tx in the slave. Now both sides send and receive OK. The question is how to make it more performant?
    I noticed that when i keep pressed both buttons only one of them sends, I would have expected that they both send intermittently.

  • Hi 

    You mean to say you are never able to get bi-directional communication, it will only send in one direction at a time? 

    It might be for the simple reason that both sides are too responsive. Since the button press on each side is unlikely to be processed at exactly the same time one side will start a transaction before the other one has a new packet ready?

    Alternatively the problem could be that you are not able to prepare a new transaction while the current one is ongoing. If you try to send a new packet on either side while it is currently in the middle of a transaction then the new packet should be buffered, so that as soon as the current transaction is complete you will immediately send the next one. Then you should be able to schedule the communication more efficiently. 

    Best regards
    Torbjørn

  • How would I go about implementing this buffered communication?

  • Hi 

    The first step would have to be to change your current SPI read/write functions to be non blocking rather than blocking, so that you are able to process button presses and prepare the next SPI packet while the current packet is being transmitted. 

    The normal way to do this is to implement some kind of state machine, and remove all the steps in your code where you are running a while loop to wait for some event to occur, like the line below:

    while (!radio_spi_transfer_finished) // wait for finish

    Then you can process and prepare a new buffer while the current one is being transmitted, and as soon as the ongoing transaction is complete you can immediately proceed with the next. 

    Best regards
    Torbjørn

Related