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

Trying to understand how SPI Slave should be implemented

Greetings!

I'm trying to set up an nRF52 board as an SPI slave so that it would receive chunks of data irregularly from the master device (a Raspberry Pi in this case, if it's important). However, I am not sure if I understand correctly the purpose of the SPIS API and how exactly should I use it. Here is how I'm doing it currently:

#include "nrfx_spis.h"

static nrfx_spis_t m_spis = NRFX_SPIS_INSTANCE(0);

static uint8_t rx_buffer[64];
static uint8_t tx_buffer[] = "HELLO";

static void spis_event_handler(nrfx_spis_evt_t const *p_event, void *p_context)
{
	nrfx_err_t err_code;

	switch (p_event->evt_type) {
	case NRFX_SPIS_BUFFERS_SET_DONE:
		NRF_LOG_DEBUG("Buffers Set Done");
		break;

	case NRFX_SPIS_XFER_DONE:
		NRF_LOG_DEBUG("XFer Done; RX %d / TX %d, Received: ", p_event->rx_amount, p_event->tx_amount);

		for (size_t i = 0; i < p_event->rx_amount; i++) {
			NRF_LOG_DEBUG("%d: %x", i, rx_buffer[i]);
		}

		memset(rx_buffer, 0, 64);

		err_code = nrfx_spis_buffers_set(&m_spis, tx_buffer, 6, rx_buffer, 63);
		APP_ERROR_CHECK(err_code);

		break;

	default:
		break;

	};
}

static void spis_init()
{
	nrfx_err_t err_code;
	nrfx_spis_config_t spis_config = {
		.mosi_pin     = 11,
		.miso_pin     = 12,
		.sck_pin      = 13,
		.csn_pin      = 14,
		.mode         = NRF_SPIS_MODE_0,
		.bit_order    = NRF_SPIS_BIT_ORDER_MSB_FIRST,
		.csn_pullup   = NRFX_SPIS_DEFAULT_CSN_PULLUP,
		.miso_drive   = NRFX_SPIS_DEFAULT_MISO_DRIVE,
		.def          = NRFX_SPIS_DEFAULT_DEF,
		.orc          = NRFX_SPIS_DEFAULT_ORC,
		.irq_priority = NRFX_SPIS_DEFAULT_CONFIG_IRQ_PRIORITY,
	};

	memset(rx_buffer, 0, 64);

	err_code = nrfx_spis_init(&m_spis, &spis_config, spis_event_handler, NULL);
	APP_ERROR_CHECK(err_code);

	err_code = nrfx_spis_buffers_set(&m_spis, tx_buffer, 6, rx_buffer, 63);
	APP_ERROR_CHECK(err_code);
}

int main(void) {
    // ...
    spis_init();
    // ...
	for (;;)
	{
		idle_state_handle();
	}


(I understand that I suppose to adjust buffers after transfers, but I don't think it's important for now).

Issue is, I'm not sure I completely understand the nrfx_spis_buffers_set function.

I.e, here is how I understand what I am doing. Please correct me if I misunderstood anything:

1. Init SPI device
2. Tell SPI that I have a RX and TX buffers ready with nrfx_spis_buffers_set
3. When Master initiates transfer, Master and Slave exchange bytes, up to tx_buffer_length bytes sent and rx_buffer_length received
4. After the transfer, event handler is called with NRFX_SPIS_XFER_DONE event type, tx_amount bytes transferred and rx_amount bytes received
5. Within the event handler, I must updates buffers if necessary and call nrfx_spis_buffers_set again with new data to be read/sent

For reference, here is the code for raspberry pi:
#!/usr/bin/env python3

import pigpio

pi = pigpio.pi()
if not pi.connected:
    print("connection failed")
    exit()

# slave 0, speed 50000, mode 0
s = pi.spi_open(0, 50000, 0)

data = bytearray("hello, world", "utf-8")

(count, rx_data) = pi.spi_xfer(s, data)
print("Received %s bytes: %s" % (count, rx_data))


So I'm trying to exchange "hello, world" string from raspberry pi and "HELLO" string from nRF52. However, in the end, I get something messed up:

<debug> app: XFer Done; RX 8 / TX 6, Received:
<debug> app: 0: 68 (h)
<debug> app: 1: 65 (e)
<debug> app: 2: 6C (l)
<debug> app: 3: 6C (l)
<debug> app: 4: 6F (o)
<debug> app: 5: 2C (,)
<debug> app: 6: 20 ( )
<debug> app: 7: 77 (w)
<debug> app: Buffers Set Done
<debug> app: XFer Done; RX 2 / TX 2, Received:
<debug> app: 0: 6F (o)
<debug> app: 1: 72 (r)
<debug> app: Buffers Set Done


And on raspberry pi side:
Received 12 bytes: bytearray(b'HELLO\x00\xff\xff\xffHD\xff')


(I understand that I keep sending HELLO over and over again, but where did that D even came from?)

I was able to get a proper result once (i.e. withtin a single transfer, 12 bytes in one direction, 6 in another, all properly sent), but I was never able to get that again.
I think I should also mention that I tried enabling anomaly 109 workaround, but then I can't even init SPIS because of NRFX_ERROR_INTERNAL.

What am I doing wrong? I have a suspicion that I'm messing up buffers by declaring them like that; But is my understand of the process of SPI transfer correct? What here could cause those incomplete (and sometimes incorrect) transfers? What could be improved in the code?

Thanks in advance.

  • I have noticed that setting higher speed (50000 -> 1000000) causes transmissions to be more reliable and to occur in a single event more often, which is kinda counter-intuitive for me, but I hope this gives someone a clue to what is going on here.

  • Hi,

     

    Do you have a logic analyzer so that you can look at the SPI signals on the bus itself? 

    How often do you read out with the unwanted characters ? Once in a while, or every time?

     

    Kind regards,

    Håkon

  • Hello!

    I'm planning on getting a logical analyzer, but this won't happen tomorrow, unfortunately.

    As I said, when I had a speed of 5 * 10^4 (I checked now on 5 * 10^5 and still had same issues), I only received successful read once; afterwards I was either missing some bytes, read some bytes wrong, or both. However, when I set the speed to 10^6, it caused data transmission to be a bit more reliable. I tried sending bytes few dozen times at a different times (after some time of nRF52 running, right after reset, etc.), and I still do receive garbage, but every third time.

    I would really like to hear if my understanding of SPI Slave is correct or if I'm missing something; And what could theoretically be the cause for transfer issues at lower speed.

    Thanks. 

  • SergeyK said:
    I would really like to hear if my understanding of SPI Slave is correct or if I'm missing something; And what could theoretically be the cause for transfer issues at lower speed.

     Your understanding is correct. If you want to update the buffer, you call nrfx_spis_buffers_set(), and this is safe to call when the former SPI transaction is done (ie: in the _DONE event of the evt-handler). There isn't anything that should cause it to misbehave at 500 kHz vs. 1 MHz. If you get an ignored transaction (not setting buffer for instance), it should instead clock out the current content of "DEF" register:

    https://infocenter.nordicsemi.com/topic/ps_nrf52810/spis.html?cp=3_4_0_5_19_4_34#register.DEF

     

    It should not clock out a partial buffer in that case. That sounds more of a issue on the master side, unless the spi slave buffer is written "mid transfer" by the application firmware, which it does not look like is happening on your side, especially if the test program is that small.

     

    Kind regards,

    Håkon

  • Okay, thanks a lot! I think I have some SPI theory to read. I also have few other micro controller boards lying around, will try using them; plus I'll check it with logic analyzer when I'll get or borrow one.

Related