Fast Way to start a SPI transmission with DMA?

Stefan311
Posts: 5
Joined: Sun Nov 12, 2023 6:59 am

Fast Way to start a SPI transmission with DMA?

Postby Stefan311 » Sun Nov 12, 2023 8:13 am

Hi,

I am new to the ESP32, but my first project already pushes this controller to its limits.

The goal is to capture a very uncommon video signal on the FSPI port, and re-transmit it as a VGA-signal.
The input signal have 3 wires: a combined SYNC signal and 2 Bit color (well, 4 shades of green...) signals. The idea is to get this signals into a 4-bit SPI port, receive them as a long DMA transmission, and finally process them into the VGA buffer.

The VGA part was easy. The SPI part was also not difficult to set up.
BUT: to capture the full line of the image, I have so start the SPI transmisson FAST. I have 5µs between the sync impulse and the first pixel. In my youth naivety i though: "a 240MHz processor should get that easily", bot no - before the transmission starts, half of the line is already gone.

I have taken some looks into the spi_device_queue_trans source code to find out why the SPI startup does take so long. It seems the function does a lot of housekeeping and places the transmission into a queue. A ISR takes the transmission from the queue and after a lot of housekeeping again it finally starts the transmission. This all takes time. No way to get this in 5µs.

Ok. Time to go deeper. I took the technical reference manual, and implemented the whole SPI thing by writing registers. But: to my surprise the SPI registers does not even change if i write something into them! Are they protected somehow? For an IOT device this would make sense, but in my case... no.

So: is there a fast way to start a SPI transmission? Any other ideas?

Greetings
Stefan

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

Re: Fast Way to start a SPI transmission with DMA?

Postby ESP_Sprite » Sun Nov 12, 2023 1:07 pm

Stefan311 wrote:
Sun Nov 12, 2023 8:13 am
Ok. Time to go deeper. I took the technical reference manual, and implemented the whole SPI thing by writing registers. But: to my surprise the SPI registers does not even change if i write something into them!
Did you remember to de-reset and un-clockgate the peripheral first? If it's off, it won't do anything.

Stefan311
Posts: 5
Joined: Sun Nov 12, 2023 6:59 am

Re: Fast Way to start a SPI transmission with DMA?

Postby Stefan311 » Sun Nov 12, 2023 2:53 pm

ESP_Sprite wrote: Did you remember to de-reset and un-clockgate the peripheral first? If it's off, it won't do anything.
Thanks for the answer.

For the clockgate I did

Code: Select all

    REG_WRITE(SPI_CLK_GATE_REG(2), 7);
After you sayed "de-reset" i searched for a way to do this, found only this:
SPI_SOFT_RESET Software reset enable bit. If this bit is set, the SPI clock line, CS line, and data
line are reset. Can be configured in CONF state. (WT)
which does not sound like a reset of the whole SPI controller, but I tried anyway:

Code: Select all

    REG_SET_FIELD(SPI_SLAVE_REG(2), SPI_SOFT_RESET, 1);
    REG_SET_FIELD(SPI_SLAVE_REG(2), SPI_SOFT_RESET, 0);
The results are the same:

Code: Select all

Startup...
DMA: GDMA_IN_CONF0=00000000 GDMA_IN_CONF1=0000000c GDMA_IN_LINK=01100000 GDMA_IN_PERI_SEL=0000003f
SPI: SPI_CMD_REG=00000000 SPI_ADDR_REG=00000000 SPI_USER_REG=00000000 SPI_USER1_REG=00000000 SPI_USER2_REG=00000000 SPI_CTRL_REG=00000000 SPI_MS_DLEN_REG=00000000 SPI_MISC_REG=00000000 SPI_DMA_CONF_REG=00000000

Setup SPI...
DMA: GDMA_IN_CONF0=00000008 GDMA_IN_CONF1=0000100c GDMA_IN_LINK=0019f1f4 GDMA_IN_PERI_SEL=00000000
SPI: SPI_CMD_REG=00000000 SPI_ADDR_REG=00000000 SPI_USER_REG=00000000 SPI_USER1_REG=00000000 SPI_USER2_REG=00000000 SPI_CTRL_REG=00000000 SPI_MS_DLEN_REG=00000000 SPI_MISC_REG=00000000 SPI_DMA_CONF_REG=00000000

Start SPI...
DMA: GDMA_IN_CONF0=00000008 GDMA_IN_CONF1=0000100c GDMA_IN_LINK=0019f1f4 GDMA_IN_PERI_SEL=00000000
SPI: SPI_CMD_REG=00000000 SPI_ADDR_REG=00000000 SPI_USER_REG=00000000 SPI_USER1_REG=00000000 SPI_USER2_REG=00000000 SPI_CTRL_REG=00000000 SPI_MS_DLEN_REG=00000000 SPI_MISC_REG=00000000 SPI_DMA_CONF_REG=00000000

sleep(1)...
DMA start: GDMA_IN_CONF0=00000008 GDMA_IN_CONF1=0000100c GDMA_IN_LINK=0019f1f4 GDMA_IN_PERI_SEL=00000000
SPI_CMD_REG=00000000 SPI_ADDR_REG=00000000 SPI_USER_REG=00000000 SPI_USER1_REG=00000000 SPI_USER2_REG=00000000 SPI_CTRL_REG=00000000 SPI_MS_DLEN_REG=00000000 SPI_MISC_REG=00000000 SPI_DMA_CONF_REG=00000000

End
What else should I do to un-reset and un-clockgate the SPI?

User avatar
ok-home
Posts: 68
Joined: Sun May 02, 2021 7:23 pm
Location: Russia Novosibirsk
Contact:

Re: Fast Way to start a SPI transmission with DMA?

Postby ok-home » Sun Nov 12, 2023 3:13 pm

Hello
maybe this will help you
https://github.com/ok-home/logic_analyzer/tree/master

include/logic_analyzer_hal.h
Gets samples into ESP32 buffer
logic_analyzer_config_t - capture configuration
start_logic_analyzer(logic_analyzer_config_t *config) - capture start
void (*logic_analyzer_cb_t)(uint8_t *samle_buf, int samples, int sample_rate) - callback after data capture

Stefan311
Posts: 5
Joined: Sun Nov 12, 2023 6:59 am

Re: Fast Way to start a SPI transmission with DMA?

Postby Stefan311 » Sun Nov 12, 2023 3:21 pm

ok-home wrote:
Sun Nov 12, 2023 3:13 pm
Hello
maybe this will help you
https://github.com/ok-home/logic_analyzer/tree/master

include/logic_analyzer_hal.h
Gets samples into ESP32 buffer
logic_analyzer_config_t - capture configuration
start_logic_analyzer(logic_analyzer_config_t *config) - capture start
void (*logic_analyzer_cb_t)(uint8_t *samle_buf, int samples, int sample_rate) - callback after data capture
Interesting! Thanks!

Stefan311
Posts: 5
Joined: Sun Nov 12, 2023 6:59 am

Re: Fast Way to start a SPI transmission with DMA?

Postby Stefan311 » Sun Nov 12, 2023 3:36 pm

ESP_Sprite wrote:
Sun Nov 12, 2023 1:07 pm
Did you remember to de-reset and un-clockgate the peripheral first? If it's off, it won't do anything.
Found a hint in ok-home's link:

Code: Select all

    REG_CLR_BIT(SYSTEM_PERIP_CLK_EN0_REG, SYSTEM_SPI2_CLK_EN);
    REG_SET_BIT(SYSTEM_PERIP_CLK_EN0_REG, SYSTEM_SPI2_CLK_EN);
    REG_SET_BIT(SYSTEM_PERIP_RST_EN0_REG, SYSTEM_SPI2_RST);
    REG_CLR_BIT(SYSTEM_PERIP_RST_EN0_REG, SYSTEM_SPI2_RST);
Now the SPI does someting!

Code: Select all

SPI: SPI_CMD_REG=00000000 SPI_ADDR_REG=00000000 SPI_USER_REG=10002000 SPI_USER1_REG=b8410000 SPI_USER2_REG=78000000 SPI_CTRL_REG=003c8000 SPI_MS_DLEN_REG=00000000 SPI_MISC_REG=0000003e SPI_DMA_CONF_REG=08000001

User avatar
ok-home
Posts: 68
Joined: Sun May 02, 2021 7:23 pm
Location: Russia Novosibirsk
Contact:

Re: Fast Way to start a SPI transmission with DMA?

Postby ok-home » Mon Nov 13, 2023 3:59 am

Hi

Code: Select all

REG_CLR_BIT(SYSTEM_PERIP_CLK_EN0_REG, SYSTEM_SPI2_CLK_EN);
    REG_SET_BIT(SYSTEM_PERIP_CLK_EN0_REG, SYSTEM_SPI2_CLK_EN);
    REG_SET_BIT(SYSTEM_PERIP_RST_EN0_REG, SYSTEM_SPI2_RST);
    REG_CLR_BIT(SYSTEM_PERIP_RST_EN0_REG, SYSTEM_SPI2_RST);
The best way for ESP32

Code: Select all

periph_module_enable(PERIPH_HSPI_MODULE);
periph_module_enable(PERIPH_SPI_DMA_MODULE)

Stefan311
Posts: 5
Joined: Sun Nov 12, 2023 6:59 am

Re: Fast Way to start a SPI transmission with DMA?

Postby Stefan311 » Tue Nov 14, 2023 3:45 pm

After hours of try&error I found a good solution:

Set up the FSPI and GDMA by using the regular C functions:

Code: Select all

	ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
	ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &devcfg, &spi));
Do a fake transmission to init all registers:

Code: Select all

	ESP_ERROR_CHECK(spi_device_queue_trans(spi, &transfer, 0));
Create the DMA Buffers:

Code: Select all

	ABG_PIXBUF1 = heap_caps_malloc(4096, MALLOC_CAP_DMA | MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL);
	ABG_PIXBUF2 = heap_caps_malloc(4096, MALLOC_CAP_DMA | MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL);
	ABG_DMALIST = heap_caps_malloc(24, MALLOC_CAP_DMA | MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL);
	ABG_DMALIST[0] = 0x80ffffff;
	ABG_DMALIST[1] = (int)ABG_PIXBUF1;
	ABG_DMALIST[2] = (int)&ABG_DMALIST[3];
	ABG_DMALIST[3] = 0x80ffffff;
	ABG_DMALIST[4] = (int)ABG_PIXBUF2;
	ABG_DMALIST[5] = (int)&ABG_DMALIST[0];  // Yes, a circular buffer!
Alter the DMA settings and assign the DMA buffers:

Code: Select all

	assert(REG_READ(GDMA_IN_PERI_SEL_CH1_REG) == 0);
	REG_SET_BIT(GDMA_IN_LINK_CH1_REG, GDMA_INLINK_STOP_CH1);
	REG_SET_BIT(GDMA_IN_CONF1_CH1_REG, GDMA_IN_CHECK_OWNER_CH1);
	REG_SET_FIELD(GDMA_IN_LINK_CH1_REG, GDMA_INLINK_ADDR_CH1, (int)ABG_DMALIST);
	REG_SET_BIT(GDMA_IN_LINK_CH1_REG, GDMA_INLINK_START_CH1);
Now I can start the transmission in the ISR really fast!

Code: Select all

	REG_SET_BIT(GDMA_IN_LINK_CH1_REG, GDMA_INLINK_RESTART_CH1);
	REG_SET_BIT(SPI_CMD_REG(2), SPI_USR);
So thanks to ok-home and ESP_Sprite for your suggestions. You got me to the right path.

Who is online

Users browsing this forum: No registered users and 66 guests