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

  • Hi 

    When it comes to SPI the chip select line is used to signal the start and end of a transaction, so as long as you don't pull the chip select low for each transaction, but pull it low for the duration of both transactions, the slave should read it as one transaction. 

    With that being said, there is no 256 byte limit on the SPIM or SPIS interfaces in the nRF52840. 

    If you take a look at the TXD or RXD MAXCNT registers (like this one) you will see that they can be as large as 0xFFFF, or 65535 bytes. 

    Most likely you are thinking of the nRF52832, which was the first device in the nRF52 series, and was a bit more limited in terms of how large EasyDMA buffers you could use. 

    Best regards
    Torbjørn 

  • Thank you for the reply.

    Yes, as stated in the subject of my question, I am using nrf52832.

    So the trick with keeping the line low will work for the 52832 also?

    If not, what are my alternatives? Any example code?

    Please note, the communication must be bi-directional, and it can happen in either direction at any arbitrary time.

  • Hi 

    Sorry about that, I read PCA10040 and thought of the 52840 for some reason...

    Unfortunately that trick won't really work with the nRF52832, since both the SPIM and SPIS interfaces are limited to 255 bytes maximum. 

    I think you need to split the update into two separate transactions, and possibly use a header byte or similar at the start of the transaction to signify whether or not there are more transactions coming which should be appended to the first. 

    Then you need to compile the two transaction into a single message on the receiving end, before passing it along to other parts of the system. 

    I don't think we have any examples showing this exact procedure, but you basically just need to send two transactions in succession whenever the payload in question is larger than 255 bytes (or larger than 254, if you use 1 byte for the header). 

    Best regards
    Torbjørn

  • So basically creating a global 300 bytes buffer and a bool called "complex_transaction".
    When IRQ triggers, if first byte of the payload is 254, complex_transaction will be set to true and the next transaction will be automatically appended to the buffer after the first one.

    Does that sound right to you? How do you see it?

  • Hi 

    That was more or less my original idea, but I think I might have come up with a better one:

    Rather than adding header bytes or similar to your data it is possible to simply use the length of the packet as an indication whether or not there is another packet coming. 

    Essentially if you receive a packet that is 255 bytes long then you assume that the real message is longer, and that the next packet will have to be appended to the first. 

    The advantage of this method is that you don't need to manually append the data from the two packets, since you can just set the EasyDMA pointer of the SPI slave to the right place in the buffer for the second transaction. 

    The only special case here is if you ever need to send a packet that is exactly 255 bytes long, since that would cause the SPI slave to assume another transaction was coming. 

    To solve this issue you either need to send a 1 byte dummy packet after the 255 byte transaction, to wrap it up, or you need to include a length field or something at the start of the first packet so the SPI slave can tell whether or not the second packet is needed (similar to the first suggestion, but if you only include the header on the first packet you can still append the two packets seamlessly without requiring a memcpy). 

    Best regards
    Torbjørn

Related