SPI: is it a Singleton?

Status
Not open for further replies.

blakeAlbion

Well-known member
Hello!

I see my Teensy has multiple sets of SPI pins. Can I run SPI on more than one set of pins simultaneously?
The documentation suggests no. It seems SPI is a "Singleton": One instance per application. I can point SPI at different pins, but I can't seem to have two SPI instances at the same time (SPI1, SPI2, SPI3, etc.) I suppose this may be due to the potential use of interrupts.

But I wanted to make sure of this. The MIDI library for Teensy lets me get around this by creating more than one instance on different serial ports. Can I do this with SPI?

Thanks,

Ben
 
the port itself is a singleton class yes, however the chipselect (CS) is what you toggle for different devices attached to the same port. The singleton is able to access all of them provided the device(s) tri-states are functional.
 
Actually sort of a two part answer, which may depend on what Teensy board you have.

Example T4 and 4.1 have exposed pins for 3 SPI ports, although the SPI2 pins are a little harder to get access to.

And if you use the normal SPI.transfer methods, these calls are synchronous, i.e. they wait until the transfer has fully completed before it returns to the user code. As such your code will only be able to do one SPI port at a time.
This includes all of the mthods, like: SPI.transfer(val), SPI.transfer16(val), SPI.transfer(buffer, count); SPI.transfer(buffer, retbuf, count);

However the Teensy version of the SPI library has another method: SPI.transfer(buffer, retbuf, count, event_responder), which is an asynchronous version that uses DMA to do the transfer. It will return and you can get a callback with the event responder object when it completes. With this you can have transfers on multiple SPI ports at the same time.

Also some of our libraries, like many of the display libraries have DMA code in them, that also allow you to do things like update the screen without having to wait for the update to complete.
 
Seems like libraries need to do a little more to support multiple spi devices. Like, my program opens up SPI, and then I ask each SPI device library for their respective settings. Then I get to manage and orchestrate when transfers occur. As it is, I’m on the outside looking in on how each device’s library uses SPI. When it comes to I2C and SPI, I wish each and every library satisfied a requirement to show how it can work with other devices on the same protocol/bus. Because the value of any device increases if it works well with other devices.
 
you mean handling 1 CS pin per device on the same bus is an issue? Ports cannot do parallel tasks, and SPI devices are synchronous, so it makes no sense to setup multiple objects if they would all toggle the same port and talk at same time over a single MOSI pin

If your car's driver door is a singleton, how many people can enter at once? Do we create several objects (people) and shove them all in the same door? :)
 
Bus rules apply?

i2c each device with a unique slave ID can share the bus and be addressed in turn.

SPI each device with a unique CS pin for chip select can share the bus ( as long as it has tristate hardware on the bus lines ) and can be selected in turn for a complete transaction.
> except when DMA transfers are in use as noted in p#3 - that usage requires management by the sketch to exclude overlap when multiple devices are connected to one bus.
 
Well, there are 3 instances on Teensy 4.1: SPI, SPI1, SPI2, and a Wire instance for each I2C port. Both libraries do indeed have well established and well documented ways to use more than 1 chip with each instance / port.

When it comes to I2C and SPI, I wish each and every library satisfied a requirement to show how it can work with other devices on the same protocol/bus.

The reality we face today is a long software legacy from days when almost all boards had only a single SPI and single I2C port. Many libraries, including ones I've personally written, still have "Wire" or "SPI" instances names hard-coded. But we are in a period of transition where new libraries are starting to get constructors or begin() functions which take a references or pointer to the SPI or Wire instance to use.


Seems like libraries need to do a little more to support multiple spi devices. Like, my program opens up SPI, and then I ask each SPI device library for their respective settings. Then I get to manage and orchestrate when transfers occur.

An API for this was added to SPI years ago. I wrote it for Teensy, and then it took over 1 year to convince the Arduino developers to eventually accept it. That SPI is beginTransaction() and endTransaction(). It's entire purpose is to manage the cases where difference SPI chips on the same bus require different settings.

However, the SPI transaction API doesn't work in a way like "ask each SPI device library for their respective settings". How it does work is well documented. Whether that way was a sound decision was based on over 1 year of intense technical debate with the Arduino developers. Today it is indeed widely used and does very successfully manage the need for each device to use different settings at different times without conflicting.
 
I think in there future there is unfortunately no other way than to ignore the Arduino devs. You just cant wait for them, that takes way too much time.
If we want to support our better hardware, it's really needed to have software drivers for things like SPI, I2C etc that you can just give the data with an api call and they do the rest in the background, non-blocking, or, if really wanted, blocking. it would need a "config" (i.e. speed, cs-pin or address, etc) for each connected external device. on the 1170, it could run on the 2nd core.
Apart from the compiler/2nd core problem.

Paul, maybe take a look at the esp32.
It would be not that bad to be compatible to esp32 (for these things) at least.
 
Hah! Never mind my comments. I can't help myself. The truth is this kind of development is the Wild West, and I would not have it any other way. I like figuring out how to get multiple devices to work together. I am grateful to anyone who has made a repo with solution to work with a device. Because the resources of this environment are limited, I understand why we don't try to solve multi-device integration issues. Each time we need to do something like this may be a special snowflake.

But anyway, I'm going to stick with best practice and have a custom CS pin for each device... Just one more thing. That implies it's okay for multiple devices to share the other 3 SPI pins? Assuming they're all powered at 3.3v, is that really okay? (basically meaning it's a higher-impedance CMOS-style logic pin)
 
If the other devices play nice - what the should do (as always there are exceptions) - you can share the other pins.
The CS pins should have pull-up resistors, so that they can't see data when their CS pin is not configured, yet.

For slower SPI, up to ~10MHZ or a bit more, you can just use bitbanging, which works witch every pin... currently, the hardware-spi makes only sense for way faster spi (or with DMA) that cant be done via bitbang.
 
you could wrap all devices in a queue system and just queue up writes back to back, the wrapper will take care of the devices back-to-back, it is possible but a complexity to get there :) and also don't forget about the response handling! (callbacks are a must unless you are fire and forgetting)
 
I think in there future there is unfortunately no other way than to ignore the Arduino devs.

Sadly, I believe you are right. We do eventually need to move both SPI and Wire to a non-blocking transaction queue approach like Espressif is using for SPI.

https://docs.espressif.com/projects...p32/api-reference/peripherals/spi_master.html

Right now, with the pandemic still draining away time and 1170 looming is not ideal for a major redesign on these widely used libraries. But later this year, I do believe we need to move in this direction.

I had some private conversations with Arduino months ago that lead me to believe they will not be interested in non-blocking APIs. Unless they change their minds (and maybe even if they do) trying to collaborate with Arduino probably is only going to slow development without any real benefit.
 
I can look at this. I have already used the espressif arduino APIs, and they are quite usable.
(Not everything is gold... their hardware can't do fast I2C )
So if there is a change that you look at my code and we can talk about it, i'm willing to use their approach and port it to teensy.
Maybe with DMA for everything (for each SPI two DMAs for MISO + MOSI) so that there is a chance to use that for the T4.x, too.
How many DMA Channels has the 1170?
 
One final question on this thread.
I am trying to use an SPI SD card reader. One of those really cheap, one-bit ones. It works on my Uno.

I am trying to use Teensy 4 pin 14 for CS. The test code is showing that initialization failed. I'm just using the sample read/write program here:

Code:
#include <SPI.h>
#include <SD.h>

File myFile;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(115200);
  while (!Serial && millis() < 3000)
    ;

  Serial.println("Hello");
  
  Serial.print("Initializing SD card...");

  if (!SD.begin(14)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");

I will check my wiring again, but is there anything else I need to know?

Thanks,

Ben
 
Status
Not open for further replies.
Back
Top