ESP32-C3: драйвер АЦП непрерывного режима преобразования |
![]() |
Добавил(а) microsin | ||||||||||||||||||||||||||||||||||
Аналого-цифровой преобразователь, встроенный в чип ESP32-C3, с помощью АЦП может измерять аналоговые сигналы, поступающие на определенные выводы корпуса [2]. Дополнительно имеется функционал прямого доступа к памяти (Direct Memory Access, DMA), позволяющий эффективно получать с АЦП результаты преобразования. У ESP32-C3 есть 2 блока ADC (АЦП), который может использоваться в следующих сценариях: • Генерация однократного (one-shot) результата преобразования ADC. [Концепция DMA-драйвера ADC] ADC в непрерывном режиме преобразования составляется из нескольких кадров преобразования (Conversion Frame). Conversion Frame: один кадр преобразования содержит несколько результатов преобразования (Conversion Result). Размер кадра преобразования конфигурируется в байтах вызовом функции adc_continuous_new_handle(). Достоинство этого режима драйвера АЦП в том, что он позволяет аппаратно оцифровывать данные сразу с нескольких каналов, и это не занимает процессорное время ядра. [Обзор функционала] В следующих секциях этого документа (перевод [1]) описываются типовые шаги инсталляции драйвера непрерывного режима АЦП (ADC continuous mode), и непрерывное чтение результатов преобразования ADC из группы каналов: • Resource Allocation (выделение ресурсов): раскрывается, какие параметры должны быть установлены для инициализации ADC для драйвера непрерывного режима, и как отменять его инициализацию. [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, вызовите 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_del_continuous_iir_filter(). Примечание: если вы используете два фильтра на одном и том же канале ADC, то будет действовать только первый из них. Монитор. Кода ADC работает в непрерывном режиме, доступны 2 монитора. Вы можете установить один или два порога монитора на работающем канале ADC, тогда монитор будет вызывать прерывания на каждом цикле выборки, если результат преобразования выйдет за пределы порога. Чтобы создать монитор ADC, вам нужно настроить adc_monitor_config_t и вызвать adc_new_continuous_monitor(). • adc_monitor_config_t::adc_unit: конфигурирует блок ADC канала, к которому вы хотите подключить монитор. Как только монитор был создан, вы можете с ним работать в своем приложении через следующие API-функции. • adc_continuous_monitor_enable(): разрешение монитора. Пример инициализации ADC Continuous Mode Driver показан ниже. Макрос ESP_ERROR_CHECK служит для автоматического перехвата ситуации ошибки с выводом соответствующего сообщения. adc_continuous_handle_t handle = NULL; Отмена инициализации драйвера, освобождение блока ADC: ESP_ERROR_CHECK(adc_continuous_deinit(handle)); [Конфигурации ADC] После того, как драйвер непрерывного режима инициализирован, настройте adc_continuous_config_t для конфигурирования измерения аналогового сигнала ADC IO: • adc_continuous_config_t::pattern_num: количесво каналов ADC, которые будут использоваться. Установите adc_digi_pattern_config_t: • adc_digi_pattern_config_t::atten: ослабление сигнала (ADC attenuation). См. тему "On-Chip Sensor and Analog Signal Processing" в техническом руководстве [3]. Замечание: для того, чтобы узнать выводы корпуса для соответствующего номера канала, обратитесь к техническому руководству для вашего используемого микроконтроллера. Кроме того, вы можете использовать функции 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_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: это событие генерируется, когда завершается один кадр преобразования. Показанные выше 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. 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()).
Примечания: (1) Пользователь может отменить ранее зарегистрированный callback с помощью вызова этой функции и установки значения NULL в параметре cbs удаляемого callback. В следующей таблице приведено краткое описание используемых структур драйвера. Для полного описания полей этих структур обратитесь к документации [1].
Используемые макросы и типы: 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, Прототип обработчика события драйвера непрерывного режима АЦП. [Ссылки] 1. ESP32-C3 Analog to Digital Converter (ADC) Continuous Mode Driver site:espressif.com. |