I am working on a project with a bunch of Raspberry devices and have hit a bit of a road block.
I have a central raspberry Pi 4 that has 4 peripheral Raspberry Pi Picos all connected through an SPI bus.
For testing purposes the Picos are running a dummy program – they just write a string of numbers into the bus. The problem is, I am not able to read out the sent data on the Raspberry Pi 4. If only one Pico is connected at a time my program works fine, but as soon as another is connected to the bus, I only read zeros.
I think the problem might be, that the picos are fighting for contention in the data line, which is causing the zeros.
What is causing the problem?
Here is my code for the RPi4 (SPI master, in Python using spidev):
import spidev
import RPi.GPIO as GPIO
import time
import threading
import concurrent.futures
# Setup GPIO for manual CS
GPIO.setmode(GPIO.BCM)
DEVICE2_CS = 17
GPIO.setup(DEVICE2_CS, GPIO.OUT, initial=GPIO.HIGH)
# SPI for CE1
spi0 = spidev.SpiDev()
spi0.open(0, 1) # Bus 0, CE0
spi0.max_speed_hz = 50000
# SPI for manual CE0
spi1 = spidev.SpiDev()
spi1.open(0, 0)
spi1.max_speed_hz = 50000
def read_device_0():
adc = spi0.xfer2([1,2,3,4])
time.sleep(0.1)
value = adc
return value
def read_device_1():
adc = spi1.xfer2([1,2,3,4])
time.sleep(0.1)
value = adc
return value
try:
while True:
val0 = read_device_0()
val1 = read_device_1()
print("Device 0: {val0} , Device 1: {val1} ")
time.sleep(1)
except KeyboardInterrupt:
pass
finally:
spi0.close()
spi1.close()
GPIO.cleanup()
And here the code for the Picos (SPI slaved, in C using the Raspberry Pi Pico extension in VS Code):
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/spi.h"
#ifdef CYW43_WL_GPIO_LED_PIN
#include "pico/cyw43_arch.h"
#endif
#ifndef LED_DELAY_MS
#define LED_DELAY_MS 250
#endif
// Perform initialisation
int pico_led_init(void) {
#if defined(PICO_DEFAULT_LED_PIN)
// A device like Pico that uses a GPIO for the LED will define PICO_DEFAULT_LED_PIN
// so we can use normal GPIO functionality to turn the led on and off
gpio_init(PICO_DEFAULT_LED_PIN);
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
return PICO_OK;
#elif defined(CYW43_WL_GPIO_LED_PIN)
// For Pico W devices we need to initialise the driver etc
return cyw43_arch_init();
#endif
}
// Turn the led on or off
void pico_set_led(bool led_on) {
#if defined(PICO_DEFAULT_LED_PIN)
// Just set the GPIO on or off
gpio_put(PICO_DEFAULT_LED_PIN, led_on);
#elif defined(CYW43_WL_GPIO_LED_PIN)
// Ask the wifi "driver" to set the GPIO on or off
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, led_on);
#endif
}
#define BUF_LEN 0x100
void printbuf(uint8_t buf[], size_t len) {
size_t i;
for (i = 0; i < len; ++i) {
if (i % 16 == 15)
printf("%02x\n", buf[i]);
else
printf("%02x ", buf[i]);
}
// append trailing newline if there isn't one
if (i % 16) {
putchar('\n');
}
}
int main() {
int rc = pico_led_init();
hard_assert(rc == PICO_OK);
pico_set_led(true);
// Enable UART so we can print
stdio_init_all();
#if !defined(spi_default) || !defined(PICO_DEFAULT_SPI_SCK_PIN) || !defined(PICO_DEFAULT_SPI_TX_PIN) || !defined(PICO_DEFAULT_SPI_RX_PIN) || !defined(PICO_DEFAULT_SPI_CSN_PIN)
#warning spi/spi_slave example requires a board with SPI pins
puts("Default SPI pins were not defined");
#else
printf("SPI slave example\n");
// Enable SPI 0 at 1 MHz and connect to GPIOs
spi_init(spi_default, 1000 * 1000);
spi_set_slave(spi_default, true);
gpio_set_function(PICO_DEFAULT_SPI_RX_PIN, GPIO_FUNC_SPI);
gpio_set_function(PICO_DEFAULT_SPI_SCK_PIN, GPIO_FUNC_SPI);
gpio_set_function(PICO_DEFAULT_SPI_TX_PIN, GPIO_FUNC_SPI);
gpio_set_function(PICO_DEFAULT_SPI_CSN_PIN, GPIO_FUNC_SPI);
// Make the SPI pins available to picotool
bi_decl(bi_4pins_with_func(PICO_DEFAULT_SPI_RX_PIN, PICO_DEFAULT_SPI_TX_PIN, PICO_DEFAULT_SPI_SCK_PIN, PICO_DEFAULT_SPI_CSN_PIN, GPIO_FUNC_SPI));
uint8_t out_buf[BUF_LEN], in_buf[BUF_LEN];
// Initialize output buffer
for (size_t i = 0; i < BUF_LEN; ++i) {
out_buf[i] = i;
}
//printf("SPI slave says: When reading from MOSI, the following buffer will be written to MISO:\n");
//printbuf(out_buf, BUF_LEN);
for (size_t i = 0; ; ++i) {
if (gpio_get(PICO_DEFAULT_SPI_CSN_PIN)==0){
pico_set_led(true);
// Write the output buffer to MISO, and at the same time read from MOSI.
spi_write_read_blocking(spi_default, out_buf, in_buf, BUF_LEN);
pico_set_led(false);
sleep_ms(10);
// Write to stdio whatever came in on the MOSI line.
//printf("SPI slave says: read page %d from the MOSI line:\n", i);
//printbuf(in_buf, BUF_LEN);
}
}
#endif
}
EDIT: Answering Milliways on 14.10:I'm sorry for not knowing the correct way to answer questions in forums like this, this my first time. So in advance apologies for anything I write in the wrong place or the wrong way. (So even if this is the wrong way of addressing comments, I'm sorry and please tell me the right way to do it.) Regarding the "default way", I explained in my comment that I set it up through the SPI settings (either through the on board settings application if you have a screen connected or through raspi-config and then enabling SPI). And it was suggested that I work on getting two devices running first. That is exactly what I am trying to do.As I explained in the original post, it is working just fine for one Pico. But as soon as two (or more) are connected at the same time, my program stops working.
Edit: The imports threading and concurrent futures are from tests I ran, to see if it would work with that (It didn't). They serve no purpose for this question, I'm sorry for any confusion it may have caused. Further it was asked how everything is connected. As I explained in the original post, the Raspberry Pi Picos are connected through a shared bus. This means they share a Master Input Slave Output (MISO), Maser Out Salve In (MOSI) and a clock (SCLK) line. Each pico has a seperate power and chip select connection. As for the manual pins mixing with the pre-existing CS, I found an article in which they suggest it is very much possible to use both at the same time. In my code I have deleted out most of the manual CS selection code I used from their example, you may ignore the lines"#Setup GPIO for manual CS" until "GPIO.setup(...)" as they are not really relevant for this specific question, unless anyone suggests using only manual chip select. I apologize once more if it caused any confusion. @Milliways: I don't think I quite understand the last sentence in your comment...("Indeed I don't understand why as the Pi has 2 SPI devices one with 2 CS the other 3") What do you mean with 2 SPI devices? Why does one have 3 Chip selects? Are you referring to the "hidden" SPI buses that you can enable on certain other pins?