Программирование ARM ESP32-C3: драйвер АЦП непрерывного режима преобразования Tue, May 06 2025  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


ESP32-C3: драйвер АЦП непрерывного режима преобразования Печать
Добавил(а) microsin   

Аналого-цифровой преобразователь, встроенный в чип ESP32-C3, с помощью АЦП может измерять аналоговые сигналы, поступающие на определенные выводы корпуса [2]. Дополнительно имеется функционал прямого доступа к памяти (Direct Memory Access, DMA), позволяющий эффективно получать с АЦП результаты преобразования.

У ESP32-C3 есть 2 блока ADC (АЦП), который может использоваться в следующих сценариях:

• Генерация однократного (one-shot) результата преобразования ADC.
• Генерация непрерывных результатов преобразования ADC.

[Концепция DMA-драйвера ADC]

ADC в непрерывном режиме преобразования составляется из нескольких кадров преобразования (Conversion Frame).

Conversion Frame: один кадр преобразования содержит несколько результатов преобразования (Conversion Result). Размер кадра преобразования конфигурируется в байтах вызовом функции adc_continuous_new_handle().
Conversion Result: один результат преобразования содержит несколько байтов, см. SOC_ADC_DIGI_RESULT_BYTES. Его структура adc_digi_output_data_t, включающая блок АЦП (ADC unit), канал АЦП (ADC channel), и сырые данные преобразования.

ESP32 C3 ADC conversion frame

Достоинство этого режима драйвера АЦП в том, что он позволяет аппаратно оцифровывать данные сразу с нескольких каналов, и это не занимает процессорное время ядра.

[Обзор функционала]

В следующих секциях этого документа (перевод [1]) описываются типовые шаги инсталляции драйвера непрерывного режима АЦП (ADC continuous mode), и непрерывное чтение результатов преобразования ADC из группы каналов:

• Resource Allocation (выделение ресурсов): раскрывается, какие параметры должны быть установлены для инициализации ADC для драйвера непрерывного режима, и как отменять его инициализацию.
• Конфигурации ADC: описывается, как конфигурировать один или несколько ADC для работы в непрерывном режиме.
• Управление ADC: описываются функции управления ADC.
• Register Event Callbacks (регистрация функций обратного вызова для обработки событий): описывается, как привязать код пользователя к функции события ADC в режиме непрерывного преобразования.
• Read Conversion Result (чтение результата преобразования): раскрывается, как получить результат оцифровки ADC.
• Аппаратные ограничения: описываются ограничения, связанные с особенностями использования ADC.
• Power Management: информация, относящаяся к управлению питанием.
• IRAM Safe: описываются функции, безопасные для использования IRAM.
• Thread Safety: перечисляются API-функции, которые гарантируют безопасное использование драйвера в контексте многопоточности (thread-safe).

[Resource Allocation]

Драйвер непрерывного режима реализован на основе модуля ESP32-C3 SAR ADC. Различные модели ESP могут иметь разное количество независимых блоков ADC (у ESP32-C3 их два, см. [2]).

Для создания дескриптора драйвера непрерывного режима (ADC continuous mode driver handle) настройте требуемую структуру конфигурации adc_continuous_handle_cfg_t:

adc_continuous_handle_cfg_t::max_store_buf_size: установите максимальный размер пула в байтах, и драйвер сохранит результат преобразования ADC в этот пул. Если этот пул заполнен, то новый результат преобразования будет потерян.
adc_continuous_handle_cfg_t::conv_frame_size: установите размер кадра преобразования ADC, в байтах.
adc_continuous_handle_cfg_t::flags: установка флагов может поменять поведение драйвера.
   - flush_pool: автоматический слив пула, когда он полон.

После того, как были сделаны эти настройки конфигурации для ADC, вызовите  adc_continuous_new_handle() с подготовленной adc_continuous_handle_cfg_t. Эта функция может потерпеть неудачу из-за различных ошибок, таких как недопустимые аргументы, недостаточно памяти, и т. д.

В особенности, когда функция возвратит ESP_ERR_NOT_FOUND, то это значит, что нет свободного канала GDMA.

Если драйвер непрерывного режима ADC больше не нужен, то вы должны отменить его инициализацию вызовом adc_continuous_deinit().

IIR-фильтр. Когда ADC работает в непрерывном режиме, доступны фильтры с бесконечной импульсной характеристикой, или БИХ (Infinite Impulse Response, IIR). Чтобы создать фильтр ADC IIR, вы должны настроить adc_continuous_iir_filter_config_t и вызвать adc_new_continuous_iir_filter().

• adc_digi_filter_config_t::unit: блок ADC.
• adc_digi_filter_config_t::channel: канал ADC, который будет фильтроваться.
• adc_digi_filter_config_t::coeff: коэффициент фильтра.

Чтобы освободить ресурсы фильтра, когда он не нужен, вы должны вызвать adc_del_continuous_iir_filter().

Примечание: если вы используете два фильтра на одном и том же канале ADC, то будет действовать только первый из них.

Монитор. Кода ADC работает в непрерывном режиме, доступны 2 монитора. Вы можете установить один или два порога монитора на работающем канале ADC, тогда монитор будет вызывать прерывания на каждом цикле выборки, если результат преобразования выйдет за пределы порога. Чтобы создать монитор ADC, вам нужно настроить adc_monitor_config_t и вызвать adc_new_continuous_monitor().

• adc_monitor_config_t::adc_unit: конфигурирует блок ADC канала, к которому вы хотите подключить монитор.
• adc_monitor_config_t::channel: канал, который вы хотите отслеживать монитором.
• adc_monitor_config_t::h_threshold: верхний порог, результат преобразования выше которого будет вызывать прерывание. Если этот порог не используется, установите здесь -1.
• adc_monitor_config_t::l_threshold: нижний порог, результат преобразования ниже которого будет вызывать прерывание. Если этот порог не используется, установите здесь -1.

Как только монитор был создан, вы можете с ним работать в своем приложении через следующие API-функции.

adc_continuous_monitor_enable(): разрешение монитора.
adc_continuous_monitor_disable(): запрет монитора.
adc_monitor_register_callbacks(): регистрация пользовательских функций обратного вызова, в которых предпринимаются нужные действия, когда значение из ADC выйдет за установленные пороги.
adc_del_continuous_monitor(): удалит созданный монитор и освободит связанные с ним ресурсы.

Пример инициализации ADC Continuous Mode Driver показан ниже. Макрос ESP_ERROR_CHECK служит для автоматического перехвата ситуации ошибки с выводом соответствующего сообщения.

adc_continuous_handle_t handle = NULL;
adc_continuous_handle_cfg_t adc_config = { .max_store_buf_size = 1024, .conv_frame_size = 256, }; ESP_ERROR_CHECK(adc_continuous_new_handle(&adc_config, &handle));

Отмена инициализации драйвера, освобождение блока ADC:

ESP_ERROR_CHECK(adc_continuous_deinit(handle));

[Конфигурации ADC]

После того, как драйвер непрерывного режима инициализирован, настройте adc_continuous_config_t для конфигурирования измерения аналогового сигнала ADC IO:

• adc_continuous_config_t::pattern_num: количесво каналов ADC, которые будут использоваться.
• adc_continuous_config_t::adc_pattern: список конфигураций для каждого канала ADC, которые будут использоваться, см. описание дальше.
• adc_continuous_config_t::sample_freq_hz: ожидаемая частота выборок ADC в Гц.
• adc_continuous_config_t::conv_mode: непрерывный режим преобразования (continuous conversion mode).
• adc_continuous_config_t::format: выходной формат преобразования.

Установите adc_digi_pattern_config_t:

• adc_digi_pattern_config_t::atten: ослабление сигнала (ADC attenuation). См. тему "On-Chip Sensor and Analog Signal Processing" в техническом руководстве [3].
• adc_digi_pattern_config_t::channel: номер канала IO соответствующего ADC. См. пояснения далее.
• adc_digi_pattern_config_t::unit: блок ADC, которому принадлежит IO.
• adc_digi_pattern_config_t::bit_width: разрядность сырого результата преобразования.

Замечание: для того, чтобы узнать выводы корпуса для соответствующего номера канала, обратитесь к техническому руководству для вашего используемого микроконтроллера. Кроме того, вы можете использовать функции adc_continuous_io_to_channel() и adc_continuous_channel_to_io() для получения соответствия между выводами IO и каналами ADC.

Чтобы эти настройки вступили в силу, вызовите adc_continuous_config() с упомянутой выше заполненной конфигурационной структурой adc_digi_pattern_config_t. Этот вызов API может закончиться неудачей, например из-за неправильных предоставленных параметров, тогда функция возвратит ESP_ERR_INVALID_ARG. Если функция возвратила ESP_ERR_INVALID_STATE, то это значит, что драйвер непрерывного режима ADC уже запущен, и вы сейчас не должны вызывать эту API-функцию.

Для разрешения или запрета фильтра ADC IIR вы должны вызвать adc_continuous_iir_filter_enable() или adc_continuous_iir_filter_disable() соответственно. Для разрешения или запрета монитора ADC вызывайте adc_continuous_monitor_enable() или adc_continuous_monitor_disable().

[Управление ADC]

Запуск и остановка. Вызовите adc_continuous_start() для запуска оцифровки ADC на сконфигурированных каналах и генерации результатов преобразования.

adc_continuous_handle_t handle = NULL;
continuous_adc_init(channel, sizeof(channel) / sizeof(adc_channel_t), &handle);

adc_continuous_evt_cbs_t cbs = { .on_conv_done = s_conv_done_cb, }; ESP_ERROR_CHECK(adc_continuous_register_event_callbacks(handle, &cbs, NULL)); ESP_ERROR_CHECK(adc_continuous_start(handle));

И наоборот, для остановки преобразований вызовите adc_continuous_stop().

ESP_ERROR_CHECK(adc_continuous_stop(handle));

Регистрация обработчиков событий (Event Callbacks). Вызовом  adc_continuous_register_event_callbacks() вы можете присоединить вашу собственную функцию обработчика к ISR (Interrupt Service Routine) драйвера. Поддерживаемые события для функции обратного вызова (event callback) перечислены adc_continuous_evt_cbs_t.

• adc_continuous_evt_cbs_t::on_conv_done: это событие генерируется, когда завершается один кадр преобразования.
• adc_continuous_evt_cbs_t::on_pool_ovf: генерируется, когда внутренний пул заполнен. Более свежие преобразования будут отбрасываться.

Показанные выше callback-функции будут вызываться в контексте обработчика прерывания (ISR), поэтому ваша функция должна быть написана таким образом, чтобы она соблюдала правила контекста ISR (т. е. она не должна быть слишком сложной, и не должна влиять на планировщик RTOS). В этих callback-ах не должно быть никакой блокирующей выполнение логики. Прототип callback-функции декларируется в adc_continuous_callback_t.

Вы также можете зарегистрировать свой собственный контекст, когда вызываете adc_continuous_register_event_callbacks(), с помощью параметра user_data. Через этот указатель пользовательские данные могут быть напрямую переданы в callback-функцию.

Функция adc_continuous_register_event_callbacks() может потерпеть неудачу с выдачей кода ошибки, такого как ESP_ERR_INVALID_ARG. В частности, когда разрешена опция CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE эта ошибка может означать, что callback-функции не находятся во внутреннем ОЗУ микроконтроллера (память IRAM). Для получения подробной информации просмотрите лог ошибок (idf.py monitor). Кроме того, функция может потерпеть неудачу с ошибкой ESP_ERR_INVALID_STATE, которая показывает, что драйвер непрерывного режима ADC уже запущен, и подобный вызов в такой ситуации недопустим.

Conversion Done Event. Когда драйвер завершил преобразование, он сгенерирует событие adc_continuous_evt_cbs_t::on_conv_done и заполнит данные события (event data). Данные события содержат указатель на буфер, ссылающийся на буфер кадра преобразования, вместе с его размером (см. описание структуры adc_continuous_evt_data_t).

Следует отметить, что буфер данных adc_continuous_evt_data_t::conv_frame_buffer выделяется и обслуживается самим драйвером. Поэтому никогда не освобождайте эту часть памяти.

Замечание: когда разрешена Kconfig-опция (настраивается idf.py menuconfig) CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE, регистрируемые callback-и и функции, которые из них вызываются, все должны быть размещены в IRAM. Также должны быть размещены в IRAM и используемые ими переменные (память FLASH не должна использоваться).

Pool Overflow Event. Драйвер непрерывного режима ADC имеет внутренний пул для сохранения результатов преобразования. Когда этот пул заполнен, об этой (возможно аварийной для приложения) ситуации сообщит событие переполнения (pool overflow event). В такой ситуации драйвер не будет заполнять данные события (event data). Это обычно происходит, когда частота чтения данных из пула вызовами adc_continuous_read() меньше, чем скорость преобразований ADC.

[Чтение результатов преобразования]

После вызова adc_continuous_start() запустятся непрерывные преобразования ADC. Вызовите adc_continuous_read() для получения результатов преобразования каналов ADC. Вам необходимо предоставить буфер для получения сырых результатов преобразования.

Функция adc_continuous_read() каждый раз при вызове пытается прочитать ожидаемую длину преобразования.

• Когда вызываете adc_continuous_read(), вы можете запросить прочитать результат преобразования указанной длины. Однако иногда могут быть фактически доступны результаты с длиной меньше, чем было запрошено, в этом случае функция все еще перенесет данные из внутреннего пула в предоставленный вами буфер. Таким образом, чтобы узнать, сколько результатов преобразования на самом деле было перемещено в ваш буфер, проверьте значение out_length.
• Если во внутреннем пуле не было сгенерировано результатов преобразования, то функция заблокирует выполнение на время timeout_ms до момента, когда результаты преобразования будут сгенерированы. Если это время истекло, и результатов все еще не было, то функция возвратит ESP_ERR_TIMEOUT.
• Если генерируемые результаты заполнят внутренний пул, то более новые сгенерированные результаты будут отбрасываться. В следующий раз, когда произойдет вызов adc_continuous_read(), она возвратит ESP_ERR_INVALID_STATE для индикации такой ситуации.

API-функция adc_continuous_read() призвана дать вам возможность прочитать все результаты непрерывных преобразований ADC.

Результаты преобразования ADC, прочитанные функцией adc_continuous_read(), это сырые данные. Чтобы вычислить фактическое измеренное напряжение на соответствующем входе ADC, может использоваться следующая формула:

Vout = Dout * Vmax / Dmax

.. где:

Vout Цифровое значение выходного напряжения.

Dout Сырое числовое значение результата преобразования ADC.

Vmax Максимальное измеряемое входное аналоговое напряжение, связанное с ослаблением на канале ADC (ADC attenuation), см. главу "On-Chip Sensor and Analog Signal Processing" в своем даташите (например [3]).

Dmax Максимальное значение сырого результата преобразования ADC, которое зависит от настроенной разрядности выборки 2bitwidth, где bitwidth это значение поля сконфигурированной структуры adc_digi_pattern_config_t::bit_width, как было показано выше.

Для дополнительного улучшения точности, чтобы преобразовать сырое значение выборки ADC в значение mV, см. описание драйвера калибровки АЦП [5].

[Аппаратные ограничения]

Определенный блок АЦП (ADC unit) в любой момент времени может работать только в одном рабочем режиме - либо в режиме непрерывного преобразования (continuous mode) либо в режиме запускаемых однократных преобразований (one-shot mode). Эту защиту обеспечивает adc_continuous_start().

Генератор случайного числа (Random Number Generator, RNG) использует ADC в качестве входного источника случайных данных. Когда работает драйвер непрерывного режима ADC, случайные числе, генерируемые из RNG, будут менее случайными.

Функционал ADC2 DMA больше не поддерживается для получения результатов преобразования ADC из-за аппаратных ограничений, поскольку наблюдаются нестабильные результаты. Описание этой проблемы можно найти в ESP32C3 Errata. Для совместимости вы можете разрешить опцию CONFIG_ADC_CONTINUOUS_FORCE_USE_ADC2_ON_C3_S3 для принудительного использования ADC2.

[Power Management]

Когда разрешено управление питанием (power management) т. е. включена опция CONFIG_PM_ENABLE, частота тактов шины APB может подстраиваться, когда система находится в состоянии ожидания (idle state), что потенциально влияет на поведение непрерывного преобразования ADC.

Однако драйвер непрерывного режима может предотвратить это изменение установкой блокировки управления питанием (power management lock) типа ESP_PM_APB_FREQ_MAX. Блокировка захватывается, когда запущено непрерывное преобразование вызовом adc_continuous_start(). Подобным образом блокировка освобождается после вызова adc_continuous_stop(). Таким образом, adc_continuous_start() и adc_continuous_stop() должны появляться парно, иначе управление питанием не будет работать правильно.

[IRAM Safe]

Все API-функции драйвера непрерывного преобразования ADC не являются IRAM-safe. Они не должны запускаться, когда кэш запрещен. Разрешением Kconfig-опции CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE внутренний ISR-обработчик драйвера становится IRAM. Это означает, что даже когда кэш запрещен, драйвер все еще будет сохранять результаты преобразования в свой внутренний пул.

[Thread Safety]

Для драйвера непрерывного режима преобразования ADC не гарантируется, что он будет безопасным в контексте многопоточности (not thread-safe). Однако драйвером обеспечивается взаимное исключение для одновременного использования аппаратуры АЦП (hardware mutual exclusion), см. описание аппаратных ограничений [].

[Пример приложения]

Рабочий проект примера continuous_read вы можете взять в папке examples/peripherals/adc/ каталога установки ESP-IDF (см. также [4]). Он демонстрирует, как реализовать ADC Continuous Read Mode (DMA Mode) на платах разработчика ESP32-C3, чтобы считывать аналоговые напряжения на выводах GPIO. Этот пример может быть запущен и на других чипах Espressif (см. README.md проекта).

[Справочник по API-функциям ADC Continuous Read Mode]

Для использования драйвера необходимо подключить заголовочный файл esp_adc/adc_continuous.h:

#include "esp_adc/adc_continuous.h"

Этот заголовочный файл входит в состав компонента esp_adc. Для декларации, что ваш компонент приложения зависит от компонента esp_adc, добавьте следующую строчку в ваш файл CMakeLists.txt (он находится в папке с исходным кодом вашего проекта, обычно в подкаталоге main):

REQUIRES esp_adc

или:

PRIV_REQUIRES esp_adc

В следующей таблице приведено краткое описание назначение API-функций драйвера. Полное описание функций и их прототипы см. в документации [1].

adc_continuous_new_handle Инициализирует драйвер непрерывного режима АЦП (ADC continuous driver) и получает его дескриптор (handle). Этот дескриптор впоследствии используется в API-функциях для обращения к драйверу, а также для его остановки и отмены инициализации (см. adc_continuous_stop(), adc_continuous_deinit()).

Функция Описание
adc_continuous_config Установит требуемую конфигурацию драйвера непрерывного режима ADC.
adc_continuous_register_event_callbacks Регистрирует функции обратного вызова для обработки событий драйвера(1)(2)(3).
adc_continuous_start Запустит ADC в непрерывном режиме (с использованием DMA). После этого начинает автоматически работать аппаратура АЦП, и начнут генерироваться события драйвера.
adc_continuous_read Вычитывает результаты преобразования АЦП, работающего в непрерывном режиме.
adc_continuous_stop Останавливает АЦП. После этого работа аппаратуры АЦП прекращается.
adc_continuous_deinit Отменяет инициализацию драйвера непрерывного режима АЦП.
adc_continuous_flush_pool Сольет накопившиеся данные внутреннего пула драйвера(4).
adc_continuous_io_to_channel Позволяет узнать канал ADC из предоставленного номера порта GPIO.
adc_continuous_channel_to_io Позволяет узнать номер вывода GPIO по предоставленному каналу ADC.

Примечания:

(1) Пользователь может отменить ранее зарегистрированный callback с помощью вызова этой функции и установки значения NULL в параметре cbs удаляемого callback.
(2) Когда разрешена опция CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE, сам callback и все вызываемые из него функции должны быть помещены в память IRAM. Используемые переменные (включая user_data) должны также быть помещены в RAM.
(3) Эту API-функцию можно вызывать только когда драйвер непрерывного режима АЦП не был запущен. Чтобы это определить, проверьте возвращаемое значение.
(4) Эта API-функция не должна вызываться в контексте прерывания (из кода ISR).

В следующей таблице приведено краткое описание используемых структур драйвера. Для полного описания полей этих структур обратитесь к документации [1].

Структура Описание
adc_continuous_handle_cfg_t Начальная конфигурация драйвера непрерывного режима АЦП. Устанавливает размер пула данных и размер кадра преобразования.
adc_continuous_config_t Конфигурация драйвера непрерывного режима АЦП. Устанавливает частоту дискретизации (выборок), режим преобразования, формат выходных данных.
adc_continuous_evt_data_t Структура данных события драйвера.
adc_continuous_evt_cbs_t Группа функций обратного вызова (callback) драйвера непрерывного режима. Эти callback-функции должны работать в окружении ISR, а также их код и все вызываемые из этого кода функции должны находиться в IRAM.
adc_continuous_callback_t on_conv_done Функция обратного вызова (callback), вызываемая при завершении кадра преобразования. См. субсекцию "Driver Backgrounds" в заголовочном файле драйвера для объяснения концепции кадра преобразования (conversion frame).
adc_continuous_callback_t on_pool_ovf Функция обратного вызова (callback), вызываемая при событии переполнения внутреннего пула.

Используемые макросы и типы:

ADC_MAX_DELAY

Максимальное значение таймаута чтения АЦП, может задавать бесконечную блокировку в функции adc_continuous_read, если операционная система это поддерживает.

typedef struct adc_continuous_ctx_t *adc_continuous_handle_t

Тип для дескриптора драйвера непрерывного режима АЦП.

typedef bool (*adc_continuous_callback_t)(adc_continuous_handle_t handle,
                                          const adc_continuous_evt_data_t *edata,
                                          void *user_data)

Прототип обработчика события драйвера непрерывного режима АЦП.

[Ссылки]

1. ESP32-C3 Analog to Digital Converter (ADC) Continuous Mode Driver site:espressif.com.
2. ESP32-C3: работа со встроенным АЦП.
3. ESP32 Technical Reference Manual, On-Chip Sensors and Analog Signal Processing site:espressif.com.
4. 250430continuous_read.zip - проект continuous_read, документация.
5. Analog to Digital Converter (ADC) Calibration Driver site:espressif.com.

 

Добавить комментарий


Защитный код
Обновить

Top of Page