WORKAROUND: ADC in continuous mode MISSING SAMPLES - 255 Sample LIMIT
Posted: Mon Nov 27, 2023 2:28 am
Hi,
I've set up the ADC in continuous (DMA) mode to store samples to a 1024 byte buffer with 256 byte frame size. The sample rate is 40 kHz and the resolution is 12 bits. I'm feeding a 2.2v, 1 kHz sine wave to the input (input looks good on the scope).
I've set up the code with a task which is woken up for each frame (on_conv_done callback) and copies frame data to a static buffer until all frames have been copied. I use two static buffers, and the "main" task prints out 128 samples (256 bytes) from the other buffer then swaps the buffers (with mutex use to prevent contentions). This is a bit crude, but I just want to make sure the sampling is working correctly without any ring buffer overflows.
I have been pasting the 128 samples into Excel and plotting the sine wave to check what it looks like.
It looks like the ADC sampling is dropping or missing every 10th and 11th sample, i.e. 9 samples look OK, then there is a jump which looks like 2 samples are missing. The plot looks OK if I insert a 2 row gap every 9 rows. See attachments.
I know the ring buffer is getting cleared in time because I included a callback for "on_pool_ovf" which generates an execption, and it doesn't get called.
I have tried different sample rates but that doesn't change anything.
Here is my code (main.c):
ESP32 (ESP32-WROVER-B module)
ESP-IDF 5.1.2
Why am I losing every 10th and 11th sample?
I've set up the ADC in continuous (DMA) mode to store samples to a 1024 byte buffer with 256 byte frame size. The sample rate is 40 kHz and the resolution is 12 bits. I'm feeding a 2.2v, 1 kHz sine wave to the input (input looks good on the scope).
I've set up the code with a task which is woken up for each frame (on_conv_done callback) and copies frame data to a static buffer until all frames have been copied. I use two static buffers, and the "main" task prints out 128 samples (256 bytes) from the other buffer then swaps the buffers (with mutex use to prevent contentions). This is a bit crude, but I just want to make sure the sampling is working correctly without any ring buffer overflows.
I have been pasting the 128 samples into Excel and plotting the sine wave to check what it looks like.
It looks like the ADC sampling is dropping or missing every 10th and 11th sample, i.e. 9 samples look OK, then there is a jump which looks like 2 samples are missing. The plot looks OK if I insert a 2 row gap every 9 rows. See attachments.
I know the ring buffer is getting cleared in time because I included a callback for "on_pool_ovf" which generates an execption, and it doesn't get called.
I have tried different sample rates but that doesn't change anything.
Here is my code (main.c):
Code: Select all
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_adc/adc_continuous.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const char *TAG = "MAIN";
#define ADC_BUFFER_SIZE 1024
#define ADC_FRAME_SIZE 256
#define SEM_WAIT_TIME 50 // Ticks
// Use two buffers so we don't have to stop the ADC while printing out a frame:
uint8_t sample_data_uint8_a[ADC_FRAME_SIZE] = {0};
uint8_t sample_data_uint8_b[ADC_FRAME_SIZE] = {0};
uint8_t *p_store_buffer = sample_data_uint8_a;
uint8_t *p_print_buffer = sample_data_uint8_b;
static SemaphoreHandle_t l_buffer_mutex;
static TaskHandle_t l_store_task_handle;
static TaskHandle_t l_main_task_handle;
static adc_continuous_handle_t l_adc_handle = NULL;
#define ENSURE_TRUE(ACTION) \
do \
{ \
BaseType_t __res = (ACTION); \
assert(__res == pdTRUE); \
(void)__res; \
} while (0)
static void adc_store_task(void *arg);
// =============== Conversion Done Callback: ========================================================
static bool IRAM_ATTR adc_conversion_done_cb(adc_continuous_handle_t handle, const adc_continuous_evt_data_t *edata, void *user_data)
{
BaseType_t must_yield = pdFALSE;
// Notify "store" task that a frame has finished.
vTaskNotifyGiveFromISR(l_store_task_handle, &must_yield);
return (must_yield == pdTRUE);
}
// ================ Ring buffer overflow callback: ==================================================
static bool IRAM_ATTR adc_pool_overflow_cb(adc_continuous_handle_t handle, const adc_continuous_evt_data_t *edata, void *user_data)
{
// OVERFLOW! We don't expect this to happen, so generate exception.
assert(false);
return false; // Ssouldn't get to here.
}
// ========== Configure the ADC in continuous mode: ============
void adc_init(void)
{
adc_continuous_handle_cfg_t continuous_handle_config =
{
.max_store_buf_size = ADC_BUFFER_SIZE,
.conv_frame_size = ADC_FRAME_SIZE,
};
ESP_ERROR_CHECK( adc_continuous_new_handle(&continuous_handle_config, &l_adc_handle) );
adc_digi_pattern_config_t pattern_config =
{
.unit = ADC_UNIT_1,
.channel = ADC_CHANNEL_0,
.bit_width = 12,
.atten = ADC_ATTEN_DB_11,
};
adc_continuous_config_t adc_config =
{
.pattern_num = 1,
.adc_pattern = &pattern_config,
.sample_freq_hz = 60 * 1000,
.conv_mode = ADC_CONV_SINGLE_UNIT_1,
.format = ADC_DIGI_OUTPUT_FORMAT_TYPE1,
};
ESP_ERROR_CHECK( adc_continuous_config(l_adc_handle, &adc_config) );
adc_continuous_evt_cbs_t adc_callbacks =
{
.on_conv_done = adc_conversion_done_cb,
.on_pool_ovf = adc_pool_overflow_cb,
};
ESP_ERROR_CHECK( adc_continuous_register_event_callbacks(l_adc_handle, &adc_callbacks, NULL) );
ESP_ERROR_CHECK( adc_continuous_start(l_adc_handle) );
}
// ADC store task:
// Woken up by the callback when a frame is finished.
// Read the frame data from the ADC and store to static buffer.
// This must be done frequently to prevent ring buffer overflow.
static void adc_store_task(void *arg)
{
while (true)
{
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
esp_err_t read_result;
do
{
// Read all available frames from the ring buffer (probably only 1):
ENSURE_TRUE( xSemaphoreTake(l_buffer_mutex, SEM_WAIT_TIME) );
uint32_t bytes_read = 0;
read_result = adc_continuous_read(l_adc_handle, p_store_buffer, ADC_FRAME_SIZE, &bytes_read, 0);
ENSURE_TRUE( xSemaphoreGive(l_buffer_mutex) );
} while (read_result == ESP_OK); // ESP_OK means more frames may be available
// Result should be a timeout (no more frames available):
assert(read_result == ESP_ERR_TIMEOUT);
// Wake the main task to print out the latest frame:
xTaskNotifyGive(l_main_task_handle);
}
}
void app_main(void)
{
ESP_LOGI(TAG, "Test ADC Continuous Mode");
l_main_task_handle = xTaskGetCurrentTaskHandle();
l_buffer_mutex = xSemaphoreCreateMutex();
assert(l_buffer_mutex != NULL);
const BaseType_t APP_CORE = 1;
xTaskCreatePinnedToCore(adc_store_task, "ADC_Store", 4096, NULL, 6, &l_store_task_handle, APP_CORE);
adc_init();
// Main task: Print out the current "print" buffer, then swap buffers:
while (true)
{
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
size_t n_samples = ADC_FRAME_SIZE / sizeof(adc_digi_output_data_t);
ESP_LOGI(TAG, "------- %zu samples available (%lu bytes) ------", n_samples, (uint32_t)ADC_FRAME_SIZE);
// Sample data is actually an array of adc_digi_output_data_t structs:
adc_digi_output_data_t *p_sample_data_adc = (adc_digi_output_data_t *)p_print_buffer;
for (size_t i = 0; i < n_samples - 1; i += 2)
{
// Note sample order 2, 1, 3, 4, ... , N, N-1:
adc_digi_output_data_t *p_sample_a = p_sample_data_adc + i + 1;
adc_digi_output_data_t *p_sample_b = p_sample_data_adc + i;
printf("%d\n", p_sample_a->type1.data);
printf("%d\n", p_sample_b->type1.data);
}
// Swap the "store" buffer with the "print" buffer:
ENSURE_TRUE( xSemaphoreTake(l_buffer_mutex, SEM_WAIT_TIME) );
if (p_store_buffer == sample_data_uint8_a)
{
p_store_buffer = sample_data_uint8_b;
p_print_buffer = sample_data_uint8_a;
}
else
{
p_store_buffer = sample_data_uint8_a;
p_print_buffer = sample_data_uint8_b;
}
ENSURE_TRUE( xSemaphoreGive(l_buffer_mutex) );
vTaskDelay(1); // Don't hog the CPU from this task!
}
}
ESP-IDF 5.1.2
Why am I losing every 10th and 11th sample?