SPI going crazy in perfboard

I have the following circuit that will drive a gas furnace

To power the circuit I use a LM2596 DC-DC converter which feeds 5 volts to the V5 pin on the ESP32.

Initially I developed the circuit in a breadboard and developed the code there. Now that I feel it's ready for implementation I constructed the circuit as shown in the diagram in a perfboard (PCB will be the next step once everything works). One of the main reasons of using a perfboard is that the circuit containts a TFT display and three buttons, which are separated from the circuit using shielded cable and JST connectors (because the whole thing goes inside a control panel and the screen is in the door while the controller is inside).

Everything works, except for the fact that the SD card will fail to initialize if the TFT display JST connector is connected. In the breadboard everything worked without an issue. I did some physical debugging and here's what I found:

  • The issue happens when powering through a 12V power supply or when powering with USB (with and without connecting the LM2596)
  • There are no shorts and the SPI pins show continuity with the JST connector and the SD module without issues.
  • The TFT display is NOT the cause of the issue, I removed it but the issue persists when the shielded cable is connected
  • I found that the SD card will fail to initialize when both the CLK and MOSI pins are connected to the shielded cable. If only one CLK or MOSI are connected to the cable (with VCC,GND,G2,G15 connected), the SD card will initialize without an issue.

The shielded cable is 1.5m long, I have tried grounding the shield to the ESP32 GND but it doesn't have any effect. The tests have been performed in an office, I am not sure if EMI from power sockets could be the issue.

Perhaps there's some weird EMI limitations on the perfboard? I am not sure what to try next so any suggestions would be very welcome.

I found that when supplying a ESP32 with 5V from a switching regulator, a 47uF Tant. and a 103 ceramic cap on the 3.3V pin worked well.

Using GPIO_NUM_33 as an output may cause issue. Sometimes using GPIO_NUM_2, the LED can cause issues with devices that need a good Hi/Lo signal.

The ESP32 has 2 SPI channels with each SPI channel capable of driving 3 devices per channel. SPI Master Driver - ESP32 - — ESP-IDF Programming Guide latest documentation

It's bad practice to clutter up the end of the ESP with the antenna if WiFi is to be used.

  • Noticed that you have both R2 and R3 connected as pullups for IO35.
  • SPI is very sensitive to cable length ... a remedy is most likely using a slower SPI clock.
  • If still issues, could add a 47Ω series resistor to the CLK, MOSI and MISO wires (3 total).
  • Similar problem here [SOLVED]

Thanks for the power suggestion, I will consider this when designing the PCB.
GPIO_NUM_33 is used as the chip select for the MAX31855, I haven't had issues with it but will keep in mind.

I am actually a bit confused about the "3 devices per channel", IIRC the CS for each device would theoretically allow for as many devices as I can connect to a CS pin, no?

Also, I am going to use ESP-NOW, which uses a 2.4GHz communication protocol which I assume is similar to WiFi. Thanks for the heads up, I am also thinking the steel control panel might act as a Faraday cage... but I'll figure that out in the future

  • Ups, I need to correct that on the schematic. But on the real circuit there's one pullup for each of the input buttons.
  • Oh, that makes a lot of sense. I'll try lowering the clock frequency and see how it goes.
  • Sometimes when checking the continuity I got 40 ohms resistance between the JST pin and the CLK GPIO of the ESP32. Could that have something to do with the lack of a series resistor?

Thanks for the suggestions, I really appreciate it

You probably won't need them, but series resistors in the SPI lines would help with "ringing" and EMI if the length of the SPI lines are long and the clock rate is high.

Just tried adjusting the SPI SCLK frequency as per the suggestions, but still can't get it to work. Here's a sample code that I am using to test the functionality of the board, it uses one of the thermocouple modules, the SD card and the shift register to control LEDs. I don't even have the TFT display connected or initialized on the code, so the issue is 100% that the clock signal gets crazy when it's connected to the shielded cable.

#include "Adafruit_MAX31856.h"  // thermocouple card library (56)
#include <SPI.h>
#include <ShiftRegisterSPI.h>   // custom SPI library for SN74HC595
#include <SD.h>                 // SD memory card library (SPI is required)
#include <FS.h>                 // So that ESP32 recognizes SD card

const int thermoCS = 33;        // thermocouple CS pin
const int latchPin = 17;        // CS pin for shift register 74HC595
const int gasPin = 0;           // SR pin for gas relay. Pins mapped from (1-8) to Q0-Q7
const int faPin = 1;            // SR pin for FA relay. Pins mapped from (1-8) to Q0-Q7
const int fsaPin = 2;           // SR pin for FSA relay. Pins mapped from (1-8) to Q0-Q7
        
float temp;

ShiftRegisterSPI<1> shift_register(latchPin);
Adafruit_MAX31856 thermo(thermoCS);

void setup() {
  Serial.begin(115200);
  SPI.begin(); // use MOSI=23,CLK=18 (VSPI)

  thermo.begin();
  thermo.setThermocoupleType(MAX31856_TCTYPE_R); // later add to user interface

  shift_register.set(gasPin, LOW);
  shift_register.set(faPin, LOW);
  shift_register.set(fsaPin, LOW);

  while (SD.begin() == false) {
    Serial.println("SD CARD ERROR");
  }

  SPI.setClockDivider(SPI_CLOCK_DIV128);
  //SPI.setFrequency(100000);
}

void loop() {
  readTemps();
  readFiles();
  
  shift_register.set(gasPin, HIGH);
  shift_register.set(faPin, LOW);
  shift_register.set(fsaPin, LOW);

  delay(3000);
  
  shift_register.set(gasPin, HIGH);
  shift_register.set(faPin, HIGH);
  shift_register.set(fsaPin, LOW);

  delay(3000);
  
  shift_register.set(gasPin, HIGH);
  shift_register.set(faPin, HIGH);
  shift_register.set(fsaPin, HIGH);

  delay(3000);
  
  Serial.printf("\n Temperature is: %.2f \n",temp);
}

void readTemps() {
  temp = thermo.readThermocoupleTemperature();
}

void readFiles() {
  Serial.println();

  File myFile = SD.open("/1.txt", FILE_READ);
  if (myFile == false) {
    Serial.println("Can't find/open file");
  }
  while(myFile.available()) {
    Serial.write(myFile.read());
  }
  myFile.close();
  Serial.println();
}

The code works perfectly if there's no CLK connection to the shielded cable, once I connect it the SPI activation of LEDs and file reading through the SD card doesn't work. If I reset the ESP with the CLK pin connected to the shielded cable I get the "SD CARD ERROR" message.

I don't have a scope at the moment, so I am not sure that these lines are changing anything:

  SPI.setClockDivider(SPI_CLOCK_DIV128);
  //SPI.setFrequency(100000);

I tried all the dividers and frequencies as low as 100 Hz. Couldn't get it to work.

Try a different cable, one that has twisted pairs (CAT-5), configured like this ...

image
reference

On most ESP32 WROOM/WROVER development boards, GPIO_NUM_33 is on portB which is an input only pin but can be programmed for output using the ESP32's GPIO RTC API, GPIO & RTC GPIO - ESP32 - — ESP-IDF Programming Guide latest documentation.

Wow, this one worked! I used some CAT5e FTP cable I had laying around. It's a bit shorter, but it's still about 1m long. There were no more issues, however, I am not a fan of how stiff it is compared to the multiconductor shielded cable (CAT5 uses wires while the multiconductor uses 22 awg cables).

So, in a final effort to use the original shielded cable, I added 100 ohm resistors in series to the CLK, MOSI and DC pins that go to the shielded wire (I don't have 47 ohm resistors). Now the SD card does work when the shielded cable is connected, however when I connect the TFT display to the shielded cable it doesn't output anything (I tested an example from the TFT library and it doesn't work). Is this due to using 100 ohm resistors instead of 47 ohm?

I don't think so ... probably due to signal crosstalk due to parallel conductors. There are some quite flexible cables with twisted pairs available, but I don't have a source or part number.

I gave up with the shielded cable and the resistors, and went back to the CAT5e FTP cable, but for some reason now it doesn't work? It's the same issue, once I connect the CLK pin to the cable everything goes nuts.

Is using an extension cable with SPI really this difficult? I don't know what else to try

image
GPIO33 appear as an input/output on Espressif's page

perhaps you are referring to GPIO34-39?
image

But still, the thermocouple is working fine

Maybe SPI.setClockDivider(SPI_CLOCK_DIV128); wasn't working.

Perhaps this method could set CLK to 100 kHz.
Could try 500000 (500 kHz) , 1000000 (1 MHz) or others ...

#include "Adafruit_MAX31856.h"  // thermocouple card library (56)
#include <SPI.h>
#include <ShiftRegisterSPI.h>   // custom SPI library for SN74HC595
#include <SD.h>                 // SD memory card library (SPI is required)
#include <FS.h>                 // So that ESP32 recognizes SD card

const int thermoCS = 33;        // thermocouple CS pin
const int latchPin = 17;        // CS pin for shift register 74HC595
const int gasPin = 0;           // SR pin for gas relay. Pins mapped from (1-8) to Q0-Q7
const int faPin = 1;            // SR pin for FA relay. Pins mapped from (1-8) to Q0-Q7
const int fsaPin = 2;           // SR pin for FSA relay. Pins mapped from (1-8) to Q0-Q7

float temp;

ShiftRegisterSPI<1> shift_register(latchPin);
Adafruit_MAX31856 thermo(thermoCS);

void setup() {
  Serial.begin(115200);
  SPI.begin(); // use MOSI=23,CLK=18 (VSPI)
  SPI.setFrequency(100000); // 100kHz CLK

  thermo.begin();
  thermo.setThermocoupleType(MAX31856_TCTYPE_R); // later add to user interface

  shift_register.set(gasPin, LOW);
  shift_register.set(faPin, LOW);
  shift_register.set(fsaPin, LOW);

  while (SD.begin() == false) {
    Serial.println("SD CARD ERROR");
  }
}

void loop() {
  readTemps();
  readFiles();

  shift_register.set(gasPin, HIGH);
  shift_register.set(faPin, LOW);
  shift_register.set(fsaPin, LOW);

  delay(3000);

  shift_register.set(gasPin, HIGH);
  shift_register.set(faPin, HIGH);
  shift_register.set(fsaPin, LOW);

  delay(3000);

  shift_register.set(gasPin, HIGH);
  shift_register.set(faPin, HIGH);
  shift_register.set(fsaPin, HIGH);

  delay(3000);

  Serial.printf("\n Temperature is: %.2f \n", temp);
}

void readTemps() {
  temp = thermo.readThermocoupleTemperature();
}

void readFiles() {
  Serial.println();

  File myFile = SD.open("/1.txt", FILE_READ);
  if (myFile == false) {
    Serial.println("Can't find/open file");
  }
  while (myFile.available()) {
    Serial.write(myFile.read());
  }
  myFile.close();
  Serial.println();
}

I did try using the SPI.setFrequency() command, but still got no results. As you say, maybe the CLK frequency isn't changing so I'll test that tomorrow with a oscilloscope at school.

There's a GitHub page where they talk about the TFT display failing when attempting to read from an SD card and they say

But, are you running long cables to the display and SD, if so don't underestimate the effect of propagation errors due to a lack of screening and reflections. You may need pull-up resistors for example.

Another forum thread talks again about this

One could definitely argue in favor of pullups on the (active low) CS lines for SPI devices....

And there's also this

Its good design practice to avoid leaving unused pins floating. High impedance input(s) = antennae(s) just waiting to pickup RF or EMI.

Not sure though if this is pointing in the right direction. What I know is the problem lies in using a long cable with the SPI lines while driving multiple SPI devices. It doesn't matter if there's a device connected to the cable, so there must be some distortion of the CLK/MOSI signal from picked up noise. I tried grounding to the ESP-GND, to mains earth and also to both. Non of these methods eliminated the issue...

FYI -
ESP-Now is good, but it's only for ESP-to-ESP - there's no "ESP-Now" app.

Took a closer look at your schematic referring to the thermocouple circuit where both a MAX31856 and a MAX31855 is shown, but your code only includes Adafruit_MAX31856.h

The Adafruit-MAX31855-library is here where you could include Adafruit_MAX31855.h

Adafruit MAX31856 (from your schematic):
image

Adafruit MAX31855 (from your schematic):
image

For both breakouts, 3V3out shouldn't be connected to VCC which is also a 3.3 output. The main problem is that with VIN unconnected and floating, it would cause noise and power glitches on the system because the EN (enable) pin of the on board regulator is tied to VIN and it needs to be terminated (not floating):
MAX31855:image MAX31856: image

Another problem is that if using a long cable to provide 3.3V power, some voltage drop and other issues may occur.

Solution:
I would suggest connecting VIN of the Adafruit breakout to your 5V power and leave the 3V3 terminal unconnected (the internal IC will now get stable 3.3V power from the regulator).
Adafruit MAX31855 schematic
Adafruit MAX31856 schematic

Regarding SPI:

  • How about trying a flexible Cat-6 Gigabit Ethernet Patch Internet Cable (with twisted pairs)?
  • May also need series resistors connected as shown (MISO resistor at the slave device):

image reference

With a slower CLK frequency, could use 100Ω to 220Ω resistors.

Your code:

Here's your code using the MAX31856 in non-blocking, continuous read mode by monitoring the DRDY signal:

#include "Adafruit_MAX31856.h"  // thermocouple card library (56)
#include <SPI.h>
#include <ShiftRegisterSPI.h>   // custom SPI library for SN74HC595
#include <SD.h>                 // SD memory card library (SPI is required)
#include <FS.h>                 // So that ESP32 recognizes SD card

const int thermoCS = 33;        // thermocouple CS pin
const int thermoDRDY = 32;      // thermocouple DRDY pin

const int latchPin = 17;        // CS pin for shift register 74HC595
const int gasPin = 0;           // SR pin for gas relay. Pins mapped from (1-8) to Q0-Q7
const int faPin = 1;            // SR pin for FA relay. Pins mapped from (1-8) to Q0-Q7
const int fsaPin = 2;           // SR pin for FSA relay. Pins mapped from (1-8) to Q0-Q7

float temp;

ShiftRegisterSPI<1> shift_register(latchPin);
Adafruit_MAX31856 thermo(thermoCS);

void setup() {
  pinMode(thermoDRDY, INPUT);
  Serial.begin(115200);
  SPI.begin(); // use MOSI=23,CLK=18 (VSPI)
  SPI.setFrequency(1000000); // 1MHz CLK
  if (!thermo.begin()) {
    Serial.println("Could not initialize thermocouple.");
    while (1) delay(10);
  }
  thermo.setThermocoupleType(MAX31856_TCTYPE_R); // later add to user interface
  thermo.setConversionMode(MAX31856_CONTINUOUS);

  shift_register.set(gasPin, LOW);
  shift_register.set(faPin, LOW);
  shift_register.set(fsaPin, LOW);

  while (SD.begin() == false) {
    Serial.println("SD CARD ERROR");
  }
}

void loop() {
  readTemps();
  readFiles();

  shift_register.set(gasPin, HIGH);
  shift_register.set(faPin, LOW);
  shift_register.set(fsaPin, LOW);

  delay(3000);

  shift_register.set(gasPin, HIGH);
  shift_register.set(faPin, HIGH);
  shift_register.set(fsaPin, LOW);

  delay(3000);

  shift_register.set(gasPin, HIGH);
  shift_register.set(faPin, HIGH);
  shift_register.set(fsaPin, HIGH);

  delay(3000);

  Serial.printf("\n Temperature is: %.2f \n", temp);
}

void readTemps() {
 if (!digitalRead(thermoDRDY)) temp = thermo.readThermocoupleTemperature();
}

void readFiles() {
  Serial.println();

  File myFile = SD.open("/1.txt", FILE_READ);
  if (myFile == false) {
    Serial.println("Can't find/open file");
  }
  while (myFile.available()) {
    Serial.write(myFile.read());
  }
  myFile.close();
  Serial.println();
}

Yep, ESP-NOW is indeed ESP-to-ESP. The way I set it up is there are 3 ESPs in the gas kiln, 2 are used to monitor gas/air pressures on each side of the kiln and the other one is used to receive the gas/air pressures from the other two and also read the kiln temperature. At the moment with that data I only publish it to a cloud server, the idea is to also control the kiln which is why I am making this board.

In the first stages of this project I used the MAX31855 module, however I upgraded to the MAX31856 because it can read type S and type R thermocouples. In the schematic I have both because maybe in the future I would like to read 2 thermocouples to get an idea of temperature distribution inside the kiln and also as a safety feature.

About the 3V3 to VCC, that actually makes a lot of sense! For the MAX31856 I can definitely connect the VIN pin to the 5V pin of the ESP32 or directly to the buck converter right? However my MAX31855 board doesn't have a VIN pin, it only has a VCC pin. I've tried it before and it will work with 5V so should I connect it to the 5V pin of the ESP32 or buck converter?

On this point, I am also feeding VCC to the TFT display from the 3V3 pin from the ESP32. Perhaps the long distance cable is causing a voltage drop? Will test with 5V input instead. I will also try getting a CAT-6 cable, the one that I can get is twisted pair (I think all are).

Thank you very much for all the suggestions! Let's hope this works :slight_smile:

The Adafruit MAX31856 uses the mic5225-3.3 regulator which has VIN rating: 2.3V to 16V.
Therefore, you could optionally use your buck converter's 12V output to connect to VIN on the Adafruit MAX31856 breakout board. This is the same VIN as the regulator.
I think connecting VIN directly to the buck converter's 5V output is the best choice.

The Adafruit MAX31855 breakout board uses the LP298XS regulator which has VIN rating: 2.1V to 16V. This breakout board's image shows the silkscreen label being VIN.

OK, a closer look at your image shows a board like below (from an unbranded supplier). From the link, it says "the input voltage is 3-5v DC" There's no regulator on this board, so connecting VCC directly to the buck converter's 5V output would mean that an I2C logic level converter is needed (this also works with SPI signals).

Note that the Adafruit MAX31856 Library by default uses one-shot conversion mode that is blocking and delays code execution for 250ms. I think this delay is interfering with other SPI code execution and could be the main cause of the SPI issues. Try the example using the DRDY signal and DRDY pin on the module. This is non-blocking and will at least make your code much more responsive.

Yes, the TFT display is power hungry and there will be some voltage drop. If its compatible with a 5V power and the 3.3V logic levels from the ESP32, then 5V power is the best option.

Yes, I think so. (avoid any flat cable types with parallel conductors)