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).
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");
}