ESP32 SPI external flash driver help

zazas321
Posts: 231
Joined: Mon Feb 01, 2021 9:41 am

ESP32 SPI external flash driver help

Postby zazas321 » Mon Oct 18, 2021 8:30 am

Hello. I have recently written my own I2C driver, now I am trying to work with the SPI and write my own external flash memory driver.
The flash chip datasheet:
https://datasheet-pdf.com/PDF/GD5F1GQ4U ... ice-791680

I have a function that is supposed to send some data over SPI:

Code: Select all

static void spi_cmd(spi_device_handle_t spi, const uint8_t cmd)
{
    esp_err_t ret;
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));       //Zero out the transaction
    t.length=8;                     //Command is 8 bits
    t.tx_buffer=&cmd;               //The data is the cmd itself
    t.user=(void*)0;                //D/C needs to be set to 0
    ret=spi_device_polling_transmit(spi, &t);  //Transmit!
    assert(ret==ESP_OK);            //Should have had no issues.
}

I initialise SPI bus:

Code: Select all

void SPI_init(){
    esp_err_t ret;
    //spi_device_handle_t spi;
    spi_bus_config_t buscfg={
        .miso_io_num=PIN_NUM_MISO,
        .mosi_io_num=PIN_NUM_MOSI,
        .sclk_io_num=PIN_NUM_CLK,
        .quadwp_io_num=-1,
        .quadhd_io_num=-1,
        .max_transfer_sz=PARALLEL_LINES*320*2+8
    };

    spi_device_interface_config_t devcfg={

        .clock_speed_hz=10*1000*1000,           //Clock out at 10 MHz
        .mode=0,                                //SPI mode 0
        .spics_io_num=PIN_NUM_CS,               //CS pin
        .queue_size=7,                          //We want to be able to queue 7 transactions at a time
        //.pre_cb=lcd_spi_pre_transfer_callback,  //Specify pre-transfer callback to handle D/C line
    };
    
Note that spi_device_handle_t I have created as global because I may need to access the SPI functions from other components so it is more convenient to have it global even thought this may not be the best solution.

My full .c file:

Code: Select all


#include "GD5F1GQ4UEYIG.h"

spi_device_handle_t external_flash;


static void spi_cmd(spi_device_handle_t spi, const uint8_t cmd)
{
    esp_err_t ret;
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));       //Zero out the transaction
    t.length=8;                     //Command is 8 bits
    t.tx_buffer=&cmd;               //The data is the cmd itself
    t.user=(void*)0;                //D/C needs to be set to 0
    ret=spi_device_polling_transmit(spi, &t);  //Transmit!
    assert(ret==ESP_OK);            //Should have had no issues.
}


uint32_t GD5F1_get_id(spi_device_handle_t spi)
{
    //get_id cmd
    spi_cmd(spi, 0x9F);

    spi_transaction_t t;
    memset(&t, 0, sizeof(t));
    t.length=8*3;
    t.flags = SPI_TRANS_USE_RXDATA;
    t.user = (void*)1;

    esp_err_t ret = spi_device_polling_transmit(spi, &t);
    assert( ret == ESP_OK );

    return *(uint32_t*)t.rx_data;
}



void GD5F1_init(spi_device_handle_t spi)
{

    //detect LCD type
    uint32_t GD5F1_id = GD5F1_get_id(spi);
    printf("GD5F1_id: %08X\n", GD5F1_id);

}



void SPI_init(){
    esp_err_t ret;
    //spi_device_handle_t spi;
    spi_bus_config_t buscfg={
        .miso_io_num=PIN_NUM_MISO,
        .mosi_io_num=PIN_NUM_MOSI,
        .sclk_io_num=PIN_NUM_CLK,
        .quadwp_io_num=-1,
        .quadhd_io_num=-1,
        .max_transfer_sz=PARALLEL_LINES*320*2+8
    };

    spi_device_interface_config_t devcfg={

        .clock_speed_hz=10*1000*1000,           //Clock out at 10 MHz
        .mode=0,                                //SPI mode 0
        .spics_io_num=PIN_NUM_CS,               //CS pin
        .queue_size=7,                          //We want to be able to queue 7 transactions at a time
        //.pre_cb=lcd_spi_pre_transfer_callback,  //Specify pre-transfer callback to handle D/C line
    };

    //Initialize the SPI bus
    ret=spi_bus_initialize(EEPROM_HOST, &buscfg, DMA_CHAN);
    ESP_ERROR_CHECK(ret);
    //Attach the LCD to the SPI bus
    ret=spi_bus_add_device(EEPROM_HOST, &devcfg, &external_flash);
    ESP_ERROR_CHECK(ret);
}
my full .h file:

Code: Select all

#ifndef GD5F1GQ4UEYIG_H
#define GD5F1GQ4UEYIG_H


#ifdef __cplusplus
extern "C" {
#endif

#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
#include "string.h"



#define EEPROM_HOST    HSPI_HOST
#define DMA_CHAN    2

#define PIN_NUM_MISO 19 // OK
#define PIN_NUM_MOSI 23 // OK
#define PIN_NUM_CLK  18 // OK
#define PIN_NUM_CS   5  // OK
#define PARALLEL_LINES 16



extern spi_device_handle_t external_flash;

void SPI_init();
uint32_t GD5F1_get_id(spi_device_handle_t spi);
void GD5F1_init(spi_device_handle_t spi);




#ifdef __cplusplus
}
#endif

#endif
And in my main.c I do:

Code: Select all

    SPI_init(); // initialise SPI bus
    printf("getting spi id \n");
    GD5F1_init(external_flash); // initialise SPI device GD5F1 ( external flash)

I am trying to understand a little bit more about how SPI works and how can I read from registers.
https://ibb.co/3Skn8nL

For example, when I send the command 9F ( Read ID), do I also need to send something else? For example, do I need to send the addresses as shown in Read ID command description?

The result I get from my read_id command is:

Code: Select all

getting spi id 
GD5F1_id: 00FFFFFF

zazas321
Posts: 231
Joined: Mon Feb 01, 2021 9:41 am

Re: ESP32 SPI external flash driver help

Postby zazas321 » Mon Oct 18, 2021 12:26 pm

After watching this video, I have decided to follow a different approach:
https://www.youtube.com/watch?v=El6sEEQ ... =Michaelee

Code: Select all


void GD5F1_read_reg(spi_device_handle_t spi, uint8_t reg, uint8_t* data){
    spi_transaction_t t;
    
    cs_low();
    memset(&t,0,sizeof(t));
    t.length = 8;
    t.tx_buffer = ®
    spi_device_transmit(spi,&t);

    memset(&t,0,sizeof(t));
    t.length = 8;
    t.flags = SPI_TRANS_USE_RXDATA;
    spi_device_transmit(spi,&t);

    cs_high();
    *data = t.rx_data[0];
}

void GD5F1_read_id(spi_device_handle_t spi){
    uint8_t reg = 0x9F;
    uint8_t id;
    GD5F1_read_reg(spi,reg,&id);
    printf("ID read = %02X \n",id);

}

This seems to make more sense than the esp-idf SPI examples. However, I still read the same result 0xEF.

ESP_Sprite
Posts: 8922
Joined: Thu Nov 26, 2015 4:08 am

Re: ESP32 SPI external flash driver help

Postby ESP_Sprite » Tue Oct 19, 2021 12:43 am

According to your datasheet, the command 0x9F needs an address byte indicating where in the ID space you want to start the read.

zazas321
Posts: 231
Joined: Mon Feb 01, 2021 9:41 am

Re: ESP32 SPI external flash driver help

Postby zazas321 » Tue Oct 19, 2021 4:46 am

Hey. Thank you very much.

This is exactly what is confusing me. How do I send an additionall address byte indicating the ID? For example I have these 2 functions:

Code: Select all

void GD5F1_read_reg(spi_device_handle_t spi, uint8_t reg, uint8_t* data){
    spi_transaction_t t;
    cs_low();

    memset(&t,0,sizeof(t));
    t.length = 8;
    t.tx_buffer = ®
    spi_device_transmit(spi,&t);

    memset(&t,0,sizeof(t));
    t.length = 8;
    t.flags = SPI_TRANS_USE_RXDATA;
    spi_device_transmit(spi,&t);
    
    cs_high();
    *data = t.rx_data[0];
}



void GD5F1_read_id(spi_device_handle_t spi){
    uint8_t reg = 0x9F;
    uint8_t id;
    GD5F1_read_reg(spi,reg,&id);
    printf("ID read = %02X \n",id);

}

Could you please show me how should I need to modify them to get to read the device ID?

zazas321
Posts: 231
Joined: Mon Feb 01, 2021 9:41 am

Re: ESP32 SPI external flash driver help

Postby zazas321 » Tue Oct 19, 2021 5:21 am

From what I understand, I just need to send an additional byte as with the address. Please see my modified function below:

Code: Select all


void GD5F1_read_id(spi_device_handle_t spi,uint8_t* data){
    spi_transaction_t t;
    uint16_t reg2 = 0x9f00;

    cs_low();
    memset(&t,0,sizeof(t));
    t.length = 16;
    t.tx_buffer = &reg2;
    spi_device_transmit(spi,&t);

    memset(&t,0,sizeof(t));
    t.length = 8;
    t.flags = SPI_TRANS_USE_RXDATA;
    spi_device_transmit(spi,&t);

    cs_high();
    *data = t.rx_data[0];
    

}
As you can see from the function above, I am sending 2 bytes instead of 1 ( t.length = 16 and my reg2 contains 2 bytes) 0x9F for the command and 00 for the address ). With this command, I expect to read the C8 as described in the datasheet.

In my main.c I call:

Code: Select all

        uint8_t id;
        GD5F1_read_id(external_flash,&id);
        printf("ID read = %02X \n",id);
The result is not what I expect:

Code: Select all


ID read = 40 
ID read = 40 
ID read = 40 
ID read = 40 
ID read = 40 
ID read = 40 
ID read = 40 
ID read = 40 
ID read = 40 

What I am concerned is whether my SPI transmission is correct or whether I should try and look for hardware issues on my board.

zazas321
Posts: 231
Joined: Mon Feb 01, 2021 9:41 am

Re: ESP32 SPI external flash driver help

Postby zazas321 » Tue Oct 19, 2021 5:50 am

I have also tried another method:

Code: Select all

static void spi_cmd(spi_device_handle_t spi, const uint8_t cmd)
{
    esp_err_t ret;
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));       //Zero out the transaction
    t.length=8;                     //Command is 8 bits
    t.tx_buffer=&cmd;               //The data is the cmd itself
    //t.user=(void*)0;                //D/C needs to be set to 0
    ret=spi_device_polling_transmit(spi, &t);  //Transmit!
    assert(ret==ESP_OK);            //Should have had no issues.
}

static void spi_data(spi_device_handle_t spi, const uint8_t *data, int len)
{
    esp_err_t ret;
    spi_transaction_t t;
    if (len==0) return;             //no need to send anything
    memset(&t, 0, sizeof(t));       //Zero out the transaction
    t.length=len*8;                 //Len is in bytes, transaction length is in bits.
    t.tx_buffer=data;               //Data
    //t.user=(void*)1;                //D/C needs to be set to 1
    ret=spi_device_polling_transmit(spi, &t);  //Transmit!
    assert(ret==ESP_OK);            //Should have had no issues.
}

uint8_t get_id2(spi_device_handle_t spi)
{

    spi_cmd(spi, 0x9f);
    spi_data(spi,0x00,1);

    spi_transaction_t t;
    memset(&t, 0, sizeof(t));
    t.length=8;
    t.flags = SPI_TRANS_USE_RXDATA;
    //t.user = (void*)1;

    esp_err_t ret = spi_device_polling_transmit(spi, &t);
    assert( ret == ESP_OK );
    printf("rx data = %02X \n",*t.rx_data);
    return *t.rx_data;
}

Then In my main.c I call:

Code: Select all

        id = get_id2(external_flash);
        printf("id in task = %u \n",id);
 
The results are even more strange:

Code: Select all

rx data = EF
id in task = 239 
rx data = 16 
id in task = 22 
rx data = 00 
id in task = 0 
rx data = 00 
id in task = 0 
rx data = 00 
id in task = 0 
The first time data is read it returned 0xEF, then 0x16 and then after that just 0

ESP_Sprite
Posts: 8922
Joined: Thu Nov 26, 2015 4:08 am

Re: ESP32 SPI external flash driver help

Postby ESP_Sprite » Tue Oct 19, 2021 6:47 am

Okay, no offense, but now you're just throwing stuff at the wall to see if it sticks. Every transaction you do takes CS down at the start and up at the end, so you're simply sending separate bytes and then aborting the transfer. That works for the LCD you copied the code from, but not for a flash chip.

If you actually want this to work properly, the best bet is to use the half-duplex mode of the SPI controller, then sending both the command, address and receiving the response in one transfer. Effectively:
* Read the docs first, for good measure.
* Configure your device to be half-duplex, using a command of 8 bits and an address length of 8 bits. (Note you may need to change that once you actually want to write/read data to the actual amount of address bits your flash reads and writes need. For reading the ID, you can then override it by specifying the deviating address length in spi_transaction_ext_t.)

Code: Select all

spi_device_interface_config_t config={
	.command_bits=8,
	.address_bits=8,
	.flags=SPI_DEVICE_HALFDUPLEX.
}
* Use the command/address members to send that data, then receive what you need:

Code: Select all

spi_transaction_t t={
	.cmd=0x9F,
	.addr=0x00,
	.flags=SPI_TRANS_USE_RXDATA,
	.rxlength=8
}
spi_device_polling_transmit(spi, &t);
printf("rx data = %02X \n",*t.rx_data);
(Note that all the code here is untested, there may be small mistakes.)

zazas321
Posts: 231
Joined: Mon Feb 01, 2021 9:41 am

Re: ESP32 SPI external flash driver help

Postby zazas321 » Tue Oct 19, 2021 8:52 am

Thank you very much for the response. It is getting more clear how I need to use the SPI but not quite there yet. I would like to understand the below:

I have my get_id function below:

Code: Select all

void get_id2(spi_device_handle_t spi)
{
    spi_transaction_t t;
	t.cmd=0x9F;
	t.addr=0x00;
	t.flags=SPI_TRANS_USE_RXDATA;
	t.rxlength=16;

    spi_device_polling_transmit(spi, &t);
    printf("rx data = %04X \n",*t.rx_data);
}



I am getting the following errors:

Code: Select all

E (2610) spi_master: check_trans_valid(694): txdata transfer > host maximum
rx data = 000C
E (3620) spi_master: check_trans_valid(694): txdata transfer > host maximum
rx data = 00B0
E (4620) spi_master: check_trans_valid(694): txdata transfer > host maximum
rx data = 00B0
E (5620) spi_master: check_trans_valid(694): txdata transfer > host maximum
rx data = 00B0
E (6620) spi_master: check_trans_valid(694): txdata transfer > host maximum
rx data = 00B0
E (7620) spi_master: check_trans_valid(694): txdata transfer > host maximum
rx data = 00B0
E (8620) spi_master: check_trans_valid(694): txdata transfer > host maximum
rx data = 00B0
E (9620) spi_master: check_trans_valid(694): txdata transfer > host maximum
rx data = 00B0
E (10620) spi_master: check_trans_valid(694): txdata transfer > host maximum
I would like to understand more about the SPI structure:

From the datasheet I need to first send 2 bytes (command and address and then read 2 bytes back (manufacturer ID and device ID). So from what I understand, I first need to transmit 2 bytes, then receive 2 bytes.

My question:

Does this function:

Code: Select all

spi_device_polling_transmit
Automatically transmits and then switches the mode to receive mode?

zazas321
Posts: 231
Joined: Mon Feb 01, 2021 9:41 am

Re: ESP32 SPI external flash driver help

Postby zazas321 » Tue Oct 19, 2021 9:07 am

I have connected the osciloscope and confirmed that the following function:

Code: Select all

void get_id2(spi_device_handle_t spi)
{
    spi_transaction_t t;
    uint8_t command_byte = 0x9F;
    uint8_t address_byte = 0x00;
	t.cmd=command_byte;
	t.addr=address_byte;
    
	t.flags=SPI_TRANS_USE_RXDATA;
	t.rxlength=16;

    spi_device_polling_transmit(spi, &t);
    printf("rx data = %04X \n",*t.rx_data);
}

Does not do anything. It does not even generate clock on my SPI pin.

I have then tested this command:

Code: Select all


void GD5F1_read_id(spi_device_handle_t spi,uint8_t* data){
    spi_transaction_t t;

    uint16_t reg2 = 0x9f00;

    cs_low();
    memset(&t,0,sizeof(t));
    t.length = 16;
    t.tx_buffer = &reg2;
    spi_device_transmit(spi,&t);


    memset(&t,0,sizeof(t));
    t.length = 16;
    spi_device_transmit(spi,&t);

    cs_high();
    *data = t.rx_data;
}
I know that the above function may not be correct. Since I am using tx_buffer and in the documentation it is mentioned not to touch it (I have seen it being used in some other examples though). But the important thing is, that I can see the clock being generated. Any suggestions what is happening and why the first function does nto generate the clock?

ESP_Sprite
Posts: 8922
Joined: Thu Nov 26, 2015 4:08 am

Re: ESP32 SPI external flash driver help

Postby ESP_Sprite » Wed Oct 20, 2021 1:06 am

You're not using named initializers in your code (like I did in mine), nor are you clearing the spi_transaction_t structure using memset(). That leads to random data ending up in the members of that struct that you have not written, causing you that trouble.

Who is online

Users browsing this forum: No registered users and 103 guests