Периферийное устройство RMT (Remote Control Transceiver) было разработано для работы в качестве трансивера инфракрасного канала обмена данными. Однако из-за гибкости своего формата данных RMT может быть расширен до универсального приемопередатчика общего назначения, передающего или принимающего многие другие типы сигналов. С точки зрения сетевого уровня аппаратные средства RMT содержат как физический уровень, так и канальный уровень. Физический уровень определяет среду связи и представление битового сигнала. Уровень канала передачи данных определяет формат кадра RMT. Минимальный блок данных в кадре называется символом RMT, который представлен типом rmt_symbol_word_t в драйвере.
ESP32-C3 cодержит несколько каналов периферийного устройства RMT(1). Каждый канал можно независимо сконфигурировать либо как передатчик, либо как приемник.
Примечание (1): разные типы микроконтроллеров серии ESP могут содержать на борту разное количество каналов RMT. Для получения подробной информации обратитесь к техническому руководству (TRM) на свой чип ESP. Драйвер не запрещает вам запрашивать выделение дополнительных каналов RMT, но будет возвращена ошибка, когда закончатся соответствующие аппаратные ресурсы. Поэтому всегда проверяйте возвращаемое значение из API драйвера.
Обычно периферийное устройство RMT может использоваться в следующих сценариях:
• Передача или прием infrared-сигналов по любому IR-протоколу, например протоколу NEC (см. описание далее). • Обычный генератор цифровой последовательности. • Передача сигналов в аппаратно-управляемом цикле, конечное или бесконечное количество раз. • Многоканальная одновременная передача. • Модулировать несущую для выходного сигнала или демодулировать несущую из входного сигнала.
[Разметка символов RMT]
Аппаратура RMT определяет данные своим собственным шаблоном - символ RMT. На диаграмме ниже показаны поля бит символа RMT. Каждый символ состоит из двух пар двух значений. Первое значение в паре это 15-разрядное значение, представляющее длительность сигнала в тиках RMT. Второе значение в паре однобитное, представляющее логический уровень сигнала, т. е. лог. 1 или лог. 0.
Рис. 1. Структура символа RMT. Здесь L это уровень сигнала (signal level).
[Обзор RMT-передатчика]
Распространение данных (data path) и управления (control path) канала передачи (RMT TX channel) показано на следующей картинке:
Рис. 2. Передатчик RMT.
Драйвер кодирует данные пользователя в формат данных RMT, затем передатчик RMT может генерировать сигналы в соответствии закодированной информацией. Он также может модулировать высокочастотный сигнал несущей перед тем, как он будет направлен на ножку порта GPIO.
[Обзор RMT-приемника]
Распространение данных (data path) и управления (control path) канала приема (RMT RX channel) показано на следующей картинке:
Рис. 3. Приемник RMT.
Приемник RMT может оцифровывать поступающие сигналы в формат данных RMT и сохранять эти данные в память. Также можно указать приемнику базовые характеристики приходящего сигнала, благодаря чему можно распознать событие остановки сигнала, и можно отфильтровать помехи и шум. Периферийное устройство RMT также поддерживает демодулирование высокочастотной несущей из базового сигнала.
[Обзор функционала RMT]
Описание функционала RMT разделено в следующих секциях:
• Выделение ресурсов (Resource Allocation) - здесь раскрывается, как выделять и правильно конфигурировать каналы RMT. Также описывается, как освобождать каналы и другие ресурсы, когда они больше не нужны. • Модуляция и демодуляция несущей (Carrier Modulation and Demodulation) - описывается, как модулировать и демодулировать сигналы несущей для каналов TX и RX соответственно. • Регистрация Event Callbacks - раскрывается, как регистрировать предоставленные пользователем callback-функции обработки событий для получения событий канала RMT. • Разрешение и запрет канала - показано, как разрешать и запрещать канал RMT. • Инициирование транзакции TX - описаны шаги для инициации транзакции для канала TX. • Несколько каналов одновременной передачи - описано, как собрать несколько каналов в синхронную группу, чтобы их передачи запускались одновременно. • Инициирование транзакции RX - описаны шаги для инициации транзакции для канала RX. • RMT Encoder - фокусировка на том, как написать пользовательский кодер комбинированием нескольких примитивных кодеров, предоставленных драйвером. • Power Management - описывается, как разные источники тактирования влияют на энергопотребление. • Cache Safe - описывается, как запретить влияние кэща на драйвер RMT, и советы по смягчению этого влияния. • Thread Safety - перечислено, какие API вызовы гарантируют безопасную работу драйвера в условиях многопоточности. • Kconfig Options - описываются различные опции Kconfig, поддерживаемые драйвером RMT.
[Выделение ресурсов]
Оба вида каналов RMT, TX и RX, представлены в драйвере типом rmt_channel_handle_t. Драйвер внутри себя управляет, какие каналы доступны, и выдает свободный канал по запросу.
Инсталляция канала RMT TX. Для установки канала RMT TX существует структура конфигурации, которая должна быть предварительно определена: rmt_tx_channel_config_t. Ниже описаны поля этой структуры конфигурации.
rmt_tx_channel_config_t::gpio_num установит номер порта GPIO, используемый передатчиком.
rmt_tx_channel_config_t::clk_src выбирает источник тактирования для канала RMT. Доступные источники тактов перечислены в rmt_clock_source_t. Обратите внимание: выбранный источник тактов также используются другими каналами, и это означает следующее: пользователь должен гарантировать, что конфигурация тактирования будет одинаковой для выделения других каналов, независимо от того, какие они, TX или RX. Как влияет на энергопотребление выбор различных источников тактирования, см. секцию Power Management.
rmt_tx_channel_config_t::resolution_hz установит разрешающую способность внутреннего счетчика тиков. Параметр интервалов времени сигнала RMT вычисляется на основе этого тика.
rmt_tx_channel_config_t::mem_block_symbols смысл этой опции несколько отличается в зависимости от того, разрешен ли DMA backend, или нет.
- если DMA разрешен через rmt_tx_channel_config_t::with_dma, то это поле управляет размером внутреннего буфера DMA. Чтобы достичь лучшей пропускной способности и уменьшить нагрузку на CPU, вы можете установить здесь увеличенное значение, например 1024. - если DMA не используется, это поле управляет размером выделенного блока памяти, которым владеет канал, и он должен быть не менее 48.
rmt_tx_channel_config_t::trans_queue_depth устанавливает глубину внутренней очереди транзакции. Чем глубже очередь, тем больше транзакций может быть подготовлено в backlog.
rmt_tx_channel_config_t::invert_out используется для принятия решения, инвертировать ли сигнал RMT перед отправкой его на вывод GPIO.
rmt_tx_channel_config_t::with_dma разрешает DMA backend для канала. Использование DMA позволяет значительно снять с CPU нагрузку по обслуживанию канала. Однако DMA доступен не на всех чипах ESP, пожалуйста обратитесь к техническому руководству перед тем, как разрешать эту опцию. Иначе вы можете столкнуться с ошибкой ESP_ERR_NOT_SUPPORTED.
rmt_tx_channel_config_t::intr_priority установит приоритет прерывания. Если установлено 0, то драйвер будет использовать прерывание с низким или средним приоритетом (уровень приоритета может быть одним из значений 1, 2 или 3), иначе приоритет показывается значением этого поля. Используйте числовую форму (1, 2, 3), но не битовую маску ((1 < < 1), (1 < < 2), (1 < < 3)). Имейте в виду, что как только приоритет прерывания установлен, он не может быть изменен, пока не произойдет удаление канала вызовом rmt_del_channel().
rmt_tx_channel_config_t::allow_pd устанавливает, позволяет ли драйвер системе выключать периферийное устройство во время light sleep mode. Перед входом в sleep система будет сохранять контекст регистра RMT, который будет восстановлен позже, когда система выйдет из sleep mode. Выключение периферийного устройства может больше экономить питание, но ценой большего расхода памяти под сохранение контекста регистра. Это компромисс между энергопотреблением и потреблением памяти. Эта опция конфигурации зависит от конкретной аппаратной функции, если вы включите ее на неподдерживаемом чипе, вы увидите сообщение об ошибке, например, не удается отключить питание в режиме light sleep.
Когда структура rmt_tx_channel_config_t заполнена важными параметрами, пользователь может вызвать rmt_new_tx_channel(), чтобы выделить и инициализировать канал TX. Эта функция возвратит дескриптор канала RMT, если канал запустился корректно. В частности, когда в пуле ресурсов RMT больше нет свободных каналов, эта функция возвращает ошибку ESP_ERR_NOT_FOUND. Если какая-либо фича (например DMA) не поддерживается аппаратно, функция возвратит ошибку ESP_ERR_NOT_SUPPORTED.
rmt_channel_handle_ttx_chan=NULL;
rmt_tx_channel_config_ttx_chan_config={ .clk_src=RMT_CLK_SRC_DEFAULT,// выбор источника тактирования .gpio_num=0,// номер GPIO .mem_block_symbols=64,// размер блока памяти 64 * 4 = 256 байт .resolution_hz=1*1000*1000,// разрешающая способность тика 1 МГц, // т. е. 1 тик соответствует 1 микросекунде .trans_queue_depth=4,// установит количество транзакций, которое // может ожидать в фоне .flags.invert_out=false,// не инвертировать выходной сигнал .flags.with_dma=false,// не нужно использовать DMA
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config,&tx_chan));
Инсталляция канала RMT RX. Для установки канала RMT RX необходимо предварительно заполнить структуру rmt_rx_channel_config_t. Ниже описаны поля этой структуры конфигурации.
rmt_rx_channel_config_t::gpio_num установит номер порта GPIO, используемый приемником.
rmt_rx_channel_config_t::clk_src выбирает источник тактирования для канала RMT. Доступные источники тактов перечислены в rmt_clock_source_t. Обратите внимание: выбранный источник тактов также используются другими каналами, и это означает следующее: пользователь должен гарантировать, что конфигурация тактирования будет одинаковой для выделения других каналов, независимо от того, какие они, TX или RX. Как влияет на энергопотребление выбор различных источников тактирования, см. секцию Power Management.
rmt_rx_channel_config_t::resolution_hz установит разрешающую способность внутреннего счетчика тиков. Параметр интервалов времени сигнала RMT вычисляется на основе этого тика.
rmt_rx_channel_config_t::mem_block_symbols смысл этой опции несколько отличается в зависимости от того, разрешен ли DMA backend, или нет.
- если DMA разрешен через rmt_rx_channel_config_t::with_dma, то это поле управляет максимальным размером внутреннего буфера DMA. - если DMA не используется, это поле управляет размером выделенного блока памяти, которым владеет канал, и он должен быть не менее 48.
rmt_rx_channel_config_t::invert_in используется для установки инверсии входного сигнала перед тем, как он будет передан в приемник RMT. Инверсия выполняется матрицей GPIO matrix вместо периферийного устройства RMT.
rmt_rx_channel_config_t::with_dma разрешает DMA для канала. Использование DMA позволяет значительно снять с CPU нагрузку по обслуживанию канала. Однако DMA доступен не на всех чипах ESP, пожалуйста обратитесь к техническому руководству перед тем, как разрешать эту опцию. Иначе вы можете столкнуться с ошибкой ESP_ERR_NOT_SUPPORTED.
rmt_rx_channel_config_t::intr_priority установит приоритет прерывания. Если установлено 0, то драйвер будет использовать прерывание с низким или средним приоритетом (уровень приоритета может быть одним из значений 1, 2 или 3), иначе приоритет показывается значением этого поля. Используйте числовую форму (1, 2, 3), но не битовую маску ((1 < < 1), (1 < < 2), (1 < < 3)). Имейте в виду, что как только приоритет прерывания установлен, он не может быть изменен, пока не произойдет удаление канала вызовом rmt_del_channel().
rmt_rx_channel_config_t::allow_pd устанавливает, позволяет ли драйвер системе выключать периферийное устройство во время light sleep mode. Перед входом в sleep система будет сохранять контекст регистра RMT, который будет восстановлен позже, когда система выйдет из sleep mode. Выключение периферийного устройства может больше экономить питание, но ценой большего расхода памяти под сохранение контекста регистра. Это компромисс между энергопотреблением и потреблением памяти. Эта опция конфигурации зависит от конкретной аппаратной функции, если вы включите ее на неподдерживаемом чипе, вы увидите сообщение об ошибке, например, не удается отключить питание в режиме light sleep.
Когда структура rmt_rx_channel_config_t заполнена обязательными параметрами, пользователи могут вызвать rmt_new_rx_channel(), чтобы выделить и инициализировать канал RX. Эта функция возвратит дескриптор канала RMT, если канал запустился корректно. В частности, когда в пуле ресурсов RMT больше нет свободных каналов, эта функция возвращает ошибку ESP_ERR_NOT_FOUND. Если какая-либо фича (например DMA) не поддерживается аппаратно, функция возвратит ошибку ESP_ERR_NOT_SUPPORTED.
rmt_channel_handle_trx_chan=NULL;
rmt_rx_channel_config_trx_chan_config={ .clk_src=RMT_CLK_SRC_DEFAULT,// выбор источника тактирования .resolution_hz=1*1000*1000,// разрешающая способность тика 1 МГц, // т. е. 1 тик соответствует 1 мкс .mem_block_symbols=64,// размер блока памяти 64 * 4 = 256 байт .gpio_num=2,// номер порта GPIO .flags.invert_in=false,// не инвертировать входной сигнал .flags.with_dma=false,// не нужно использовать DMA backend
};
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config,&rx_chan));
Важное замечание: когда одновременно выделено несколько каналов RMT, прескалер группы определяется на основе разрешающей способности первого канала. Затем драйвер выберет подходящий прескалер от низкого до высокого. Чтобы избежать конфликта прескалера при выделении нескольких каналов, выделяйте каналы Чтобы избежать предопределенных конфликтов при распределении нескольких каналов, распределяйте каналы в порядке их целевой разрешающей способности, либо от самого высокого к самому низкому, либо от самого низкого к самому высокому.
Деинсталляция канала RMT. Если инсталлированный ранее канал RMT больше не нужен, то рекомендуется освободить связанные с этим ресурсы вызовом rmt_del_channel(), после чего эти программные и аппаратные ресурсы могут быть задействованы для других целей.
[Модуляция и демодуляция несущей]
Передатчик RMT может генерировать сигнал несущей и модулировать его в сигнал сообщения. По сравнению с сигналом сообщения частота сигнала несущей значительно выше. Кроме того, для сигнала несущей пользователь может установить только частоту и скважность. Приемник RMT может демодулировать сигнал несущей из приходящего сигнала. Имейте в виду, что модуляция и демодуляция несущей поддерживается не всеми чипами ESP поэтому проконсультируйтесь с техническим руководством, или вы можете столкнуться с ошибкой ESP_ERR_NOT_SUPPORTED.
Связанные с несущей конфигурации находятся в rmt_carrier_config_t:
rmt_carrier_config_t::frequency_hz установит частоту несущей в Гц.
rmt_carrier_config_t::polarity_active_low установит полярность несущей, т. е. уровень, на котором применяется несущая.
rmt_carrier_config_t::always_on устанавливает, нужно ли выводить несущую, даже когда завершена передача данных. Эта опция действует только для канала TX.
Важное замечание: для канала RX мы не должны устанавливать частоту несущей точно на теоретическое значение. Рекомендуется оставить допуск на частоту несущей. Например, во фрагменте кода ниже мы устанавливаем частоту 25 кГц вместо 38 кГц, сконфигурированной на стороне TX. Причина в том, что отражение и преломление происходят, когда сигнал проходит по воздуху, что приводит к искажению на стороне приемника.
rmt_carrier_config_ttx_carrier_cfg={ .duty_cycle=0.33,// скважность 33% .frequency_hz=38000,// 38 кГц .flags.polarity_active_low=false,// несущая должна модулироваться // высоким уровнем
}; // модуляция несущей для канала TX
ESP_ERROR_CHECK(rmt_apply_carrier(tx_chan,&tx_carrier_cfg));
rmt_carrier_config_trx_carrier_cfg={ .duty_cycle=0.33,// скважность 33% .frequency_hz=25000,// несущая 25 кГц, должна быть ниже, // чем частота несущей передатчика .flags.polarity_active_low=false,// несущая модулируется высоким // уровнем
}; // демодуляция несущей из канала RX
ESP_ERROR_CHECK(rmt_apply_carrier(rx_chan,&rx_carrier_cfg));
[Регистрация Event Callbacks]
Когда на канале RMT происходит событие (например завершена передача или завершен прием), CPU оповещается прерыванием об этом событии. Если у вас есть какая-то функция, которая должна быть вызвана для определенного события, то вы можете зарегистрировать callback для этого события в обработчике прерывания драйвера RMT (Interrupt Service Routine, ISR) вызовом rmt_tx_register_event_callbacks() и rmt_rx_register_event_callbacks() для каналов TX и RX соответственно. Поскольку зарегистрированные callback-функции вызываются в контексте прерывания, они должны завершаться как можно быстрее, чтобы не блокировать выполнение других прерываний и остального кода. Например, необходимо гарантировать, что в callback-функции вызываются только те API-функции FreeRTOS, которые имеют суффикс FromISR. У callback-функции двоичное возвращаемое значение, используемое для указания того, была ли задача с более высоким приоритетом разблокирована вызовом callback.
События канала TX перечислены в rmt_tx_event_callbacks_t:
rmt_tx_event_callbacks_t::on_trans_done установит callback-функцию для события завершения передачи ("trans-done"). Прототип функции декларирован в rmt_tx_done_callback_t.
События канала RX перечислены в rmt_rx_event_callbacks_t:
rmt_rx_event_callbacks_t::on_recv_done установит callback-функцию для события завершения приема ("receive-done"). Прототип функции декларирован в rmt_rx_done_callback_t.
Важное замечание: не эквивалентно "receive-done" "receive-finished". Этот callback может быть также вызван во время частичного приема ("partial-receive-done"), множество раз во время одной транзакции приема.
Пользователи могут также сохранить свой собственный контекст в rmt_tx_register_event_callbacks() и rmt_rx_register_event_callbacks(), через параметр user_data. User data непосредственно передаются в каждую callback-функцию.
В теле callback-функции пользователи могут извлекать связанные с событием данные, которые драйвер заполняет в edata. Имейте в виду, что указатель edata действителен только внутри callback, не пытайтесь сохранить этот указатель и использовать его вне callback-функции.
Данные события TX-done определены в rmt_tx_done_event_data_t:
rmt_tx_done_event_data_t::num_symbols показывает количество переданных символов RMT. Это также отражает размер артефактов кодирования. Обратите внимание, что это значение также учитывает символ EOF, который добавляется драйвером, чтобы пометить конец одной транзакции.
Данные события RX-complete определены в rmt_rx_done_event_data_t:
rmt_rx_done_event_data_t::received_symbols указывает на принятые символы RMT. Эти символы сохранены в параметре buffer функции rmt_receive(). Пользователи не должны освобождать этот буфер приема перед возвратом из callback. Если вы также разрешили фичу частичного приема (partial receive), то буфер пользователя будет использоваться как "буфер второго уровня", где его содержимое может быть перезаписано поступающими впоследствии данными. В этом случае вы должны скопировать принятые данные в другое место, если хотите сохранить их или обработать позже.
rmt_rx_done_event_data_t::num_symbols показывает количество принятых символов RMT. Это значение не превышает параметр buffer_size функции rmt_receive(). Если buffer_size недостаточен для размещения всех принятых символов RMT, то драйвер сохранит только максимальное количество символов, которое сможет сохранить buffer, и лишние символы отбрасываются или игнорируются.
rmt_rx_done_event_data_t::is_last показывает, является ли текущий принятый буфер последним в одной транзакции. Это полезно, когда вы разрешили фичу partial reception через rmt_receive_config_t::extra_rmt_receive_flags::en_partial_rx.
[Разрешение и запрет канала]
Функция rmt_enable() должна быть предварительно вызвана перед передачей или приемом символов RMT. Для каналов TX резрешение канала разрешает определенное прерывание и подготавливает аппаратуру для диспетчеризации транзакций. Для каналов RX разрешение канала разрешает прерывание, однако приемник не запускается в этот момент, поскольку характеристики приходящего сигнала еще не определены. Приемник запускается в rmt_receive().
Функция rmt_disable() делает противоположное путем запрета прерывания и очисткой всех ожидающих обработки прерываний. Передатчик и приемник также отключаются.
RMT это специальное коммуникационное периферийное устройство, которое не может отправлять потоки сырых байт наподобие интерфейсов SPI и I2C. RMT может только посылать данные в своем собственном формате rmt_symbol_word_t. Однако аппаратура не помогает в преобразовании данных пользователя в символы RMT, это может быть сделано только программно так называемым энкодером (RMT Encoder). Этот энкодер отвечает за кодирование данных пользователя в символы RMT, и затем запись в блок памяти RMT или буфер DMA. Как создавать RMT encoder, см. его описание далее.
Как только вы создали encoder, можно инициировать транзакцию TX вызовом rmt_transmit(). Эта функция принимает позиционный параметр наподобие дескриптора канала, дескриптора encoder и буфер полезной нагрузки. Помимо этого вам также нужно предоставить относящуюся к передаче конфигурацию в структуре rmt_transmit_config_t:
rmt_transmit_config_t::loop_count устанавливает количество циклов передачи. После того, как передатчик завершит один цикл передачи, он перезапустит ту же самую передачу снова, если это значение не установлено в 0. Поскольку циклы передачи управляются аппаратно, канал RMT может использоваться для генерации множества периодических последовательностей с минимальным вмешательством CPU.
- Установка rmt_transmit_config_t::loop_count в -1 означает бесконечные циклы передачи. В этом случае канал не остановит передачу, пока не произойдет вызов rmt_disable(). Событие "trans-done" вообще не генерируется. - Установка rmt_transmit_config_t::loop_count в положительное число означает количество итераций. В этом случае событие "trans-done" произойдет, когда завершено указанное количество итераций.
Важное замечание: фича передачи циклами поддерживается не всеми чипами ESP так что обратитесь к техническому руководству по вашему чипу перед конфигурированием этой опции, иначе вы можете столкнутьс с ошибкой ESP_ERR_NOT_SUPPORTED.
rmt_transmit_config_t::eot_level установит выходной уровень, когда передатчик завершит работу или останорит работу из-за вызова rmt_disable().
rmt_transmit_config_t::queue_nonblocking определяет, ждать ли свободный слот в очереди передачи, когда она заполнена. Если это значение установлено в true, то функция возвратит код ошибки ESP_ERR_INVALID_STATE, когда очередь заполнена. Иначе функция заблокирует выполнение в ожидании, пока не станет доступным слот в очереди.
Важное замечание: здесь есть ограничение на размер передачи, если rmt_transmit_config_t::loop_count установлен в ненулевое значение, например чтобы разрешить фичу циклической передачи. Закодированные символы RMT не должны превышать размер аппаратного блока памяти RMT, иначе вы можете увидеть сообщение об ошибке, так как артефакты кодирования не могут превышать аппаратный блок памяти для циклической передачи. Если вам нужно запустить в цикле большую транзакцию, то можете попробовать один из следующих методов.
• Увеличьте rmt_tx_channel_config_t::mem_block_symbols. Этот способ не сработает, если также разрешен DMA backend. • Настройте encoder и сконструируйте бесконечный цикл в функции кодирования. См. также документацию RMT Encoder.
Внутренне rmt_transmit() создает дескриптор транзакции и посылает его в очередь заданий, которая отправляется в ISR. Таким образом возможно, что транзакция еще не запущена, когда произойдет возврат из rmt_transmit(). Невозможно повторно использовать или модифицировать буфер полезной нагрузки, пока транзакция не завершится. Вы можете получить событие завершения транзакции путем регистраии callback-функции вызовом rmt_tx_register_event_callbacks(). Чтобы убедиться, что все ожидающие транзакции завершены, вы также можете использовать rmt_tx_wait_all_done().
[Несколько каналов одновременной передачи]
В некоторых приложениях управления реального времени (real-time, например для одновременного приведения в действие двух рук робота), вы можете не хотеть получить любой временной дрейф между разными каналами. Драйвер RMT может помочь в этом путем созлания так называемого менеджера синхронизации (Sync Manager). В драйвере менеджер синхронизации представлен типом rmt_sync_manager_handle_t. Процедура синхронной передачи RMT показана на рисунке:
Рис. 4. RMT TX Sync.
Инсталляция RMT Sync Manager. Чтобы создать менеджер синхронизации, пользователю нужно указать, какие каналы обслуживаются, в rmt_sync_manager_config_t:
rmt_sync_manager_config_t::tx_channel_array указывает на массив обслуживаемых каналов TX.
rmt_sync_manager_config_t::array_size устанавливает количество обслуживаемых каналов.
В случае успеха rmt_new_sync_manager() может возвратить дескриптор менеджера. Эта функция может также завершиться неудачей из-за разных ошибок, таких как неправильные аргументы, и т. д. В частности, когда sync manager был инсталлирован ранее, и сейчас нет аппаратных ресурсов для создания другого менеджера, эта функция возвратит ошибку ESP_ERR_NOT_FOUND. Дополнительно, если sync manager не поддерживается аппаратурой, будет возвращена ошибка ESP_ERR_NOT_SUPPORTED. Обратитесь к техническому руководству на ваш ESP перед использованием фичи менеджера синхронизации.
Запуск одновременной передачи. Для любого управляемого канала передачи он не запускает машину до тех пор, пока rmt_transmit () не будет вызван для всех каналов в rmt_sync_manager_config_t::tx_channel_array. Перед этим канал просто переводится в состояние ожидания. Каналы передачи обычно завершают свои транзакции в разное время из-за различных транзакций, что приводит к потере синхронизации. Поэтому перед перезапуском одновременной передачи пользователю необходимо вызвать rmt_sync_reset (), чтобы снова синхронизировать все каналы.
Вызов rmt_del_sync_manager() может освободить sync manager и позволить каналам инициировать впоследствии независимые транзации.
// Инсталляция каналов друг за другом: for(inti=0;i<2;i++){ rmt_tx_channel_config_ttx_chan_config={ .clk_src=RMT_CLK_SRC_DEFAULT,// выбор источника тактирования .gpio_num=tx_gpio_number[i],// номер GPIO .mem_block_symbols=64,// размер блока памяти, 64 * 4 = 256 байт .resolution_hz=1*1000*1000,// разрешающая способность 1 МГц .trans_queue_depth=1,// установит количество транзакций, // которые могут ожидать в фоне }; ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config,&tx_channels[i]));
}
// разрешение каналов for(inti=0;i<2;i++){ ESP_ERROR_CHECK(rmt_enable(tx_channels[i]));
}
// инсталляция sync manager
rmt_sync_manager_handle_tsynchro=NULL;
rmt_sync_manager_config_tsynchro_config={ .tx_channel_array=tx_channels, .array_size=sizeof(tx_channels)/sizeof(tx_channels[0]),
};
ESP_ERROR_CHECK(rmt_new_sync_manager(&synchro_config,&synchro));
ESP_ERROR_CHECK(rmt_transmit(tx_channels[0],led_strip_encoders[0], led_data,led_num*3,&transmit_config)); // tx_channels[0] не начнет передавать, пока пока не произойдет // возврат вызова `rmt_transmit()` для tx_channels[1]
ESP_ERROR_CHECK(rmt_transmit(tx_channels[1],led_strip_encoders[1], led_data,led_num*3,&transmit_config));
[Инициирование транзакции RX]
Как также обсуждалось выше в секции "Разрешение и запрет канала", вызов rmt_enable() не подготавливает RX для приема символов RMT. Пользователю нужно указать базовые характеристики приходящих сигналов в rmt_receive_config_t:
rmt_receive_config_t::signal_range_min_ns указывает минимальную допустимую длительность либо лог. 1, либо лог. 0. Ширина импульса меньше этого значения считается импульсом помехи и игнорируется аппаратно.
rmt_receive_config_t::signal_range_max_ns указывает максимально допустмую длительность импульса либо для лог. 1, либо для лог. 0. Ширина импульса, которя больше этого значения, обрабатывается как сигнал Stop, и приемник немедленно генерирует событие завершения приема (receive-complete event).
Если приходящий пакет длинный, так что он не может за один раз быть сохранен в буфере, то вы можете разрешить фичу частичного приема установкой в true флага rmt_receive_config_t::extra_rmt_receive_flags::en_partial_rx. В этом случае драйвер запустит callback-функцию rmt_rx_event_callbacks_t::on_recv_done несколько раз за одну транзакцию, когда буфер пользователя почти полон. Вы можете проверить значение :cpp:member::rmt_rx_done_event_data_t::is_last, чтобы узнать, завершилась ли транзакция. Обратите внимание, что эти фичи поддерживаются не всеми представителями серии чипов ESP, потому что полагаются на аппаратные возможности наподобие "ping-pong receive" или "DMA receive".
Приемник RMT запустит RX-машину после того как пользователь вызовет rmt_receive() с предоставленной конфигурацией, описанной выше. Следует отметить, что эта конфигурация зависит от транзакции, и это означает, что для начала нового раунда приема пользователю нужно снова установить rmt_receive_config_t. Приемник сохранит приходящие сигналы в свой внутренний блок памяти или буфер DMA, в формате rmt_symbol_word_t.
Из-за ограниченного размера блока памяти приемник RMT оповещает драйвер о необходимости копирования и удаления накопленных символов способом пинг-понга.
Место назначения копии должно быть предоставлено в параметре buffer функции rmt_receive(). Если этот буфер переполнился из-за недостаточного размера, то приемник может продолжить работу, но переполненные символы отбрасываются и выводится следующее сообщение об ошибке: "user buffer too small, received symbols truncated". Обратите внимание на цикл жизни параметра buffer, чтобы обеспечить его наличие, т. е. он не должен освобождаться до того, как приемник завершил работу или остановлен.
Приемник останавливается драйвером, когда он завершил работу, например принят сигнал с длительностью больше rmt_receive_config_t::signal_range_max_ns. Пользователю нужно вызвать rmt_receive() снова, чтобы перезапустить приемник, если это необходимо. Пользователь может получить принятые данные в callback-функции rmt_rx_event_callbacks_t::on_recv_done. Для дополнительной информации см. также "Регистрация Event Callbacks".
staticboolexample_rmt_rx_done_callback(rmt_channel_handle_tchannel, constrmt_rx_done_event_data_t*edata, void*user_data)
{ BaseType_thigh_task_wakeup=pdFALSE; QueueHandle_treceive_queue=(QueueHandle_t)user_data; // отправка принятых символов RMT в задачу парсера xQueueSendFromISR(receive_queue,edata,&high_task_wakeup); // возвратит, пробуждена ли какая-либо задача returnhigh_task_wakeup==pdTRUE;
}
QueueHandle_treceive_queue=xQueueCreate(1,sizeof(rmt_rx_done_event_data_t));
rmt_rx_event_callbacks_tcbs={ .on_recv_done=example_rmt_rx_done_callback,
};
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(rx_channel,&cbs,receive_queue));
// Следующие требования интервалов времени основаны на протоколе NEC:
rmt_receive_config_treceive_config={ .signal_range_min_ns=1250,// самая короткая длительность сигнала NEC // 560 µs, 1250 ns < 560 µs, правильный // сигнал не обрабатывается как шум .signal_range_max_ns=12000000,// самая большая длительность сигнала NEC // 9000 µs, 12000000 ns > 9000 µs, // приемник не остановится раньше
};
rmt_symbol_word_traw_symbols[64];// 64 символа должно быть достаточно для // стандартного кадра NEC
// все готово для приема
ESP_ERROR_CHECK(rmt_receive(rx_channel,raw_symbols,sizeof(raw_symbols), &receive_config));
// ожидание сигнала завершения приема (RX-done)
rmt_rx_done_event_data_trx_data;
xQueueReceive(receive_queue,&rx_data,portMAX_DELAY);
// парсинг принятых символов
example_parse_nec_frame(rx_data.received_symbols,rx_data.num_symbols);
[RMT Encoder]
RMT encoder это часть транзакции RMT TX, которая отвечает за генерацию и запись корректных символов RMT в аппаратную память или буфер DMA в определенное время. Есть некоторые специальные ограничения для функции кодирования:
• Во время одной транзакции функция кодирования может быть вызвана несколько раз. Это необходимо, потому что целевой блок памяти RMT не может хранить сразу все артефакты. Чтобы преодолеть это ограничение, драйвер использует метод пинг-понга, где сессия кодирования делится на несколько частей. Это означает, что кодер должен отслеживать свое состояние, чтобы продолжить кодировать с того места, где он остановился на предыдущей части. • Функция кодирования работает в ISR-контексте. Для ускорения сессии кодирования настоятельно рекомендуется поместить её код в IRAM. Это позволит избежать случайных задержек при кодировании из-за промахов кэша.
Чтобы помочь быстрее применить драйвер RMT, из коробки предоставляются наиболее широко используемые кодеры. Они могут либо работать по отдельности, либо могут соединяться в цепочку друг с другом в новый кодер. См. также Composite Pattern [2] для описания лежащего под этим принципа. Драйвер определил интерфейс кодировщика в rmt_encoder_t, он содержит следующие функции:
rmt_encoder_t::encode фундаментальная функция кодера. Здесь происходит сеанс кодирования.
- Функция encode может быть вызвана несколько раз в одной транзакции. Она должна возвратить состояния текущей сессии кодирования. - Поддерживаемые состояния перечислены в rmt_encode_state_t. Если результат содержит RMT_ENCODING_COMPLETE, то это означает, что текущий кодер завершил работу. - Если результат содержит RMT_ENCODING_MEM_FULL, то программа должна выйти из текущей сессии, так как нет места для сохранения большего количества артефактов кодирования.
rmt_encoder_t::reset должна сбросить состояние кодирования обратно к начальному состоянию (кодер RMT поддерживает свое состояние).
- Если передатчик RMT остановлен вручную без сброса соответствующего кодера, последующая сессия кодирования может быть ошибочной. - Эта функция также неявно вызывается в rmt_disable().
rmt_encoder_t::del должна освободить ресурсы, выделенные кодером.
Copy Encoder. Вызов rmt_new_copy_encoder() создает копию кодера. Основная функциональность копии кодера состоит в копировании символов RMT из пространства пользователя (user space) в слой драйвера. Это обычно используется для кодирования const данных, например данные не меняются runtime после инициализации, как лидирующий код в протоколе IR.
Должна быть предварительно предоставлена структура конфигурации rmt_copy_encoder_config_t перед вызовом rmt_new_copy_encoder(). В настоящее время эта конфигурация зарезервирована для будущего расширения, и пока не имеет конкретного использования или установки элементов.
Bytes Encoder. Кодер байт создается вызовом rmt_new_bytes_encoder(). Основное его назначение - динамически преобразовать поток байт user space в символы RMT. Это обычно используется для кодирования динамических данных, например полей адреса и команды в протоколе IR.
Предварительно должна быть предоставлена структура конфигурации rmt_bytes_encoder_config_t перед вызовом rmt_new_bytes_encoder():
rmt_bytes_encoder_config_t::bit0 и rmt_bytes_encoder_config_t::bit1 необходимы для указания, как кодер предствляет бит 0 и бит 1 в формате rmt_symbol_word_t.
rmt_bytes_encoder_config_t::msb_first установит endianness каждого байта. Если установлено в true, то encoder кодирует порядок бит в байте по принципу MSB (Most Significant Bit, самый старший бит идет первым). Иначе порядок бит кодируется по принципу LSB ((Least Significant Bit, самый младший бит идет первым).
Помимо примитивных кодеров, предоставленных драйвером, пользователь может реализовать свой собственный кодер, соединяя друг с другом в цепочку существующие кодеры. Обычная цепочка кодеров показана на рисунке:
Рис. 5. Цепочка RMT Encoder.
Simple Callback Encoder. Простой callback encoder создается вызовом rmt_new_simple_encoder(). Этот простой callback encoder позволяет вам предоставить callback-функцию, которая читает данные из userspace и записывает символы в поток вывода без цепочек с другими кодерами. Сам callback получает указатель на данные, переданные в rmt_transmit(), counter показывает количество символов, уже готовых к записи callback-функцией в этой передаче, и указатель, куда записывать кодированные символы RMT, а также свободное там пространство. Если пространства недостаточно для callback-функции, чтобы что-то закодировать, то она может возвратить 0 и RMT будет ждать передачи предыдущих символов, и callback-функция будет вызвана снова, теперь уже с большим свободным пространством. Если callback-функция успешно запишет символы RMT, то она должна возвратить количество записанных символов.
Предварительно должна быть предоставлена структура конфигурации rmt_simple_encoder_config_t перед вызовом rmt_new_simple_encoder():
rmt_simple_encoder_config_t::callback и rmt_simple_encoder_config_t::arg предоставляют callback-функцию и непрозрачный элемент (opaque), который будет передан этой функции.
rmt_simple_encoder_config_t::min_chunk_size указывает минимальный объем свободного пространства в символах, куда кодер сможет записать некоторые данные. Другими словами, когда это количество свободного пространства передано кодеру, он никогда не должен возвращать 0 (за исключением случаев, когда кодер кодирует символы).
Хотя функциональность процесса кодирования, использующего simple callback encoder, обычно также может быть реализована путем связывания других кодеров, simple callback может быть более простым для понимания и поддержки, чем цепочка кодеров.
В этой врезке демонстрируется, как написать кодер протокола пульта дистанционного управления NEC. Протокол NEC IR использует импульсное кодирование расстояния бит сообщения. Каждый пакет импульсов имеет длину 562.5 мкс, логические биты передаются следующим образом. Стоит упомянуть, что первым посылается младший значащий бит каждого байта.
• Лог. 0: импульс 562.5 мкс с последующим интервалом 562.5 мкс, с общим временем передачи 1.125 мс. • Лог. 1: импульс 562.5 мкс, за которым идет интервал 1.6875 мс, с общим временем передачи 2.25 мс.
Когда ключ передается пультом управления, переданное сообщение содержит следующие элементы в указанном порядке:
Рис. 6. Кадр IR NEC.
• Начальный импульс 9 мс, также называемый "импульсом АРУ" (AGC). • Интервал 4.5 мс. • 8-битный адрес для принимающего устройства. • 8-битная логическая инверсия адреса. • 8-битная команда. • 8-битная логическая инверсия команды. • Конечный импульс 562.5 мкс для обозначения конца передачи сообщения.
Затем вы можете сконструировать функцию NEC rmt_encoder_t::encode в том же порядке, например:
// Представление кода сканирования IR NEC typedefstruct{ uint16_taddress; uint16_tcommand;
}ir_nec_scan_code_t;
// Конструкция кодера в виде комбинации примитивных кодеров typedefstruct{ rmt_encoder_tbase;// базовый "class" декларирует стандартный интерфейс кодера rmt_encoder_t*copy_encoder;// copy_encoder используется для кодирования начального // и конечного импульса rmt_encoder_t*bytes_encoder;// bytes_encoder используется для кодирования данных // адреса и команды rmt_symbol_word_tnec_leading_symbol;// лидирующий код NEC в представлении RMT rmt_symbol_word_tnec_ending_symbol;// завершающий код NEC в представлении RMT intstate;// запись текущего состояния кодирования, т. е. в какой фазе кодирования // мы находимся
}rmt_ir_nec_encoder_t;
staticsize_trmt_encode_ir_nec(rmt_encoder_t*encoder,rmt_channel_handle_tchannel, constvoid*primary_data,size_tdata_size, rmt_encode_state_t*ret_state)
{ rmt_ir_nec_encoder_t*nec_encoder=__containerof(encoder,rmt_ir_nec_encoder_t,base); rmt_encode_state_tsession_state=RMT_ENCODING_RESET; rmt_encode_state_tstate=RMT_ENCODING_RESET; size_tencoded_symbols=0; ir_nec_scan_code_t*scan_code=(ir_nec_scan_code_t*)primary_data; rmt_encoder_handle_tcopy_encoder=nec_encoder->copy_encoder; rmt_encoder_handle_tbytes_encoder=nec_encoder->bytes_encoder; switch(nec_encoder->state){ case0:// отправка leading кода encoded_symbols+=copy_encoder->encode(copy_encoder,channel, &nec_encoder->nec_leading_symbol, sizeof(rmt_symbol_word_t),&session_state); if(session_state&RMT_ENCODING_COMPLETE){ nec_encoder->state=1;// мы можем переключиться в следующее состояние // только когда текущий кодер завершил работу }if(session_state&RMT_ENCODING_MEM_FULL){ state|=RMT_ENCODING_MEM_FULL; gotoout;// выход, если нет свободного места для размещения // других артефактов кодирования } // проваливаемся дальше case1:// отправка адреса encoded_symbols+=bytes_encoder->encode(bytes_encoder,channel, &scan_code->address, sizeof(uint16_t),&session_state); if(session_state&RMT_ENCODING_COMPLETE){ nec_encoder->state=2;// мы можем переключиться в следующее состояние // только когда текущий кодер завершил работу } if(session_state&RMT_ENCODING_MEM_FULL){ state|=RMT_ENCODING_MEM_FULL; gotoout;// выход, если нет свободного места для размещения // других артефактов кодирования } // проваливаемся дальше case2:// отправка команды encoded_symbols+=bytes_encoder->encode(bytes_encoder,channel, &scan_code->command, sizeof(uint16_t),&session_state); if(session_state&RMT_ENCODING_COMPLETE){ nec_encoder->state=3;// мы можем переключиться в следующее состояние // только когда текущий кодер завершил работу } if(session_state&RMT_ENCODING_MEM_FULL){ state|=RMT_ENCODING_MEM_FULL; gotoout;// выход, если нет свободного места для размещения // других артефактов кодирования } // проваливаемся дальше case3:// отправка ending кода encoded_symbols+=copy_encoder->encode(copy_encoder,channel, &nec_encoder->nec_ending_symbol, sizeof(rmt_symbol_word_t),&session_state); if(session_state&RMT_ENCODING_COMPLETE){ nec_encoder->state=RMT_ENCODING_RESET;// назад к изначальной сессии // кодирования state|=RMT_ENCODING_COMPLETE;// указываем вызывающему коду, что кодирование // NEC завершено } if(session_state&RMT_ENCODING_MEM_FULL){ state|=RMT_ENCODING_MEM_FULL; gotoout;// выход, если нет свободного места для размещения // других артефактов кодирования } } out: *ret_state=state; returnencoded_symbols;
}
Полный пример кода можно найти в peripherals/rmt/ir_nec_transceiver. В приведенном выше куске кода мы использовали оператор switch-case и несколько goto для реализации машины состояния. По этому шаблону пользователи могут сконструировать более сложные IR-протоколы.
Использование RMT в составе этих компонентов (на примере чтения датчика температуры DS18B20) см. в папке espressif__ds18b20/examples/ds18b20_read (находится в папке managed_components вашего проекта).
Как детектировать все датчики DS18B20, подключенные к шине 1-Wire:
// Инсталляция шины 1-wire:
onewire_bus_handle_tbus=NULL;
onewire_bus_config_tbus_config={ .bus_gpio_num=EXAMPLE_ONEWIRE_BUS_GPIO, .flags={ .en_pull_up=true,// разрешает внутренний pull-up резистор в случае, // когда на внешнем устройстве его нет }
};
onewire_bus_rmt_config_trmt_config={ .max_rx_bytes=10,// 1 байт команды ROM + 8 байт номера ROM // + 1 байт команды устройства
};
ESP_ERROR_CHECK(onewire_new_bus_rmt(&bus_config,&rmt_config,&bus));
// Создается 1-wire итератор, используемый для поиска устройств на шине:
ESP_ERROR_CHECK(onewire_new_device_iter(bus,&iter));
ESP_LOGI(TAG,"Создан итератор устройств OneWire, начнем поиск..."); do{ search_result=onewire_device_iter_get_next(iter, &next_onewire_device); if(search_result==ESP_OK) {// найдено новое устройство. Проверим, можно ли // сделать апгрейд до DS18B20 ds18b20_config_tds_cfg={}; onewire_device_address_taddress; // Проверим, является ли это устройство DS18B20, и если так, // то возвратим дескриптор ds18b20: if(ESP_OK==ds18b20_new_device_from_enumeration(&next_onewire_device, &ds_cfg, &ds18b20s[ds18b20_device_num])) { ds18b20_get_device_address(ds18b20s[ds18b20_device_num],&address); ESP_LOGI(TAG,"Найден DS18B20[%d], адрес: %016llX", ds18b20_device_num,address); ds18b20_device_num++; } else { ESP_LOGI(TAG,"Найдено неизвестное устройство, адрес: %016llX", next_onewire_device.address); } }
}while(search_result!=ESP_ERR_NOT_FOUND);
ESP_ERROR_CHECK(onewire_del_device_iter(iter));
ESP_LOGI(TAG,"Поиск завершен, найдено %d устройств DS18B20", ds18b20_device_num); // Теперь у вас есть дескриптор датчика DS18B20, можно использовать // его для чтения температуры.
Запуск преобразования температуры и чтение результата:
ESP_ERROR_CHECK(ds18b20_trigger_temperature_conversion_for_all(bus)); for(inti=0;i<ds18b20_device_num;i++){ ESP_ERROR_CHECK(ds18b20_get_temperature(ds18b20s[i],&temperature)); ESP_LOGI(TAG,"temperature read from DS18B20[%d]: %.2fC",i,temperature);
}
Когда разрешено управление питанием, например включена опция CONFIG_PM_ENABLE, система может подстраивать или запрещать источник тактирования перед уходом в режим sleep. В результате база времени внутри RMT может не работать так, как ожидалось.
Драйвер может решить эту проблему путем создания блокировки управления питанием (power management lock). Тип блокировки устанавливается, основываясь на различных источниках тактирования. Драйвер будет захватывать блокировку в rmt_enable(), и освобождать блокировку в rmt_disable(). Благодаря этому любые транзакции RMT между этими двумя действия будут гарантировано выполнены корректно и стабильно.
[Cache Safe]
По умолчанию прерывание RMT откладывается, когда кэш отключен по таким причинам, как запись или удаление основной флэш-памяти. Таким образом, прерывание транзакции не обрабатывается вовремя, что неприемлемо в приложении реального времени. Что еще хуже, когда транзакция RMT полагается на прерывание ping-pong для последовательного кодирования или копирования символов RMT, отложенное прерывание может привести к непредсказуемому результату.
Имеются Kconfig-опции CONFIG_RMT_TX_ISR_CACHE_SAFE и CONFIG_RMT_RX_ISR_CACHE_SAFE, обладающие следующим функционалом:
1. Разрешается обслуживание прерывания даже когда cache запрещен. 2. Все функции, используемые в ISR, помеаются в IRAM(2). 3. Объект драйвера помещается в DRAM, если он случайно был отображен на PSRAM.
Примечание (2): callback-функция, например rmt_tx_event_callbacks_t::on_trans_done, и функции, запускаемые им самим, должны также находиться в IRAM, пользователи должны сами об этом позаботиться.
Эта опция Kconfig позволяет обработчику прерывания работать, когда cache запрещен, но ценой повышенного расхода IRAM.
Обратите внимание, что когда разрешена опция CONFIG_RMT_TX_ISR_CACHE_SAFE, вы также должны поместить в IRAM функции кодера (в основном rmt_encoder_t::encode и rmt_encoder_t::reset). Вы можете использовать RMT_ENCODER_FUNC_ATTR для декорирования ваших функций кодера.
Другая Kconfig-опция CONFIG_RMT_RECV_FUNC_IN_IRAM также может поместить rmt_receive() в IRAM. Чтобы функцию receive можно было использовать даже при отключенном flash cache.
[Thread Safety]
Драйвером гарантируется безопасное использование в потоках (thread-safe) функций rmt_new_tx_channel(), rmt_new_rx_channel() и rmt_new_sync_manager(). Это значит, что пользователь может вызывать их из различных задач RTOS без необходимости защищать и дополнительными блокировками. Другие функции, у которых rmt_channel_handle_t и rmt_sync_manager_handle_t являются первым позиционным параметром, не являются thread-safe. Это значит, что пользователь должен избегать их использовангие из нескольких задач.
В ISR-контексте разрешено использовать только функцию rmt_receive().
[Kconfig Options]
CONFIG_RMT_TX_ISR_CACHE_SAFE и CONFIG_RMT_RX_ISR_CACHE_SAFE управляют, может ли по умолчанию обработчик ISR работать в условиях запрещенного cache(3).
CONFIG_RMT_ENABLE_DEBUG_LOG используется для разрешения лога отладки ценой увеличения размера бинарного кода firmware.
CONFIG_RMT_RECV_FUNC_IN_IRAM управляет, куда поместить функцию приема RMT (IRAM или Flash)(3).
Примечание (3): см. также Cache Safe для дополнительной информации.
[Примеры приложений]
peripherals/rmt/led_strip демонстрирует, как использовать периферийное устройство RMT для управления лентой WS2812 LED. Можно поменять количество LED в ленте и эффект бегущего огня.
peripherals/rmt/led_strip_simple_encoder демонстрирует, как использовать периферийное устройство RMT для управления лентой WS2812 LED путем реализации callback-функции, которая преобразует пиксели RGB в формат, распознаваемый аппаратурой.
peripherals/rmt/ir_nec_transceiver демонстрирует, как использовать периферийное устройство RMT для реализации кодирования и декодирования протокола дистанционного управления IR NEC.
peripherals/rmt/dshot_esc демонстрирует, как использовать канал RMT TX для бесконечной передачи в цикле. Конструируется кодер RMT для цифрового протокола DShot. Этот протокол используется в основном для обмена между полетными контроллерами и контроллерами управления скоростью, предоставляя хорошую защищенность от электрических помех в сравнении с традиционными аналоговыми протоколами.
peripherals/rmt/onewire демонстрирует, как симулировать аппаратный протокол 1-wire с помощью библиотеки onewire_bus, и считывать данные из нескольких датчиков температуры DS18B20, параллельно подключенных к одной шине. Эта библиотека собрана на паре каналов приема и передачи периферийного устройства RMT.
peripherals/rmt/musical_buzzer демонстрирует, как использовать канал RMT TX для управления пассивной пищалкой, чтобы проиграть простую мелодию. Каждая музыкальная нота представлена постоянной частотой сигнала PWM фиксированной длительности.
[FAQ]
Почему RMT передает больше данных, чем ожидалось?
Кодирование передачи RMT происходит в контексте ISR. Если процесс кодирования RMT затянется (например, из-за вывода в лог или трассировки процедуры), или если он задерживается из-за латентности прерывания и вытесняющих прерываний, то аппаратный передатчик может прочитать из памяти до того, как кодер запишет в нее. Следовательно, передатчик будет в конечном итоге отправлять устаревшие данные. Хотя невозможно дать команду передатчику сделать паузу и подождать, эту проблему можно решить, используя комбинацию следующих стратегий:
- Увеличить rmt_tx_channel_config_t::mem_block_symbols, шагами по 48. - Поместить функцию кодирования в IRAM с помощью атрибута IRAM_ATTR. - Разрешить rmt_tx_channel_config_t::with_dma, если доступен DMA. - Инсталлировать драйвер RMT на отдельном ядре CPU, чтобы избежать конфликтов ресурсов на одном и том же CPU с другими периферийными устройствами, активно использующими прерывания (например такими, как WiFi, Bluetooth).
Эти заголовочные файлы декларируют API, предоставляемое компонентом esp_driver_rmt. Чтобы определить, что ваш компонент (например приложение main, которое тоже является компонентом), добавьте в свой CMakeLists.txt:
REQUIRES esp_driver_rmt
или
PRIV_REQUIRES esp_driver_rmt
В следующей таблице приведено краткое описание API-функций драйвера RMT. Полное описание функций, используемых структур и типов см. в документации [1].
Функция
Описание
rmt_new_tx_channel
Создает канал RMT TX.
rmt_transmit
Передает данные через канал RMT TX.
rmt_tx_wait_all_done
Ожидание завершения всех транзакций TX.
rmt_tx_register_event_callbacks
Установит обработчики событий (event callbacks) для канала RMT TX.
rmt_new_sync_manager
Создаст менеджер синхронизации (sync manager) для нескольких каналов TX, чтобы управляемые им каналы могли начать передачу одновременно.
rmt_del_sync_manager
Удалит менеджер синхронизации.
rmt_sync_reset
Сбросит менеджер синхронизации.
rmt_tx_switch_gpio
Переключит GPIO для канала RMT TX.
rmt_new_rx_channel
Создаст канал RMT RX.
rmt_receive
Инициирует задание приема для канала RMT RX.
rmt_rx_register_event_callbacks
Установит обработчики событий (event callbacks) для канала RMT RX.
rmt_del_channel
Удалит канал RMT.
rmt_apply_carrier
Применит фичу модуляции для канала TX или фичу демодуляции для канала RX.
rmt_enable
Разрешит канал RMT.
rmt_disable
Запретит канал RMT.
rmt_new_bytes_encoder
Создаст кодер байт RMT, который может кодировать поток байт в символы RMT.
rmt_bytes_encoder_update_config
Обновит конфигурацию кодера байт.
rmt_new_copy_encoder
Создаст копию кодера RMT, который копирует указанные символы RMT в память RMT.
rmt_new_bitscrambler_encoder
Создает кодер RMT BitScrambler.
rmt_new_simple_encoder
Создает RMT simple callback encoder, который использует callback-функцию для преобразования приходящих данных в символы RMT.
rmt_del_encoder
Удалит кодер RMT.
rmt_encoder_reset
Сбросит кодер RMT.
rmt_alloc_encoder_mem
Вспомогательная функция для правильного выделения памяти кодеру RMT.
[Ссылки]
1. Remote Control Transceiver (RMT) site:docs.espressif.com. 2. Composite pattern site:wikipedia.org.