硬件平台ESP32-S3-LCD-EV-Board
-2的开发板(v1.5),软件平台vs code加ESP-ID插件(基于SDK版本v5.5.2),外加ADC芯片ZJC2400-16,如何做到高速采样(1msps以上)?经过反复实验,单独一个采样点一个采样点的读取是正确的,但这个无实际意义,因为如果一直读取几十毫秒的采样数据会占用大量cpu时间,如果被打断,则会有采样数据丢失;如果简单采用DMA+SPI的方式,在DMA期间CNV(CS)持续拉低,不符合ZJC2400的时序,只能读出第一个采样点的正确数据,如果采用SPI 链表 DMA 传输的方式,由于每个采样点采样完成要产生中断,会导致实际运行采样率只有不到100KSPS的速度,请问哪位大神有什么办法可以做到每读取一个采样点后就把CS拉高再接着读取下一个采样点,又不占用多少CPU时间又可以做到高速采样?
ESP32-S3 SPI DMA
Re: ESP32-S3 SPI DMA
hi
Try switching to slave DMA mode, create external SCL and СS, for example using RMT.
or, more correctly, DMA-Controlled Configurable Segmented Transfer
Try switching to slave DMA mode, create external SCL and СS, for example using RMT.
or, more correctly, DMA-Controlled Configurable Segmented Transfer
-
PhiradarXu
- Posts: 2
- Joined: Mon Jun 08, 2026 12:52 pm
Re: ESP32-S3 SPI DMA
感谢指导,我之前的测试结果,使用DMA分段传输,每个DMA段内部还是CS一直拉低,要满足外部ADC时序要求,只能是每个DMA段只接收1个采样点数据(18bits),这样的话整个运行效率还是很低,实际采样率基本都是100KSPS以内。请帮忙详细说明下“slave DMA mode, create external SCL and СS, for example using RMT”如何实施?之前实验过用RMT产生CS信号,SPI master DMA Mode很难和RMT产生的CS信号对齐起来。外接SPI设备原厂的说法是主控的SPI要支持NSS模式,ESP32-S3应该本身就不支持DMA模式下的NSS模式吧。
Re: ESP32-S3 SPI DMA
Unfortunately, I couldn't find the datasheet for your ADC.
Technically, both the ESP and ADC will become slave devices, while the RMT generates CS and SCLC and acts as the master.
The ESP only needs SCLC, while the ADC needs CS and SCLC (and possibly also CONV). Run SPI DMA, everyone waits for CS and SCLC, then run the CS and SCLC generator on the RMT in synchronous mode for the required number of bits in a loop (if you need non-stop operation). The DMA will generate an interrupt when it counts the required number of bytes, depending on how you configure it.
I did something similar for another ADC, a CS/CONV/CLK generator (sorry for the Russian comments on the code).
Technically, both the ESP and ADC will become slave devices, while the RMT generates CS and SCLC and acts as the master.
The ESP only needs SCLC, while the ADC needs CS and SCLC (and possibly also CONV). Run SPI DMA, everyone waits for CS and SCLC, then run the CS and SCLC generator on the RMT in synchronous mode for the required number of bits in a loop (if you need non-stop operation). The DMA will generate an interrupt when it counts the required number of bytes, depending on how you configure it.
I did something similar for another ADC, a CS/CONV/CLK generator (sorry for the Russian comments on the code).
Code: Select all
/**
* @file adc_conv_cs_clk.c
* @brief Реализация генерации сигналов АЦП через RMT с настраиваемой частотой
* @details
* Модуль настраивает три канала RMT для генерации сигналов CONV, CS, CLK.
* Все три канала стартуют синхронно (аппаратный sync manager) и работают
* в бесконечном циклическом режиме. Параметры временных интервалов вычисляются
* динамически на основе желаемой частоты дискретизации.
*
*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_encoder.h"
#include "esp_log.h"
#include "esp_check.h"
#include "adc_conv_cs_clk.h"
/** @brief Тег для логирования */
static const char *TAG = "adc_conv_cs_clk";
/*------------------------- Настройки RMT (аппаратные, не изменяются) ------------------*/
#define RMT_TX_DIV (1) /**< Делитель тактовой частоты (80 МГц / 1 = 80 МГц) */
#define RMT_TX_CLK_OUT (80 * 1000 * 1000 / RMT_TX_DIV) /**< Разрешение 80 МГц -> период 12.5 нс */
#define TX_BLOCK_SYMBOL (SOC_RMT_MEM_WORDS_PER_CHANNEL * 1) /**< Один блок памяти на канал */
/*------------------------- Базовые ограничения (минимальные значения) ----------------*/
#define ADC_TICK_MIN 15 /**< Минимальная базовая единица времени (тики RMT) – ограничение аппаратуры */
#define ADC_BUSY_MIN_US 3 /**< Минимальное время преобразования АЦП (BUSY) в микросекундах */
#define ADC_BUSY_MIN_TICKS ((int)(ADC_BUSY_MIN_US * 80.0f)) /**< Минимум BUSY в тиках (1 мкс = 80 тиков при 12.5 нс) */
/*------------------------- Значения по умолчанию (используются при sampling_interval_us == -1) -*/
#define ADC_TICK_DEFAULT 25 /**< Базовый тик по умолчанию */
#define ADC_BUSY_DEFAULT_TICKS (11 * ADC_TICK_DEFAULT) /**< BUSY по умолчанию (275 тиков ≈ 3.44 мкс) */
/*------------------------- Уровни сигналов (с учётом флага invert_out = true) ----------*/
/**
* @brief Из-за инверсии на выводе (invert_out = true) логическому 0 в коде соответствует
* высокий уровень на пине, а логической 1 – низкий уровень.
*/
#define LOW_LVL 1 /**< Логический 0 (на пине будет высокий уровень) */
#define HIGHT_LVL 0 /**< Логическая 1 (на пине будет низкий уровень) */
/*------------------------- Глобальные дескрипторы (для деинита) ------------------------*/
static rmt_channel_handle_t s_tx_chan_conv = NULL;
static rmt_channel_handle_t s_tx_chan_cs = NULL;
static rmt_channel_handle_t s_tx_chan_clk = NULL;
static rmt_encoder_handle_t s_tx_enc_conv = NULL;
static rmt_encoder_handle_t s_tx_enc_cs = NULL;
static rmt_encoder_handle_t s_tx_enc_clk = NULL;
static rmt_sync_manager_handle_t s_sync_mgr = NULL;
/*------------------------- Вспомогательная функция для вычисления параметров ----------*/
/**
* @brief Рассчитать временные параметры на основе желаемого интервала выборки
* @param sampling_interval_us желаемый период (мкс), или -1 для значений по умолчанию
* @param out_tick указатель для сохранения базового тика (ADC_TICK)
* @param out_busy указатель для сохранения длительности BUSY в тиках
* @return ESP_OK или ESP_ERR_INVALID_ARG, если интервал слишком мал
*/
static esp_err_t calculate_timing_params(int sampling_interval_us, int *out_tick, int *out_busy)
{
if (sampling_interval_us == -1) {
*out_tick = ADC_TICK_DEFAULT;
*out_busy = ADC_BUSY_DEFAULT_TICKS;
ESP_LOGD(TAG, "Using default timing: tick=%d, busy=%d ticks", *out_tick, *out_busy);
return ESP_OK;
}
if (sampling_interval_us <= 0) {
ESP_LOGE(TAG, "Invalid sampling interval: %d us (must be >0 or -1)", sampling_interval_us);
return ESP_ERR_INVALID_ARG;
}
// Период в тиках: 1 мкс = 80 тиков (период RMT 12.5 нс)
int period_ticks = (int)(sampling_interval_us * 80.0f);
if (period_ticks < (ADC_BUSY_MIN_TICKS + 9 * ADC_TICK_MIN)) {
ESP_LOGE(TAG, "Sampling interval %d us too small, minimum %.0f us",
sampling_interval_us,
ceilf((ADC_BUSY_MIN_TICKS + 9 * ADC_TICK_MIN) / 80.0f));
return ESP_ERR_INVALID_ARG;
}
// Ищем подходящий tick: предполагаем, что tick = ADC_TICK_DEFAULT, а busy подстраивается
int tick = ADC_TICK_DEFAULT;
int busy = period_ticks - 9 * tick;
if (busy < ADC_BUSY_MIN_TICKS) {
// Если busy получается слишком маленьким, уменьшаем tick (но не ниже минимума)
tick = (period_ticks - ADC_BUSY_MIN_TICKS) / 9;
if (tick < ADC_TICK_MIN) {
tick = ADC_TICK_MIN;
}
busy = period_ticks - 9 * tick;
if (busy < ADC_BUSY_MIN_TICKS) {
// Всё равно не хватает – интервал слишком мал
ESP_LOGE(TAG, "Cannot achieve requested interval %d us with given constraints", sampling_interval_us);
return ESP_ERR_INVALID_ARG;
}
}
*out_tick = tick;
*out_busy = busy;
ESP_LOGI(TAG, "Computed timing: interval=%d us -> adc_clk=%.4f us, adc_busy=%.4f us (actual period %.2f us)",
sampling_interval_us, (tick*2)/ 80.0f, busy/ 80.0f, (busy + 9*tick) / 80.0f);
return ESP_OK;
}
/*------------------------- Реализация основных функций -------------------------------*/
esp_err_t adc_conv_cs_clk_init_start(int adc_conv_gpio, int adc_cs_gpio, int adc_clk_gpio, int sampling_interval_us)
{
esp_err_t ret = ESP_OK;
int adc_tick, adc_busy_ticks;
// ----- Защита от повторной инициализации -----
if (s_tx_chan_conv != NULL || s_tx_chan_cs != NULL || s_tx_chan_clk != NULL) {
ESP_LOGE(TAG, "ADC RMT channels already initialized. Call adc_conv_cs_clk_deinit() first.");
return ESP_ERR_INVALID_STATE;
}
// ----- Проверка корректности номеров GPIO -----
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_GPIO(adc_conv_gpio), ESP_ERR_INVALID_ARG, TAG,
"Invalid ADC_CONV GPIO: %d", adc_conv_gpio);
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_GPIO(adc_cs_gpio), ESP_ERR_INVALID_ARG, TAG,
"Invalid ADC_CS GPIO: %d", adc_cs_gpio);
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_GPIO(adc_clk_gpio), ESP_ERR_INVALID_ARG, TAG,
"Invalid ADC_CLK GPIO: %d", adc_clk_gpio);
// ----- Расчёт временных параметров -----
ret = calculate_timing_params(sampling_interval_us, &adc_tick, &adc_busy_ticks);
if (ret != ESP_OK) {
return ret;
}
// ----- Вычисление производных длительностей (в тиках) -----
int adc_clk_l = adc_tick; // длительность низкого уровня CLK
int adc_clk_h = adc_tick; // длительность высокого уровня CLK
int adc_conv_l = adc_tick; // длительность импульса CONV
int adc_cs_l = 4 * (adc_clk_l + adc_clk_h); // 4 * (2*tick) = 8*tick
int adc_cs_h = adc_conv_l + adc_busy_ticks; // пауза между циклами
// ----- Формирование таблиц символов RMT (динамически, на стеке) -----
rmt_symbol_word_t adc_conv_syms[2];
rmt_symbol_word_t adc_cs_syms[2];
rmt_symbol_word_t adc_clk_syms[5];
// Сигнал CONV
adc_conv_syms[0] = (rmt_symbol_word_t){
.duration0 = adc_cs_l, .level0 = HIGHT_LVL,
.duration1 = adc_conv_l, .level1 = LOW_LVL
};
adc_conv_syms[1] = (rmt_symbol_word_t){
.duration0 = adc_busy_ticks, .level0 = HIGHT_LVL,
.duration1 = 0, .level1 = HIGHT_LVL
};
// Сигнал CS
adc_cs_syms[0] = (rmt_symbol_word_t){
.duration0 = adc_cs_l, .level0 = LOW_LVL,
.duration1 = adc_cs_h, .level1 = HIGHT_LVL
};
adc_cs_syms[1] = (rmt_symbol_word_t){
.duration0 = 0, .level0 = HIGHT_LVL,
.duration1 = 0, .level1 = HIGHT_LVL
};
// Сигнал CLK: 4 тактовых импульса + пауза
for (int i = 0; i < 4; i++) {
adc_clk_syms[i] = (rmt_symbol_word_t){
.duration0 = adc_clk_l, .level0 = LOW_LVL,
.duration1 = adc_clk_h, .level1 = HIGHT_LVL
};
}
adc_clk_syms[4] = (rmt_symbol_word_t){
.duration0 = adc_cs_h, .level0 = HIGHT_LVL,
.duration1 = 0, .level1 = HIGHT_LVL
};
// ----- Конфигурация и запуск RMT (как и ранее, но с локальными массивами) -----
rmt_copy_encoder_config_t copy_enc_cfg = {};
rmt_tx_channel_config_t tx_chan_cfg = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.mem_block_symbols = TX_BLOCK_SYMBOL,
.flags = {
.invert_out = true,
.io_loop_back = true // esp_idf v5.5.1
},
.resolution_hz = RMT_TX_CLK_OUT,
.trans_queue_depth = 1,
};
// Канал для CONV
tx_chan_cfg.gpio_num = adc_conv_gpio;
ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&tx_chan_cfg, &s_tx_chan_conv),
cleanup, TAG, "rmt_new_tx_channel(CONV)");
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_enc_cfg, &s_tx_enc_conv),
cleanup, TAG, "rmt_new_copy_encoder(CONV)");
ESP_GOTO_ON_ERROR(rmt_enable(s_tx_chan_conv), cleanup, TAG, "rmt_enable(CONV)");
// Канал для CS
tx_chan_cfg.gpio_num = adc_cs_gpio;
ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&tx_chan_cfg, &s_tx_chan_cs),
cleanup, TAG, "rmt_new_tx_channel(CS)");
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_enc_cfg, &s_tx_enc_cs),
cleanup, TAG, "rmt_new_copy_encoder(CS)");
ESP_GOTO_ON_ERROR(rmt_enable(s_tx_chan_cs), cleanup, TAG, "rmt_enable(CS)");
// Канал для CLK
tx_chan_cfg.gpio_num = adc_clk_gpio;
ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&tx_chan_cfg, &s_tx_chan_clk),
cleanup, TAG, "rmt_new_tx_channel(CLK)");
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_enc_cfg, &s_tx_enc_clk),
cleanup, TAG, "rmt_new_copy_encoder(CLK)");
ESP_GOTO_ON_ERROR(rmt_enable(s_tx_chan_clk), cleanup, TAG, "rmt_enable(CLK)");
// Менеджер синхронизации
rmt_channel_handle_t tx_channels[] = {s_tx_chan_clk, s_tx_chan_conv, s_tx_chan_cs};
rmt_sync_manager_config_t sync_cfg = {
.tx_channel_array = tx_channels,
.array_size = sizeof(tx_channels) / sizeof(rmt_channel_handle_t),
};
ESP_GOTO_ON_ERROR(rmt_new_sync_manager(&sync_cfg, &s_sync_mgr),
cleanup, TAG, "rmt_new_sync_manager");
// Запуск циклической передачи
rmt_transmit_config_t transmit_cfg = {
.loop_count = -1,
.flags.eot_level = 0,
.flags.queue_nonblocking = 0,
};
ESP_GOTO_ON_ERROR(rmt_transmit(s_tx_chan_clk, s_tx_enc_clk, adc_clk_syms, sizeof(adc_clk_syms), &transmit_cfg),
cleanup, TAG, "rmt_transmit(CLK)");
ESP_GOTO_ON_ERROR(rmt_transmit(s_tx_chan_conv, s_tx_enc_conv, adc_conv_syms, sizeof(adc_conv_syms), &transmit_cfg),
cleanup, TAG, "rmt_transmit(CONV)");
ESP_GOTO_ON_ERROR(rmt_transmit(s_tx_chan_cs, s_tx_enc_cs, adc_cs_syms, sizeof(adc_cs_syms), &transmit_cfg),
cleanup, TAG, "rmt_transmit(CS)");
ESP_LOGI(TAG, "ADC RMT started: CONV=%d, CS=%d, CLK=%d, interval=%d us",
adc_conv_gpio, adc_cs_gpio, adc_clk_gpio,
sampling_interval_us == -1 ? 6 : sampling_interval_us);
return ESP_OK;
cleanup:
ESP_LOGE(TAG, "Initialization failed, cleaning up");
adc_conv_cs_clk_deinit();
return ret;
}
void adc_conv_cs_clk_deinit(void)
{
if (s_tx_chan_conv) {
rmt_disable(s_tx_chan_conv);
rmt_del_channel(s_tx_chan_conv);
s_tx_chan_conv = NULL;
}
if (s_tx_chan_cs) {
rmt_disable(s_tx_chan_cs);
rmt_del_channel(s_tx_chan_cs);
s_tx_chan_cs = NULL;
}
if (s_tx_chan_clk) {
rmt_disable(s_tx_chan_clk);
rmt_del_channel(s_tx_chan_clk);
s_tx_chan_clk = NULL;
}
if (s_tx_enc_conv) {
rmt_del_encoder(s_tx_enc_conv);
s_tx_enc_conv = NULL;
}
if (s_tx_enc_cs) {
rmt_del_encoder(s_tx_enc_cs);
s_tx_enc_cs = NULL;
}
if (s_tx_enc_clk) {
rmt_del_encoder(s_tx_enc_clk);
s_tx_enc_clk = NULL;
}
if (s_sync_mgr) {
rmt_del_sync_manager(s_sync_mgr);
s_sync_mgr = NULL;
}
ESP_LOGI(TAG, "ADC RMT channels deinitialized");
}
Who is online
Users browsing this forum: No registered users and 2 guests