Fast Way to start a SPI transmission with DMA?
Fast Way to start a SPI transmission with DMA?
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
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
-
- Posts: 9766
- Joined: Thu Nov 26, 2015 4:08 am
Re: Fast Way to start a SPI transmission with DMA?
Did you remember to de-reset and un-clockgate the peripheral first? If it's off, it won't do anything.
Re: Fast Way to start a SPI transmission with DMA?
Thanks for the answer.ESP_Sprite wrote: Did you remember to de-reset and un-clockgate the peripheral first? If it's off, it won't do anything.
For the clockgate I did
Code: Select all
REG_WRITE(SPI_CLK_GATE_REG(2), 7);
which does not sound like a reset of the whole SPI controller, but I tried anyway: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)
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);
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
Re: Fast Way to start a SPI transmission with DMA?
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
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
Re: Fast Way to start a SPI transmission with DMA?
Interesting! Thanks!ok-home wrote: ↑Sun Nov 12, 2023 3:13 pmHello
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
Re: Fast Way to start a SPI transmission with DMA?
Found a hint in ok-home's link:ESP_Sprite wrote: ↑Sun Nov 12, 2023 1:07 pmDid you remember to de-reset and un-clockgate the peripheral first? If it's off, it won't do anything.
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);
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
Re: Fast Way to start a SPI transmission with DMA?
Hi
The best way for ESP32
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);
Code: Select all
periph_module_enable(PERIPH_HSPI_MODULE);
periph_module_enable(PERIPH_SPI_DMA_MODULE)
Re: Fast Way to start a SPI transmission with DMA?
After hours of try&error I found a good solution:
Set up the FSPI and GDMA by using the regular C functions:
Do a fake transmission to init all registers:
Create the DMA Buffers:
Alter the DMA settings and assign the DMA buffers:
Now I can start the transmission in the ISR really fast!
So thanks to ok-home and ESP_Sprite for your suggestions. You got me to the right path.
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));
Code: Select all
ESP_ERROR_CHECK(spi_device_queue_trans(spi, &transfer, 0));
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!
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);
Code: Select all
REG_SET_BIT(GDMA_IN_LINK_CH1_REG, GDMA_INLINK_RESTART_CH1);
REG_SET_BIT(SPI_CMD_REG(2), SPI_USR);
Who is online
Users browsing this forum: No registered users and 76 guests