/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdio.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "adc_continuous.h"
#define EXAMPLE_ADC_UNIT ADC_UNIT_1
// Макросы для преобразования значения unit в строку
#define _EXAMPLE_ADC_UNIT_STR(unit) #unit
#define EXAMPLE_ADC_UNIT_STR(unit) _EXAMPLE_ADC_UNIT_STR(unit)
// Макросы, определяющие
#define EXAMPLE_ADC_CONV_MODE ADC_CONV_SINGLE_UNIT_1 // режим преобразования (один АЦП)
#define EXAMPLE_ADC_ATTEN ADC_ATTEN_DB_12 // аттенюацию (12 дБ)
#define EXAMPLE_ADC_BIT_WIDTH SOC_ADC_DIGI_MAX_BITWIDTH // ширину бит (максимально возможную для данного АЦП)
// Макрос, который устанавливает
#define CONFIG_LOG_DEFAULT_LEVEL_VERBOSE 1
// уровень логирования по умолчанию на "подробный" (verbose)
// Макрос, который устанавливает
#define CONFIG_LOG_MAXIMUM_LEVEL 5
// максимальный уровень логирования на 5, что соответствует уровню "отладка" (debug)
// определяют тип выходных данных АЦП и функции для получения канала и данных
// в зависимости от целевой платформы (ESP32 или ESP32S2)
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
#define EXAMPLE_ADC_OUTPUT_TYPE ADC_DIGI_OUTPUT_FORMAT_TYPE1
#define EXAMPLE_ADC_GET_CHANNEL(p_data) ((p_data)->type1.channel)
#define EXAMPLE_ADC_GET_DATA(p_data) ((p_data)->type1.data)
#else
#define EXAMPLE_ADC_OUTPUT_TYPE ADC_DIGI_OUTPUT_FORMAT_TYPE2
#define EXAMPLE_ADC_GET_CHANNEL(p_data) ((p_data)->type2.channel)
#define EXAMPLE_ADC_GET_DATA(p_data) ((p_data)->type2.data)
#endif
// Размер буфера для чтения данных с АЦП (256 байт)
#define EXAMPLE_READ_LEN 100
// В зависимости от целевой платформы определяет массив из двух каналов АЦП,
// которые будут использоваться в программе
#if CONFIG_IDF_TARGET_ESP32
static adc_channel_t channel[2] = {ADC_CHANNEL_6, ADC_CHANNEL_7};
#else
static adc_channel_t channel[2] = {ADC_CHANNEL_2, ADC_CHANNEL_3};
#endif
static TaskHandle_t s_task_handle; // Переменная для хранения дескриптора задачи
static const char *TAG = "EXAMPLE"; // Строка для логирования
// Функция обратного вызова, которая будет вызвана при завершении преобразования АЦП
// Она помечена как IRAM_ATTR, чтобы гарантировать, что она будет размещена в быстрой памяти
static bool IRAM_ATTR s_conv_done_cb(adc_continuous_handle_t handle,
const adc_continuous_evt_data_t *edata, void *user_data)
{
ESP_LOGD(TAG, "s_conv_done_cb");
// Переменная для определения, нужно ли переключать контекст задачи после вызова этой функции
BaseType_t mustYield = pdFALSE;
//Notify that ADC continuous driver has done enough number of conversions
// Функция, которая уведомляет задачу (указанную в s_task_handle) о том,
// что АЦП завершил достаточное количество преобразований
// Флаг mustYield передается по ссылке, чтобы указать, нужно ли переключить контекст задачи
// Если mustYield установлен в pdTRUE, это означает, что задача с более высоким приоритетом
// готова к выполнению, и необходимо выполнить переключение контекста
vTaskNotifyGiveFromISR(s_task_handle, &mustYield);
ESP_LOGD(TAG, "s_conv_done_cb");
return (mustYield == pdTRUE);
}
// Функции для инициализации непрерывного режима АЦП
// Она принимает массив каналов, количество каналов и указатель на дескриптор АЦП
static void continuous_adc_init(adc_channel_t *channel, uint8_t channel_num, adc_continuous_handle_t *out_handle)
{
printf("---7---\n");
// Переменная handle для хранения дескриптора непрерывного режима АЦП
adc_continuous_handle_t handle = NULL;
printf("---8---\n");
// Структура конфигурации для непрерывного режима АЦП,
// устанавливая максимальный размер буфера хранения и размер кадра преобразования
adc_continuous_handle_cfg_t adc_config = {
.max_store_buf_size = 1024,
.conv_frame_size = EXAMPLE_READ_LEN,
};
printf("---9---\n");
// Создает новый дескриптор для непрерывного режима АЦП с заданной конфигурацией
// Если происходит ошибка, она обрабатывается с помощью макроса ESP_ERROR_CHECK
ESP_ERROR_CHECK(adc_continuous_new_handle(&adc_config, &handle));
printf("---10---\n");
// Создает структуру конфигурации для непрерывного режима АЦП, устанавливая
adc_continuous_config_t dig_cfg = {
.sample_freq_hz = 20 * 1000, // частоту выборки (20 кГц),
.conv_mode = EXAMPLE_ADC_CONV_MODE, // режим преобразования,
.format = EXAMPLE_ADC_OUTPUT_TYPE, // и формат выходных данных
};
// Создает массив конфигураций для каналов АЦП, инициализируя его нулями
adc_digi_pattern_config_t adc_pattern[SOC_ADC_PATT_LEN_MAX] = {0};
// Устанавливает количество каналов в конфигурации
dig_cfg.pattern_num = channel_num;
printf("---11---\n");
// Цикл, который проходит по всем каналам из channel_num
for (int i = 0; i < channel_num; i++) {
// Аттенюация (параметр, который определяет, насколько сигнал будет ослаблен)
// для текущего канала
adc_pattern[i].atten = EXAMPLE_ADC_ATTEN;
// Номер канала для текущего элемента массива adc_pattern
// Используется побитовая операция AND с 0x7, чтобы гарантировать,
// что номер канала находится в допустимом диапазоне (0-7)
adc_pattern[i].channel = channel[i] & 0x7;
// Номер АЦП для текущего элемента массива
adc_pattern[i].unit = EXAMPLE_ADC_UNIT;
// Ширина бит для текущего элемента массива
adc_pattern[i].bit_width = EXAMPLE_ADC_BIT_WIDTH;
// Логирует значение аттенюации для текущего канала в формате hex (шестнадцатеричный)
// TAG используется для идентификации источника сообщения в логах
ESP_LOGI(TAG, "adc_pattern[%d].atten is :%"PRIx8, i, adc_pattern[i].atten);
// Логирует номер канала для текущего элемента массива
ESP_LOGI(TAG, "adc_pattern[%d].channel is :%"PRIx8, i, adc_pattern[i].channel);
// Логирует номер АЦП для текущего элемента массива
ESP_LOGI(TAG, "adc_pattern[%d].unit is :%"PRIx8, i, adc_pattern[i].unit);
}
// Устанавливает массив конфигураций каналов
dig_cfg.adc_pattern = adc_pattern;
// Применяет конфигурацию непрерывного режима АЦП с помощью функции adc_continuous_config
// Если происходит ошибка, она обрабатывается с помощью макроса ESP_ERROR_CHECK
ESP_ERROR_CHECK(adc_continuous_config(handle, &dig_cfg));
// Передает созданный дескриптор handle обратно в вызывающую функцию через указатель out_handle
// Это позволяет другим частям кода использовать этот дескриптор для работы с АЦП
*out_handle = handle;
}
void app_main(void)
{
printf("---1---\n");
esp_log_level_set(TAG, ESP_LOG_VERBOSE);
ESP_LOGD(TAG, "---DEBUG1---");
printf("---2---\n");
// Переменная ret для хранения кода ошибки, возвращаемого функциями ESP-IDF
esp_err_t ret;
printf("---3---\n");
// Переменная ret_num для хранения количества байт, прочитанных из АЦП
uint32_t ret_num = 0;
// Массив result размером EXAMPLE_READ_LEN (100 байт) для хранения данных, считанных с АЦП
uint8_t result[EXAMPLE_READ_LEN] = {0};
// Заполняет массив result значением 0xcc (в шестнадцатеричном формате)
memset(result, 0xcc, EXAMPLE_READ_LEN);
printf("---4---\n");
ESP_LOGD(TAG, "---DEBUG2---");
// Получает дескриптор текущей задачи
s_task_handle = xTaskGetCurrentTaskHandle();
printf("---5---\n");
// Переменная handle для хранения дескриптора непрерывного режима АЦП
adc_continuous_handle_t handle = NULL;
printf("---6---\n");
// Вызывает функцию continuous_adc_init, передавая массив каналов,
// количество каналов (вычисляется как размер массива channel,
// делённый на размер одного элемента) и указатель на handle,
// чтобы инициализировать дескриптор АЦП
continuous_adc_init(channel, sizeof(channel) / sizeof(adc_channel_t), &handle);
ESP_LOGD(TAG, "---DEBUG3---");
printf("---7---\n");
// Создает структуру cbs, которая содержит указатели
// на функции обратного вызова для обработки событий
adc_continuous_evt_cbs_t cbs = {
.on_conv_done = s_conv_done_cb,
};
printf("---8---\n");
ESP_LOGD(TAG, "---DEBUG4---");
// Регистрирует функции обратного вызова для обработчика событий АЦП
// Если происходит ошибка, она обрабатывается с помощью макроса ESP_ERROR_CHECK
ESP_ERROR_CHECK(adc_continuous_register_event_callbacks(handle, &cbs, NULL));
printf("---11---\n");
// Запускает непрерывный режим АЦП
// Если происходит ошибка, она обрабатывается с помощью макроса ESP_ERROR_CHECK
printf("---12---\n");
ESP_ERROR_CHECK(adc_continuous_start(handle));
ESP_LOGD(TAG, "---DEBUG5---");
while (1) {
/**
* This is to show you the way to use the ADC continuous mode driver event callback.
* This `ulTaskNotifyTake` will block when the data processing in the task is fast.
* However in this example, the data processing (print) is slow, so you barely block here.
*
* Without using this event callback (to notify this task), you can still just call
* `adc_continuous_read()` here in a loop, with/without a certain block timeout.
*/
ESP_LOGD(TAG, "---DEBUG6---");
// Блокирует выполнение текущей задачи до тех пор, пока не будет получено уведомление
// Параметр pdTRUE указывает на то, что необходимо сбросить уведомление,
// а portMAX_DELAY указывает, что задача может блокироваться на неопределённое время
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// Создает массив символов unit, который инициализируется строковым представлением
// значения EXAMPLE_ADC_UNIT
// Макрос EXAMPLE_ADC_UNIT_STR преобразует значение EXAMPLE_ADC_UNIT
// (в данном случае ADC_UNIT_1) в строку, что позволяет использовать его для логирования
char unit[] = EXAMPLE_ADC_UNIT_STR(EXAMPLE_ADC_UNIT);
ESP_LOGD(TAG, "---DEBUG7---");
while (1) {
// Функция adc_continuous_read, чтобы прочитать данные из АЦП
ret = adc_continuous_read(
handle, // дескриптор непрерывного режима АЦП
result, // массив, в который будут записаны считанные данные
EXAMPLE_READ_LEN, // количество байт для чтения
&ret_num, // указатель на переменную, в которую будет записано количество фактически считанных байт
0); // указывает, что функция не должна блокироваться, если данные недоступны
// Поверка, успешно ли выполнено чтение данных из АЦП
if (ret == ESP_OK) {
ESP_LOGD(TAG, "---DEBUG8---");
// Логирует информацию о результате чтения
// Выводит код результата ret в шестнадцатеричном формате и количество прочитанных байт ret_num
ESP_LOGI("TASK", "ret is %x, ret_num is %"PRIu32" bytes", ret, ret_num);
// Цикл для обработки считанных данных
for (int i = 0; i < ret_num; i += SOC_ADC_DIGI_RESULT_BYTES) {
ESP_LOGD(TAG, "---DEBUG9---");
// Приводит указатель на текущий элемент массива result к типу adc_digi_output_data_t,
// чтобы получить доступ к данным АЦП
adc_digi_output_data_t *p = (adc_digi_output_data_t*)&result[i];
// Использует макрос EXAMPLE_ADC_GET_CHANNEL,
// чтобы получить номер канала из структуры данных АЦП
uint32_t chan_num = EXAMPLE_ADC_GET_CHANNEL(p);
// Использует макрос EXAMPLE_ADC_GET_DATA,
// чтобы получить значение данных из структуры данных АЦП
uint32_t data = EXAMPLE_ADC_GET_DATA(p);
/* Check the channel number validation, the data is invalid if the channel num exceed the maximum channel */
// Проверяет, является ли номер канала допустимым,
// сравнивая его с максимальным количеством каналов для данного АЦП
if (chan_num < SOC_ADC_CHANNEL_NUM(EXAMPLE_ADC_UNIT)) {
ESP_LOGI(TAG, "Unit: %s, Channel: %"PRIu32", Value: %"PRIx32, unit, chan_num, data);
} else { // Если номер канала (chan_num) недопустим
ESP_LOGW(TAG, "Invalid data [%s_%"PRIu32"_%"PRIx32"]", unit, chan_num, data);
}
}
/**
* Because printing is slow, so every time you call `ulTaskNotifyTake`, it will immediately return.
* To avoid a task watchdog timeout, add a delay here. When you replace the way you process the data,
* usually you don't need this delay (as this task will block for a while).
*/
// Задержка в 1 миллисекунду
// Это позволяет избежать таймаута наблюдателя задач, давая время другим задачам выполняться
vTaskDelay(1);
ESP_LOGD(TAG, "---DEBUG10---");
// Проверяет, произошла ли ошибка таймаута при чтении данных из АЦП
// Если ret равно ESP_ERR_TIMEOUT, это означает, что данные недоступны
} else if (ret == ESP_ERR_TIMEOUT) {
//We try to read `EXAMPLE_READ_LEN` until API returns timeout, which means there's no available data
break;
}
}
}
// Останавливает непрерывный режим АЦП
// Если происходит ошибка, она обрабатывается с помощью макроса ESP_ERROR_CHECK
ESP_ERROR_CHECK(adc_continuous_stop(handle));
ESP_LOGD(TAG, "---DEBUG11---");
// Деинициализирует дескриптор непрерывного режима АЦП
// Также проверяет на наличие ошибок с помощью ESP_ERROR_CHECK
ESP_ERROR_CHECK(adc_continuous_deinit(handle));
ESP_LOGD(TAG, "---DEBUG12---");
}