Using VSPI and HSPI simultaneously

Greetings, just getting to know arduino and I'm wiring up a basic project which involves data being received off a Dragino LoRa Shield transciever, before being displayed on a ST7920 LCD screen. My micro-controller of choice is the ESP32 Devkitc v4.

I want my receiver and LCD screen to be running constantly so if any message is picked up, it can be displayed almost instantaneously, and by my understanding, this means utilising both SPI buses on the ESP (VSPI & HSPI). Both the Dragino LoRa Shield and LCD screen work perfectly when connected to the default VSPI pins, however, whenever I try getting them to run simultaneously, the device connected to HSPI does not seem to work at all. Not sure if the 'LoRa.h' or 'U8g2lib.h' libraries have some code that require them to be used via the default VSPI. I am aware of the option to run both devices of the same SPI lines, albeit with different chip selects to toggle between each device, but I want each to always be running.

Below is some test code. I just want some basic function to run off both, for the Dragino to receive some packets off another ESP I have transmitting, and the LCD to display some example text. Any suggestions?

#include <U8g2lib.h>
#include <LoRa.h>

// VSPI CONNECTED VIA DEFAULT PINS
U8G2_ST7920_128X64_F_SW_SPI u8g2(U8G2_R0, /* clock=*/ 18, /* data=*/ 23, /* CS=*/ 5, /* reset=*/ 22); // ESP32

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

//define the pins used by the transceiver module (HSPI CONNECTED)
#define ss 26
#define rst 16
#define dio0 17

//The Default HSPI Pins
#define HSPI_SCK 14 
#define HSPI_MISO 12
#define HSPI_MOSI 13 
#define HSPI_CS 15 

//Via tutorial from 
SPIClass *hspi = NULL;

void setup(void) {
  u8g2.begin();
  u8g2.enableUTF8Print();		// enable UTF8 support for the Arduino print() function

 hspi = new SPIClass(HSPI);
  

  hspi->begin(HSPI_SCK, HSPI_MISO, HSPI_MOSI, HSPI_CS);

  pinMode(hspi->pinSS(), OUTPUT);

    //initialize Serial Monitor
  Serial.begin(115200);
  while (!Serial);
  Serial.println("LoRa Receiver");

  //setup LoRa transceiver module
  LoRa.setPins(ss, rst, dio0);
  
  //replace the LoRa.begin(---E-) argument with your location's frequency 
  while (!LoRa.begin(915E6)) {
    Serial.println(".");
    delay(500);
  }
   // Change sync word (0xF3) to match the receiver
  // The sync word assures you don't get LoRa messages from other LoRa transceivers
  // ranges from 0-0xFF
  LoRa.setSyncWord(0xF3);
  Serial.println("LoRa Initializing OK!");
}

void loop(void) {

  // try to parse packet
  int packetSize = LoRa.parsePacket();
  if (packetSize) {
    // received a packet
    Serial.print("Received packet '");

    // read packet
    while (LoRa.available()) {
      String LoRaData = LoRa.readString();
      Serial.print(LoRaData); 
    }

    // print RSSI of packet
    Serial.print("' with RSSI ");
    Serial.println(LoRa.packetRssi());
  }

  u8g2.setFont(u8g2_font_unifont_t_chinese2);  // use chinese2 for all the glyphs of "你好世界"
  u8g2.setFontDirection(0);
  u8g2.clearBuffer();
  u8g2.setCursor(0, 15);
  u8g2.print("Hello World!");
  u8g2.setCursor(0, 40);
  u8g2.print("你好世界");		// Chinese "Hello World" 
  u8g2.sendBuffer();
  
  delay(1000);

}

I would use the same SPI bus for both the LoRa device and the display, I do this myself often, no issues.

There is no performance gain achieved by running the LoRa device and display on seperate SPI buses.

The one exception I would make is that if your using an SD card to have that on a sperate SPI bus, the SD card does not seem to be that reliable when it runs on an SPI bus with another device.

Its just not the case that you should set the syncword to any value between 0 and xFF.

Some syncwords wont work, some will be leaky (you get packets with other syncwords) and some syncwords cause reduced sensitivity.

I have no experience with any ESP32 based processors. But I think that you see that wrong. After all, you will need to read the LoRa (it does not matter on which SPI interface) and next display the data (it does not matter which interface).

Cheap crap does not release (tri-state) the the MISO line; hence the conflict. One 3.3V boards there is also no reason to use active modules; you only need the wires connection.

My comment was not based on the use of those dodgy SD card adapters, but on the use of 3.3V SD card holders that have direct access to all the pins.

This reliability issue I have experienced is only seen with ESP32s.

Having a crack at that now. Would it just involve hooking up the LoRa shield and LCD MOSI/MISO/CLK to the same node (LCD doesn't need MISO) and toggle between chips with:

  digitalWrite(LORA_CS, LOW);  // Enable LoRa Shield
  digitalWrite (LCD_CS, HIGH); // Disable LCD

Before doing any LoRa/LCD operations?

No need for you to add code to 'toggle' between devices.

The LoRa library and the display library will automatically use the CS pins you told the respective library about. That's how SPI busses work.

According to this SPI tutorial on Random Nerd Tutorials, it suggests you need to toggle each CS pin high or low, depending on which device is used? Am I interpreting something wrong? So by your advice, because each device has its own chip select, everything should run smoothly as long as:

  • Each device has as its own unique CS pin.
  • All SPI devices are connected to the same SPI (MISO/MOSI/CLK) pins.

Or is the CS pin used exclusively to read from an SPI device? So only the LoRa CS pin is really relevant in this situation as the LCD is being written to?

That can be a recommendation.

Now that is plain optimism. I like it. Reality is that many libraries do set their CS pin LOW when initializing the device, but never set it HIGH when they are not busy with it. Sure some libraries do, but not all for them.

The ESP32 SD library is one of those that sets the CS pin LOW, but never HIGH thereafter. It doesn't have anything to do with the hardware. If you switch the CS pin manually, it works as it is supposed to.

I am assuming that you are using the default HSPI pins for that as well. I found that changing the default pins works.

I use

  #define HSPI_SCK 17
  #define HSPI_MISO 16
  #define HSPI_MOSI 4
  #define SD_CS 15

And initiate the object with those pins

  hspi->begin(HSPI_SCK, HSPI_MISO, HSPI_MOSI, HSPI_CS);

It does depend on the library accepting a pointer to the SPI object, not all of them do. The problem being that you need to tell the library which SPI port to use, and also the library should not initialize this SPI again, possibly changing the pins back to their default. The SD library works fine when using HSPI and modified pins. Don't know about the others. You could of course modify the library to work with HSPI

This actually appears to refer to software SPI

This is how it;s done with an SD card

  if (hspi == NULL) {
    hspi = new SPIClass(HSPI);
  }
  hspi->begin(HSPI_SCK, HSPI_MISO, HSPI_MOSI, SD_CS);
  hspi->setFrequency(SPI_FRQ);

  if (!SD.begin(SD_CS, *hspi, SPI_FRQ)) {

Thank you for your help, I have gotten both my SPI devices to run off VSPI and HSPI simultaneously! The software SPI seemed to work, although I also had to manually set the chip select of the HSPI to high and low in order for the LoRa shield to receive any data and for the LCD to display. Would it be good practice to also include a CS pin toggle for the VSPI?

#include <U8g2lib.h>
#include <LoRa.h>

//Define which LCD module we are using and the respective pins
U8G2_ST7920_128X64_F_SW_SPI u8g2(U8G2_R0, /* clock=*/ 17, /* data=*/ 4, /* CS=*/ 15, /* reset=*/ 22); // ESP32 defaults are 18, 23, 5, arbitrary 22

// Statements to determine if LCD is running in I2C or SPI mode
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

//define the pins used by the transceiver module
#define cs 5
#define rst 14
#define dio0 2

//#define LCD_CS 16
#define LORA_CS 5

//LCD HSPI pins
#define HSPI_SCK 17
#define HSPI_MISO 16
#define HSPI_MOSI 4
#define LCD_SS 15

SPIClass *hspi = NULL;

String name = "Garbage"; //in SRAM

void setup(void) {
  // Enbale LCD functions

  hspi = new SPIClass(HSPI);

  hspi->begin(HSPI_SCK, HSPI_MISO, HSPI_MOSI, LCD_SS);
  pinMode(hspi->pinSS(), OUTPUT);
  u8g2.begin();
  u8g2.enableUTF8Print();		// enable UTF8 support for the Arduino print() function

    //initialize Serial Monitor
  Serial.begin(115200);
  while (!Serial);
  Serial.println("LoRa Receiver");

  //setup LoRa transceiver module
  LoRa.setPins(cs, rst, dio0);
  
  //replace the LoRa.begin(---E-) argument with your location's frequency 
  while (!LoRa.begin(915E6)) {
    Serial.println(".");
    delay(500);
  }
   // Change sync word (0xF3) to match the receiver
  // The sync word assures you don't get LoRa messages from other LoRa transceivers
  // ranges from 0-0xFF
  LoRa.setSyncWord(0xF3);
  Serial.println("LoRa Initializing OK!");
}

void loop(void) {

  // try to parse packet
  int packetSize = LoRa.parsePacket();
  if (packetSize) {
    // received a packet
    Serial.print("Received packet '");

    // read packet
    while (LoRa.available()) {
      String LoRaData = LoRa.readString();
      Serial.print(LoRaData); 
    }

    // print RSSI of packet
    Serial.print("' with RSSI ");
    Serial.println(LoRa.packetRssi());
  }

  digitalWrite(hspi->pinSS(), LOW); //enable to write
  // Display Data recieved via LoRa
  u8g2.setFont(u8g2_font_unifont_t_chinese2);  // use chinese2 for all the glyphs of "你好世界"
  u8g2.setFontDirection(0);
  u8g2.clearBuffer();
  u8g2.setCursor(0, 15);
  u8g2.print("Hello");
  u8g2.drawStr(0, 50, name.c_str()); //write String from SRAM
  u8g2.sendBuffer();
  digitalWrite(hspi->pinSS(), HIGH); //disable 

  delay(1000);
}

Not unless you intend to use more devices on the same bus.
There is no need, and it does require a few clock cycles to do that.

Hmm looking at your code you appear to have defined the lcd to use sw SP and the also initiated hw SPI on the same pins. I just wonder what that does in the end.

Could you elaborate? Are you speaking on:

#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

Most of my code is a mix of examples from LoRa tutorials and the U8g2 library PrintUTF8 example. I can see how I setup the hardware SPI in the defines but also do it via software in my code. What's the difference and why does this work?

no

This defines the LCD to use SW_SPI
but

#define HSPI_SCK 17
#define HSPI_MISO 16
#define HSPI_MOSI 4
#define LCD_SS 15

SPIClass *hspi = NULL;

and

hspi = new SPIClass(HSPI);

  hspi->begin(HSPI_SCK, HSPI_MISO, HSPI_MOSI, LCD_SS);
  pinMode(hspi->pinSS(), OUTPUT);  // this shouldn't be required, btw.
  u8g2.begin();

starts hw HSPI on the same pins.

If the library has been told to use SW_SPI it will use different function calls, in fact it will bit-bang SPI which is of course significantly slower and more CPU intensive. Still the library may be using task on the 2nd core. I have no idea about that. HW_SPI obvious uses a peripheral and just transfers 4-byte words to that and let the hardware do the rest.

I suspect that the call to

overrules the whole hardware SPI initialization.

It all just depends on the library and i suggest you take up questions about it with the author, who is a forum member (though i forgot his handle)

Basically i think that if you comment the whole hardware SPI out, it will still work.

1 Like

Why use that, when you could use hardware spi with;

U8G2_ST7920_128X64_F_HW_SPI u8g2(

I will bear that in mind.

Apart from the issue with SD on ESP32, I have not yet needed to go outside of a library using SPI with digitalWrites() to make them work.

Maybe one day.

I have also on the ethernet libraries on an esp32, and on both the ethernet and SD libraries on an esp8266. I rarely use more than 1 SPI device,

Well how will you tell the library to use HSPI instead of the default VSPI ?

The pin definitions are there most likely only for SW_SPI, and unless you can pass a pointer to the object, how will it know what to use ?

Just use the default hardware SPI for LoRa and display.

Recently I had been looking for a decent outdoor readable LCD display and was trying out the Hobby Components ST7565 LCD.

I had a LoRa packet logger working well enough, displaying packet details using the U8g2lib with the ST7565 and logging to SD too. Used an ESP32C3.

So 3 SPI devices on the same bus working just fine.

Yes that is true.

I am not saying that it is not going to work, but with larger data transfers, 2 busses is obviously faster.

I would probably go for 2 SPI devices on the same bus in this case, which is still significantly better than using 1 SW SPI.

So my straightforward approach, one SPI bus, that I already knew worked, does work.

Why is two busses 'obviously' faster since the processor only talks to one SPI device at a time ?

Well first of all, it's an ESP32, there are 2 processors.

But even if there weren't or even if you don't use them. 32-bit words are transferred in the background to either of the busses, and once those words are at the FIFO, then those bits are going to get shifted out and or in, by hardware. 2 Busses, that actually function independently. Once task has been created, either FIFO can request the next byte, This happens in the background, after the command is given to do so. Either bus has to wait for it's process to be completed, before it can initiate another process, but it doesn't have to wait for the other bus.

This is the theory. Of course the initialization takes some time.
Autonomously is the word i was looking for, they can shift the bytes in & out the bytes in parallel.. They don't have to wait for each other. And this is just on 1 core of course. On 2 cores you can have 2 software processes running in the foreground at the same time.

If the traffic is mainly one way, there will be less for the processors to do. and the benefit will be bigger, but regardless the moving of data across a 32-bit bus at 240MHz is significantly less impacting than 1-bit at 40MHz (or 75MHz which i think is the max for an ESP32) like for an SD-card, and which works fine for a W5500 Ethernet chip.

Technically speaking i guess 1 Device HW_SPI and 1 device SW_SPI could be almost as fast as 2 HW busses, if the Software output is done by one of the processors which is not used for anything else. Sometimes i think i am not using the ESP32 to it's full efficiency, but i guess i don't really have to. It is really quite fast.