Can we run multiple SPI devices on a Teensy++ 2.0?

Status
Not open for further replies.

Roger Parkinson

Active member
Can we run multiple SPI devices on a Teensy++ 2.0? Here is what I'm doing:
1) an SD card with its CS on pin 20
2) a tiny LCD from Adafruit (http://www.adafruit.com/products/326) with its CS on pin 7
3) an RTC
4) a pulse detector, which is just a one wire thing on pin 0 (two other pins supply power)
The MISO, MOSI and SCLK are on 23, 22, 21

Now, when I run my code everything works except the LCD stays blank.
When I comment out the init of the SD card specifically:
Code:
	if (!SD.begin(select)) {
		Serial.println("Card failed, or not present");
		// don't do anything more:
		return;
	}
	Serial.println("card initialised.");
Then the LCD works perfectly.
It feels like I can only have one of them but when I read about SPI it says this ought to work.
I set pinMode to OUTPUT for the two CS pins, but I don't know if I need that, it didn't help anyway.
My real goal is to get this running on a Teensy 2.0 which is slightly smaller, and small is good here, but I had problems that I put down to running out of memory but might have been similar to this.
So if there's a solution I hope it applies to Teensy 2.0 as well.
And I'd use a Teensy 3 but I have too many old Teensy boards lying around to not use them!

The code is in https://github.com/RogerParkinson/HeartMonitor.git
Just before the last commit I tried changing the SD SS to use pin 8 rather than pin 20. It made no difference, but that's what is there now.
Also, that's an Eclipse project rather than an Arduino one which might confuse people not expecting it, but the code is visible anyway.

Thanks for any help
Roger
 
I simplified this down to isolate the problem and I put it back onto the Teensy 2.0 because, after all, that is where I need to end up.
So here is the complete sketch from the Arduino IDE (no Eclipse involved, but I much prefer working with Eclipse)
Code:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SD.h>

#define OLED_DC 3
#define OLED_CS 6
#define OLED_CLK 1
#define OLED_MOSI 2 //Data
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

#if (SSD1306_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif

Sd2Card card;

const int chipSelect = 0; 
void setup()   {                
  delay(5000);
  pinMode(chipSelect, OUTPUT); 
  pinMode(OLED_CS, OUTPUT); 
  Serial.begin(9600);
  Serial.println("start");
/* // if this is not commented out then the LCD stays blank
  if (!card.init(chipSelect)) {
    Serial.println("Card failed, or not present");
    }
  else {
    Serial.println("card initialised.");
  }
*/  
  display.begin(SSD1306_SWITCHCAPVCC);
  display.display(); // show splashscreen
  Serial.println("display done");
  delay(5000);
  display.clearDisplay();
}
void loop() {
  display.display(); // show splashscreen
  Serial.println("display done");
  delay(5000);
  display.clearDisplay();
  delay(1000);
}
All this is doing is initialising the SD (card.init(chipSelect)) and showing the splash screen on the display.
As noted in the code comments, the splashscreen only shows up if I comment out the card.init(chipSelect). It always gets to the final Serial.println in setup(), just doesn't display anything.
The only things connected to the Teensy 2.0 at this point are the LCD and the SD, which is one of Paul's mounted piggyback on the Teensy.
Other programs run independently on this setup suggest that both SD and the LCD are fully functional in every way, just not together.
I tried various pins for OLED_CS and it is currently on 6.
Thanks for any help
Roger
 
Thanks for the reply. I changed the program around to write to the LCD then init the SD all in setup()
Then I added some more code to loop() to make it clearer that it was writing to the LCD.
The result was that the initial LCD operation worked, as you would expect, then the SD init waits for a while, several seconds before returning false.
The subsequent LCD operations in loop() fail to do anything, I can see the loop running from the Serial.println() calls, but nothing on the LCD.
Here's the revised code:
Code:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SD.h>

#define OLED_DC 3
#define OLED_CS 6
#define OLED_CLK 1
#define OLED_MOSI 2 //Data
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

#if (SSD1306_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif

Sd2Card card;
int count =0;

const int chipSelect = 0; 
void setup()   {                
  delay(5000);
  pinMode(chipSelect, OUTPUT); 
  pinMode(OLED_CS, OUTPUT); 
  Serial.begin(9600);
  Serial.println("start");

  display.begin(SSD1306_SWITCHCAPVCC);
  display.clearDisplay();
  // draw mulitple circles
  testdrawcircle();
  display.display();
  delay(2000);
  Serial.println("display done");
  
  if (!card.init(chipSelect)) {
    Serial.println("Card failed, or not present");
    }
  else {
    Serial.println("card initialised.");
  }
}
void loop() {
  display.clearDisplay();
  // draw mulitple circles
  testdrawcircle();
  display.display();
  delay(2000);
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  count++;
  display.println("hello world");
  Serial.println("hello world");
  display.display();
  delay(1000);
}
void testdrawcircle(void) {
  for (int16_t i=0; i<display.height(); i+=2) {
    display.drawCircle(display.width()/2, display.height()/2, i, WHITE);
    display.display();
  }
}
It isn't much different, but I took out the splash screen display because it was doing something odd that doesn't look important just now and I replaced it with drawing circles and a text message.
 
The idea with spi driving multiple things is:

1) Each thing has a "slave select" line (ss), it's like a chip select line.
2) When a device's ss is high it means "don't listen/respond", when ss is low it means "listen/respond to the clock and data"
3) All ss lines are usually high, you set a device's ss line low, talk to it, then set it back high
4) I know some devices actually use the high to low (starting) transition of ss to sync their internal logic, some use the ending low to high to sync, some don't use either one (they are smart enough to not need the transition).
5) So, the most "compatible" way to use ss is:

if ss isn't high set it high - keep track of time
if ss hasn't been high for about 1 spi clock period then delay about 1 spi clock period
set ss low
Send/receive
set ss high again - keep track of the time

Both your display code AND your card code must know about ss so they can drive it.

I see:
card.init(chipSelect)
So I'm guessing the card code knows how to drive ss properly.

I don't see anything display code that mentions ss.

Try setting both ss lines high, delay a bit, then set the displays ss line low, then issue your display commands, then set its ss line high again.
 
Thanks, DeweyOxberger. You've confirmed that it ought to work and what you say about the highs and lows is convincing.
The LCD CS is specified by OLED_CS and passed into the display class at
Code:
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
From there the library code ought to set it high and low as required. Which doesn't mean it actually does, but I took a look at the code myself and it seems to be doing what you describe.
I also, as you suggested, tried forcing the pins to high and low (with delays) but I didn't get any change.
There were some suggestions elsewhere that I might be just running out of memory. The Adafruit libraries are quite large and they use a lot of data for graphics.
I switched to an ascii only library (since I only expect to use ascii here this is fine) and tried that. I have a lot more free memory now, but I still have the same problem. :(

I also tried shifting my pins around a bit. I interchanged reset (4) with OLED_CS (3) and that made no dfference. I shifted OLED_CS to 21 (which is an analog pin) and that has the same problem.

So I went a different route on this. I know the OLED library doesn't care if the SPI pins are the official ones or not so I shifted them all. My pins are now connected to
Code:
#define OLED_DC 8
#define OLED_CS 6
#define OLED_CLK 9
#define OLED_MOSI 7 //Data
#define OLED_RESET 4
Which worked immediately. I can get the RESET pin back by connecting the official reset pin to that (not easy on a breadboard so I haven't yet).
So, my immediate problem is solved, at the cost of some pins which I think I can spare. The OLED has a Wire interface I could switch to if I run out and the slower speed is unlikely to matter because I'll be updating the display about once every 20 seconds or so. Still a bit of a puzzle why the original way didn't work though.
 
Still a bit of a puzzle why the original way didn't work though.

My guess would be the Adafruit library isn't really using the SPI port, but directly manipulating the pins to create SPI. I haven't looked at its code, so this is only a guess. But usually when a library lets you choose all the pins, its software-based synthesis of the SPI signals. Libraries that use the SPI peripheral generally only left you choose the SS (or CS) pin. The other 3 pins are fixed because they're controlled by the SPI port.

When one library configures the SPI peripheral, those pins become unusable as normal I/O pins. Likewise, when the I/O-based library configures the pins for normal I/O, they can't be used by the SPI peripheral.
 
Thanks, Paul. I found information on the Adafruit forum that confirms your view that is isn't using 'real' SPI at all.
So the rule is that when a library takes that approach I need to avoid sharing the real SPI pins with a real SPI library.
Okay, I think I understand this now. Thanks again.
 
I don't know if this is true for their SSD1306 library or not, but with the Adafruit ST7735 library, they offer two constructors:

Code:
  Adafruit_ST7735(uint8_t CS, uint8_t RS, uint8_t SID, uint8_t SCLK, uint8_t RST);

  Adafruit_ST7735(uint8_t CS, uint8_t RS, uint8_t RST);

The first version (with 5 pins specified) will use software bit-banged SPI, the second one will use hardware SPI.

- Peter
 
They do have two constructors on the SSD1306 library, but the other constructor is for the I2C option. I2C works fine when I solder the jumpers as instructed by their tutorial.
The SPI constructor takes all 5 params.
 
Status
Not open for further replies.
Back
Top