Use this forum to chat about hardware specific topics for the ESP8266 (peripherals, memory, clocks, JTAG, programming)

User avatar
By MarekB
#6204 Hi,
I am trying to send bytes via SPI but it does not seem to work. I have an LCD attached at the other end of the cable and I can drive it with Arduino Due just fine. Now I am trying to port the code to ESP8266.
I do not have a logic analyzer with me at the moment so it's kinda hard to figure out what's wrong. Maybe you guys can spot something obvious or give some hints.
I am driving CS, DC and Reset for LCD myself, I want to use big-endian (sending high byte then low byte) and I only want to send the Data stored in W0, W1,... (no Cmd, no Dummy, no Addr, no MISO). Here's how I set the pins:

Code: Select allWRITE_PERI_REG(PERIPHS_IO_MUX, 0x105); //clear bit9
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12);// GPIO12 - Reset for LCD (I do not need MISO)
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 2);//configure io to spi mode GPIO13 - MOSI
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, 2);//configure io to spi mode GPIO14 - CLK   
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15);// GPIO15 - CS
gpio16_output_conf();                                                     // GPIO16 - DC

SET_PERI_REG_MASK(SPI_USER(HSPI), SPI_USR_MOSI );  // use data only (no addr, no cmd, no dummy, command/data for LCD is defined by the DC pin low/high status)
CLEAR_PERI_REG_MASK(SPI_USER(HSPI), SPI_FLASH_MODE | SPI_WR_BYTE_ORDER | SPI_USR_MISO | SPI_USR_ADDR | SPI_USR_COMMAND | SPI_USR_DUMMY);   // big endian (MSB)
// SPI clock=CPU clock/8
WRITE_PERI_REG(SPI_CLOCK(HSPI),
   ((1 & SPI_CLKDIV_PRE) << SPI_CLKDIV_PRE_S) |
   ((3 & SPI_CLKCNT_N) << SPI_CLKCNT_N_S) |
   ((1 & SPI_CLKCNT_H) << SPI_CLKCNT_H_S) |
   ((3 & SPI_CLKCNT_L) << SPI_CLKCNT_L_S)); //clear bit 31,set SPI clock div


After that I have code that send a sequence of bytes (byte by byte) to initialize the screen. The function for sending a byte looks like this:

Code: Select allvoid spiwrite(uint8_t c) {
   uint32 regvalue;
   uint32 data = (uint32)c;
      
   while (READ_PERI_REG(SPI_CMD(HSPI))&SPI_USR); //waiting for spi module available
   SET_PERI_REG_MASK(SPI_USER(HSPI), SPI_USR_MOSI);
   CLEAR_PERI_REG_MASK(SPI_USER(HSPI), SPI_FLASH_MODE | SPI_WR_BYTE_ORDER | SPI_USR_MISO | SPI_USR_ADDR | SPI_USR_DUMMY | SPI_USR_COMMAND);

   WRITE_PERI_REG(SPI_USER1(HSPI), (7 & SPI_USR_MOSI_BITLEN) << SPI_USR_MOSI_BITLEN_S);  // 8 bits
   WRITE_PERI_REG(SPI_W0(HSPI), data);  // I also tried data << 24 to shift the byte I want to send to the highest/most significant byte of 32 bits of W0 but it did not make a difference
   SET_PERI_REG_MASK(SPI_CMD(HSPI), SPI_USR);   // send
}


Not sure if I need to call SET_PERI_REG_MASK and CLEAR_PERI_REG_MASK on every send, probably not.
And if you guys have some of your working code (especially when using SPI_USR_MOSI and SPI_W0) then please send a link or attach here. Thanks a lot.
User avatar
By TheLastMutt
#6477 Hi!

Here is my code, which is receiving 16 bits from SPI bus, but using no command, no address etc like you want to do.
Chip-select is manually controlled, two chip-select pins are configured. To make this code write to the bus, you probably just need to exchange SPI_FLASH_USR_DIN and SPI_FLASH_USR_DOUT. Maybe add SPI_WR_BYTE_ORDER if you want to send high byte first.

I had a quick look at your code and I don't see SPI_CS_SETUP | SPI_CS_HOLD in your SPI_FLASH_USER but I don't know what that really does and how important it is.
You seem to use a different header file than I do because I don't recognize some register definitions. Is this from a newer SDK?


Code: Select allvoid ICACHE_FLASH_ATTR
spi_master_init(uint8_t spi_no)
{
    if (spi_no > 1) {
      return;
    }
   
    while (READ_PERI_REG(SPI_FLASH_CMD(spi_no)));         //waiting for spi module available

    WRITE_PERI_REG(PERIPHS_IO_MUX, 0x105); //clear bit9
   
    WRITE_PERI_REG(SPI_FLASH_CLOCK(spi_no), 0x1003043); //clear bit 31,set SPI clock div

    if (spi_no == SPI) {
//         PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_CLK_U, 1); // SPI Clock
//         PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_CMD_U, 1); // CS0
//         PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_DATA0_U, 1); // SPIQ
//         PIN_FUNC_SELECT(PERIPHS_IO_MUX_SD_DATA1_U, 1); // SPID
//        PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U,1); // GPIO0 is SPICS2
   } else if (spi_no == HSPI) {
        PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, 2);//HSPIQ
        PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 2);//HSPID
        PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, 2);//HSPICLK
        //PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15);//HSPICS, cannot be used on ESP-01, Version2, pin tied to GND on PCB
        // Use GPIO4/5 instead
        PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4);
        GPIO_OUTPUT_SET(4, 1);
        PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5);
        GPIO_OUTPUT_SET(5, 1);
    }

    uart0_sendStr("SPI init done\r\n");
}

uint16_t ICACHE_FLASH_ATTR
spi_master_16bit_read(uint8_t spi_no, uint8_t chipselect)
{
    uint32_t regvalue;
    uint16_t readvalue;

    if (spi_no > 1) {
        spi_no = HSPI;
    }

    //waiting for spi module available
    while (READ_PERI_REG(SPI_FLASH_CMD(spi_no))&SPI_FLASH_USR);

    regvalue = READ_PERI_REG(SPI_FLASH_USER(spi_no));
    // We want to receive data only, so no command, address and DOUT
    regvalue = regvalue | SPI_CS_SETUP | SPI_CS_HOLD | SPI_FLASH_USR_DIN | SPI_RD_BYTE_ORDER;
    // BIT2 is undocumented bit, example code wants it cleared
    regvalue = regvalue & (~(BIT2 | SPI_USR_COMMAND | SPI_FLASH_USR_ADDR | SPI_FLASH_DOUT | SPI_DOUTDIN));
    WRITE_PERI_REG(SPI_FLASH_USER(spi_no), regvalue);
   
     // Test pattern...
    //WRITE_PERI_REG(SPI_FLASH_C0(spi_no), 0xAAAAAAAA);
   
    // Receive 16 bits only, no transmit
    SET_PERI_REG_BITS(SPI_FLASH_USER1(spi_no),SPI_USR_DIN_BITLEN,15,SPI_USR_DIN_BITLEN_S);
   
    GPIO_OUTPUT_SET(chipselect, 0); // set CS low
     
    SET_PERI_REG_MASK(SPI_FLASH_CMD(spi_no), SPI_FLASH_USR);      //transmission start
    while (READ_PERI_REG(SPI_FLASH_CMD(spi_no))&SPI_FLASH_USR);         //waiting for spi module available again
   
    GPIO_OUTPUT_SET(chipselect, 1); // set CS high
   
    os_printf("SPI_FLASH_C0 0x%08x\r\n", READ_PERI_REG(SPI_FLASH_C0(spi_no)));
    readvalue = (uint16_t)(READ_PERI_REG(SPI_FLASH_C0(spi_no)) >> 16);
    return readvalue;
}

User avatar
By MarekB
#6654 I got it working basically with the code I posted. Here's what my main problem was. The LCD has a separate DC pin which when it's low then LCD expects a command and when it's high it expects data. So the sequence should be DC low, send command, DC high, send data and then again DC low, send command, DC high...I am using SPI_W0 to send both command and data. I have not really realized that when you execute
Code: Select allSET_PERI_REG_MASK(SPI_CMD(HSPI), SPI_USR);   // send
the SPI starts to shift out data while the code continues to execute. So when I set DC low and executed SET_PERI_REG_MASK(SPI_CMD(HSPI), SPI_USR); SPI started to shift out the command but my code continued to run and set the DC pin high (while SPI was still shifting out) so LCD never got the full command. The solution was simple, just do
Code: Select allwhile (READ_PERI_REG(SPI_CMD(HSPI))&SPI_USR); //waiting for spi module available
before trying to change the DC pin.
If it's of any help, this is the code I am using now:
SPI setup:
Code: Select allWRITE_PERI_REG(PERIPHS_IO_MUX, 0x105); //clear bit9
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12);//GPIO12 - HSPIQ MISO, not using it for MISO but as LCD reset
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 2);//configure io to spi mode GPIO13 - HSPID MOSI
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, 2);//configure io to spi mode GPIO14 - CLK
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, 2);//configure io to spi mode GPIO15 - CS
gpio16_output_conf();                // DC

//SET_PERI_REG_MASK(SPI_USER(HSPI), SPI_CS_SETUP | SPI_CS_HOLD | SPI_USR_MOSI);  // use data only (no addr, no cmd, this is driven by DC pin)
SET_PERI_REG_MASK(SPI_USER(HSPI), SPI_USR_MOSI);  // seems to work without SPI_CS_SETUP | SPI_CS_HOLD, not sure what they do
CLEAR_PERI_REG_MASK(SPI_USER(HSPI), SPI_FLASH_MODE | SPI_WR_BYTE_ORDER | SPI_USR_MISO | SPI_USR_ADDR | SPI_USR_COMMAND | SPI_USR_DUMMY);   // big endian (MSB)
// SPI clock=CPU clock/2
WRITE_PERI_REG(SPI_CLOCK(HSPI),
   ((0 & SPI_CLKDIV_PRE) << SPI_CLKDIV_PRE_S) |
   ((1 & SPI_CLKCNT_N) << SPI_CLKCNT_N_S) |
   ((0 & SPI_CLKCNT_H) << SPI_CLKCNT_H_S) |
   ((1 & SPI_CLKCNT_L) << SPI_CLKCNT_L_S));


functions to send 1 and 2 bytes:
Code: Select allvoid spiwrite(uint8_t c) {
   while (READ_PERI_REG(SPI_CMD(HSPI))&SPI_USR); //waiting for spi module available
   WRITE_PERI_REG(SPI_USER1(HSPI), (7 & SPI_USR_MOSI_BITLEN) << SPI_USR_MOSI_BITLEN_S);   // 8 bits
   WRITE_PERI_REG(SPI_W0(HSPI), (uint32)data); // the data to be sent
   SET_PERI_REG_MASK(SPI_CMD(HSPI), SPI_USR);   // send
}

#define lowByte(w) ((uint8_t) ((w) & 0xff))
#define highByte(w) ((uint8_t) ((w) >> 8))

void spiwrite16(uint16_t w) {
   while (READ_PERI_REG(SPI_CMD(HSPI))&SPI_USR); //waiting for spi module available
   WRITE_PERI_REG(SPI_USER1(HSPI), (15 & SPI_USR_MOSI_BITLEN) << SPI_USR_MOSI_BITLEN_S); // 16 bits
   WRITE_PERI_REG(SPI_W0(HSPI), lowByte(w) << 8 | highByte(w));
   SET_PERI_REG_MASK(SPI_CMD(HSPI), SPI_USR);   // send
}


To your question about register definitions. I am still on 0.9.3 SDK but I copied some .h and .c from 0.9.4. These are the includes I am using:
Code: Select all#include "spi_register.h" // from 0.9.4 IoT_Demo
#include "ets_sys.h"
#include "osapi.h"
#include "uart.h"
#include "os_type.h"
#include "gpio16.h" //from 0.9.4 IoT_Demo
#include "gpio.h"


Btw. the way I figured the DC thing was to use a logic analyzer. A very poor one (in comparison to a real thing) but still very capable (for what it is). As I do not have a proper one with me at the moment I uploaded the logic_analyzer library code to Arduino Mini and used the Open Bench Logic Sniffer to capture the waveforms. Had to fiddle with it for a little (especially with the triggers) and clock down SPI to only a few kHz to be able to capture anything but it worked great at the end.