Raspberry Pi 5 IoT In C - SPI with GPIO 5
Written by Harry Fairhead   
Sunday, 21 September 2025
Article Index
Raspberry Pi 5 IoT In C - SPI with GPIO 5
GPIO 5 Functions
SPI Read/Write Function
Loopback Example Using GPIO5

SPI Read/Write Function

The basic read/write function first writes a set of bytes and reads the same number back. This sounds simple, but achieving maximum throughput and automatic control of the CS lines makes it more complicated. A simple approach would be to set up a loop that writes a byte to the FIFO buffer and then attempts to read one. You would need to check that the Tx FIFO had space before writing and you would need to check that the Rx FIFO had something to read each time through the loop. This works well at clock speeds less than about 10MHz. At higher clock speeds what happens is that the FIFO transmits the frame before the next frame has been stored and so it spends time between frames empty. This slows things down and it resets the CS line which is automatically active when there is data in the Tx FIFO and set to inactive when it is empty. At lower clock speeds, the Tx FIFO stays full and the CS line stays active until all of the frames have been sent.

A better but more complicated algorithm first places as many items of data in the Tx FIFO as it can hold and then starts a loop that reads data from the Rx FIFO while keeping the Tx FIFO fed with data. The only subtle point is that we have avoid filling the Rx FIFO buffer as well as the Tx FIFO buffer:

int spi_write_read_blocking(SPI spi, const uint8_t *src,
                               uint8_t *dst, size_t len)
{
    const size_t fifo_depth = 64;
    size_t rx_remaining = len, tx_remaining = len;
    for (int i = 0; i < 
fifo_depth < len ? fifo_depth : len)-1; i++) { spi->DR = (uint32_t)*src++; --tx_remaining; } while (rx_remaining || tx_remaining) { if (tx_remaining && spi_is_writable(spi) && spi_rx_num(spi) < fifo_depth) { spi->DR = (uint32_t)*src++; --tx_remaining; } if (rx_remaining && spi_is_readable(spi)) { *dst++ = (uint8_t)spi->DR; --rx_remaining; } } return (int)len; }

This function is a modification of the same one in the Pico SDK.

This works up to about 6MHz. After this the Tx FIFO eventually empties and the CS line is deactivated until another frame is read to transmit. At 10MHz it takes 82Bytes to empty the Tx FIFO and the first 82Bytes are transmitted at 1250 KBytes per second and following bytes at 300KBytes per second. A more serious problem in some cases is the toggling of the CS line which some slaves cannot work with.

If the CS line toggling is a problem then you have no choice but to take control of the GPIO line manually. Simply reset the CS line into GPIO mode, set its direction to output and change it from high to low and back again around the call to write_read_blocking. This doesn’t speed things up, it simply ensures that the CS line is active for the entire transfer.

If you want to go faster than this then DMA is required and this is beyond the scope of this book.

You can create the spi_write16_read16_blocking which is in the Pico SDK simply by changing all occurrences of uint8_t to uint16_t. You can also create an spi_write32_read32_blocking, which isn’t in the Pico SDK, by changing uint8_t to uint32_t. Notice that you have to change the frame size to 16 bits or 32 bits to make these functions work. If you don’t then only the number of bits specified are transferred.

It is also trivial to create read_blocking and write_blocking functions in each frame size by discarding received data and by sending dummy data.

Two functions that are not in the Pico SDK can be used to set the CS line to activate and how the CS line should behave between data frames:

void spi_set_slave(SPI spi, int slave)
{
    spi_enable(spi, false);
    spi->SER = 1ul << slave;
    spi_enable(spi, true);
}

and

void spi_set_CS_toggle(SPI spi, bool enable)
{
    spi_enable(spi, false);
    if (enable)
    {
        spi->CTRLR0 = (spi->CTRLR0) | 0x1000000;
    }
    else
    {
        spi->CTRLR0 = (spi->CTRLR0) & 0xFFFFFFFFFEFFFFFF;
    }
    spi_enable(spi, true);
}

Notice that the number of CS lines varies according to the SPI channel in use and it is up to you to initialize all of the GPIO lines in use, including the CS lines.



Last Updated ( Wednesday, 24 September 2025 )