Nano ESP32 SSD1306 and MCP23x17 SPI Conflict?

I'm using an OLED (SSD1306) and MCP23x17 with a Nano ESP32 (both SPI).

I'm able to display "test7" on the OLED when the [display.] block is located before the if(!mcp.begin_SPI block, but not when placed after this block (as shown below).

Is there some conflict here that I'm not seeing?


#include <Adafruit_MCP23X17.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SPI.h>

// set pins on MCP23S17
const byte Sensor[5] = { 0, 1, 2, 3, 4 };
const byte Trigger[5] = { 8, 9, 10, 11, 12 };

//OLED display (width, height, D1(MOSI), D0(CLK), DC(MISO), RESET, CS)
Adafruit_SSD1306 display(128, 64, 38, 48, 47, 4, 13);

const byte CS_PIN = D10;  //chip-select for MCP

Adafruit_MCP23X17 mcp;

void setup() {
  Serial.begin(9600);
  delay(1500);

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;)
      ;  // Don't proceed, loop forever
  }

  if (!mcp.begin_SPI(CS_PIN)) {
    Serial.println("Error.");
    while (1)
      ;
  }

  delay(1000);
  display.clearDisplay();
  display.setTextSize(1);               // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);  // Draw white text
  display.setCursor(0, 0);              // Start at top-left corner
  display.println(F("test7"));
  display.display();
  delay(1000);

  //set MCP PinMode for Sensor/Trigger
  for (int i = 0; i < 5; i++) { mcp.pinMode(Sensor[i], INPUT); }
  for (int j = 0; j < 5; j++) { mcp.pinMode(Trigger[j], OUTPUT); }

  //set all Triggers to LOW/OFF
  for (int k = 0; k < 5; k++) { mcp.digitalWrite(Trigger[k], LOW); }

}

void loop() {
  Serial.println("loop");
}

Could you post an annotated schematic showing your wiring setup, along with some clear photos? Please include links to the technical information for the hardware devices and be sure to show all power sources, including batteries and other supplies.

Also, in your sketch there is a mix of pins defined by numbers, like (at least) the 48 and 47 here:

Adafruit_SSD1306 display(128, 64, 38, 48, 47, 4, 13);

... and by labels, like the CS_PIN:

const byte CS_PIN = D10;  //chip-select for MCP

Please convert all pin numbers to labels, and make sure to select By GPIO Number (legacy) in the Tools > Pin Numbering menu. This should improve your results!
You can read more about those settings here.

I believe this is the update you suggested. However, still having the same problem. The "testxx" properly displays when the display-block is before mcp.begin_SPI, but doesn't update when the block is after.

#include <Adafruit_MCP23X17.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SPI.h>

// set pins on MCP23S17
const byte Sensor[5] = { 0, 1, 2, 3, 4 };
const byte Trigger[5] = { 8, 9, 10, 11, 12 };

#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels

// Declaration for SSD1306 display connected using software SPI (default case):
#define OLED_MOSI 38
#define OLED_CLK 48
#define OLED_DC 47
#define OLED_CS 13
#define OLED_RESET 4
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
                         OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

const byte CS_PIN = 21;  //chip-select for MCP

Adafruit_MCP23X17 mcp;

void setup() {
  Serial.begin(9600);
  delay(1500);

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;)
      ;  // Don't proceed, loop forever
  }

  if (!mcp.begin_SPI(CS_PIN)) {
    Serial.println("Error.");
    while (1)
      ;
  }

  delay(1000);
  display.clearDisplay();
  display.setTextSize(1);               // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);  // Draw white text
  display.setCursor(0, 0);              // Start at top-left corner
  display.println(F("test10"));
  display.display();
  delay(1000);

  //set MCP PinMode for Sensor/Trigger
  for (int i = 0; i < 5; i++) { mcp.pinMode(Sensor[i], INPUT); }
  for (int j = 0; j < 5; j++) { mcp.pinMode(Trigger[j], OUTPUT); }

  //set all Triggers to LOW/OFF
  for (int k = 0; k < 5; k++) { mcp.digitalWrite(Trigger[k], LOW); }
}

void loop() {
  Serial.println("loop");
}

Here's a link to the hardware devices:

ESP32

OLED

And here's the wiring. As you can see, the display functions properly and is displaying "test 9" (from when I uploaded the code with the display-block before the mcp.begin_SPI).

Thanks for the update, not quite what I meant though. I was very brief in my message so I'll try to be clearer this time... :wink:

Pin numbers on the Nano ESP32 are very confusing due to conflicting needs. For some background, you can read a lengthy introduction on the subject I wrote some time ago, but the core of the issue is that those numbers can be interpreted in two different ways, and there is often no context to determine which one is correct.

I converted the following lines from GPIO numbers to the actual pin labels:

#define OLED_MOSI  D11
#define OLED_CLK   D13
#define OLED_DC    D12
#define OLED_CS    A6
#define OLED_RESET A3

const byte CS_PIN = D10;

Please try to write pin references like this in your sketches; it is good practice and works on all Arduino boards!

Now, for this to be truly effective with the SSD1306 library, please confirm you have selected By GPIO Number (legacy) in the Tools > Pin Numbering menu of the IDE.
This is very important for library compatibility.


Please use the above suggestions in all your Nano ESP32 projects.

BUT, after writing all of this, I just realized your problem might also be due to how those libraries implement SPI?

You mention that the SSD1306 library is using software SPI, while the MCP library only allows you to set the CS pin (and therefore uses the hardware SPI implementation, I guess?). If so, that would explain why after the MCP initializes, the display won't work anymore - both need to use the same strategy for them to play nice.

EDIT: even more important, the display pins match the picture, but it seems your MCP23S17 is very shy :face_with_hand_over_mouth:. Where is it and how is that connected to the Nano?

1 Like

If both devices are using the same SPI bus, set both CS pins to OUTPUT and HIGH before calling the setup functions. I do that just after the Serial.begin() call.

Here's my updated code and I've been using legacy pin numbering (by GPIO). However, I do find that a bit counter-intuitive, as I'm specifically NOT "using GPIO" for my numbering--instead I'm using D# and A# (which is why I changed my numbering to specifically reference the GPIO number). Regardless, the OLED still only updates properly when that block is before the mcp.begin_SPI block.

As far as "where's the mcp chip"...I'm merely trying to get my first OLED working and incorporated into my code before I connect it to my final product.

It sounds like the actual issue is with the HW vs SW SPI--which I'll have to dig into further to figure out how to untangle.

#include <Adafruit_MCP23X17.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SPI.h>

// set pins on MCP23S17
const byte Sensor[5] = { 0, 1, 2, 3, 4 };
const byte Trigger[5] = { 8, 9, 10, 11, 12 };

#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels

// Declaration for SSD1306 display connected using software SPI (default case):
#define OLED_MOSI D11
#define OLED_CLK D13
#define OLED_DC D12
#define OLED_CS A6
#define OLED_RESET A3
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
                         OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

const byte CS_PIN = D10;  //chip-select for MCP

Adafruit_MCP23X17 mcp;

void setup() {
  Serial.begin(9600);
  delay(1500);

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;)
      ;  // Don't proceed, loop forever
  }



  if (!mcp.begin_SPI(CS_PIN)) {
    Serial.println("Error.");
    while (1)
      ;
  }

  delay(1000);
  display.clearDisplay();
  display.setTextSize(1);               // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);  // Draw white text
  display.setCursor(0, 0);              // Start at top-left corner
  display.println(F("test11"));
  display.display();
  delay(1000);

  //set MCP PinMode for Sensor/Trigger
  for (int i = 0; i < 5; i++) { mcp.pinMode(Sensor[i], INPUT); }
  for (int j = 0; j < 5; j++) { mcp.pinMode(Trigger[j], OUTPUT); }

  //set all Triggers to LOW/OFF
  for (int k = 0; k < 5; k++) { mcp.digitalWrite(Trigger[k], LOW); }
}

void loop() {
  Serial.println("loop");
}

Unfortunately, setting both pins HIGH did not solve the issue either...

Did you try this? Without it, you may get a bit of bus contention.

void setup() {
  Serial.begin(9600);
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH);
  pinMode(OLED_CS, OUTPUT);
  digitalWrite(OLED_CS, HIGH);
#rest of your setup

Here's the code, still not able to display "test12":

#include <Adafruit_MCP23X17.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SPI.h>

// set pins on MCP23S17
const byte Sensor[5] = { 0, 1, 2, 3, 4 };
const byte Trigger[5] = { 8, 9, 10, 11, 12 };

#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels

// Declaration for SSD1306 display connected using software SPI (default case):
#define OLED_MOSI D11
#define OLED_CLK D13
#define OLED_DC D12
#define OLED_CS A6
#define OLED_RESET A3
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
                         OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

const byte CS_PIN = D10;  //chip-select for MCP



Adafruit_MCP23X17 mcp;

void setup() {
  Serial.begin(9600);
  pinMode(OLED_CS, OUTPUT);
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(OLED_CS, HIGH);
  digitalWrite(CS_PIN, HIGH);
  delay(1500);

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;)
      ;  // Don't proceed, loop forever
  }




  if (!mcp.begin_SPI(CS_PIN)) {
    Serial.println("Error.");
    while (1)
      ;
  }

  delay(1000);
  display.clearDisplay();
  display.setTextSize(1);               // Normal 1:1 pixel scale
  display.setTextColor(SSD1306_WHITE);  // Draw white text
  display.setCursor(0, 0);              // Start at top-left corner
  display.println(F("test12"));
  display.display();
  delay(1000);

  //set MCP PinMode for Sensor/Trigger
  for (int i = 0; i < 5; i++) { mcp.pinMode(Sensor[i], INPUT); }
  for (int j = 0; j < 5; j++) { mcp.pinMode(Trigger[j], OUTPUT); }

  //set all Triggers to LOW/OFF
  for (int k = 0; k < 5; k++) { mcp.digitalWrite(Trigger[k], LOW); }
}

void loop() {
  Serial.println("loop");
}

Since I'm just breadboarding here, would it matter that my mcp chip isn't physically attached?

How would this missing mcp interfere with a different SPI object?

Not finding much info on setting hardware SPI on the OLED. The Adafruit_SSD1306 library indicates this in the example sketch. However, simply adding this uncommented block to use hardware SPI doesn't work, so there must be more involved. In fact, the OLED display block doesn't display anything (regardless if it's before or after the mcp.begin_SPI block).

What more do I need to "use hardware SPI for this OLED"?

// Declaration for SSD1306 display connected using software SPI (default case):
#define OLED_MOSI   9
#define OLED_CLK   10
#define OLED_DC    11
#define OLED_CS    12
#define OLED_RESET 13
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
  OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

/* Comment out above, uncomment this block to use hardware SPI
#define OLED_DC     6
#define OLED_CS     7
#define OLED_RESET  8
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
  &SPI, OLED_DC, OLED_RESET, OLED_CS);
*/

Those pins (D11-13) are SPI on the Uno. It won't work on a Mega.
You use a ESP32?

I'm using this ESP32 Nano

The SPI pins are D11-13 on the Nano ESP32.
The pic in post#5 shows only one device connected to the SPI. Does that work?
Which MCP device do you have? Does it work by itself?

Edit: How I would go about troubleshooting this is connect both devices to the SPI pins D11-13.
On one, connect the CS pin to 3.3v (disabled), and the other to the CS pin for that device.
Run the begin function on the device connected to the CS pin. Does that work?

Now switch the devices. Move the CS pin connected to 3.3v to it's CS pin, and the other CS to 3.3v. Does that work?

Some devices (very few) will not release the MISO line.

I believe I have my pins set and defined correctly, as the OLED functions properly by itself. It only stops working properly when the display block is executed after the mcp.begin.

The mcp works fine without the OLED attached (regardless of where the OLED display block is located).

So the display works if the mcp is connected but mcp.begin isn't run?

Edit: If you are certain the pins are connected correctly, that indicates the MCP is not releasing the MISO line after the begin call.

Correct. I just went ahead and hacked the OLED into the overall project (which contains the mcp).

The OLED still only works when the display block is before the mcp.begin_SPI block.

Got a voltmeter?
Check the voltage on the MCP CS pin after the mcp.begin() call. It should be near 3volts. If it is near zero, that is the problem.
You might be able to cheat by resetting the mcp CS pin HIGH after the begin call.
Just a thought.

.