Page 1 of 2

[Solved] SPI slave data loss with Raspberry Pi at 100k baud

Posted: Wed May 20, 2020 2:28 pm
by fd_
Hi all,
I'm trying to use an STM32F103C8T6 on a generic 'Blue Pill' type board as an SPI slave to a Raspberry Pi, but I just cannot get it to work at any reasonably high data rate. I've spent days trying all sorts of combinations, but in vain. I'm desperately hoping you guys can help me out :| :)

In the most basic setup, the STM32 requests an SPI message from the Raspberry Pi by setting an extra GPIO pin ("Request Pin") to high every second. My custom Linux kernel driver on the Pi has an interrupt handler installed on that Request Pin that sends an SPI message containing the string 'Hello!' to the STM32. My kernel module registers as an SPI driver, and a device tree overlay links the driver to SPI0 on the Pi.

Strangely, this only works at very low baud rates: At spi-max-frequency = <10000> (in the dto), the STM32 can read the SPI message. However, when I use spi-max-frequency = <100000>, the STM32 just receives 'H!' most of the time, so the majority of the message's content gets lost. Sometimes, it even fails at 10k baud already.

I'm now wondering what could cause this problem. Granted, I'm only using SPI in polling mode, but surely that setup should still allow a baud rate beyond 10k? The connections between the STM32 and the Pi are cheap jumper wires directly connecting the pins (got rid of the breadboard in between), and I already tried different lengths (between 10 and 30cm). I have used the very same wires to connect an SPI ENC28J60 ethernet chip to the Pi, and saw speeds of over 3Mbit/s (on the network layers, so the underlying SPI data rate must have been even higher).

Does anyone have any idea what could be causing this problem? I feel like I must be making some stupid mistake, but I just cannot find it. I've tried for days now, testing tons of different SPI setup variations, but to no avail.

I'm using the STM32 Core (As far as I understood, it should support generic boards as well?), and am using the HAL library for setting up the SPI slave, since no Arduino library is included in the core for that. I'm programming and monitoring the STM32 from a separate MacBook Pro (not from the Raspberry Pi), and am using Arduino 1.8.10 (newer versions crash on my multi-monitor macOS system as soon as I open the serial monitor). The STM32F103 is running its preinstalled bootloader, and I'm programming the board via Serial (using the recommended STM32CubeProgrammer method).

Here's my code (stripped of error handling for brevity):

Code: Select all

#include "stm32f1xx.h"

#define REQUEST_PIN PB8
#define SPI_MESSAGE_SIZE 7

char spi_buffer[SPI_MESSAGE_SIZE];
SPI_HandleTypeDef spi;

void setup_spi() {   
    HAL_Init();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_SPI1_CLK_ENABLE();

    spi.Instance = SPI1;
    spi.Init.Mode = SPI_MODE_SLAVE; 
    spi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
    spi.Init.Direction = SPI_DIRECTION_2LINES_RXONLY;
    spi.Init.CLKPhase = SPI_PHASE_1EDGE;
    spi.Init.CLKPolarity = SPI_POLARITY_LOW;
    spi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    spi.Init.DataSize = SPI_DATASIZE_8BIT;
    spi.Init.FirstBit = SPI_FIRSTBIT_MSB;
    spi.Init.NSS = SPI_NSS_SOFT;
    spi.Init.TIMode = SPI_TIMODE_DISABLE;
    spi.Init.CRCPolynomial = 10;
    HAL_SPI_Init(&spi);

    GPIO_InitTypeDef  GPIO_InitStruct;
    GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    __HAL_SPI_ENABLE(&spi);
}

void setup() {
    setup_spi();
    pinMode(REQUEST_PIN, OUTPUT);
    digitalWrite(REQUEST_PIN, LOW);
    Serial.begin(9600);
    Serial.println("Started");
}

void loop() {
    digitalWrite(REQUEST_PIN, HIGH);
    delay(1);
    digitalWrite(REQUEST_PIN, LOW);

    HAL_StatusTypeDef result;
    if ((result = HAL_SPI_Receive(&spi, (uint8_t *)spi_buffer, SPI_MESSAGE_SIZE, 1000)) != HAL_OK) {
       Serial.println("Receiving failed!");
    }
    
    Serial.println(spi_buffer);

    delay(1000);
}
STM32 Serial ouput:

10k baud (most of the time):
21:50:53.401 -> Hello!
21:50:54.385 -> Hello!
21:50:55.428 -> Hello!
21:50:56.447 -> Hello!
21:50:57.449 -> Hello!
21:50:58.464 -> Hello!
21:50:59.465 -> Hello!
21:51:00.481 -> Hello!
100k baud:
21:22:30.832 -> Receiving failed!
21:22:30.832 -> H!
21:22:31.851 -> Receiving failed!
21:22:31.851 -> H!
21:22:33.856 -> Receiving failed!
21:22:33.856 -> H!
21:22:35.834 -> Receiving failed!
21:22:35.834 -> H!
21:22:37.856 -> Receiving failed!
21:22:37.856 -> H!


Pin connections:
Raspberry Pi -> STM32
GPIO 8 (CE0) -> A4 (SPI1 NSS)
GPIO 11 (SCLK) -> A5 (SPI1 SCK)
GPIO 9 (MISO) -> A6 (SPI1 MISO)
GPIO 10 (MOSI) -> A7 (SPI1 MOSI)
GPIO 27 (Request Pin) -> B8
3V3 (Pin 1) -> 3.3
GND (Pin 6) -> G
(STM32 GND, A9 and A10 are also connected to a separate computer via a USB UART adapter for programming and serial monitor)

Kernel module excerpts:

Code: Select all

static char *hello = "Hello!";

void send_spi_message(void) {
    spi_write(spi_dev, hello, strlen(hello));
}

static int spi_test_probe(struct spi_device *spi_dev) {
    request_pin = of_get_named_gpio_flags(spi_dev->dev.of_node, "request_pin", 0, 0);
    int request_pin_req = devm_gpio_request(&spi_dev->dev, request_pin, "request");
    request_pin_irq = gpiod_to_irq(gpio_to_desc(request_pin));
    request_threaded_irq(request_pin_irq, send_spi_message, NULL, IRQF_TRIGGER_RISING, "request", spi_dev);
}

static struct spi_driver my_spi_test_driver = {
    .driver = {
        .name = "my_spi_test",
    },
    .probe = my_spi_test_probe,
    .remove = my_spi_test_remove
};

static int __init my_spi_test_init(void) {
    return spi_register_driver(&my_spi_test_driver);
}
Device Tree Overlay:

Code: Select all

/dts-v1/;
/plugin/;

/ {
    compatible = "brcm,bcm2835", "brcm,bcm2836", "brcm,bcm2708", "brcm,bcm2709";

	fragment@0 {
		target = <&spidev0>;
		__overlay__ {
			status = "disabled";
		};
	};

	fragment@1 {
		target = <&spi0>;
		__overlay__ {
			#address-cells = <1>;
			#size-cells = <0>;
			status = "okay";

			spi_test@0 {
				compatible = "my_spi_test";
				reg = <0>;
				request-pin = <&gpio 27 0>; // Active high
				spi-max-frequency = <100000>;
                status = "okay";
			};
		};
	};
};

Re: SPI slave data loss with Raspberry Pi at 100k baud

Posted: Wed May 20, 2020 4:15 pm
by ag123
just 2 cents, you may need a logic analyzer to aid looking at the signals on the wire against the clock.
spi tend to require clock sync and the turnaround may have missed cycles.

Re: SPI slave data loss with Raspberry Pi at 100k baud

Posted: Wed May 20, 2020 4:44 pm
by fd_
Thanks for your response! Well, I was hoping I could get around that since I don't have one yet. Will look into ordering one though.
Does that mean you suspect the SPI implementation in the Pi or STM32 to have any quirks? I was hoping that since they work for other people / other peripherals, the issue would be some simple configuration problem in my code :|

Re: SPI slave data loss with Raspberry Pi at 100k baud

Posted: Wed May 20, 2020 6:14 pm
by stevestrong
If you are polling the data, do not use delay(), the reception part may not be delayed by any means.
You should test first the software with a simpler setup, e.g. try to interconnect two bluepill boards.
RasPi has 5V output, the STM has 3.3V input...

Re: SPI slave data loss with Raspberry Pi at 100k baud

Posted: Wed May 20, 2020 6:22 pm
by fd_
stevestrong wrote: Wed May 20, 2020 6:14 pm If you are polling the data, do not use delay(), the reception part may not be delayed by any means.
You should test first the software with a simpler setup, e.g. try to interconnect two bluepill boards.
RasPi has 5V output, the STM has 3.3V input...
Thanks for your tips! The Raspberry Pi has 3.3V logic, although it is powered by 5V from the USB. Unfortunately, I only have the one Blue Pill board I'm using, but I'll look into the delay function. Hm, thinking about it: I only have 1ms delay between requesting the data and calling HAL_SPI_Receive, can that really cause that much of a problem?

Re: SPI slave data loss with Raspberry Pi at 100k baud

Posted: Wed May 20, 2020 7:21 pm
by stevestrong
Well, then it has to do with the HAL_SPI_Receive() function, I do not know what exactly is processed within that.

Re: SPI slave data loss with Raspberry Pi at 100k baud

Posted: Wed May 20, 2020 8:49 pm
by ag123
for a logic analyzer which probably won't get you very high speeds there are a few stm32 based implementations
e.g.
viewtopic.php?f=10&t=116
and scroll below near the bottom of the post, there are a few other implementations

Re: SPI slave data loss with Raspberry Pi at 100k baud

Posted: Wed May 20, 2020 10:32 pm
by mrburnette
SPI on the Raspberry Pi has some known peculiarities
https://www.raspberrypi.org/forums/view ... hp?t=19489
Excerpt
Attaching a logic-analyzer to the "relevant" lines on the RPI (Enable, MISO,MOSI,Clock as well as the interrupt line plus signal on the Can-bus itself) showed that there where times when the RPI stopped sending SPI requests and the bus was idle for typically 4ms.
...
So investigating the issue resulted in realizing, that for the RPI the SPI driver is also based on a workqueue model (which runs at normal priorities - not RealTime scheduling) and is implemented interrupt driven (so no DMA, but still better than polling).
Opinion: the Raspberry Pi Foundation has made many compromises in the Raspbian releases. Coming from a real UNIX background (Solaris, HP-UX, AT&T UNIX System V) and years of Linux flavors, the Raspberry Pi sometimes throws me into having to really think through constrained implementation.

Options, maybe:
https://www.google.com/search?q=raspber ... i+realtime

Good luck.

Ray

Re: SPI slave data loss with Raspberry Pi at 100k baud

Posted: Thu May 21, 2020 6:14 am
by ag123
@ray, nice post ! that's quite interesting as 'non-real time' spi may mean 'gaps between bytes' , it would seem some kind of framing protocol would be needed so that 'bad' packets needs to be re-transmitted. it is interesting as it implies some of those simpler strict clock / timing based 'peripherals' may even have problem with that, e.g. Rpi need to check for response and re-transmit.

Re: SPI slave data loss with Raspberry Pi at 100k baud

Posted: Thu May 21, 2020 9:17 pm
by fd_
mrburnette wrote: Wed May 20, 2020 10:32 pm SPI on the Raspberry Pi has some known peculiarities
https://www.raspberrypi.org/forums/view ... hp?t=19489
Excerpt
Attaching a logic-analyzer to the "relevant" lines on the RPI (Enable, MISO,MOSI,Clock as well as the interrupt line plus signal on the Can-bus itself) showed that there where times when the RPI stopped sending SPI requests and the bus was idle for typically 4ms.
...
So investigating the issue resulted in realizing, that for the RPI the SPI driver is also based on a workqueue model (which runs at normal priorities - not RealTime scheduling) and is implemented interrupt driven (so no DMA, but still better than polling).
Opinion: the Raspberry Pi Foundation has made many compromises in the Raspbian releases. Coming from a real UNIX background (Solaris, HP-UX, AT&T UNIX System V) and years of Linux flavors, the Raspberry Pi sometimes throws me into having to really think through constrained implementation.

Options, maybe:
https://www.google.com/search?q=raspber ... i+realtime

Good luck.

Ray
Hi,
Thanks for your thoughts! I read through the thread you linked, but I'm pretty sure it shouldn't be relevant to the problem I'm seeing: AFAICT, the RPi thread was about SPI efficiency in the first place, not about failed transfers, and the situation seems to have improved significantly over the course of the thread (keep in mind the RPi was pretty new when the thread started). Also, given that the RPi is the SPI master and thus in control of the clock, I don't think clock jitter/pauses could lead to outright data loss for data sent from master to slave (am I wrong here?). And lastly, for the 100k baud rate, I'm consistently seeing the exact same data loss for every transfer (receiving 'H!' instead of 'Hello!' every time), which to me doesn't plausibly seems like an effect of non-realtime scheduling. I've now looked at the kernel driver for the ENC28J60 SPI ethernet chip (which I have working with the Pi at > 3MBit/s), and it seems pretty identical to my custom SPI driver in terms of SPI usage.

I really feel at a loss here. I think I'll try Roger (Clark)'s Core again to see if it fixes the problem for some reason.