SPI to multiple peripherals

I’m trying to use SPI to communicate with multiple peripherals.

Looking at per/spi.h I’m guessing that the idea is that ultimately you will be able to create multiple instances of SpiHandle for each peripheral that shares an SPI bus but have different CS pins. It looks like what’s currently there is hardcoded so that Init.NSS uses SPI_NSS_HARD_OUTPUT, which directs the CS output to libdaisy pin 7.

Looking at the code in per/spi.cpp I’m guessing that what I need to do is set Init.NSS to SPI_NSS_SOFT together with some other settings to specify the appropriate GPIO pin, but I’m not sure how to set that up. It’s getting rather deeper into all the STM32 settings than I’m comfortable with!

1 Like

So as far as I can tell, the Hardware NSS output should only be used if you’re using a single device, I don’t think the SPI peripheral can handle multiple hardware chip-selects.

What can be done for multiple devices on a single bus is to use Software NSS, and then manage the CS lines outside of the calls to the SPI Rx/Tx commands.

If you do set the Init.NSS to SPI_NSS_SOFT you can use a dsy_gpio set to any pin you want to use for the chip select. Then before sending the read or write commands just pull that pin down, and then pull it back high after.

When we get to reworking the libdaisy SPI Peripheral we will be adding some level of internally managed software chip select, but I’m still undecided on how best to support multiple devices, but it will be possible.

If you have any other specific questions before I get to fixing up the libdaisy SPI stuff, I’ll be happy to help :grinning_face_with_smiling_eyes:

1 Like

That makes complete sense.

I’ve managed to get my setup working by setting Init.NSS to SPI_SOFT, specifying separate GPIO pins for CS for each peripheral, and passing the SPI SCK and the CS lines through AND gates before connecting to the clocks on the peripherals. Seems to work fine.

Nice!

FWIW provided your devices are actually SPI and not some other 3-wire serial control then you should be able to send the SCK/Data lines to all devices at once. The device-specific CS pin being pulled low is what tells a given device to start paying attention.

That said, if you’ve got it working then no need to mess with it! :slight_smile:

One of the devices I’m using only has serial data and clock inputs, so it’s really a synchronous serial device rather than SPI. Putting both devices on the SPI so that I can use the hardware rather than doing a software serial out seems to be working niceley.

I guess one other option might be to use an USART to do synchronous serial, but are the clocks for any of the USARTs currently available on gpio pins using libdaisy? All I can see is asynchronous serial using uart.h.

Hi people, I was trying to make a software NSS, but failed, need some help.

  1. I changed in oled_ssd130x.h
spi_config.nss            = SpiHandle::Config::NSS::SOFT;
  1. recompiled libdaisy
  2. in my project added under main(void)
  petal.Init();
  GPIO channel_select;
  channel_select.Init(daisy::seed::D7, GPIO::Mode::OUTPUT);
 //same pin as originally for CS
  channel_select.Write(false);
  petal.display.Fill(true);
  petal.DelayMs(1000);
  channel_select.Write(true);
  display.Update()

Checking high and low voltage is alternating I see the D7 pin works as coded. But no image on the screen.

Hello Mr. Capacitor Connoisseur! :wink:

petal.display.Fill(0); - that just fills display with no color, so there won’t be anything to show. At the very least you should use .Fill(true) to actually have a fully lit display.

Nice to see you, anti.
Yep, that’s a misprint.
Actually I tried different stuff, no luck…

Should the NSS be initiated in class SSD130xDriver?
btw I’m using SSD1309 display

I’m not an expert in libDaisy as I only use the hardware part, software side is managed with OpenWare. But from a quick look, you seem to be missing a call to display.Update() that actually sends data. Filling/drawing is done in pixel buffer only, it doesn’t get sent to display until you tell it to do this.

As for NSS, it looks like OLED driver won’t be setting it on your behalf if software control is used, so your code does what it’s supposed to.

Thank you for your help, I updated the code snippet above.
My problem lies somewhere deeper, I see on YouTube how they send bit by bit and beginning of the transfer is aligned with channel select LOW, but I cannot understand how to do it in libdaysy.
Should I switch CS in SendCommand and SendData to start actual communication with my peripheral?

void Init(Config config)
    {
.......
                // Display Clock Divide Ratio
                transport_.SendCommand(0xD5);
                transport_.SendCommand(0x80);
                // Multiplex Ratio
                transport_.SendCommand(0xA8);
                transport_.SendCommand(0x2F);
                // COM Pins
                transport_.SendCommand(0xDA);
                transport_.SendCommand(0x12);
........
        }

            void Update()
.......
        {
            transport_.SendCommand(0xB0 + i);
            transport_.SendCommand(0x00);
            transport_.SendCommand(high_column_addr);
            transport_.SendData(&buffer_[width * i], width);
        }

It looks like you’ll have to do make something similar to SSD130x4WireSpiTransport, but configured to use soft CS and with CS pin passed in config. CS pin toggling can be performed by SendCommand/SendData methods and won’t be visible outside of that class.
Then you can create a custom board that instantiates SSD130xDriver with that soft CS transport and use that instead of current driver that uses hard CS.
I would expect main function to contain something similar to this:

  custom_board.Init();
  while(true) {
    custom_board.display.Fill(true);  // Draw on display here
    custom_board.display.Update();
    custom_board.DelayMs(20); // 50 FPS
  }

Just toggling GPIO pin manually outside of driver code is insufficient, you also must do it for individual commands that are being sent when display gets initialized. And they are not exposed outside of driver, so doing it in transport seems to be the only way.

AAA! It works!
Thank you so much!
For someone who need the code.

class SSD130x4WireSpiTransport_SOFT
{
    ....
    void Init(const Config& config)
    {
          .......
         spi_config.nss            = SpiHandle::Config::NSS::SOFT;
         //spi_config.pin_config.nss  = {DSY_GPIOG, 10};
         channel_select.Init(daisy::seed::D23, GPIO::Mode::OUTPUT); // Use D7 as default
          ......
    };
    
    void SendCommand(uint8_t cmd)
    {
        channel_select.Write(0);
        dsy_gpio_write(&pin_dc_, 0);
        spi_.BlockingTransmit(&cmd, 1);
        channel_select.Write(1);
    };

    void SendData(uint8_t* buff, size_t size)
    {
        channel_select.Write(0);       
        dsy_gpio_write(&pin_dc_, 1);
        spi_.BlockingTransmit(buff, size);
        channel_select.Write(1);
    };

 private:
.....
    GPIO channel_select;
1 Like