ESP32-WROVER-KIT use of SPI master

valentin
Posts: 8
Joined: Mon May 21, 2018 5:37 am

ESP32-WROVER-KIT use of SPI master

Postby valentin » Mon May 21, 2018 6:23 am

Hello all,

I try to use the ESP32-WROVER-KIT as an SPI master and a STM32F746DISCO as an SPI slave and I am having some problems with the communication. I use the latest official release of the esp-idf, v3.0.

Below the ESP32 configuration and code snippets:

Code: Select all

#define SPI_CHANNEL    HSPI_HOST
#define SPI_CLOCK      500000 // Default SPI clock 500KHz

/*
 * SPI master modes for ESP32:
 * - Mode 0: CPOL = 0, CPHA = 0
 * - Mode 1: CPOL = 0, CPHA = 1
 * - Mode 2: CPOL = 1, CPHA = 1
 * - Mode 3: CPOL = 1, CPHA = 0
 */
#define SPI_MODE       2 // Default SPI mode 2

#define SPI_CLK_GPIO   GPIO_NUM_14 // Shared with JTAG/MicroSD
#define SPI_CS_GPIO    GPIO_NUM_15 // Shared with JTAG/MicroSD
#define SPI_MISO_GPIO  GPIO_NUM_12 // Shared with JTAG/MicroSD
#define SPI_MOSI_GPIO  GPIO_NUM_13 // Shared with JTAG/MicroSD

uint8_t myRxBuffer[SPI_MAX_DMA_LEN] = {};
uint8_t myTxBuffer[SPI_MAX_DMA_LEN] = {};

spi_device_handle_t spi_handle;

// Initialize the SPI2 device in master mode
void spi_master_config(void) {
	// Configuration for the SPI bus
	spi_bus_config_t buscfg = {
		.mosi_io_num = SPI_MOSI_GPIO,
		.miso_io_num = SPI_MISO_GPIO,
		.sclk_io_num = SPI_CLK_GPIO,
		.quadwp_io_num = -1,
		.quadhd_io_num = -1,
		.max_transfer_sz = SPI_MAX_DMA_LEN,
	};

	// Configuration for the SPI master interface
	spi_device_interface_config_t devcfg = {
		.command_bits = 0,
		.address_bits = 0,
		.dummy_bits = 0,
		.duty_cycle_pos = 128,
		.cs_ena_pretrans = 0,
		.cs_ena_posttrans = 3, // Keep the CS low 3 cycles after transaction, to stop the master from missing the last bit when CS has less propagation delay than CLK
		.clock_speed_hz = SPI_CLOCK,
		.mode = SPI_MODE,
		.spics_io_num = SPI_CS_GPIO,
		.queue_size = 1,
		.flags = 0,
		.pre_cb = NULL,
		.post_cb = NULL,
	};

	// Initialize and enable SPI
	spi_bus_initialize(SPI_CHANNEL, &buscfg, 1);
	spi_bus_add_device(SPI_CHANNEL, &devcfg, &spi_handle);
}

// Full buffer DMA transfer
int32_t spi_dma_transfer_bytes(uint8_t *data, uint16_t size) {
	esp_err_t trans_result = ESP_OK;
	spi_transaction_t trans_t;

	// Prepare transaction parameters
	if (data == myRxBuffer) {
		trans_t.rx_buffer = myRxBuffer;
		trans_t.tx_buffer = NULL;
	} else {
		trans_t.rx_buffer = NULL;
		trans_t.tx_buffer = myTxBuffer;
	}
	trans_t.rxlength = 0;
	trans_t.length = 8 * size;
	trans_t.flags = 0;
	trans_t.cmd = 0;
	trans_t.addr = 0;
	trans_t.user = NULL;

	// Perform transaction
	trans_result = spi_device_transmit(spi_handle, &trans_t);
	if (ESP_OK != trans_result) {
		return SPI_TRANSFER_CANCELLED;
	}

	return size;
}
As for the test code, I just explain it here as it is very simple:
- On the slave side, SPI is initialized, then an echo loop is entered. The slave reads 1 byte then writes it back on the bus. Both reading and writing should be blocking, meaning the slave remains in the read/write code until it receives CS assertion + CLK.
- On the master side, SPI is initialized, then the master waits 5sec (to be sure the slave finished it's initialization), writes 0x55 on the bus, waits another 5sec (to be sure the slave is ready to send the return byte) and reads the byte back. All this inside a loop

Problems encountered and fixed (need confirmation from ESP32):
- The SPI modes on the esp-idf are not according to the standard, mode 2 and 3 are being swapped. Normally SPI mode 2 means CPOL = 1, CPHA = 0 but in esp-idf drivers I see mode 2 meaning CPOL = 1 and CPHA = 1 (same configuration set for the slave). So I use mode 2 for the master and mode 3 for the slave to be in sync
- The implementation of "miso_delay_mode" and "extra_dummy" are not correct in the spi driver code. Based on information found here http://esp-idf.readthedocs.io/en/latest ... aster.html, the configuration should be like this:
- if the GPIO matrix is not used, there should be no MISO delay introduced and also no extra dummy bit
- if the GPIO matrix is used, there should be a MISO delay introduced if the SPI clock is higher than 40MHz, and a extra dummy bit if the clock is higher than 8.8MHz
- Below the code I propose before configuring the SPI polarity (function spi_intr())

Code: Select all

nodelay=1;
extra_dummy=0;
if (!host->no_gpio_matrix) {
	if (effclk >= apbclk/2) {
		nodelay=0;
	}
	if (effclk >= (8800000)) {
		extra_dummy=1;          //Note: This only works on half-duplex connections. spi_bus_add_device checks for this.
	}
}
- Problems with GPIO interrupt triggering. I wanted to use a handshake signal between the slave and master (now I don't use it anymore) and saw issues with the GPIO interrupt triggering. The interrupt was configured to be triggered on both edges, and saw consecutive interrupts being raised even though the edge was stable. To overcome this, I ignored all the interrupts which come with a difference < 1ms, adding an extra check that the edge should be different (it's impossible to have consecutive interrupts on the same GPIO edge). This code worked, but the issue relies in HW in my opinion, and should be addressed. Now I don't need a handshake anymore, I just want that the SPI master writes/reads data whenever he wants, and at that time the SPI slave should be ready to read/write data.

Problems encountered and not solved
- When writing data on SPI, the slave does not read it correctly. See attached
SPI_master_write.png
SPI_master_write.png (18.41 KiB) Viewed 11814 times
. Based on the CPHA = 1, latching the data by the slave should happen on the 2nd clk edge (rising edge). Instead of the correct 0x55, the slave reads random data. Shouldn't the MOSI data be shifted to the right by 1 cycle, so that the 01010101b pattern is more stable on the rising clk edge? In the picture I see the 1b on the MOSI rising at the same time as the CLK, on the same edge the slave reads data, so it will not be read correctly.
- When reading data on SPI, the master does not read it correctly. See attached
SPI_master_read.png
SPI_master_read.png (17.18 KiB) Viewed 11814 times
. First of all I always see short pulses on CS line, which causes pulses on all the other lines, even CLK. See attached
SPI_master_read_zoomed.png
SPI_master_read_zoomed.png (17.06 KiB) Viewed 11814 times
. Why are these pulses appearing and why does it influences the CLK, and therefore the transfer? I tried managing the CS from software, and the same pulses occur, so it may not be related to CS, but something else?
- During read, why do I see data on MOSI, as I set the "tx_buffer" to NULL in the read phase, MOSI should be unchanged...

Thanks in advance for anyone who can shed some light in these problems,
Valentin

valentin
Posts: 8
Joined: Mon May 21, 2018 5:37 am

Re: ESP32-WROVER-KIT use of SPI master

Postby valentin » Tue May 22, 2018 5:08 am

I wired 2 ESP32 WROVER together via SPI, to be sure I don't have an issue on the STM32F7 board, but things are not improved. I also changed the SPI pins to fit with the esp32 example:

Code: Select all

#define SPI_MODE       0 // Default SPI mode 0

#define SPI_CLK_GPIO   GPIO_NUM_15 // Shared with JTAG/MicroSD
#define SPI_CS_GPIO    GPIO_NUM_14 // Shared with JTAG/MicroSD
#define SPI_MISO_GPIO  GPIO_NUM_13 // Shared with JTAG/MicroSD
#define SPI_MOSI_GPIO  GPIO_NUM_12 // Shared with JTAG/MicroSD
And here is the slave implementation:

Code: Select all

// Initialize the SPI2 device in slave mode
void spi_slave_config(void) {
	// Configuration for the SPI bus
	spi_bus_config_t buscfg = {
		.mosi_io_num = SPI_MOSI_GPIO,
		.miso_io_num = SPI_MISO_GPIO,
		.sclk_io_num = SPI_CLK_GPIO,
		.quadwp_io_num = -1,
		.quadhd_io_num = -1,
		.max_transfer_sz = SPI_MAX_DMA_LEN,
	};

    // Configuration for the SPI slave interface
    spi_slave_interface_config_t slvcfg = {
    		.mode = SPI_MODE,
			.spics_io_num = SPI_CS_GPIO,
			.queue_size = 1,
			.flags = 0,
			.post_setup_cb = NULL,
			.post_trans_cb = NULL
    };

    // Enable pull-ups on SPI lines so we don't detect rogue pulses when no master is connected.
    gpio_set_pull_mode(SPI_MOSI_GPIO, GPIO_PULLUP_ONLY);
    gpio_set_pull_mode(SPI_CLK_GPIO, GPIO_PULLUP_ONLY);
    gpio_set_pull_mode(SPI_CS_GPIO, GPIO_PULLUP_ONLY);

	// Init and enable SPI
	spi_slave_initialize(SPI_CHANNEL, &buscfg, &slvcfg, 1);
}

// Full buffer DMA transfer
static int32_t spi_slave_dma_transfer_bytes(uint8_t *data, uint16_t size) {
	esp_err_t trans_result = ESP_OK;
	spi_slave_transaction_t trans_t;

	// Prepare transaction parameters
	if (data == myRxBuffer) {
		trans_t.rx_buffer = myRxBuffer;
		trans_t.tx_buffer = NULL;
	} else {
		trans_t.rx_buffer = NULL;
		trans_t.tx_buffer = myTxBuffer;
	}
	trans_t.length = 8 * size;
	trans_t.user = NULL;

	// Perform transaction
	trans_result = spi_slave_transmit(SPI_CHANNEL, &trans_t, portMAX_DELAY);
	if (ESP_OK != trans_result) {
		return SPI_TRANSFER_CANCELLED;
	}

	return (trans_t.trans_len / 8);
}
I also removed my change in the esp-idf related to miso_delay_mode, to test with the original driver.

Test results:
- when exchanging only 1 byte (0x55), I get a much better hit rate, but still sometimes the byte is not received properly at the slave side. the pulses on CS are gone, but I still have short pulses on MISO line, and since the I use the SPI with only 1 phase (read or write, as the other buffer is NULL), I would expect that a master read would have nothing on MOSI line, and a master write would have nothing on MISO line, but this is not the case
- when exchanging more than 1 byte (I test with 4 bytes), I usually get the first frame correctly exchanged, then a lot of frames with 0, some frames just with the first byte received by the slave (and the rest 0), and from time to time I get an additional correct frame

Questions:
- as the spi_master example in the esp-idf uses a handshake line, is there a limitation of esp-idf in that way? normally a handshake is not necessary if you ensure the SPI slave is ready to read/write on the SPI when the master is generating clocks
- this is what I do from the test part: I add delays on the SPI master before reading and writing on SPI to be sure the SPI slave entered the "SPI read/SPI write" phase. So, the SPI driver would normally have everything prepared (SPI/DMA configured), and just waiting for the CS assertion and CLK pulses to do some work on the bus. This is why I am saying a handshake is not necessary in my test. If the SPI slave would have been busy with something else (other tasks), then indeed it could miss data on SPI

Any ideas?
Valentin

ESP_michael
Posts: 37
Joined: Mon Aug 28, 2017 10:25 am

Re: ESP32-WROVER-KIT use of SPI master

Postby ESP_michael » Tue May 22, 2018 8:16 am

Hi valentin,

For the speed (500kHz) you used to test, there's no timing issue at all if all the configurations are right. So please don't care about ``no_delay`` and ``dummy`` too much. Set ``no_delay=0`` and ``dummy=0`` it should work.

The maximum frequency is highly related to the slave (MISO delay after the clock). When the slave is ideal, the master can read at 26.6MHz, however, if the slave is slow (like ESP32 slave, which has a MISO delay of 75ns in the worst case), the reading frequency can be no higher than 8.8MHz. Currently the driver is for the ideal case, however the programming guide (till 92c469b5) is a conservative value. We plan to release a patch to allow users to configure the MISO delay by themselves and the driver will calculate the frequency limitation and apply dummy bits and miso delay automatically. I hope this will be merged soon.

The general definition of mode 2 is: CPOL=1 and CPHA=0, see https://en.wikipedia.org/wiki/Serial_Pe ... erface_Bus. The data on the bus meets the timing of mode 2 well. You can configure your slave to mode 2 and try again confidently (Don't for get to revert other timing configurations in the driver).

BTW, the miso/mosi delay mode/num, together with ``ck_idle_edge`` and ``ck_in_edge``/``ck_out_edge`` are for mode timing compensation, and do not 100% correspond with CPOL and CPHA. They should be configured according to the TRM https://www.espressif.com/sites/default ... ual_en.pdf. Touching them may cause unexpected timing issues on the bus. If you have issues use the driver and probably related to the timing, we are glad to help you.

For the random data on the MOSI when read:
In full-duplex mode, the MOSI should always be enabled. If no data is configured, it tries to fetch something from the internal FIFO. Usually we don't care about what's on the MOSI in a time only MISO is meaningful. If you do care about this, prepare a buffer full of 0 for it.

For the glitches on the bus, I guess is about your wiring and ground: Use shorter lines to connect two boards, and connect grounds between them AS MUCH AS POSSIBLE. If this doesn't fix your issue, please come back and let's try to find out what's wrong with the GPIO.

valentin
Posts: 8
Joined: Mon May 21, 2018 5:37 am

Re: ESP32-WROVER-KIT use of SPI master

Postby valentin » Tue May 22, 2018 12:22 pm

Hi xiaoxufeng,

Thanks for your answer. I have some updates on this topic.
First of all, I got another ESP32 board to be the SPI counterpart, to be sure I don't mess something up on the SPI slave. So, 2 ESP32-WROVER-KIT are connected now.

Then, I changed the GPIOs I use to be sure I use non-native ones, to kick in the miso delay mode (I reverted all my changes from spi_master.c). I also don't want a conflict with the MicroSD, so I changed the configuration like this:

Code: Select all

/*
 * The ECOM-COMM will work only if:
 * - JTAG is not used (no jumpers present on JP8)
 * - Camera is not used (nothing plugged on JP4)
 * - MicroSD can be used but only with SDMMC peripheral in 1- line mode
 * - RGB LED is not used
 */
#define SPI_CLK_GPIO   GPIO_NUM_12 // Shared with JTAG/MicroSD in 4- line mode
#define SPI_CS_GPIO    GPIO_NUM_4  // Shared with LED/Camera/MicroSD in 4- line mode
#define SPI_MISO_GPIO  GPIO_NUM_26 // Shared with Camera
#define SPI_MOSI_GPIO  GPIO_NUM_27 // Shared with Camera
With the above GPIO configuration, I should be able to use the MicroSD with SDMMC controller in 1- line mode and to not interfere with the SPI2 which I use for other purpose.

Then, the issues are related to the following:
- Setting the
tx_buffer
or
rx_buffer
pointers to NULL when trying to avoid the read/write phase (for example, if the SPI master wants to send data, it sets the rx_buffer to NULL, if it wants to read data, it sets the tx_buffer to NULL, same applying for the slave) is not working as expected. This causes the SPI counterpart to only receive the first byte correctly, and the subsequent bytes are received as 0x00, even though the DMA transfers all the data. This has to be a problem in the driver, as this situation is not normal! If I set both rx_buffer and tx_buffer to valid buffers, and clear them all to 0x00 before starting a SPI transaction, then the complete SPI frame is received/sent
- After applying the above "patch", on the SPI master side, always the first bit is missed (read as 0). The
cs_ena_pretrans
is specified that is not supported for full-duplex, only half-duplex. But, I set this to 1, removed the check in the spi_master.c and I see on my logic analyzer the extra delay with CS asserted before starting the CLK and the SPI master no longer misses the first bit. So actually, the HW supports this feature for full-duplex also!

With the above 2 "patches" and SPI configuration, I am able to do SPI communication with various length and data between 2 ESP32 boards. The above issues have to be checked by Espressif and fixed properly.

About the GPIO interrupt issues, I no longer use them, but the issue is there and most likely caused by ESP32 HW. I use a 20cm wire for all my board-to-board connections and never had an issue so far. More, bad wiring could never cause consecutive interrupt generation on the same signal edge (how can we have 2 consecutive rising edge interrupt if the interrupt is configured for both rising and falling edge?)!

Thanks,
Valentin

Who is online

Users browsing this forum: ESP_Roland and 98 guests