1 - Does anyone know any place I can get more information about adc_ll and gdma_ll functions? or better an example?
There is no information about them on esp-idf docs search. I got some help from ChatGPT, but it is version dependent and not so "intelligent"
2 - The code I put together on VSCode is this one, and the issue is it does not generate DMA RX_SUC_EOF event or any other.
There are a lot of comments with the tests I have done.
If you know what I am missing, please tell me.
- /*
- ADC_ESP32S3_Direto.c
- Author: Klaus Fensterseifer
- Jan2025
- ESP-IDF v5.3.2
- ESP32-S3 N16R8 (16 MB Quad SPI flash, 8 MB Octal PSRAM die)
- From Compilation Summary:
- Flash Code 8MB
- DIRAM 340KB
- Flash Data 3.3MB
- IRAM 16kB
- RTC FAST 8kB
- Reads 3 ADC inputs in sequence
- Uses the DMA to fill a buffer with the ADC samples
- Interrupts the main task when the buffer is full
- - a new buffer is set to be filled by the DMA
- - the main task deal with the data from the filled buffer
- Sample frequency = 480kHz (3 ADCs = 160kHz each ADC input)
- Size of the buffer = should be large, 3000 or 30000, but he DMA setup restricts it to 4095 bytes (or 2047 12 bits samples)
- Similar approach = https://github.com/kaefe64/Arduino_uSDX_Pico_FFT_Proj
- */
- #include <stdio.h>
- #include "freertos/FreeRTOS.h"
- #include "freertos/task.h"
- #include "esp_log.h"
- #include "soc/soc.h"
- #include "soc/lldesc.h"
- #include "soc/gdma_struct.h"
- #include "soc/soc_caps.h"
- //#ifndef __DECLARE_RCC_ATOMIC_ENV
- //#define __DECLARE_RCC_ATOMIC_ENV()
- //#endif
- #include "hal/adc_ll.h"
- #include "hal/adc_hal_common.h"
- #include "hal/gdma_ll.h"
- #include "hal/clk_gate_ll.h" //
- #include "hal/gpio_ll.h" //
- //#include "esp_task_wdt.h" //
- #include "esp_timer.h" //
- //#include "esp_private/periph_ctrl.h" //
- //#include "esp_private/adc_share_hw_ctrl.h" //
- //#define ADC_BUS_CLK_ATOMIC() PERIPH_RCC_ATOMIC()
- #include "hal/spi_types.h"
- #include "hal/spi_ll.h"
- #include "esp_private/spi_common_internal.h"
- #include "esp_private/adc_dma.h"
- #define DMA_BUFFER_SIZE 100 //2000 // Tamanho do buffer DMA max = 4095 / 2 = 2047
- #define DMA_BUFFER_NUM 20 // number of DMA buffers (if size is small, increase the number of buffers)
- #define TAG "LowLevel_ADC_DMA"
- #define DMA_CHANNEL 2 //wifi uses DMA channel 0
- static uint16_t dma_buffer[DMA_BUFFER_NUM][DMA_BUFFER_SIZE] __attribute__((aligned(4)));
- static volatile bool buffer_ready = false; // buffer filled
- static uint16_t active_buffer_idx = 0; //buffer index
- static uint16_t active_buffer_main = 0; //buffer index
- static uint16_t active_buffer_cnt = 0; //buffer index
- static uint16_t active_buffer_cnt_main = 0; //buffer index
- // Descritor DMA
- static lldesc_t dma_descriptor[DMA_BUFFER_NUM] = {0};
- /*
- #include "hal/timer_ll.h"
- //=================================================================
- //=================================================================
- void configure_timer_for_adc(void) {
- // Configurar o timer periférico para gerar eventos
- uint32_t timer_group = 0; // Grupo do timer (0 ou 1)
- uint32_t timer_index = 0; // Índice do timer (0 ou 1 dentro do grupo)
- timer_ll_set_auto_reload(timer_group, timer_index, true); // Habilitar modo auto-reload
- //timer_ll_enable_auto_reload
- timer_ll_set_counter_value(timer_group, timer_index, 0); // Reiniciar contador
- //timer_ll_set_clock_prescale 1
- timer_ll_set_divider(timer_group, timer_index, 2); // Configurar divisor (ajustar para precisão)
- //timer_ll_set_alarm_value 166
- // Frequência desejada = 480 kHz
- uint32_t clock_source_freq = 80000000; // Clock base (80 MHz)
- uint32_t timer_divider = clock_source_freq / 480000; // Divisor para 480 kHz
- timer_ll_set_alarm_value(timer_group, timer_index, timer_divider);
- timer_ll_enable_alarm(timer_group, timer_index, true); // Habilitar alarme
- timer_ll_enable_counter(timer_group, timer_index, true); // Iniciar contador
- }
- // Configurar o ADC para ser acionado por um evento externo (como o timer)
- adc_ll_digi_set_trigger_source(ADC_TRIGGER_TIMER);
- adc_ll_digi_enable(); // Habilitar o ADC
- */
- //=================================================================
- // DMA Interrupt handler
- //=================================================================
- void IRAM_ATTR dma_isr_handler(void *arg)
- {
- // Check interrupt event
- uint32_t isr_evt = gdma_ll_rx_get_interrupt_status(&GDMA, DMA_CHANNEL, true); //true = raw false = status
- if(isr_evt & GDMA_LL_EVENT_RX_SUC_EOF)
- {
- uint16_t j = active_buffer_idx; //start to search for buffer ready from the last one
- for(uint16_t i=0; i<DMA_BUFFER_NUM; i++)
- {
- if(++j >= DMA_BUFFER_NUM) j = 0;
- // the descriptor with owner = 0 is the one that was filled by DMA
- if (dma_descriptor[j].owner == 0) // Buffer filled
- {
- // get the buffer index
- active_buffer_idx = j;
- active_buffer_cnt++; //guess the next buffer filled is the next one
- if(buffer_ready == false)
- {
- //processing
- active_buffer_main = active_buffer_idx;
- active_buffer_cnt_main = active_buffer_cnt;
- //process_data(dma_descriptor[0].buf, dma_descriptor[0].length);
- buffer_ready = true; //inform main
- // inform main task about buffer filled
- //xTaskNotifyFromISR(task_handle, 0, eNoAction, NULL);
- }
- // return buffer control to GDMA after processing
- dma_descriptor[j].owner = 1;
- break; //only one buffer ready (expect to receive another interrupt for other one)
- }
- }
- // clear GDMA int
- gdma_ll_rx_clear_interrupt_status(&GDMA, DMA_CHANNEL, GDMA_LL_EVENT_RX_SUC_EOF); // Limpa a interrupção de "success end of frame" (EOF)
- }
- else //another event generated the DMA interrupt - should not happen
- {
- gdma_ll_rx_clear_interrupt_status(&GDMA, DMA_CHANNEL, isr_evt); // clear all events set, but not RX SUC EOF
- }
- //buffer_ready = true; //for debug, any DMA interrupt will show something on main task
- }
- //#define __DECLARE_RCC_ATOMIC_ENV() //be used in an atomic way
- //=================================================================
- // Configuração do ADC no ESP32-S3
- //=================================================================
- static void configure_adc(void) {
- adc_ll_digi_reset();
- //adc_ll_reset_register(); //ADC regas reset, return to default values
- // SYSTEM.perip_clk_en0 is a shared register, so this function must be used in an atomic way
- // ADC_BUS_CLK_ATOMIC() {
- SYSTEM.perip_rst_en0.apb_saradc_rst = 1;
- SYSTEM.perip_rst_en0.apb_saradc_rst = 0;
- // }
- // Ativar o clock do ADC
- //adc_ll_enable_bus_clock(true); //enables ADC clock on APB bus
- // SYSTEM.perip_rst_en0 is a shared register, so this function must be used in an atomic way
- // ADC_BUS_CLK_ATOMIC() {
- SYSTEM.perip_clk_en0.apb_saradc_clk_en = true;
- // }
- adc_ll_digi_convert_limit_enable(false);
- adc_hal_set_controller(ADC_UNIT_1, ADC_HAL_CONTINUOUS_READ_MODE);
- //adc_ll_digi_clear_pattern_table(ADC_UNIT_1);
- adc_ll_digi_set_pattern_table_len(ADC_UNIT_1, 3); // 3 channels
- adc_digi_pattern_config_t adc_pattern;
- adc_pattern.atten = ADC_ATTEN_DB_0; //No input attenuation, approx. 0 - 800mV
- adc_pattern.channel = ADC_CHANNEL_3; //ADC channel index. ADC1_CH3 - GPIO 4
- adc_pattern.unit = ADC_UNIT_1; //ADC unit index.
- adc_ll_digi_set_pattern_table(ADC_UNIT_1, 0, adc_pattern);
- adc_pattern.atten = ADC_ATTEN_DB_0; //No input attenuation, ADC can measure up to approx. 0 - 800mV
- adc_pattern.channel = ADC_CHANNEL_4; //ADC channel index. ADC1_CH4 - GPIO 5
- adc_pattern.unit = ADC_UNIT_1; //ADC unit index.
- adc_ll_digi_set_pattern_table(ADC_UNIT_1, 1, adc_pattern);
- adc_pattern.atten = ADC_ATTEN_DB_0; //No input attenuation, ADC can measure up to approx. 0 - 800mV
- adc_pattern.channel = ADC_CHANNEL_5; //ADC channel index. ADC1_CH5 - GPIO 6
- adc_pattern.unit = ADC_UNIT_1; //ADC unit index.
- adc_ll_digi_set_pattern_table(ADC_UNIT_1, 2, adc_pattern);
- // ADC basic configuration
- adc_ll_set_controller(ADC_UNIT_1, ADC_LL_CTRL_DIG); // digital controler
- adc_ll_digi_set_convert_mode(ADC_LL_DIGI_CONV_ONLY_ADC1); //uses only ADC1
- adc_ll_set_sample_cycle(4); // number of clock cycles to charge the ADC internal capacitor before the conversion start
- // 2 for source impedance < 1k 10 for impedances > 10k
- //adc_ll_set_data_format(ADC_LL_DATA_FMT_12_BIT); // 12 bits format - ESP32S3 only supports 12bit
- //adc_ll_digi_set_arbiter_stable_cycle()
- adc_ll_digi_clk_sel(ADC_DIGI_CLK_SRC_APB); //use APB
- //adc_ll_digi_controller_clk_div(0,1,0); //base clock for the ADC digital controller.
- adc_ll_digi_controller_clk_div(1,1,1); //base clock for the ADC digital controller.
- //ctrl_clk = (APLL or APB) / (div_num + div_a / div_b + 1).
- adc_ll_digi_set_clk_div(2); //sampling clock determines the time taken for the ADC to perform a conversion.
- //80MHz / 500kHz = 160 500000 / 3 = 166666
- //80MHz / 481927 = 166 481927 / 3 = 160642
- //80MHz / 479041 = 167 479041 / 3 = 159680
- //interval = ((80MHz /(0 + (0 / 1) + 1)) /2) /480kHz = 83.33
- //interval = ((80MHz /(1 + (1 / 1) + 1)) /2) /480kHz = 27.77
- adc_ll_digi_set_trigger_interval(167); //Configures ADC digital controller clock cycles between ADC conversions.
- //adc_ll_digi_start(ADC_UNIT_1);
- /*
- uint32_t interval = APB_CLK_FREQ / (ADC_LL_CLKM_DIV_NUM_DEFAULT + ADC_LL_CLKM_DIV_A_DEFAULT / ADC_LL_CLKM_DIV_B_DEFAULT + 1) / 2 / freq;
- //set sample interval
- adc_ll_digi_set_trigger_interval(interval);
- //Here we set the clock divider factor to make the digital clock to 5M Hz
- adc_ll_digi_controller_clk_div(ADC_LL_CLKM_DIV_NUM_DEFAULT, ADC_LL_CLKM_DIV_B_DEFAULT, ADC_LL_CLKM_DIV_A_DEFAULT);
- adc_ll_digi_clk_sel(0); //use APB
- */
- adc_ll_digi_set_power_manage(ADC_LL_POWER_SW_ON);
- //adc_ll_digi_dma_set_eof_num(10);
- //adc_dma_ll_rx_start
- //adc_dma_start(adc_dma_t adc_dma, dma_descriptor_t *addr)
- // spi_ll_clear_intr(adc_dma.adc_spi_dev, ADC_DMA_INTR_MASK);
- // spi_ll_enable_intr(adc_dma.adc_spi_dev, ADC_DMA_INTR_MASK);
- // spi_dma_ll_rx_start(adc_dma.adc_spi_dev, adc_dma.spi_dma_ctx->rx_dma_chan.chan_id, (lldesc_t *)addr);
- // enables DMA for ADC
- adc_ll_digi_dma_enable(); //enables DMA to tranfer data from ADC direct to the memory
- adc_ll_digi_trigger_enable(); //enables ADC internal trigger to start continuous conversion
- }
- //=================================================================
- // DMA configuration for ESP32-S3
- //=================================================================
- static void configure_dma(void) {
- esp_err_t ret;
- //The DMA RX (Receive) functions are designed for scenarios where data is being transferred
- //from a peripheral (in this case, the ADC) into memory via DMA.
- // GDMA channel reset
- gdma_ll_rx_reset_channel(&GDMA, DMA_CHANNEL);
- //gdma_ll_rx_reset_channel0(&GDMA);
- //gdma_trigger_peripheral_t GDMA_TRIG_PERIPH_ADC
- /*
- gdma_dev_t *dev
- A pointer to the GDMA device structure.
- Represents the specific GDMA instance being configured. For ESP32-S3, this would typically be &GDMA.
- uint32_t channel
- Specifies the GDMA channel number to configure.
- RX channels are used for peripherals that produce data, such as the ADC.
- Example: If you are using channel 0, you pass 0 here.
- gdma_trigger_peripheral_t periph
- Specifies the type of peripheral triggering the DMA.
- For ADC, this should be GDMA_TRIG_PERIPH_ADC.
- Other possible values could include UART, SPI, I2S, etc.
- int periph_id
- Identifies the specific peripheral instance or unit to connect to the DMA.
- For ADC, this might differentiate between ADC units or specific configurations of the ADC digital controller.
- Example: If using ADC digital controller 1 (ADC1), you might pass 1.
- */
- // Connect the DMA RX channel to the ADC peripheral
- gdma_ll_rx_connect_to_periph(&GDMA, DMA_CHANNEL, GDMA_TRIG_PERIPH_ADC, 1); //1=ADC1
- // descriptor configuration
- for(int i=0; i<DMA_BUFFER_NUM; i++)
- {
- //size: max size buffer. max: 4095 bytes
- dma_descriptor[i].size = (uint32_t)(DMA_BUFFER_SIZE * sizeof(uint16_t)); // & 0xFFF max 4095 bytes
- //length: number of bytes in the buffer, should be equal or less than size.
- dma_descriptor[i].length = (uint32_t)(DMA_BUFFER_SIZE * sizeof(uint16_t));
- dma_descriptor[i].buf = (uint8_t *)dma_buffer[i];
- dma_descriptor[i].owner = 1; //release the buffer to DMA
- /*
- sosf = 0: not the beginnig of a frame
- sosf = 1: it is a frame beginnig
- If the use is a continuous stream of data, it could be set to 0
- */
- dma_descriptor[i].sosf = 0;
- /*
- eof = 0: not the end of frame
- eof = 1: it is a frame end
- If not using frames, it could be left as 0.
- */
- dma_descriptor[i].eof = 1; //=1 to set the interrupt when buffer full
- dma_descriptor[i].qe.stqe_next = &dma_descriptor[i+1];
- }
- //dma_descriptor[DMA_BUFFER_NUM-1].eof = 1;
- dma_descriptor[DMA_BUFFER_NUM-1].qe.stqe_next = &dma_descriptor[0];
- // Configurar o GDMA para o ADC
- gdma_ll_rx_set_desc_addr(&GDMA, DMA_CHANNEL, (uint32_t)&dma_descriptor[0]);
- //gdma_ll_rx_set_desc_addr(dma_dev, DMA_CHANNEL, (uint32_t)dma_descriptor);
- // transfer block size
- //gdma_ll_rx_set_block_size(DMA_CHANNEL, DMA_BUFFER_SIZE);
- // interrupt configuration
- //ret = esp_intr_alloc(ETS_DMA_IN_CH0_INTR_SOURCE, 0, dma_isr_handler, NULL, NULL);
- ret = esp_intr_alloc(ETS_DMA_IN_CH2_INTR_SOURCE, // DMA RX interrupt source DMA_CHANNEL = 2
- ESP_INTR_FLAG_IRAM, // Allocate in IRAM for fast execution.
- // ESP_INTR_FLAG_LEVEL6, // Allocate in IRAM for fast execution.
- dma_isr_handler, // Interrupt handler function
- NULL, // Argument to pass to handler
- NULL); // Optional handle (not needed here)
- if (ret != ESP_OK) {
- printf("Failed to allocate DMA interrupt\n");
- }
- //gdma_ll_force_enable_reg_clock(&GDMA, true);
- //gdma_ll_rx_restart(&GDMA, DMA_CHANNEL);
- //gdma_ll_rx_enable_auto_return(&GDMA, DMA_CHANNEL,true);
- gdma_ll_rx_set_burst_size(&GDMA, DMA_CHANNEL, 16);
- // set interrupt of end of transfer
- //GDMA.channel[0].in.int_ena.in_suc_eof = 1;
- // enables the GDMA to set the EOF interrupt
- gdma_ll_rx_enable_interrupt(&GDMA, DMA_CHANNEL, GDMA_LL_EVENT_RX_SUC_EOF, true);
- //gdma_ll_rx_enable_interrupt(&GDMA, DMA_CHANNEL, 0x2ff, true); //for debug, enables all possible interrupt event
- //gdma_ll_rx_enable_channel(&GDMA, DMA_CHANNEL, true);
- //GDMA.channel[0].in.link.start = 1; // Habilitar o canal RX
- gdma_ll_rx_start(&GDMA, DMA_CHANNEL);
- //adc_digi_start();
- //DR_REG_GDMA_BASE
- }
- uint64_t tim, tim_old;
- //=================================================================
- // Função principal
- //=================================================================
- void app_main()
- {
- uint32_t i = 0;
- //ADC1_3 GPIO4
- gpio_ll_input_enable(&GPIO, 3);
- gpio_ll_pullup_dis(&GPIO, 4);
- gpio_ll_pulldown_dis(&GPIO, 4);
- //adc_ll_set_channel_io_map(ADC_UNIT_1, ADC_CHANNEL_3, 4);
- //ADC1_4 GPIO5
- gpio_ll_input_enable(&GPIO, 4);
- gpio_ll_pullup_dis(&GPIO, 5);
- gpio_ll_pulldown_dis(&GPIO, 5);
- //adc_ll_set_channel_io_map(ADC_UNIT_1, ADC_CHANNEL_4, 5);
- //ADC1_5 GPIO6
- gpio_ll_input_enable(&GPIO, 5);
- gpio_ll_pullup_dis(&GPIO, 6);
- gpio_ll_pulldown_dis(&GPIO, 6);
- //adc_ll_set_channel_io_map(ADC_UNIT_1, ADC_CHANNEL_5, 6);
- ESP_LOGI(TAG, "************************");
- ESP_LOGI(TAG, "************************");
- ESP_LOGI(TAG, "Starting ADC with DMA...");
- ESP_LOGI(TAG, "************************");
- ESP_LOGI(TAG, "************************");
- configure_dma();
- ESP_LOGI(TAG, "1************************");
- configure_adc();
- ESP_LOGI(TAG, "2************************");
- tim_old = esp_timer_get_time();
- while (1)
- {
- if (buffer_ready)
- {
- // Main task - Data processing
- //uint16_t *processed_buffer = (active_buffer == dma_buffer_a) ? dma_buffer_b : dma_buffer_a;
- ESP_LOGI(TAG, "Buffer filled:");
- //for (int i = 0; i < DMA_BUFFER_SIZE; i++) {
- // printf("ADC[%d]: %d\n", i, processed_buffer[i]);
- //}
- printf(" cnt:%d buffer:%d [0]= %d %d %d\n",
- active_buffer_cnt_main,
- active_buffer_main,
- dma_buffer[active_buffer_main][0],
- dma_buffer[active_buffer_main][1],
- dma_buffer[active_buffer_main][2] );
- //printf("\n");
- buffer_ready = false;
- }
- if(++i>11000000) //20M ~3s
- {
- //printf(" i:%d\n",(uint16_t)(i-10000000));
- tim = esp_timer_get_time();
- printf(" time: %d ms\n",(uint16_t)((tim-tim_old)/1000));
- tim_old = tim;
- vTaskDelay(1); //to avoid watchdog trigger
- i = 0;
- }
- }
- }