As the title indicates, I had SPI working with shift registers suddenly stop working. The odd thing is that if I use the same pins to and use shiftOut, it works flawlessly (which vindicates the hardware). I have a tried a number of things, such as lowering the transfer speed, and inserting delays, but nothing seems to work. Since the hardware is proven, I think it must be related to the software. I would appreciate it if someone could review my code (it's pretty simple) and make some suggestions as I'm running out of ideas. Is there something I'm doing wrong, or something else that I need to do? I should mention that I tried another ESP32 and that also didn't work. There is a pre-processor symbol "USE_SPI" that drives my code. When I comment the symbol out, it works using the non SPI approach. Thank you.
#include <SPI.h>
#define USE_SPI
// 74HCT595 - Shift register (SIPO) for LED display.
static const int ClockPin = 18; // VSPI_CLK
static const int DataPin = 23; // VSPI_MOSI
static const int LatchPin = 5; // VSPI_CS
static unsigned int DisplayRegister = 0;
void setup() {
SPI.begin();
SPI.beginTransaction(SPISettings(14000000, MSBFIRST, SPI_MODE0));
pinMode(ClockPin, OUTPUT);
pinMode(DataPin, OUTPUT);
pinMode(LatchPin, OUTPUT);
digitalWrite(LatchPin, HIGH);
}
void loop() {
#ifdef USE_SPI
// Fast SPI shift register load
digitalWrite (LatchPin, LOW);
SPI.transfer(DisplayRegister>>8);
SPI.transfer(DisplayRegister);
digitalWrite (LatchPin, HIGH);
#else
// "Conventional" way to load the shift register
digitalWrite(LatchPin, LOW); // Do not write to the latch outputs while shifting.
shiftOut(DataPin, ClockPin, MSBFIRST, DisplayRegister>>8);
shiftOut(DataPin, ClockPin, MSBFIRST, DisplayRegister);
digitalWrite(LatchPin, HIGH); // Done shifting, write new data to the latch outputs
#endif // USE_SPI
DisplayRegister++;
delay(10);
}
Code that worked, and did not change, now does not work. Is that what you are saying?
I may be missing something, but the code is not among the first several things I would be looking at.
Please post a schematic showing all parts connected.
Please post a picture of the entire project or test setup you working with.
This sounds like something changed in the physical setup which pushed you over some hairy edge, like maybe a decoupling capacitor fell out, so now the different approach still works, however marginally, but your original code does not.
Or some other change you think should not matter, like a longer wire or how some signals are physically routed to their destinations.
The code sample was originally in a class and I extracted it into a standalone sketch. Using static along with const is probably not necessary, but it means that the value is computed once during initialization and need not be computed in the constructor. Like I said though, not really necessary, but in some sense seemed more "correct".
And to your second question. What you suggest is better although it really doesn't hurt anything to initialize SPI and not use it.
Thank you for your response and all of your points are well taken. I should mention that when using SPI, scoping the SCLK and MOSI pins shows no activity at all. That was the reason why I tried another ESP32. Given that, it seems to me that something seems wrong in the software that is not correctly configuring SPI so I would like to start there. Why is SPI not outputting signals even with no hardware connected? I know I said "suddenly", but I was in the midst of making changes to my program in other areas so I can't give a good explanation of what, if anything has changed. But I do think I should be able to at least see activity on the MOSI and SCLK pins when the software transfers data to the SPI register.
I am using the standard pin numbering for VSPI. I even wrote a few lines of code that prints out the pin numbers for verification. Surprisingly, I haven't found a good example that uses SPI.beginTransaction() so I just asked copilot to write an ESP32 VSPI program to control a 74HC595 shift register and it spit out a near identical program to mine! It did mention that there is a known issue with the clock pin (GPIO18) and suggested reassigning it to GPIO14, so that's another avenue to pursue. I will continue plugging away at it. Thanks for the suggestions.
no but it might hurt to initialize SPI and then re-define the pinMode. Actually on an ESP32 i think it doesn't matter so much but i know on an ESP8266 it really does. I think on an ESP32 the MUX settings remain until altered and the pinMode for use as GPIO can be set independently, but i am not fully sure about that.
That is an EXCELLENT observation, as it fixes my problem! In SPI mode, I was explicitly declaring MOSI, SCLK, and CS as OUTPUTs. It turns out that only CS (latch) should be (actually MUST be) explicitly declared as OUTPUT. At some point, I must have added those additional output statements thinking it was "good practice", moved on to something else without first testing it, and later found it broken. Totally unintuitive to me that the definitions for the other pins should matter. Part of the problem for me was not being able to find good examples of using the (newer) SPI library. You must have studied the hardware structure of ESPxx SPI to have realized the pin declaration issue. Well, big thank you for pointing that out, I had it working again minutes after I read your comment.
For the record, here is the cleaned up sample that now works.
#include <Arduino.h>
#include <SPI.h>
#define USE_SPI
// 74HCT595 - Shift register (SIPO) for LED display.
const int ClockPin = 18; // VSPI_CLK HSPI(14)
const int DataPin = 23; // VSPI_MOSI HSPI(13)
const int LatchPin = 5; // VSPI_CS
static unsigned int DisplayRegister = 0;
void setup() {
Serial.begin(9600);
//SPI.begin();
#ifdef USE_SPI
SPI.begin(ClockPin, -1, DataPin, LatchPin);
SPI.beginTransaction(SPISettings(24000000, MSBFIRST, SPI_MODE0));
#endif
#ifndef USE_SPI
pinMode(ClockPin, OUTPUT);
pinMode(DataPin, OUTPUT);
#endif
pinMode(LatchPin, OUTPUT);
// digitalWrite(LatchPin, HIGH); // de-select 74HCT595
}
void loop() {
digitalWrite (LatchPin, LOW);
// Fast SPI shift register load
#ifdef USE_SPI
SPI.transfer(DisplayRegister>>8);
SPI.transfer(DisplayRegister);
#else
// "Conventional" way to load the shift register
shiftOut(DataPin, ClockPin, MSBFIRST, DisplayRegister>>8);
shiftOut(DataPin, ClockPin, MSBFIRST, DisplayRegister);
#endif // USE_SPI
digitalWrite(LatchPin, HIGH); // Done shifting, write new data to the latch outputs
DisplayRegister++;
delay(500);
}
Mainly on the esp8266, which although doesn't allow for SPI pin changes, does have multiple functions for quite a few pins, which can be set explicitly to those modes, even after the initial definition.
The function like Serial.begin() & Serial.swap() actually do exactly that when you find them in the core.
I haven't dived into the ESP32 that far yet. (mind you, part of the core is pre-compiled and can not be viewed by us mortals)
The ESP32 does allow for pin function changes, and i suspected that setting the mode may be an issue, but as i said before i think, i wasn't sure since once in case of the UART, there appears to be some discrepancy, and the RX pin does not get disconnected from the UART even if it's explicitly declared as an INPUT. At least that's what i've read somewhere, never did manage to test it properly, which the RX-FIFO being just a little bit beyond me.
I did find another post from you about Serial.swap(). I suppose these concerns are all related to multi-function pins and the ability to change their function. Still, wouldn't have thought that specifying the in as OUTPUT, would affect things, as that's what SPI "wants" it to be anyway. I wish these quirks were better documented somewhere. Oh well, thanks for the additional info. I definitely learned something!