2020-07-23
md
Serial Peripheral Interface on the ESP8266
July 23, 2020
<-Serial Peripheral Interface on the Raspberry Pi

Table of contents

  1. SPI Buses on the ESP8266
  2. The HSPI Master Says Hello
  3. The HSPI Slave Select Signal

SPI Buses on the ESP8266 toc

The ESP8266 has two SPI interfaces. The first is connected to the on-board flash memory. Since the flash memory is a slave device it should be possible to connect other SPI slave devices to the same bus albeit with some complications. See the rather short SPI section in the ESP8266 Arudino Core Libraries documentation on that topic. I only want to familiarize myself with the "normal" operation of the protocol on the ESP8266 so I will restrict my investigation to the second interface called HSPI, presumably for hardware SPI, which is free and can be used in master or slave mode according to the SPI Communication User Guide of the ESP8266 Technical Reference, Version 1.5 by Espressif (2020.07) (see p. 21). The HSPI pins are shown in the Table 1-3, p. 2 of the same document.

HSPI (Master/Slave)
Chip PinFunction
Name
GPIONodemcu/D1 Mini
Arduino Pin
NumberName
9MTMSHSPICLK14D5
10MTDIHSPIQ/MISO12D6
12MTCKHSPID/MOSI13D7
13MTDOHPSICS15D8

The chip pin number of the various signals is not very important from the software point of view, the GPIO or Arduino pin numbers are those used in the Arduino framework when programming the ESP8266.

The HSPI Master Says Hello toc

The SPI library supplied with the ESP8266 Arduino core "supports the entire Arduino SPI API including transactions, ... [except setting] the Clock polarity (CPOL)" (source). In other words, SPI modes 0 and 1 are supported but not modes 2 and 3. To get an idea of what functions are implemented, take a look at the SPI.h - SPI library for esp8266 header file.

To get started, I connected a USB logic analyzer to the ESP8266 SPI bus as shown in the following diagram.

The D0 to D3 labels at the start of each line in the legend are the names given to the signal lines in Pulseview by default, although it was possible to rename them as shown to the right of the dashes. Below is a very simple sketch that can be used in the Arduino IDE or PlatformIO to send data out with the ESP8266 serial peripheral interface.

#include <Arduino.h> #include <SPI.h> #define BAUD 115200 char buff[]="Hello World"; void setup() { Serial.begin(BAUD); SPI.begin(); } void dump(char * buffer, int len) { for (int i=0; i < len ; i++) { Serial.printf("%x ", (int) buffer[i]); } Serial.print(" "); Serial.println(buffer); } void loop() { for (int i=0; i < (int) sizeof(buff) -1 ; i++) { SPI.transfer(buff[i]); } dump(buff, sizeof(buff)-1); delay(100); }

As can be seen, there is really nothing to the set up of the SPI interface. The SPIClass object SPI is constructed by default in SPI.cpp, so that all that needs to be done is to initiate it with the SPI.begin() function. This is the console output when running this sketch in PlatformIO (it would be the same in the Serial monitor of the Arduino IDE except for some of the initial lines beginning with the double dash --).

> Executing task in folder ESP_SPI_SLAVE: platformio device monitor < --- Available filters and text transformations: colorize, debug, default, direct, esp8266_exception_decoder, hexlify, log2file, nocontrol, printable, send_on_enter, time --- More details at http://bit.ly/pio-monitor-filters --- Miniterm on /dev/ttyUSB0 115200,8,N,1 --- --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World ...

At the same time, the following signals were captured with Pulseview (a GUI for the sigrok logic analyzer software).

The thing I want to point out is that the slave select signal is always active (i.e. LOW) eventhough a single byte is being transferred at a time. However if the hardware chip select attribute is set in the setup function, then the select signal is asserted when sending each byte and release between each tranferred byte.

void setup() { Serial.begin(BAUD); SPI.begin(); SPI.setHwCs(true); }

Note how it took about 8 microseconds to send an 8 bit word which corresponds to the default ESP8266 SPI frequency of 1 MHz. It can also be seen that, by default, the bytes of data are sent most significant bit first.

The HSPI Slave Select Signal toc

/* Only two possible set of pins are allowed if SPI.pins(6, 7, 8, 0) then HSPI OVERLAP is enabled if SPI.pins(14, 12, 13, xx) then HSPI is enabled the xx value is ignored and 15 is used for SS */
3 questions: 
  can D3 (SS) be used for I/O if HwCS not enabled
  can its logic be inverted
  can another I/O pin be used for HwCS

I understand that some SPI slave devices don't really monitor the falling or rising edge of the select signal. All that matters is that the signal be at its "active level" which usually means it needs to be grounded. When that is the case and when the device is the only SPI slave on the bus, then it is possible to reduce the number of needed connections by one. If a slave select signal is no longer needed, can D8 (GPIO15) be used for another purpose? As it happens D8 can be used as a general purpose input or output pin

Not using hardware CS Using D8 as GPIO pin Setup completed 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World ...

#include #include #define BAUD 115200 char buff[]="Hello World"; void setup() { Serial.begin(BAUD); SPI.begin(); delay(100); Serial.println("\n"); SPI.setHwCs(false); Serial.println("Not using hardware CS"); pinMode(D8, OUTPUT); delay(2); digitalWrite(D8, HIGH); Serial.println("Using D8 as GPIO pin"); Serial.println("Setup completed"); } void dump(char * buffer, int len) { for (int i=0; i < len ; i++) { Serial.printf("%x ", (int) buffer[i]); } Serial.print(" "); Serial.println(buffer); } void loop() { SPI.writeBytes((uint8_t *) buff, (int) sizeof(buff) - 1); delayMicroseconds(5); digitalWrite(D8, LOW); delayMicroseconds(5); digitalWrite(D8, HIGH); dump(buff, sizeof(buff)-1); delay(10); }
AS INPUT
Not using hardware CS Using D8 as GPIO input pin Setup completed D8 is HIGH D8 is LOW D8 is HIGH D8 is LOW ...

#include #include char buff[]="Hello World"; bool D8value; void setup() { Serial.begin(BAUD); SPI.begin(); delay(100); Serial.println("\n"); SPI.setHwCs(false); Serial.println("Not using hardware CS"); // there is an external pull down resistor on D8 and the ESP will not boot if D8 is high when powering up pinMode(D8, INPUT); delay(5); Serial.println("Using D8 as GPIO input pin"); D8value = digitalRead(D8); Serial.println("Setup completed"); } void loop() { SPI.writeBytes((uint8_t *) buff, (int) sizeof(buff) - 1); delayMicroseconds(5); bool d8v = digitalRead(D8); if (d8v != D8value) { Serial.printf("D8 is %s\n", (d8v) ? "HIGH" : "LOW"); D8value = d8v; } delay(10); }
//////////////////////
#include #include #define BAUD 115200 char buff[]="Hello World"; void setup() { Serial.begin(BAUD); SPI.begin(); SPI.pins(D5, // sck = green (CH1) D0-CLK D6, // miso = blue (CH2) D1-MISO D7, // mosi = yellow (CH3) D2-MOSI D8); // ss = red (CH4) D3-/SS SPI.setHwCs(true); } void dump(char * buffer, int len) { for (int i=0; i < len ; i++) { Serial.printf("%x ", (int) buffer[i]); } Serial.print(" "); Serial.println(buffer); } void loop() { SPI.writeBytes((uint8_t *) buff, (int) sizeof(buff) - 1); dump(buff, sizeof(buff)-1); delay(100); }

88 microseconds to send 11 bytes = 88 bits = 0.99799 MHz close enough to 1 MHz for our purposes!

And as one would expect, if SPI.setHWCs(false) (which the default) or if the method is never invoked then the pin is put in input mode

default frequency is 1MHz


    
    
    https://github.com/esp8266/Arduino/blob/master/libraries/SPI/SPI.h
    SPI.h 
    
    / This defines are not representing the real Divider of the ESP8266
    // the Defines match to an AVR Arduino on 16MHz for better compatibility
    #define SPI_CLOCK_DIV2 		0x00101001 //8 MHz
    #define SPI_CLOCK_DIV4 		0x00241001 //4 MHz
    #define SPI_CLOCK_DIV8 		0x004c1001 //2 MHz
    #define SPI_CLOCK_DIV16 	0x009c1001 //1 MHz
    #define SPI_CLOCK_DIV32 	0x013c1001 //500 KHz
    #define SPI_CLOCK_DIV64 	0x027c1001 //250 KHz
    #define SPI_CLOCK_DIV128 	0x04fc1001 //125 KHz
    
    
    https://www.arduino.cc/en/Reference/SPI
    
    
    
    from Master terminal
    restarted master D1 mini
    
    
    Master: Hello Slave!
    Slave: Hello Master!
    
    Master: Are you alive?
    Slave: Alive for 3629 seconds!
    
    Master: Are you alive?
    Slave: Alive for 3630 seconds!
    
    Master: Are you alive?
    Slave: Alive for 3631 seconds!
    
    Master: Are you alive?
    Slave: Alive for 3632 seconds!
    
    
    
    
    restarted slave nodemcu
    
    Master: Are you alive?
    Slave: Alive for 0 seconds!
    
    Master: Are you alive?
    Slave: Alive for 1 seconds!
    
    Master: Are you alive?
    Slave: Alive for 2 seconds!
    
    Master: Are you alive?
    Slave: Alive for 3 seconds!
    
    Master: Are you alive?
    Slave: Alive for 4 seconds!
    
    Master: Are you alive?
    Slave: Alive for 5 seconds!
    
    Master: Are you alive?
    Slave: Alive for 6 seconds!
    
    
    from slave terminal
    
    restarted slave
    
    SPISlave_Test
    Question: Are you alive?
    Answer Sent
    Question: Are you alive?
    Answer Sent
    Question: Are you alive?
    Answer Sent
    Question: Are you alive?
    Answer Sent
    Question: Are you alive?
    Answer Sent
    
    restarted master
    
    Question: Hello Slave!
    Answer Sent
    Question: Are you alive?
    Answer Sent
    Question: Are you alive?
    Answer Sent
    Question: Are you alive?
    Answer Sent
    Question: Are you alive?
    Answer Sent
    
    
 
<-Serial Peripheral Interface on the Raspberry Pi