Merge branch 'docs/add_Chinese_translation_for_api-reference/peripherals/rmt.rst_backport_v5.0' into 'release/v5.0'

docs: provide CN translation for api-reference/peripherals/rmt.rst (backport v5.0)

See merge request espressif/esp-idf!24539
This commit is contained in:
morris 2023-07-11 16:15:15 +08:00
commit c227759a4d
2 changed files with 825 additions and 164 deletions

View File

@ -1,25 +1,27 @@
Remote Control Transceiver (RMT)
================================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
The RMT (Remote Control Transceiver) peripheral was designed to act as an infrared transceiver. However, due to the flexibility of its data format, the functionality of RMT can be extended to a versatile and general purpose transceiver. From the perspective of network layering, the RMT hardware contains both physical and data link layer. The physical layer defines the communication media and bit signal representation. The data link layer defines the format of an RMT frame. The minimal data unit in the frame is called **RMT symbol**, which is represented by :cpp:type:`rmt_symbol_word_t` in the driver.
The RMT (Remote Control Transceiver) peripheral was designed to act as an infrared transceiver. However, due to the flexibility of its data format, RMT can be extended to a versatile and general-purpose transceiver, transmitting or receiving many other types of signals. From the perspective of network layering, the RMT hardware contains both physical and data link layers. The physical layer defines the communication media and bit signal representation. The data link layer defines the format of an RMT frame. The minimal data unit in the frame is called the **RMT symbol**, which is represented by :cpp:type:`rmt_symbol_word_t` in the driver.
{IDF_TARGET_NAME} contains multiple channels in the RMT peripheral. [1]_ Each channel can be configured as either transmitter or receiver, independently.
{IDF_TARGET_NAME} contains multiple channels in the RMT peripheral [1]_. Each channel can be independently configured as either transmitter or receiver.
Typically, the RMT peripheral can be used in the following scenarios:
- Transmit or receive infrared signals, with any IR protocols, e.g. NEC
- General purpose sequence generator
- Transmit signals in a hardware controlled loop, with finite or infinite number of times
- Multi-channel simultaneous transmission
- Modulate the carrier to the output signal or demodulate the carrier from the input signal
- Transmit or receive infrared signals, with any IR protocols, e.g., NEC
- General-purpose sequence generator
- Transmit signals in a hardware-controlled loop, with a finite or infinite number of times
- Multi-channel simultaneous transmission
- Modulate the carrier to the output signal or demodulate the carrier from the input signal
Layout of RMT Symbols
^^^^^^^^^^^^^^^^^^^^^
The RMT hardware defines data in its own pattern -- the **RMT symbol**. Each symbol consists of two pairs of two values. The first value in a pair describes the signal duration in RMT ticks and is 15 bits long. The second provides the signal level (high or low) and is contained in a single bit, as shown below:
The RMT hardware defines data in its own pattern -- the **RMT symbol**. The diagram below illustrates the bit fields of an RMT symbol. Each symbol consists of two pairs of two values. The first value in the pair is a 15-bit value representing the signal's duration in units of RMT ticks. The second in the pair is a 1-bit value representing the signal's logic level, i.e., high or low.
.. packetdiag:: /../_static/diagrams/rmt/rmt_symbols.diag
:caption: Structure of RMT symbols (L - signal level)
@ -34,7 +36,7 @@ The data path and control path of an RMT TX channel is illustrated in the figure
:caption: RMT Transmitter Overview
:align: center
The driver will encode user's data into RMT data format, then the RMT transmitter can generate the waveforms according to the encoding artifacts. It is also possible to modulate a high frequency carrier signal before being routed to a GPIO pad.
The driver encodes the user's data into RMT data format, then the RMT transmitter can generate the waveforms according to the encoding artifacts. It is also possible to modulate a high-frequency carrier signal before being routed to a GPIO pad.
RMT Receiver Overview
^^^^^^^^^^^^^^^^^^^^^
@ -45,190 +47,218 @@ The data path and control path of an RMT RX channel is illustrated in the figure
:caption: RMT Receiver Overview
:align: center
The RMT receiver can sample incoming signals into RMT data format, and store the data in memory. It's feasible to tell the receiver the basic characteristics of the incoming signal, so that the signal's stop condition can be recognized, and signal glitches and noise can be filtered out. The RMT peripheral also supports demodulating the high frequency carrier from the base signal.
The RMT receiver can sample incoming signals into RMT data format, and store the data in memory. It is also possible to tell the receiver the basic characteristics of the incoming signal, so that the signal's stop condition can be recognized, and signal glitches and noise can be filtered out. The RMT peripheral also supports demodulating the high-frequency carrier from the base signal.
Functional Overview
-------------------
Description of the RMT functionality is divided into the following sections:
The description of the RMT functionality is divided into the following sections:
- `Resource Allocation <#resource-allocation>`__ - covers how to allocate RMT channels with properly set of configurations. It also covers how to recycle the resources when they finished working.
- `Carrier Modulation and Demodulation <#carrier-modulation-and-demodulation>`__ - describes how to modulate carrier for TX channel and demodulate carrier for RX channel.
- `Register Event Callbacks <#register-event-callbacks>`__ - covers how to hook user specific code to RMT channel specific events.
- `Enable and Disable channel <#enable-and-disable-channel>`__ - shows how to enable and disable the RMT channel.
- `Initiate TX Transaction <#initiate-tx-transaction>`__ - describes the steps to initiate a transaction for TX channel.
- `Initiate RX Transaction <#initiate-rx-transaction>`__ - describes the steps to initiate a transaction for RX channel.
- `Multiple Channels Simultaneous Transmission <#multiple-channels-simultaneous-transmission>`__ - describes how to collect multiple channels into a sync group and start transaction at the same time.
- `RMT Encoder <#rmt-encoder>`__ - focuses on how to write a customized encoder in a combination way, with the help of the primitive encoders provided by the driver.
- `Power Management <#power-management>`__ - describes how different source clock will affect power consumption.
- `IRAM Safe <#iram-safe>`__ - describes tips on how to make the RMT interrupt work better along with a disabled cache.
- `Thread Safety <#thread-safety>`__ - lists which APIs are guaranteed to be thread safe by the driver.
- `Kconfig Options <#kconfig-options>`__ - lists the supported Kconfig options that can bring different effects to the driver.
- :ref:`rmt-resource-allocation` - covers how to allocate and properly configure RMT channels. It also covers how to recycle channels and other resources when they are no longer used.
- :ref:`rmt-carrier-modulation-and demodulation` - describes how to modulate and demodulate the carrier signals for TX and RX channels respectively.
- :ref:`rmt-register-event-callbacks` - covers how to register user-provided event callbacks in order to receive RMT channel events.
- :ref:`rmt-enable-and-disable-channel` - shows how to enable and disable the RMT channel.
- :ref:`rmt-initiate-tx-transaction` - describes the steps to initiate a transaction for a TX channel.
- :ref:`rmt-initiate-rx-transaction` - describes the steps to initiate a transaction for an RX channel.
- :ref:`rmt-multiple-channels-simultaneous-transmission` - describes how to collect multiple channels into a sync group so that their transmissions can be started simultaneously.
- :ref:`rmt-rmt-encoder` - focuses on how to write a customized encoder by combining multiple primitive encoders that are provided by the driver.
- :ref:`rmt-power-management` - describes how different clock sources affects power consumption.
- :ref:`rmt-iram-safe` - describes how disabling the cache affects the RMT driver, and tips to mitigate it.
- :ref:`rmt-thread-safety` - lists which APIs are guaranteed to be thread-safe by the driver.
- :ref:`rmt-kconfig-options` - describes the various Kconfig options supported by the RMT driver.
.. _rmt-resource-allocation:
Resource Allocation
^^^^^^^^^^^^^^^^^^^
Both RMT TX and RX channels are represented by :cpp:type:`rmt_channel_handle_t` in the driver. The available channels are managed in a resource pool, which will hand out a free channel on request.
Both RMT TX and RX channels are represented by :cpp:type:`rmt_channel_handle_t` in the driver. The driver internally manages which channels are available and hands out a free channel on request.
Install RMT TX Channel
~~~~~~~~~~~~~~~~~~~~~~
To install an RMT TX channel, there's a configuration structure that needs to be given in advance: :cpp:type:`rmt_tx_channel_config_t`:
To install an RMT TX channel, there is a configuration structure that needs to be given in advance :cpp:type:`rmt_tx_channel_config_t`. The following list describes each member of the configuration structure.
- :cpp:member:`rmt_tx_channel_config_t::gpio_num` sets the GPIO number used by the transmitter.
- :cpp:member:`rmt_tx_channel_config_t::clk_src` selects the source clock for the RMT channel. The available clocks are listed in :cpp:type:`rmt_clock_source_t`. Note that, the selected clock will also be used by other channels, which means user should ensure this configuration is same when allocating other channels, regardless of TX or RX. For the effect on power consumption of different clock source, please refer to `Power Management <#power-management>`__ section.
- :cpp:member:`rmt_tx_channel_config_t::resolution_hz` sets the resolution of the internal tick counter. The timing parameter of RMT signal is calculated based on this **tick**.
- :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols` this field have a slightly different meaning based on if the DMA backend is enabled or not. If the DMA is enabled via :cpp:member:`rmt_tx_channel_config_t::with_dma`, then this field controls the size of the internal DMA buffer. To achieve a better throughput and smaller CPU overhead, we recommend you to set a large value, e.g. ``1024``. If DMA is not used, this field controls the size of the dedicated memory block that owned by the channel, which should be at least {IDF_TARGET_SOC_RMT_MEM_WORDS_PER_CHANNEL}.
- :cpp:member:`rmt_tx_channel_config_t::trans_queue_depth` sets the depth of internal transaction queue, the deeper the queue, the more transactions can be prepared in the backlog.
- :cpp:member:`rmt_tx_channel_config_t::invert_out` is used to decide whether to invert the RMT signal before sending it to the GPIO pad.
- :cpp:member:`rmt_tx_channel_config_t::with_dma` is used to indicate if the channel needs a DMA backend. A channel with DMA attached can offload the CPU by a lot. However, DMA backend is not available on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before you enable this option. Or you might encounter :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
- :cpp:member:`rmt_tx_channel_config_t::io_loop_back` enables both the GPIO's input and output ability through the GPIO matrix peripheral. Meanwhile, if both TX and RX channels are bound to the same GPIO, then monitoring of the data transmission line can be realized.
- :cpp:member:`rmt_tx_channel_config_t::io_od_mode` configures the GPIO as open-drain mode. It is useful for simulating bi-directional buses, sucn as 1-wire bus, combined with :cpp:member:`rmt_tx_channel_config_t::io_loop_back`.
- :cpp:member:`rmt_tx_channel_config_t::gpio_num` sets the GPIO number used by the transmitter.
- :cpp:member:`rmt_tx_channel_config_t::clk_src` selects the source clock for the RMT channel. The available clocks are listed in :cpp:type:`rmt_clock_source_t`. Note that, the selected clock is also used by other channels, which means the user should ensure this configuration is the same when allocating other channels, regardless of TX or RX. For the effect on the power consumption of different clock sources, please refer to the :ref:`rmt-power-management` section.
- :cpp:member:`rmt_tx_channel_config_t::resolution_hz` sets the resolution of the internal tick counter. The timing parameter of the RMT signal is calculated based on this **tick**.
- :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols` has a slightly different meaning based on if the DMA backend is enabled or not.
Once the :cpp:type:`rmt_tx_channel_config_t` structure is populated with mandatory parameters, users can call :cpp:func:`rmt_new_tx_channel` to allocate and initialize a TX channel. This function will return an RMT channel handle if it runs correctly. Specifically, when there are no more free channels in the RMT resource pool, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. If some feature (e.g. DMA backend) is not supported by hardware, it will return :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
- If the DMA is enabled via :cpp:member:`rmt_tx_channel_config_t::with_dma`, then this field controls the size of the internal DMA buffer. To achieve a better throughput and smaller CPU overhead, we recommend you set a large value, e.g., ``1024``.
- If DMA is not used, this field controls the size of the dedicated memory block owned by the channel, which should be at least {IDF_TARGET_SOC_RMT_MEM_WORDS_PER_CHANNEL}.
.. code:: c
- :cpp:member:`rmt_tx_channel_config_t::trans_queue_depth` sets the depth of the internal transaction queue, the deeper the queue, the more transactions can be prepared in the backlog.
- :cpp:member:`rmt_tx_channel_config_t::invert_out` is used to decide whether to invert the RMT signal before sending it to the GPIO pad.
- :cpp:member:`rmt_tx_channel_config_t::with_dma` enables the DMA backend for the channel. Using the DMA allows a significant amount of the channel's workload to be offloaded from the CPU. However, the DMA backend is not available on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before you enable this option. Or you might encounter a :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
- :cpp:member:`rmt_tx_channel_config_t::io_loop_back` enables both input and output capabilities on the channel's assigned GPIO. Thus, by binding a TX and RX channel to the same GPIO, loopback can be achieved.
- :cpp:member:`rmt_tx_channel_config_t::io_od_mode` configures the channel's assigned GPIO as open-drain. When combined with :cpp:member:`rmt_tx_channel_config_t::io_loop_back`, a bi-directional bus (e.g., 1-wire) can be achieved.
Once the :cpp:type:`rmt_tx_channel_config_t` structure is populated with mandatory parameters, users can call :cpp:func:`rmt_new_tx_channel` to allocate and initialize a TX channel. This function returns an RMT channel handle if it runs correctly. Specifically, when there are no more free channels in the RMT resource pool, this function returns :c:macro:`ESP_ERR_NOT_FOUND` error. If some feature (e.g., DMA backend) is not supported by the hardware, it returns :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
.. code-block:: c
rmt_channel_handle_t tx_chan = NULL;
rmt_tx_channel_config_t tx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // select source clock
.gpio_num = 0, // GPIO number
.mem_block_symbols = 64, // memory block size, 64 * 4 = 256Bytes
.resolution_hz = 1 * 1000 * 1000, // 1MHz tick resolution, i.e. 1 tick = 1us
.mem_block_symbols = 64, // memory block size, 64 * 4 = 256 Bytes
.resolution_hz = 1 * 1000 * 1000, // 1 MHz tick resolution, i.e., 1 tick = 1 µs
.trans_queue_depth = 4, // set the number of transactions that can pend in the background
.flags.invert_out = false, // don't invert output signal
.flags.with_dma = false, // don't need DMA backend
.flags.invert_out = false, // do not invert output signal
.flags.with_dma = false, // do not need DMA backend
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &tx_chan));
Install RMT RX Channel
~~~~~~~~~~~~~~~~~~~~~~
To install an RMT RX channel, there's a configuration structure that needs to be given in advance: :cpp:type:`rmt_rx_channel_config_t`:
To install an RMT RX channel, there is a configuration structure that needs to be given in advance :cpp:type:`rmt_rx_channel_config_t`. The following list describes each member of the configuration structure.
- :cpp:member:`rmt_rx_channel_config_t::gpio_num` sets the GPIO number used by the receiver.
- :cpp:member:`rmt_rx_channel_config_t::clk_src` selects the source clock for the RMT channel. The available clocks are listed in :cpp:type:`rmt_clock_source_t`. Note that, the selected clock will also be used by other channels, which means user should ensure this configuration is same when allocating other channels, regardless of TX or RX. For the effect on power consumption of different clock source, please refer to `Power Management <#power-management>`__ section.
- :cpp:member:`rmt_rx_channel_config_t::resolution_hz` sets the resolution of the internal tick counter. The timing parameter of RMT signal is calculated based on this **tick**.
- :cpp:member:`rmt_rx_channel_config_t::mem_block_symbols` this field have a slightly different meaning based on if the DMA backend is enabled or not. If the DMA is enabled via :cpp:member:`rmt_rx_channel_config_t::with_dma`, then this field controls the maximum size of the DMA buffer. If DMA is not used, this field controls the size of the dedicated memory block that owned by the channel, which should be at least {IDF_TARGET_SOC_RMT_MEM_WORDS_PER_CHANNEL}.
- :cpp:member:`rmt_rx_channel_config_t::invert_in` is used to decide whether to invert the input signals before they going into RMT receiver. The inversion is done by GPIO matrix instead of by the RMT peripheral.
- :cpp:member:`rmt_rx_channel_config_t::with_dma` is used to indicate if the channel needs a DMA backend. A channel with DMA attached can offload the CPU by a lot. However, DMA backend is not available on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before you enable this option. Or you might encounter :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
- :cpp:member:`rmt_rx_channel_config_t::io_loop_back` is for debugging purposes only. It enables both the GPIO's input and output ability through the GPIO matrix peripheral. Meanwhile, if both TX and RX channels are bound to the same GPIO, then monitoring of the data transmission line can be realized.
- :cpp:member:`rmt_rx_channel_config_t::gpio_num` sets the GPIO number used by the receiver.
- :cpp:member:`rmt_rx_channel_config_t::clk_src` selects the source clock for the RMT channel. The available clocks are listed in :cpp:type:`rmt_clock_source_t`. Note that, the selected clock is also used by other channels, which means the user should ensure this configuration is the same when allocating other channels, regardless of TX or RX. For the effect on the power consumption of different clock sources, please refer to the :ref:`rmt-power-management` section.
- :cpp:member:`rmt_rx_channel_config_t::resolution_hz` sets the resolution of the internal tick counter. The timing parameter of the RMT signal is calculated based on this **tick**.
- :cpp:member:`rmt_rx_channel_config_t::mem_block_symbols` has a slightly different meaning based on whether the DMA backend is enabled.
Once the :cpp:type:`rmt_rx_channel_config_t` structure is populated with mandatory parameters, users can call :cpp:func:`rmt_new_rx_channel` to allocate and initialize a RX channel. This function will return an RMT channel handle if it runs correctly. Specifically, when there are no more free channels in the RMT resource pool, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. If some feature (e.g. DMA backend) is not supported by hardware, it will return :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
- If the DMA is enabled via :cpp:member:`rmt_rx_channel_config_t::with_dma`, this field controls the maximum size of the DMA buffer.
- If DMA is not used, this field controls the size of the dedicated memory block owned by the channel, which should be at least {IDF_TARGET_SOC_RMT_MEM_WORDS_PER_CHANNEL}.
.. code:: c
- :cpp:member:`rmt_rx_channel_config_t::invert_in` is used to invert the input signals before it is passed to the RMT receiver. The inversion is done by the GPIO matrix instead of by the RMT peripheral.
- :cpp:member:`rmt_rx_channel_config_t::with_dma` enables the DMA backend for the channel. Using the DMA allows a significant amount of the channel's workload to be offloaded from the CPU. However, the DMA backend is not available on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before you enable this option. Or you might encounter a :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
- :cpp:member:`rmt_rx_channel_config_t::io_loop_back` enables both input and output capabilities on the channel's assigned GPIO. Thus, by binding a TX and RX channel to the same GPIO, loopback can be achieved.
Once the :cpp:type:`rmt_rx_channel_config_t` structure is populated with mandatory parameters, users can call :cpp:func:`rmt_new_rx_channel` to allocate and initialize an RX channel. This function returns an RMT channel handle if it runs correctly. Specifically, when there are no more free channels in the RMT resource pool, this function returns :c:macro:`ESP_ERR_NOT_FOUND` error. If some feature (e.g., DMA backend) is not supported by the hardware, it returns :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
.. code-block:: c
rmt_channel_handle_t rx_chan = NULL;
rmt_rx_channel_config_t rx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // select source clock
.resolution_hz = 1 * 1000 * 1000, // 1MHz tick resolution, i.e. 1 tick = 1us
.mem_block_symbols = 64, // memory block size, 64 * 4 = 256Bytes
.resolution_hz = 1 * 1000 * 1000, // 1 MHz tick resolution, i.e., 1 tick = 1 µs
.mem_block_symbols = 64, // memory block size, 64 * 4 = 256 Bytes
.gpio_num = 2, // GPIO number
.flags.invert_in = false, // don't invert input signal
.flags.with_dma = false, // don't need DMA backend
.flags.invert_in = false, // do not invert input signal
.flags.with_dma = false, // do not need DMA backend
};
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_chan));
Uninstall RMT Channel
~~~~~~~~~~~~~~~~~~~~~
If a previously installed RMT channel is no longer needed, it's recommended to recycle the resources by calling :cpp:func:`rmt_del_channel`, which in return allows the underlying hardware to be usable for other purposes.
If a previously installed RMT channel is no longer needed, it is recommended to recycle the resources by calling :cpp:func:`rmt_del_channel`, which in return allows the underlying software and hardware resources to be reused for other purposes.
.. _rmt-carrier-modulation-and demodulation:
Carrier Modulation and Demodulation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The RMT transmitter can generate a carrier wave and modulate it onto the base signal. Compared to the base signal, the carrier frequency is usually high. In addition, user can only set the frequency and duty cycle for the carrier. The RMT receiver can demodulate the carrier from the incoming signal. Note that, carrier modulation and demodulation is not supported on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before configuring the carrier, or you might encounter a :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
The RMT transmitter can generate a carrier wave and modulate it onto the message signal. Compared to the message signal, the carrier signal's frequency is significantly higher. In addition, the user can only set the frequency and duty cycle for the carrier signal. The RMT receiver can demodulate the carrier signal from the incoming signal. Note that, carrier modulation and demodulation are not supported on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before configuring the carrier, or you might encounter a :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
Carrier related configurations lie in :cpp:type:`rmt_carrier_config_t`:
Carrier-related configurations lie in :cpp:type:`rmt_carrier_config_t`:
- :cpp:member:`rmt_carrier_config_t::frequency_hz` sets the carrier frequency, in Hz.
- :cpp:member:`rmt_carrier_config_t::duty_cycle` sets the carrier duty cycle.
- :cpp:member:`rmt_carrier_config_t::polarity_active_low` sets the carrier polarity, i.e. on which level the carrier is applied.
- :cpp:member:`rmt_carrier_config_t::always_on` sets whether to output the carrier even when the data transmission has finished. This configuration is only valid for TX channel.
- :cpp:member:`rmt_carrier_config_t::frequency_hz` sets the carrier frequency, in Hz.
- :cpp:member:`rmt_carrier_config_t::duty_cycle` sets the carrier duty cycle.
- :cpp:member:`rmt_carrier_config_t::polarity_active_low` sets the carrier polarity, i.e., on which level the carrier is applied.
- :cpp:member:`rmt_carrier_config_t::always_on` sets whether to output the carrier even when the data transmission has finished. This configuration is only valid for the TX channel.
.. note::
For RX channel, we shouldn't set the carrier frequency exactly to the theoretical value. It's recommended to leave a tolerance for the carrier frequency. For example, in the snippet below, we set the frequency to 25KHz, instead of the 38KHz that configured on the TX side. The reason is that reflection and refraction will occur when a signal travels through the air, leading to the a distortion on the receiver side.
For the RX channel, we should not set the carrier frequency exactly to the theoretical value. It is recommended to leave a tolerance for the carrier frequency. For example, in the snippet below, we set the frequency to 25 KHz, instead of the 38 KHz configured on the TX side. The reason is that reflection and refraction occur when a signal travels through the air, leading to distortion on the receiver side.
.. code:: c
.. code-block:: c
rmt_carrier_config_t tx_carrier_cfg = {
.duty_cycle = 0.33, // duty cycle 33%
.frequency_hz = 38000, // 38KHz
.flags.polarity_active_low = false, // carrier should modulated to high level
.frequency_hz = 38000, // 38 KHz
.flags.polarity_active_low = false, // carrier should be modulated to high level
};
// modulate carrier to TX channel
ESP_ERROR_CHECK(rmt_apply_carrier(tx_chan, &tx_carrier_cfg));
rmt_carrier_config_t rx_carrier_cfg = {
.duty_cycle = 0.33, // duty cycle 33%
.frequency_hz = 25000, // 25KHz carrier, should be smaller than transmitter's carrier frequency
.frequency_hz = 25000, // 25 KHz carrier, should be smaller than the transmitter's carrier frequency
.flags.polarity_active_low = false, // the carrier is modulated to high level
};
// demodulate carrier from RX channel
ESP_ERROR_CHECK(rmt_apply_carrier(rx_chan, &rx_carrier_cfg));
.. _rmt-register-event-callbacks:
Register Event Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^
When an RMT channel finishes transmitting or receiving, a specific event will be generated and notify the CPU by interrupt. If you have some function that needs to be called when those events occurred, you can hook your function to the ISR (Interrupt Service Routine) by calling :cpp:func:`rmt_tx_register_event_callbacks` and :cpp:func:`rmt_rx_register_event_callbacks` for TX and RX channel respectively. Since the registered callback functions are called in the interrupt context, user should ensure the callback function doesn't attempt to block (e.g. by making sure that only FreeRTOS APIs with ``ISR`` suffix are called from within the function). The callback function has a boolean return value, to tell the caller whether a high priority task is woke up by it.
When an event occurs on an RMT channel (e.g., transmission or receiving is completed), the CPU is notified of this event via an interrupt. If you have some function that needs to be called when a particular events occur, you can register a callback for that event to the RMT driver's ISR (Interrupt Service Routine) by calling :cpp:func:`rmt_tx_register_event_callbacks` and :cpp:func:`rmt_rx_register_event_callbacks` for TX and RX channel respectively. Since the registered callback functions are called in the interrupt context, the user should ensure the callback function does not block, e.g., by making sure that only FreeRTOS APIs with the ``FromISR`` suffix are called from within the function. The callback function has a boolean return value used to indicate whether a higher priority task has been unblocked by the callback.
TX channel supported event callbacks are listed in the :cpp:type:`rmt_tx_event_callbacks_t`:
The TX channel-supported event callbacks are listed in the :cpp:type:`rmt_tx_event_callbacks_t`:
- :cpp:member:`rmt_tx_event_callbacks_t::on_trans_done` sets a callback function for trans done event. The function prototype is declared in :cpp:type:`rmt_tx_done_callback_t`.
- :cpp:member:`rmt_tx_event_callbacks_t::on_trans_done` sets a callback function for the "trans-done" event. The function prototype is declared in :cpp:type:`rmt_tx_done_callback_t`.
RX channel supported event callbacks are listed in the :cpp:type:`rmt_rx_event_callbacks_t`:
The RX channel-supported event callbacks are listed in the :cpp:type:`rmt_rx_event_callbacks_t`:
- :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` sets a callback function for receive complete event. The function prototype is declared in :cpp:type:`rmt_rx_done_callback_t`.
- :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` sets a callback function for "receive-done" event. The function prototype is declared in :cpp:type:`rmt_rx_done_callback_t`.
User can save own context in :cpp:func:`rmt_tx_register_event_callbacks` and :cpp:func:`rmt_rx_register_event_callbacks` as well, via the parameter ``user_data``. The user data will be directly passed to each callback function.
Users can save their own context in :cpp:func:`rmt_tx_register_event_callbacks` and :cpp:func:`rmt_rx_register_event_callbacks` as well, via the parameter ``user_data``. The user data is directly passed to each callback function.
In the callback function, users can fetch the event specific data that is filled by the driver in the ``edata``. Note that the ``edata`` pointer is only valid for the duration of the callback.
In the callback function, users can fetch the event-specific data that is filled by the driver in the ``edata``. Note that the ``edata`` pointer is only valid for the duration of the callback.
The TX done event data is defined in :cpp:type:`rmt_tx_done_event_data_t`:
The TX-done event data is defined in :cpp:type:`rmt_tx_done_event_data_t`:
- :cpp:member:`rmt_tx_done_event_data_t::num_symbols` tells the number of transmitted RMT symbols. This also reflects the size of encoding artifacts.
- :cpp:member:`rmt_tx_done_event_data_t::num_symbols` indicates the number of transmitted RMT symbols. This also reflects the size of the encoding artifacts. Please note, this value accounts for the ``EOF`` symbol as well, which is appended by the driver to mark the end of one transaction.
The RX complete event data is defined in :cpp:type:`rmt_rx_done_event_data_t`:
The RX-complete event data is defined in :cpp:type:`rmt_rx_done_event_data_t`:
- :cpp:member:`rmt_rx_done_event_data_t::received_symbols` points to the received RMT symbols. These symbols are saved in the ``buffer`` parameter of :cpp:func:`rmt_receive` function. User shouldn't free this receive buffer before the callback returns.
- :cpp:member:`rmt_rx_done_event_data_t::num_symbols` tells the number of received RMT symbols. This value won't be bigger than ``buffer_size`` parameter of :cpp:func:`rmt_receive` function. If the ``buffer_size`` is not sufficient to accommodate all the received RMT symbols, the driver will truncate it.
- :cpp:member:`rmt_rx_done_event_data_t::received_symbols` points to the received RMT symbols. These symbols are saved in the ``buffer`` parameter of the :cpp:func:`rmt_receive` function. Users should not free this receive buffer before the callback returns.
- :cpp:member:`rmt_rx_done_event_data_t::num_symbols` indicates the number of received RMT symbols. This value is not larger than the ``buffer_size`` parameter of :cpp:func:`rmt_receive` function. If the ``buffer_size`` is not sufficient to accommodate all the received RMT symbols, the driver only keeps the maximum number of symbols that the buffer can hold, and excess symbols are discarded or ignored.
Enable and Disable channel
.. _rmt-enable-and-disable-channel:
Enable and Disable Channel
^^^^^^^^^^^^^^^^^^^^^^^^^^
:cpp:func:`rmt_enable` must be called in advanced before transmitting or receiving RMT symbols. For transmitters, enabling a channel will enable a specific interrupt and prepare the hardware to dispatch transactions. For RX channels, enabling a channel will enable an interrupt, but the receiver is not started during this time, as it has no idea about the characteristics of the incoming signals. The receiver will be started in :cpp:func:`rmt_receive`.
:cpp:func:`rmt_enable` must be called in advance before transmitting or receiving RMT symbols. For TX channels, enabling a channel enables a specific interrupt and prepares the hardware to dispatch transactions. For RX channels, enabling a channel enables an interrupt, but the receiver is not started during this time, as the characteristics of the incoming signal have yet to be specified. The receiver is started in :cpp:func:`rmt_receive`.
:cpp:func:`rmt_disable` does the opposite work by disabling the interrupt and clearing pending status. The transmitter and receiver will be disabled as well.
:cpp:func:`rmt_disable` does the opposite by disabling the interrupt and clearing any pending interrupts. The transmitter and receiver are disabled as well.
.. code:: c
ESP_ERROR_CHECK(rmt_enable(tx_chan));
ESP_ERROR_CHECK(rmt_enable(rx_chan));
.. _rmt-initiate-tx-transaction:
Initiate TX Transaction
^^^^^^^^^^^^^^^^^^^^^^^
RMT is a special communication peripheral as it's unable to transmit raw byte streams like SPI and I2C. RMT can only send data in its own format :cpp:type:`rmt_symbol_word_t`. However, the hardware doesn't help to convert the user data into RMT symbols, this can only be done in software --- by the so-called **RMT Encoder**. The encoder is responsible for encoding user data into RMT symbols and then write to RMT memory block or DMA buffer. For how to create an RMT encoder, please refer to `RMT Encoder <#rmt-encoder>`__.
RMT is a special communication peripheral, as it is unable to transmit raw byte streams like SPI and I2C. RMT can only send data in its own format :cpp:type:`rmt_symbol_word_t`. However, the hardware does not help to convert the user data into RMT symbols, this can only be done in software by the so-called **RMT Encoder**. The encoder is responsible for encoding user data into RMT symbols and then writing to the RMT memory block or the DMA buffer. For how to create an RMT encoder, please refer to :ref:`rmt-rmt-encoder`.
Once we got an encoder, we can initiate a TX transaction by calling :cpp:func:`rmt_transmit`. This function takes several positional parameters like channel handle, encoder handle, payload buffer. Besides that, we also need to provide a transmission specific configuration in :cpp:type:`rmt_transmit_config_t`:
Once we got an encoder, we can initiate a TX transaction by calling :cpp:func:`rmt_transmit`. This function takes several positional parameters like channel handle, encoder handle, and payload buffer. Besides, we also need to provide a transmission-specific configuration in :cpp:type:`rmt_transmit_config_t`:
- :cpp:member:`rmt_transmit_config_t::loop_count` sets the number of transmission loop. After the transmitter finished one round of transmission, it can restart the same transmission again if this value is not set to zero. As the loop is controlled by hardware, the RMT channel can be used to generate many periodic sequences at the cost of a very little CPU intervention. Specially, setting :cpp:member:`rmt_transmit_config_t::loop_count` to `-1` means an infinite loop transmission. In this situation, the channel won't stop until manually call of :cpp:func:`rmt_disable`. And the trans done event won't be generated as well. If :cpp:member:`rmt_transmit_config_t::loop_count` is set to a positive number, the trans done event won't be generated until target number of loop transmission have finished. Note that, the **loop transmit** feature is not supported on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before you configure this option. Or you might encounter :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
- :cpp:member:`rmt_transmit_config_t::eot_level` sets the output level when the transmitter finishes working or stops working by calling :cpp:func:`rmt_disable`.
- :cpp:member:`rmt_transmit_config_t::loop_count` sets the number of transmission loops. After the transmitter has finished one round of transmission, it can restart the same transmission again if this value is not set to zero. As the loop is controlled by hardware, the RMT channel can be used to generate many periodic sequences with minimal CPU intervention.
- Setting :cpp:member:`rmt_transmit_config_t::loop_count` to `-1` means an infinite loop transmission. In this case, the channel does not stop until :cpp:func:`rmt_disable` is called. The "trans-done" event is not generated as well.
- Setting :cpp:member:`rmt_transmit_config_t::loop_count` to a positive number means finite number of iterations. In this case, the "trans-done" event is when the specified number of iterations have completed.
.. note::
The **loop transmit** feature is not supported on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before you configure this option, or you might encounter :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
- :cpp:member:`rmt_transmit_config_t::eot_level` sets the output level when the transmitter finishes working or stops working by calling :cpp:func:`rmt_disable`.
.. note::
There's a limitation in the transmission size if the :cpp:member:`rmt_transmit_config_t::loop_count` is set to non-zero (i.e. to enable the loop feature). The encoded RMT symbols should not exceed the capacity of RMT hardware memory block size. Or you might see error message like ``encoding artifacts can't exceed hw memory block for loop transmission``. If you have to start a large transaction by loop, you can try either:
There is a limitation in the transmission size if the :cpp:member:`rmt_transmit_config_t::loop_count` is set to non-zero, i.e., to enable the loop feature. The encoded RMT symbols should not exceed the capacity of the RMT hardware memory block size, or you might see an error message like ``encoding artifacts can't exceed hw memory block for loop transmission``. If you have to start a large transaction by loop, you can try either of the following methods.
- Increase the :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols`. This approach doesn't work if the DMA backend is also enabled.
- Customize an encoder and construct a forever loop in the encoding function. See also `RMT Encoder <#rmt-encoder>`__.
- Increase the :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols`. This approach does not work if the DMA backend is also enabled.
- Customize an encoder and construct an infinite loop in the encoding function. See also :ref:`rmt-rmt-encoder`.
Internally, :cpp:func:`rmt_transmit` will construct a transaction descriptor and send to a job queue, which will be dispatched in the ISR. So it is possible that the transaction is not started yet when :cpp:func:`rmt_transmit` returns. To ensure all pending transaction to complete, user can use :cpp:func:`rmt_tx_wait_all_done`.
Internally, :cpp:func:`rmt_transmit` constructs a transaction descriptor and sends it to a job queue, which is dispatched in the ISR. So it is possible that the transaction is not started yet when :cpp:func:`rmt_transmit` returns. To ensure all pending transactions to complete, the user can use :cpp:func:`rmt_tx_wait_all_done`.
.. _rmt-multiple-channels-simultaneous-transmission:
Multiple Channels Simultaneous Transmission
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In some real-time control applications, we don't want any time drift in between when startup multiple TX channels. For example, to make two robotic arms move simultaneously. The RMT driver can help to manage this by creating a so-called **Sync Manager**. The sync manager is represented by :cpp:type:`rmt_sync_manager_handle_t` in the driver. The procedure of RMT sync transmission is shown as follows:
In some real-time control applications (e.g., to make two robotic arms move simultaneously), we do not want any time drift in between when startup multiple TX channels. The RMT driver can help to manage this by creating a so-called **Sync Manager**. The sync manager is represented by :cpp:type:`rmt_sync_manager_handle_t` in the driver. The procedure of RMT sync transmission is shown as follows:
.. figure:: /../_static/rmt_tx_sync.png
:align: center
@ -239,19 +269,19 @@ In some real-time control applications, we don't want any time drift in between
Install RMT Sync Manager
~~~~~~~~~~~~~~~~~~~~~~~~
To create a sync manager, user needs to tell which channels are going to be managed in the :cpp:type:`rmt_sync_manager_config_t`:
To create a sync manager, the user needs to tell which channels are going to be managed in the :cpp:type:`rmt_sync_manager_config_t`:
- :cpp:member:`rmt_sync_manager_config_t::tx_channel_array` points to the array of TX channels to be managed.
- :cpp:member:`rmt_sync_manager_config_t::array_size` sets the number of channels to be managed.
- :cpp:member:`rmt_sync_manager_config_t::tx_channel_array` points to the array of TX channels to be managed.
- :cpp:member:`rmt_sync_manager_config_t::array_size` sets the number of channels to be managed.
:cpp:func:`rmt_new_sync_manager` can return a manager handle on success. This function could also fail due to various errors such as invalid arguments, etc. Specially, when the sync manager has been installed before, and there're no hardware resources to create another manager, this function will report :c:macro:`ESP_ERR_NOT_FOUND` error. In addition, if the sync manager is not supported by the hardware, it will report :c:macro:`ESP_ERR_NOT_SUPPORTED` error. Please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before using the sync manager feature.
:cpp:func:`rmt_new_sync_manager` can return a manager handle on success. This function could also fail due to various errors such as invalid arguments, etc. Especially, when the sync manager has been installed before, and there are no hardware resources to create another manager, this function reports :c:macro:`ESP_ERR_NOT_FOUND` error. In addition, if the sync manager is not supported by the hardware, it reports a :c:macro:`ESP_ERR_NOT_SUPPORTED` error. Please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before using the sync manager feature.
Start Transmission Simultaneously
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For any managed TX channel, it won't start the machine until all the channels in the :cpp:member:`rmt_sync_manager_config_t::tx_channel_array` are called with :cpp:func:`rmt_transmit`. Before that, the channel is just put in a waiting state. Different channel usually take different time to finish the job if the transaction is different, which results in a loss of sync. So user needs to call :cpp:func:`rmt_sync_reset` to pull the channels back to the starting line again before restarting a simultaneous transmission.
For any managed TX channel, it does not start the machine until :cpp:func:`rmt_transmit` has been called on all channels in :cpp:member:`rmt_sync_manager_config_t::tx_channel_array`. Before that, the channel is just put in a waiting state. TX channels will usually complete their transactions at different times due to differing transactions, thus resulting in a loss of sync. So before restarting a simultaneous transmission, the user needs to call :cpp:func:`rmt_sync_reset` to synchronize all channels again.
Calling :cpp:func:`rmt_del_sync_manager` can recycle the sync manager and enable the channels to initiate transactions independently afterwards.
Calling :cpp:func:`rmt_del_sync_manager` can recycle the sync manager and enable the channels to initiate transactions independently afterward.
.. code:: c
@ -262,8 +292,8 @@ Calling :cpp:func:`rmt_del_sync_manager` can recycle the sync manager and enable
rmt_tx_channel_config_t tx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // select source clock
.gpio_num = tx_gpio_number[i], // GPIO number
.mem_block_symbols = 64, // memory block size, 64 * 4 = 256Bytes
.resolution_hz = 1 * 1000 * 1000, // 1MHz resolution
.mem_block_symbols = 64, // memory block size, 64 * 4 = 256 Bytes
.resolution_hz = 1 * 1000 * 1000, // 1 MHz resolution
.trans_queue_depth = 1, // set the number of transactions that can pend in the background
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &tx_channels[i]));
@ -277,30 +307,32 @@ Calling :cpp:func:`rmt_del_sync_manager` can recycle the sync manager and enable
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] won't start transmission until call of `rmt_transmit()` for tx_channels[1] returns
// tx_channels[0] does not start transmission until call of `rmt_transmit()` for tx_channels[1] returns
ESP_ERROR_CHECK(rmt_transmit(tx_channels[1], led_strip_encoders[1], led_data, led_num * 3, &transmit_config));
.. _rmt-initiate-rx-transaction:
Initiate RX Transaction
^^^^^^^^^^^^^^^^^^^^^^^
As also discussed in the `Enable and Disable channel <#enable-and-disable-channel>`__, the RX channel still doesn't get ready to receive RMT symbols even user calls :cpp:func:`rmt_enable`. User needs to specify the basic characteristics of the incoming signals in :cpp:type:`rmt_receive_config_t`:
As also discussed in the :ref:`rmt-enable-and-disable-channel`, calling :cpp:func:`rmt_enable` does not prepare an RX to receive RMT symbols. The user needs to specify the basic characteristics of the incoming signals in :cpp:type:`rmt_receive_config_t`:
- :cpp:member:`rmt_receive_config_t::signal_range_min_ns` specifies the minimal valid pulse duration (either high or low level). A pulse whose width is smaller than this value will be treated as glitch and ignored by the hardware.
- :cpp:member:`rmt_receive_config_t::signal_range_max_ns` specifies the maximum valid pulse duration (either high or low level). A pulse whose width is bigger than this value will be treated as **Stop Signal**, and the receiver will generate receive complete event immediately.
- :cpp:member:`rmt_receive_config_t::signal_range_min_ns` specifies the minimal valid pulse duration in either high or low logic levels. A pulse width that is smaller than this value is treated as a glitch, and ignored by the hardware.
- :cpp:member:`rmt_receive_config_t::signal_range_max_ns` specifies the maximum valid pulse duration in either high or low logic levels. A pulse width that is bigger than this value is treated as **Stop Signal**, and the receiver generates receive-complete event immediately.
The RMT receiver will start the RX machine after user calls :cpp:func:`rmt_receive` with the provided configuration above. Note that, this configuration is transaction specific, which means, to start a new round of reception, user needs to sets the :cpp:type:`rmt_receive_config_t` again. The receiver saves the incoming signals into its internal memory block or DMA buffer, in the format of :cpp:type:`rmt_symbol_word_t`.
The RMT receiver starts the RX machine after the user calls :cpp:func:`rmt_receive` with the provided configuration above. Note that, this configuration is transaction specific, which means, to start a new round of reception, the user needs to set the :cpp:type:`rmt_receive_config_t` again. The receiver saves the incoming signals into its internal memory block or DMA buffer, in the format of :cpp:type:`rmt_symbol_word_t`.
.. only:: SOC_RMT_SUPPORT_RX_PINGPONG
Due to the limited size of memory block, the RMT receiver will notify the driver to copy away the accumulated symbols in a ping-pong way.
Due to the limited size of the memory block, the RMT receiver notifies the driver to copy away the accumulated symbols in a ping-pong way.
.. only:: not SOC_RMT_SUPPORT_RX_PINGPONG
Due to the limited size of memory block, the RMT receiver can only save short frames whose length is not longer than the memory block capacity. Long frames will be truncated by the hardware, and the driver will report an error message: ``hw buffer too small, received symbols truncated``.
Due to the limited size of the memory block, the RMT receiver can only save short frames whose length is not longer than the memory block capacity. Long frames are truncated by the hardware, and the driver reports an error message: ``hw buffer too small, received symbols truncated``.
The copy destination should be provided in the ``buffer`` parameter of :cpp:func:`rmt_receive` function. If this buffer size is not sufficient, the receiver can continue to work but later incoming symbols will be dropped and report an error message: ``user buffer too small, received symbols truncated``. Please take care of the lifecycle of the ``buffer`` parameter, user shouldn't recycle the buffer before the receiver finished or stopped working.
The copy destination should be provided in the ``buffer`` parameter of :cpp:func:`rmt_receive` function. If this buffer overlfows due to an insufficient buffer size, the receiver can continue to work, but overflowed symbols are dropped and the following error message is reported: ``user buffer too small, received symbols truncated``. Please take care of the lifecycle of the ``buffer`` parameter, ensuring that the buffer is not recycled before the receiver is finished or stopped.
The receiver will be stopped by the driver when it finishes working (i.e. received a signal whose duration is bigger than :cpp:member:`rmt_receive_config_t::signal_range_max_ns`). User needs to call :cpp:func:`rmt_receive` again to restart the receiver, is necessary. User can get the received data in the :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` callback. See also `Register Event Callbacks <#register-event-callbacks>`__ for more information.
The receiver is stopped by the driver when it finishes working, i.e., receive a signal whose duration is bigger than :cpp:member:`rmt_receive_config_t::signal_range_max_ns`. The user needs to call :cpp:func:`rmt_receive` again to restart the receiver, if necessary. The user can get the received data in the :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` callback. See also :ref:`rmt-register-event-callbacks` for more information.
.. code:: c
@ -322,51 +354,62 @@ The receiver will be stopped by the driver when it finishes working (i.e. receiv
// the following timing requirement is based on NEC protocol
rmt_receive_config_t receive_config = {
.signal_range_min_ns = 1250, // the shortest duration for NEC signal is 560us, 1250ns < 560us, valid signal won't be treated as noise
.signal_range_max_ns = 12000000, // the longest duration for NEC signal is 9000us, 12000000ns > 9000us, the receive won't stop early
.signal_range_min_ns = 1250, // the shortest duration for NEC signal is 560 µs, 1250 ns < 560 µs, valid signal is not treated as noise
.signal_range_max_ns = 12000000, // the longest duration for NEC signal is 9000 µs, 12000000 ns > 9000 µs, the receive does not stop early
};
rmt_symbol_word_t raw_symbols[64]; // 64 symbols should be sufficient for a standard NEC frame
// ready to receive
ESP_ERROR_CHECK(rmt_receive(rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config));
// wait for RX done signal
// wait for the RX-done signal
rmt_rx_done_event_data_t rx_data;
xQueueReceive(receive_queue, &rx_data, portMAX_DELAY);
// parse the receive symbols
// parse the received symbols
example_parse_nec_frame(rx_data.received_symbols, rx_data.num_symbols);
.. _rmt-rmt-encoder:
RMT Encoder
^^^^^^^^^^^
An RMT encoder is part of the RMT TX transaction, whose responsibility is to generate and write the correct RMT symbols into hardware memory (or DMA buffer) at specific time. There're some special restrictions for an encoding function:
An RMT encoder is part of the RMT TX transaction, whose responsibility is to generate and write the correct RMT symbols into hardware memory or DMA buffer at a specific time. There are some special restrictions for an encoding function:
- An encoding function might be called for several times within a single transaction. This is because the target RMT memory block can't accommodate all the artifacts at once. We have to use the memory in a **ping-pong** way, thus the encoding session is divided into multiple parts. This requires the encoder to be **stateful**.
- The encoding function is running in the ISR context. To speed up the encoding session, it's high recommend to put the encoding function into IRAM. This can also avoid the cache miss during encoding.
- During a single transaction, the encoding function may be called multiple times. This is necessary because the target RMT memory block cannot hold all the artifacts at once. To overcome this limitation, we utilize a **ping-pong** approach, where the encoding session is divided into multiple parts. This means that the encoder needs to **keep track of its state** in order to continue encoding from where it left off in the previous part.
- The encoding function is running in the ISR context. To speed up the encoding session, it is highly recommended to put the encoding function into IRAM. This can also avoid the cache miss during encoding.
To help get started with RMT driver faster, some commonly used encoders are provided out-of-the box. They can either work alone or chained together into a new encoder. See also `Composite Pattern <https://en.wikipedia.org/wiki/Composite_pattern>`__ for the principle behind. The driver has defined the encoder interface in :cpp:type:`rmt_encoder_t`, it contains the following functions:
To help get started with the RMT driver faster, some commonly-used encoders are provided out-of-the-box. They can either work alone or be chained together into a new encoder. See also `Composite Pattern <https://en.wikipedia.org/wiki/Composite_pattern>`__ for the principle behind it. The driver has defined the encoder interface in :cpp:type:`rmt_encoder_t`, it contains the following functions:
- :cpp:member:`rmt_encoder_t::encode` is the fundamental function of an encoder. This is where the encoding session happens. Please note, the :cpp:member:`rmt_encoder_t::encode` function might be called for multiple times within a single transaction. The encode function should return the state of current encoding session. The supported states are listed in the :cpp:type:`rmt_encode_state_t`. If the result contains :cpp:enumerator:`RMT_ENCODING_COMPLETE`, it means the current encoder has finished work. If the result contains :cpp:enumerator:`RMT_ENCODING_MEM_FULL`, we need to yield from current session, as there's no space to save more encoding artifacts.
- :cpp:member:`rmt_encoder_t::reset` should reset the encoder state back to initial. The RMT encoder is stateful, if RMT transmitter stopped manually without its corresponding encoder being reset, then the following encoding session can be wrong. This function is also called implicitly in :cpp:func:`rmt_disable`.
- :cpp:member:`rmt_encoder_t::del` function should free the resources allocated by the encoder.
- :cpp:member:`rmt_encoder_t::encode` is the fundamental function of an encoder. This is where the encoding session happens.
- The function might be called multiple times within a single transaction. The encode function should return the state of the current encoding session.
- The supported states are listed in the :cpp:type:`rmt_encode_state_t`. If the result contains :cpp:enumerator:`RMT_ENCODING_COMPLETE`, it means the current encoder has finished work.
- If the result contains :cpp:enumerator:`RMT_ENCODING_MEM_FULL`, we need to yield from the current session, as there is no space to save more encoding artifacts.
- :cpp:member:`rmt_encoder_t::reset` should reset the encoder state back to the initial state (the RMT encoder is stateful).
- If the RMT transmitter is manually stopped without resetting its corresponding encoder, subsequent encoding session can be erroneous.
- This function is also called implicitly in :cpp:func:`rmt_disable`.
- :cpp:member:`rmt_encoder_t::del` should free the resources allocated by the encoder.
Copy Encoder
~~~~~~~~~~~~
A copy encoder is created by calling :cpp:func:`rmt_new_copy_encoder`. Copy encoder's main functionality is to copy the RMT symbols from user space into the driver layer. It's usually used to encode const data (i.e. data won't change at runtime after initialization), for example, the leading code in the IR protocol.
A copy encoder is created by calling :cpp:func:`rmt_new_copy_encoder`. A copy encoder's main functionality is to copy the RMT symbols from user space into the driver layer. It is usually used to encode ``const`` data, i.e., data does not change at runtime after initialization such as the leading code in the IR protocol.
A configuration structure :cpp:type:`rmt_copy_encoder_config_t` should be provided in advance before calling :cpp:func:`rmt_new_copy_encoder`. Currently, this configuration is reserved for future expansion.
A configuration structure :cpp:type:`rmt_copy_encoder_config_t` should be provided in advance before calling :cpp:func:`rmt_new_copy_encoder`. Currently, this configuration is reserved for future expansion, and has no specific use or setting items for now.
Bytes Encoder
~~~~~~~~~~~~~
A bytes encoder is created by calling :cpp:func:`rmt_new_bytes_encoder`. Bytes encoder's main functionality is to convert the user space byte stream into RMT symbols dynamically. It's usually used to encode dynamic data, for example, the address and command fields in the IR protocol.
A bytes encoder is created by calling :cpp:func:`rmt_new_bytes_encoder`. The bytes encoder's main functionality is to convert the user space byte stream into RMT symbols dynamically. It is usually used to encode dynamic data, e.g., the address and command fields in the IR protocol.
A configuration structure :cpp:type:`rmt_bytes_encoder_config_t` should be provided in advance before calling :cpp:func:`rmt_new_bytes_encoder`:
- :cpp:member:`rmt_bytes_encoder_config_t::bit0` and :cpp:member:`rmt_bytes_encoder_config_t::bit1` are necessary to tell to the encoder how to represent bit zero and bit one in the format of :cpp:type:`rmt_symbol_word_t`.
- :cpp:member:`rmt_bytes_encoder_config_t::msb_first` sets the encoding order for of byte. If it is set to true, the encoder will encode the **Most Significant Bit** first. Otherwise, it will encode the **Least Significant Bit** first.
- :cpp:member:`rmt_bytes_encoder_config_t::bit0` and :cpp:member:`rmt_bytes_encoder_config_t::bit1` are necessary to specify the encoder how to represent bit zero and bit one in the format of :cpp:type:`rmt_symbol_word_t`.
- :cpp:member:`rmt_bytes_encoder_config_t::msb_first` sets the bit endianess of each byte. If it is set to true, the encoder encodes the **Most Significant Bit** first. Otherwise, it encodes the **Least Significant Bit** first.
Besides the primitive encoders provided by the driver, user can implement his own encoder by chaining the existing encoders together. A common encoder chain is shown as follows:
Besides the primitive encoders provided by the driver, the user can implement his own encoder by chaining the existing encoders together. A common encoder chain is shown as follows:
.. blockdiag:: /../_static/diagrams/rmt/rmt_encoder_chain.diag
:caption: RMT Encoder Chain
@ -375,12 +418,12 @@ Besides the primitive encoders provided by the driver, user can implement his ow
Customize RMT Encoder for NEC Protocol
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In this section, we will demonstrate on how to write an NEC encoder. The NEC IR protocol uses pulse distance encoding of the message bits. Each pulse burst is `562.5µs` in length, logical bits are transmitted as follows. It is worth mentioning, the bytes of data bits are sent least significant bit first.
In this section, we demonstrates how to write an NEC encoder. The NEC IR protocol uses pulse distance encoding of the message bits. Each pulse burst is ``562.5 µs`` in length, logical bits are transmitted as follows. It is worth mentioning that the least significant bit of each byte is sent first.
- Logical ``0``: a `562.5µs` pulse burst followed by a `562.5µs` space, with a total transmit time of `1.125ms`
- Logical ``1``: a `562.5µs` pulse burst followed by a `1.6875ms` space, with a total transmit time of `2.25ms`
- Logical ``0``: a ``562.5 µs`` pulse burst followed by a ``562.5 µs`` space, with a total transmit time of ``1.125 ms``
- Logical ``1``: a ``562.5 µs`` pulse burst followed by a ``1.6875 ms`` space, with a total transmit time of ``2.25 ms``
When a key is pressed on the remote controller, the message transmitted consists of the following, in order:
When a key is pressed on the remote controller, the transmitted message includes the following elements in the specified order:
.. figure:: /../_static/ir_nec.png
:align: center
@ -388,13 +431,13 @@ When a key is pressed on the remote controller, the message transmitted consists
IR NEC Frame
- `9ms` leading pulse burst (also called the "AGC pulse")
- `4.5ms` space
- 8-bit address for the receiving device
- 8-bit logical inverse of the address
- 8-bit command
- 8-bit logical inverse of the command
- a final `562.5µs` pulse burst to signify the end of message transmission
- ``9 ms`` leading pulse burst, also called the "AGC pulse"
- ``4.5 ms`` space
- 8-bit address for the receiving device
- 8-bit logical inverse of the address
- 8-bit command
- 8-bit logical inverse of the command
- a final ``562.5 µs`` pulse burst to signify the end of message transmission
Then we can construct the NEC :cpp:member:`rmt_encoder_t::encode` function in the same order, for example:
@ -406,14 +449,14 @@ Then we can construct the NEC :cpp:member:`rmt_encoder_t::encode` function in th
uint16_t command;
} ir_nec_scan_code_t;
// construct a encoder by combining primitive encoders
// construct an encoder by combining primitive encoders
typedef struct {
rmt_encoder_t base; // the base "class", declares the standard encoder interface
rmt_encoder_t base; // the base "class" declares the standard encoder interface
rmt_encoder_t *copy_encoder; // use the copy_encoder to encode the leading and ending pulse
rmt_encoder_t *bytes_encoder; // use the bytes_encoder to encode the address and command data
rmt_symbol_word_t nec_leading_symbol; // NEC leading code with RMT representation
rmt_symbol_word_t nec_ending_symbol; // NEC ending code with RMT representation
int state; // record the current encoding state (i.e. we're in which encoding phase)
int state; // record the current encoding state, i.e., we are in which encoding phase
} rmt_ir_nec_encoder_t;
static size_t rmt_encode_ir_nec(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
@ -430,31 +473,31 @@ Then we can construct the NEC :cpp:member:`rmt_encoder_t::encode` function in th
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; // we can only switch to next state when current encoder finished
nec_encoder->state = 1; // we can only switch to the next state when the current encoder finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space to put other encoding artifacts
goto out; // yield if there is no free space to put other encoding artifacts
}
// fall-through
case 1: // send address
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; // we can only switch to next state when current encoder finished
nec_encoder->state = 2; // we can only switch to the next state when the current encoder finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space to put other encoding artifacts
goto out; // yield if there is no free space to put other encoding artifacts
}
// fall-through
case 2: // send command
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; // we can only switch to next state when current encoder finished
nec_encoder->state = 3; // we can only switch to the next state when the current encoder finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space to put other encoding artifacts
goto out; // yield if there is no free space to put other encoding artifacts
}
// fall-through
case 3: // send ending code
@ -466,7 +509,7 @@ Then we can construct the NEC :cpp:member:`rmt_encoder_t::encode` function in th
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space to put other encoding artifacts
goto out; // yield if there is no free space to put other encoding artifacts
}
}
out:
@ -474,52 +517,71 @@ Then we can construct the NEC :cpp:member:`rmt_encoder_t::encode` function in th
return encoded_symbols;
}
A full sample code can be found in :example:`peripherals/rmt/ir_nec_transceiver`. In the above snippet, we use a ``switch-case`` plus several ``goto`` statements to implement a `state machine <https://en.wikipedia.org/wiki/Finite-state_machine>`__ . With this pattern, user can construct a lot more complex IR protocols.
A full sample code can be found in :example:`peripherals/rmt/ir_nec_transceiver`. In the above snippet, we use a ``switch-case`` and several ``goto`` statements to implement a `Finite-state machine <https://en.wikipedia.org/wiki/Finite-state_machine>`__ . With this pattern, users can construct much more complex IR protocols.
.. _rmt-power-management:
Power Management
^^^^^^^^^^^^^^^^
When power management is enabled (i.e. :ref:`CONFIG_PM_ENABLE` is on), the system will adjust the APB frequency before going into light sleep, thus potentially changing the resolution of RMT internal counter.
When power management is enabled, i.e., :ref:`CONFIG_PM_ENABLE` is on, the system adjusts the APB frequency before going into Light-sleep, thus potentially changing the resolution of the RMT internal counter.
However, the driver can prevent the system from changing APB frequency by acquiring a power management lock of type :cpp:enumerator:`ESP_PM_APB_FREQ_MAX`. Whenever user creates an RMT channel that has selected :cpp:enumerator:`RMT_CLK_SRC_APB` as the clock source, the driver will guarantee that the power management lock is acquired after the channel enabled by :cpp:func:`rmt_enable`. Likewise, the driver releases the lock after :cpp:func:`rmt_disable` is called for the same channel. This also reveals that the :cpp:func:`rmt_enable` and :cpp:func:`rmt_disable` should appear in pairs.
However, the driver can prevent the system from changing APB frequency by acquiring a power management lock of type :cpp:enumerator:`ESP_PM_APB_FREQ_MAX`. Whenever the user creates an RMT channel that has selected :cpp:enumerator:`RMT_CLK_SRC_APB` as the clock source, the driver guarantees that the power management lock is acquired after the channel enabled by :cpp:func:`rmt_enable`. Likewise, the driver releases the lock after :cpp:func:`rmt_disable` is called for the same channel. This also reveals that the :cpp:func:`rmt_enable` and :cpp:func:`rmt_disable` should appear in pairs.
If the channel clock source is selected to others like :cpp:enumerator:`RMT_CLK_SRC_XTAL`, then the driver won't install power management lock for it, which is more suitable for a low power application as long as the source clock can still provide sufficient resolution.
If the channel clock source is selected to others like :cpp:enumerator:`RMT_CLK_SRC_XTAL`, then the driver does not install a power management lock for it, which is more suitable for a low-power application as long as the source clock can still provide sufficient resolution.
.. _rmt-iram-safe:
IRAM Safe
^^^^^^^^^
By default, the RMT interrupt will be deferred when the Cache is disabled for reasons like writing/erasing the main Flash. Thus the transaction done interrupt will not get executed in time, which is not expected in a real-time application. What's worse, when the RMT transaction relies on **ping-pong** interrupt to successively encode or copy RMT symbols, such delayed response can lead to an unpredictable result.
By default, the RMT interrupt is deferred when the Cache is disabled for reasons like writing or erasing the main Flash. Thus the transaction-done interrupt does not get handled in time, which is not acceptable in a real-time application. What is worse, when the RMT transaction relies on **ping-pong** interrupt to successively encode or copy RMT symbols, a delayed interrupt can lead to an unpredictable result.
There's a Kconfig option :ref:`CONFIG_RMT_ISR_IRAM_SAFE` that will:
There is a Kconfig option :ref:`CONFIG_RMT_ISR_IRAM_SAFE` that has the following features:
1. Enable the interrupt being serviced even when cache is disabled
2. Place all functions that used by the ISR into IRAM [2]_
3. Place driver object into DRAM (in case it's mapped to PSRAM by accident)
1. Enable the interrupt being serviced even when the cache is disabled
2. Place all functions used by the ISR into IRAM [2]_
3. Place the driver object into DRAM in case it is mapped to PSRAM by accident
This Kconfig option will allow the interrupt to run while the cache is disabled but will come at the cost of increased IRAM consumption.
This Kconfig option allows the interrupt to run while the cache is disabled but comes at the cost of increased IRAM consumption.
.. _rmt-thread-safety:
Thread Safety
^^^^^^^^^^^^^
The factory function :cpp:func:`rmt_new_tx_channel`, :cpp:func:`rmt_new_rx_channel` and :cpp:func:`rmt_new_sync_manager` are guaranteed to be thread safe by the driver, which means, user can call them from different RTOS tasks without protection by extra locks.
Other functions that take the :cpp:type:`rmt_channel_handle_t` and :cpp:type:`rmt_sync_manager_handle_t` as the first positional parameter, are not thread safe. which means the user should avoid calling them from multiple tasks.
The factory function :cpp:func:`rmt_new_tx_channel`, :cpp:func:`rmt_new_rx_channel` and :cpp:func:`rmt_new_sync_manager` are guaranteed to be thread-safe by the driver, which means, user can call them from different RTOS tasks without protection by extra locks.
Other functions that take the :cpp:type:`rmt_channel_handle_t` and :cpp:type:`rmt_sync_manager_handle_t` as the first positional parameter, are not thread-safe. which means the user should avoid calling them from multiple tasks.
.. _rmt-kconfig-options:
Kconfig Options
^^^^^^^^^^^^^^^
- :ref:`CONFIG_RMT_ISR_IRAM_SAFE` controls whether the default ISR handler can work when cache is disabled, see also `IRAM Safe <#iram-safe>`__ for more information.
- :ref:`CONFIG_RMT_ENABLE_DEBUG_LOG` is used to enabled the debug log at the cost of increased firmware binary size.
- :ref:`CONFIG_RMT_ISR_IRAM_SAFE` controls whether the default ISR handler can work when cache is disabled, see also :ref:`rmt-iram-safe` for more information.
- :ref:`CONFIG_RMT_ENABLE_DEBUG_LOG` is used to enable the debug log at the cost of increased firmware binary size.
Application Examples
--------------------
* RMT based RGB LED strip customized encoder: :example:`peripherals/rmt/led_strip`
* RMT-based RGB LED strip customized encoder: :example:`peripherals/rmt/led_strip`
* RMT IR NEC protocol encoding and decoding: :example:`peripherals/rmt/ir_nec_transceiver`
* RMT transactions in queue: :example:`peripherals/rmt/musical_buzzer`
* RMT based stepper motor with S-curve algorithm: : :example:`peripherals/rmt/stepper_motor`
* RMT-based stepper motor with S-curve algorithm: : :example:`peripherals/rmt/stepper_motor`
* RMT infinite loop for driving DShot ESC: :example:`peripherals/rmt/dshot_esc`
* RMT simulate 1-wire protocol (take DS18B20 as example): :example:`peripherals/rmt/onewire`
FAQ
---
* Why the RMT encoder results in more data than expected?
The RMT encoding takes place in the ISR context. If your RMT encoding session takes a long time (e.g., by logging debug information) or the encoding session is deferred somehow because of interrupt latency, then it is possible the transmitting becomes **faster** than the encoding. As a result, the encoder can not prepare the next data in time, leading to the transmitter sending the previous data again. There is no way to ask the transmitter to stop and wait. You can mitigate the issue by combining the following ways:
- Increase the :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols`, in steps of {IDF_TARGET_SOC_RMT_MEM_WORDS_PER_CHANNEL}.
- Place the encoding function in the IRAM.
- Enables the :cpp:member:`rmt_tx_channel_config_t::with_dma` if it is available for your chip.
API Reference
-------------
@ -532,7 +594,7 @@ API Reference
.. [1]
Different ESP chip series might have different number of RMT channels. Please refer to the [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] for details. The driver won't forbid you from applying for more RMT channels, but it will return error when there's no hardware resources available. Please always check the return value when doing `Resource Allocation <#resource-allocation>`__.
Different ESP chip series might have different numbers of RMT channels. Please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] for details. The driver does not forbid you from applying for more RMT channels, but it returns an error when there are no hardware resources available. Please always check the return value when doing `Resource Allocation <#resource-allocation>`__.
.. [2]
Callback function (e.g. :cpp:member:`rmt_tx_event_callbacks_t::on_trans_done`) and the functions invoked by itself should also reside in IRAM, users need to take care of this by themselves.
The callback function, e.g., :cpp:member:`rmt_tx_event_callbacks_t::on_trans_done`, and the functions invoked by itself should also reside in IRAM, users need to take care of this by themselves.

View File

@ -1 +1,600 @@
.. include:: ../../../en/api-reference/peripherals/rmt.rst
红外遥控 (RMT)
================================
:link_to_translation:`en:[English]`
简介
------------
红外遥控 (RMT) 外设是一个红外发射和接收控制器。其数据格式灵活可进一步扩展为多功能的通用收发器发送或接收多种类型的信号。就网络分层而言RMT 硬件包含物理层和数据链路层。物理层定义通信介质和比特信号的表示方式,数据链路层定义 RMT 帧的格式。RMT 帧的最小数据单元称为 **RMT 符号**,在驱动程序中以 :cpp:type:`rmt_symbol_word_t` 表示。
{IDF_TARGET_NAME} 的 RMT 外设存在多个通道 [1]_,每个通道都可以独立配置为发射器或接收器。
RMT 外设通常支持以下场景:
- 发送或接收红外信号,支持所有红外线协议,如 NEC 协议
- 生成通用序列
- 有限或无限次地在硬件控制的循环中发送信号
- 多通道同时发送
- 将载波调制到输出信号或从输入信号解调载波
RMT 符号的内存布局
^^^^^^^^^^^^^^^^^^^^^
RMT 硬件定义了自己的数据模式,称为 **RMT 符号**。下图展示了一个 RMT 符号的位字段:每个符号由两对两个值组成,每对中的第一个值是一个 15 位的值,表示信号持续时间,以 RMT 滴答计。每对中的第二个值是一个 1 位的值,表示信号的逻辑电平,即高电平或低电平。
.. packetdiag:: /../_static/diagrams/rmt/rmt_symbols.diag
:caption: RMT 符号结构L - 信号电平)
:align: center
RMT 发射器概述
^^^^^^^^^^^^^^^^^^^^^^^^
RMT 发送通道 (TX Channel) 的数据路径和控制路径如下图所示:
.. blockdiag:: /../_static/diagrams/rmt/rmt_tx.diag
:caption: RMT 发射器概述
:align: center
驱动程序将用户数据编码为 RMT 数据格式,随后由 RMT 发射器根据编码生成波形。在将波形发送到 GPIO 管脚前,还可以调制高频载波信号。
RMT 接收器概述
^^^^^^^^^^^^^^^^^^^^^
RMT 接收通道 (RX Channel) 的数据路径和控制路径如下图所示:
.. blockdiag:: /../_static/diagrams/rmt/rmt_rx.diag
:caption: RMT 接收器概述
:align: center
RMT 接收器可以对输入信号采样,将其转换为 RMT 数据格式并将数据存储在内存中。还可以向接收器提供输入信号的基本特征使其识别信号停止条件并过滤掉信号干扰和噪声。RMT 外设还支持从基准信号中解调出高频载波信号。
功能概述
-------------------
下文将分节概述 RMT 的功能:
- :ref:`rmt-resource-allocation` - 介绍如何分配和正确配置 RMT 通道,以及如何回收闲置信道及其他资源。
- :ref:`rmt-carrier-modulation-and demodulation` - 介绍如何调制和解调用于 TX 和 RX 通道的载波信号。
- :ref:`rmt-register-event-callbacks` - 介绍如何注册用户提供的事件回调函数以接收 RMT 通道事件。
- :ref:`rmt-enable-and-disable-channel` - 介绍如何启用和禁用 RMT 通道。
- :ref:`rmt-initiate-tx-transaction` - 介绍发起 TX 通道事务的步骤。
- :ref:`rmt-initiate-rx-transaction` - 介绍发起 RX 通道事务的步骤。
- :ref:`rmt-multiple-channels-simultaneous-transmission` - 介绍如何将多个通道收集到一个同步组中,以便同时启动发送。
- :ref:`rmt-rmt-encoder` - 介绍如何通过组合驱动程序提供的多个基本编码器来编写自定义编码器。
- :ref:`rmt-power-management` - 介绍不同时钟源对功耗的影响。
- :ref:`rmt-iram-safe` - 介绍禁用 cache 对 RMT 驱动程序的影响,并提供应对方案。
- :ref:`rmt-thread-safety` - 介绍由驱动程序认证为线程安全的 API。
- :ref:`rmt-kconfig-options` - 介绍 RMT 驱动程序支持的各种 Kconfig 选项。
.. _rmt-resource-allocation:
资源分配
^^^^^^^^^^^^^^^^^^^
驱动程序中,:cpp:type:`rmt_channel_handle_t` 用于表示 RMT 的 TX 和 RX 通道。驱动程序在内部管理可用的通道,并在收到请求时提供空闲通道。
安装 RMT TX 通道
~~~~~~~~~~~~~~~~~~~~~~
要安装 RMT TX 通道,应预先提供配置结构体 :cpp:type:`rmt_tx_channel_config_t`。以下列表介绍了配置结构体中的各个部分。
- :cpp:member:`rmt_tx_channel_config_t::gpio_num` 设置发射器使用的 GPIO 编号。
- :cpp:member:`rmt_tx_channel_config_t::clk_src` 选择 RMT 通道的时钟源。:cpp:type:`rmt_clock_source_t` 中列出了可用的时钟源。注意,其他信道将使用同一所选时钟源,因此,应确保分配的任意 TX 或 RX 通道都享有相同的配置。有关不同时钟源对功耗的影响,请参阅 :ref:`rmt-power-management`
- :cpp:member:`rmt_tx_channel_config_t::resolution_hz` 设置内部滴答计数器的分辨率。基于此 **滴答**,可以计算 RMT 信号的定时参数。
- 在启用 DMA 后端和未启用 DMA 后端的情况下,:cpp:member:`rmt_tx_channel_config_t::mem_block_symbols` 字段含义稍有不同。
- 若通过 :cpp:member:`rmt_tx_channel_config_t::with_dma` 启用 DMA则该字段可以控制内部 DMA 缓冲区大小。为实现更好的吞吐量、减少 CPU 开销,建议为字段设置一个较大的值,如 ``1024``
- 如果未启用 DMA则该字段控制通道专用内存块大小至少为 {IDF_TARGET_SOC_RMT_MEM_WORDS_PER_CHANNEL}。
- :cpp:member:`rmt_tx_channel_config_t::trans_queue_depth` 设置内部事务队列深度。队列越深,在待处理队列中可以准备的事务越多。
- :cpp:member:`rmt_tx_channel_config_t::invert_out` 决定是否在将 RMT 信号发送到 GPIO 管脚前反转 RMT 信号。
- :cpp:member:`rmt_tx_channel_config_t::with_dma` 为通道启用 DMA 后端。启用 DMA 后端可以释放 CPU 上的大部分通道工作负载,显著减轻 CPU 负担。但并非所有 ESP 芯片都支持 DMA 后端,在启用此选项前,请参阅 [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__]。若所选芯片不支持 DMA 后端,可能会报告 :c:macro:`ESP_ERR_NOT_SUPPORTED` 错误。
- :cpp:member:`rmt_tx_channel_config_t::io_loop_back` 启用通道所分配的 GPIO 上的输入和输出功能,将发送通道和接收通道绑定到同一个 GPIO 上,从而实现回环功能。
- :cpp:member:`rmt_tx_channel_config_t::io_od_mode` 配置通道分配的 GPIO 为开漏模式 (open-drain)。当与 :cpp:member:`rmt_tx_channel_config_t::io_loop_back` 结合使用时,可以实现双向总线,如 1-wire。
将必要参数填充到结构体 :cpp:type:`rmt_tx_channel_config_t` 后,可以调用 :cpp:func:`rmt_new_tx_channel` 来分配和初始化 TX 通道。如果函数运行正确,会返回 RMT 通道句柄;如果 RMT 资源池内缺少空闲通道,会返回 :c:macro:`ESP_ERR_NOT_FOUND` 错误;如果硬件不支持 DMA 后端等部分功能,则返回 :c:macro:`ESP_ERR_NOT_SUPPORTED` 错误。
.. code-block:: c
rmt_channel_handle_t tx_chan = NULL;
rmt_tx_channel_config_t tx_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 MHz 滴答分辨率,即 1 滴答 = 1 µs
.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 通道,应预先提供配置结构体 :cpp:type:`rmt_rx_channel_config_t`。以下列表介绍了配置结构体中的各个部分。
- :cpp:member:`rmt_rx_channel_config_t::gpio_num` 设置接收器使用的 GPIO 编号。
- :cpp:member:`rmt_rx_channel_config_t::clk_src` 选择 RMT 通道的时钟源。:cpp:type:`rmt_clock_source_t` 中列出了可用的时钟源。注意,其他信道将使用同一所选时钟源,因此,应确保分配的任意 TX 或 RX 通道都享有相同的配置。有关不同时钟源对功耗的影响,请参阅 :ref:`rmt-power-management`
- :cpp:member:`rmt_rx_channel_config_t::resolution_hz` 设置内部滴答计数器的分辨率。基于此 **滴答**,可以计算 RMT 信号的定时参数。
- 在启用 DMA 后端和未启用 DMA 后端的情况下,:cpp:member:`rmt_rx_channel_config_t::mem_block_symbols` 字段含义稍有不同。
- 若通过 :cpp:member:`rmt_rx_channel_config_t::with_dma` 启用 DMA则该字段可以最大化控制内部 DMA 缓冲区大小。
- 如果未启用 DMA则该字段控制通道专用内存块大小至少为 {IDF_TARGET_SOC_RMT_MEM_WORDS_PER_CHANNEL}。
- :cpp:member:`rmt_rx_channel_config_t::invert_in` 在输入信号传递到 RMT 接收器前对其进行反转。该反转由 GPIO 交换矩阵完成,而非 RMT 外设。
- :cpp:member:`rmt_rx_channel_config_t::with_dma` 为通道启用 DMA 后端。启用 DMA 后端可以释放 CPU 上的大部分通道工作负载,显著减轻 CPU 负担。但并非所有 ESP 芯片都支持 DMA 后端,在启用此选项前,请参阅 [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__]。若所选芯片不支持 DMA 后端,可能会报告 :c:macro:`ESP_ERR_NOT_SUPPORTED` 错误。
- :cpp:member:`rmt_rx_channel_config_t::io_loop_back` 启用通道所分配的 GPIO 上的输入和输出功能,将发送通道和接收通道绑定到同一个 GPIO 上,从而实现回环功能。
将必要参数填充到结构体 :cpp:type:`rmt_rx_channel_config_t` 后,可以调用 :cpp:func:`rmt_new_rx_channel` 来分配和初始化 RX 通道。如果函数运行正确,会返回 RMT 通道句柄;如果 RMT 资源池内缺少空闲通道,会返回 :c:macro:`ESP_ERR_NOT_FOUND` 错误;如果硬件不支持 DMA 后端等部分功能,则返回 :c:macro:`ESP_ERR_NOT_SUPPORTED` 错误。
.. code-block:: c
rmt_channel_handle_t rx_chan = NULL;
rmt_rx_channel_config_t rx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // 选择时钟源
.resolution_hz = 1 * 1000 * 1000, // 1 MHz 滴答分辨率,即 1 滴答 = 1 µs
.mem_block_symbols = 64, // 内存块大小,即 64 * 4 = 256 字节
.gpio_num = 2, // GPIO 编号
.flags.invert_in = false, // 不反转输入信号
.flags.with_dma = false, // 不需要 DMA 后端
};
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_chan));
卸载 RMT 通道
~~~~~~~~~~~~~~~~~~~~~
如果不再需要之前安装的 RMT 通道,建议调用 :cpp:func:`rmt_del_channel` 回收资源,使底层软件与硬件重新用于其他功能。
.. _rmt-carrier-modulation-and demodulation:
载波调制与解调
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RMT 发射器可以生成载波信号并将其调制到消息信号上。载波信号的频率远高于消息信号。此外仅支持配置载波信号的频率和占空比。RMT 接收器可以从输入信号中解调出载波信号。注意,并非所有 ESP 芯片都支持载波调制和解调功能,在配置载波前,请参阅 [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__]。若所选芯片不支持载波调制和解调功能,可能会报告 :c:macro:`ESP_ERR_NOT_SUPPORTED` 错误。
载波相关配置位于 :cpp:type:`rmt_carrier_config_t` 中,该配置中的各部分详情如下:
- :cpp:member:`rmt_carrier_config_t::frequency_hz` 设置载波频率,单位为 Hz。
- :cpp:member:`rmt_carrier_config_t::duty_cycle` 设置载波占空比。
- :cpp:member:`rmt_carrier_config_t::polarity_active_low` 设置载波极性,即应用载波的电平。
- :cpp:member:`rmt_carrier_config_t::always_on` 设置是否在数据发送完成后仍输出载波,该配置仅适用于 TX 通道。
.. note::
RX 通道的载波频率不应设置为理论值,建议为载波频率留出一定的容差。例如,以下代码片段的载波频率设置为 25 KHz而非 TX 侧配置的 38 KHz。因为信号在空气中传播时会发生反射和折射导致接收端接收的频率失真。
.. code-block:: c
rmt_carrier_config_t tx_carrier_cfg = {
.duty_cycle = 0.33, // 载波占空比为 33%
.frequency_hz = 38000, // 38 KHz
.flags.polarity_active_low = false, // 载波应调制到高电平
};
// 将载波调制到 TX 通道
ESP_ERROR_CHECK(rmt_apply_carrier(tx_chan, &tx_carrier_cfg));
rmt_carrier_config_t rx_carrier_cfg = {
.duty_cycle = 0.33, // 载波占空比为 33%
.frequency_hz = 25000, // 载波频率为 25 KHz应小于发射器的载波频率
.flags.polarity_active_low = false, // 载波调制到高电平
};
// 从 RX 通道解调载波
ESP_ERROR_CHECK(rmt_apply_carrier(rx_chan, &rx_carrier_cfg));
.. _rmt-register-event-callbacks:
注册事件回调
^^^^^^^^^^^^^^^^^^^^^^^^
当 RMT 信道生成发送或接收完成等事件时,会通过中断告知 CPU。如果需要在发生特定事件时调用函数可以为 TX 和 RX 信道分别调用 :cpp:func:`rmt_tx_register_event_callbacks`:cpp:func:`rmt_rx_register_event_callbacks`,向 RMT 驱动程序的中断服务程序 (ISR) 注册事件回调。由于上述回调函数是在 ISR 中调用的,因此,这些函数不应涉及 block 操作。可以检查调用 API 的后缀,确保在函数中只调用了后缀为 ISR 的 FreeRTOS API。回调函数具有布尔返回值指示回调是否解除了更高优先级任务的阻塞状态。
有关 TX 通道支持的事件回调,请参阅 :cpp:type:`rmt_tx_event_callbacks_t`
- :cpp:member:`rmt_tx_event_callbacks_t::on_trans_done` 为“发送完成”的事件设置回调函数,函数原型声明为 :cpp:type:`rmt_tx_done_callback_t`
有关 RX 通道支持的事件回调,请参阅 :cpp:type:`rmt_rx_event_callbacks_t`
- :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` 为“接收完成”的事件设置回调函数,函数原型声明为 :cpp:type:`rmt_rx_done_callback_t`
也可使用参数 ``user_data``,在 :cpp:func:`rmt_tx_register_event_callbacks` 和 :cpp:func:`rmt_rx_register_event_callbacks` 中保存自定义上下文。用户数据将直接传递给每个回调函数。
在回调函数中可以获取驱动程序在 ``edata`` 中填充的特定事件数据。注意,``edata`` 指针仅在回调的持续时间内有效。
有关 TX 完成事件数据的定义,请参阅 :cpp:type:`rmt_tx_done_event_data_t`
- :cpp:member:`rmt_tx_done_event_data_t::num_symbols` 表示已发送的 RMT 符号数量,也反映了编码数据大小。注意,该值还考虑了由驱动程序附加的 ``EOF`` 符号,该符号标志着一次事务的结束。
有关 RX 完成事件数据的定义,请参阅 :cpp:type:`rmt_rx_done_event_data_t`
- :cpp:member:`rmt_rx_done_event_data_t::received_symbols` 指向接收到的 RMT 符号,这些符号存储在 :cpp:func:`rmt_receive` 函数的 ``buffer`` 参数中,在回调函数返回前不应释放此接收缓冲区。
- :cpp:member:`rmt_rx_done_event_data_t::num_symbols` 表示接收到的 RMT 符号数量,该值不会超过 :cpp:func:`rmt_receive` 函数的 ``buffer_size`` 参数。如果 ``buffer_size`` 不足以容纳所有接收到的 RMT 符号,驱动程序将只保存缓冲区能够容纳的最大数量的符号,并丢弃或忽略多余的符号。
.. _rmt-enable-and-disable-channel:
启用及禁用通道
^^^^^^^^^^^^^^^^^^^^^^^^^^
在发送或接收 RMT 符号前,应预先调用 :cpp:func:`rmt_enable`。启用 TX 通道会启用特定中断,并使硬件准备发送事务。启用 RX 通道也会启用中断,但由于传入信号的特性尚不明确,接收器不会在此时启动,而是在 :cpp:func:`rmt_receive` 中启动。
相反,:cpp:func:`rmt_disable` 会禁用中断并清除队列中的中断,同时禁用发射器和接收器。
.. code:: c
ESP_ERROR_CHECK(rmt_enable(tx_chan));
ESP_ERROR_CHECK(rmt_enable(rx_chan));
.. _rmt-initiate-tx-transaction:
发起 TX 事务
^^^^^^^^^^^^^^^^^^^^^^^
RMT 是一种特殊的通信外设,无法像 SPI 和 I2C 那样发送原始字节流,只能以 :cpp:type:`rmt_symbol_word_t` 格式发送数据。然而,硬件无法将用户数据转换为 RMT 符号,该转换只能通过 RMT 编码器在软件中完成。编码器将用户数据编码为 RMT 符号,随后写入 RMT 内存块或 DMA 缓冲区。有关创建 RMT 编码器的详细信息,请参阅 :ref:`rmt-rmt-encoder`
获取编码器后,调用 :cpp:func:`rmt_transmit` 启动 TX 事务,该函数会接收少数位置参数,如通道句柄、编码器句柄和有效负载缓冲区。此外,还需要在 :cpp:type:`rmt_transmit_config_t` 中提供专用于发送的配置,具体如下:
- :cpp:member:`rmt_transmit_config_t::loop_count` 设置发送的循环次数。在发射器完成一轮发送后如果该值未设置为零则再次启动相同的发送程序。由于循环由硬件控制RMT 通道可以在几乎不需要 CPU 干预的情况下,生成许多周期性序列。
- 将 :cpp:member:`rmt_transmit_config_t::loop_count` 设置为 ``-1``,会启用无限循环发送机制,此时,除非手动调用 :cpp:func:`rmt_disable`,否则通道不会停止,也不会生成“完成发送”事件。
- 将 :cpp:member:`rmt_transmit_config_t::loop_count` 设置为正数,意味着迭代次数有限。此时,“完成发送”事件在指定的迭代次数完成后发生。
.. note::
注意,不是所有 ESP 芯片都支持 **循环发送** 功能,在配置此选项前,请参阅 [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__]。若所选芯片不支持配置此选项,可能会报告 :c:macro:`ESP_ERR_NOT_SUPPORTED` 错误。
- :cpp:member:`rmt_transmit_config_t::eot_level` 设置发射器完成工作时的输出电平,该设置同时适用于调用 :cpp:func:`rmt_disable` 停止发射器工作时的输出电平。
.. note::
如果将 :cpp:member:`rmt_transmit_config_t::loop_count` 设置为非零值,即启用循环功能,则传输的大小将受到限制。编码的 RMT 符号不应超过 RMT 硬件内存块容量,否则会出现类似 ``encoding artifacts can't exceed hw memory block for loop transmission`` 的报错信息。如需通过循环启动大型事务,请尝试以下任一方法:
- 增加 :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols`。若此时启用了 DMA 后端,该方法将失效。
- 自定义编码器,并在编码函数中构造一个无限循环,详情请参阅 :ref:`rmt-rmt-encoder`
:cpp:func:`rmt_transmit` 会在其内部构建一个事务描述符,并将其发送到作业队列中,该队列将在 ISR 中调度。因此,在 :cpp:func:`rmt_transmit` 返回时,事务可能尚未启动。为确保完成所有挂起的事务,请调用 :cpp:func:`rmt_tx_wait_all_done`
.. _rmt-multiple-channels-simultaneous-transmission:
多通道同时发送
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
在一些实时控制应用程序中,启动多个 TX 通道例如使两个器械臂同时移动应避免出现任何时间漂移。为此RMT 驱动程序可以创建 **同步管理器** 帮助管理该过程。在驱动程序中,同步管理器为 :cpp:type:`rmt_sync_manager_handle_t`。RMT 同步发送过程如下图所示:
.. figure:: /../_static/rmt_tx_sync.png
:align: center
:alt: RMT TX Sync
RMT TX 同步发送
安装 RMT 同步管理器
~~~~~~~~~~~~~~~~~~~~~~~~~
要创建同步管理器,应预先在 :cpp:type:`rmt_sync_manager_config_t` 中指定要管理的通道:
- :cpp:member:`rmt_sync_manager_config_t::tx_channel_array` 指向要管理的 TX 通道数组。
- :cpp:member:`rmt_sync_manager_config_t::array_size` 设置要管理的通道数量。
成功调用 :cpp:func:`rmt_new_sync_manager` 函数将返回管理器句柄,该函数也可能因为无效参数等错误而无法调用。在已经安装了同步管理器,且缺少硬件资源来创建另一个管理器时,该函数将报告 :c:macro:`ESP_ERR_NOT_FOUND` 错误。此外,如果硬件不支持同步管理器,将报告 :c:macro:`ESP_ERR_NOT_SUPPORTED` 错误。在使用同步管理器功能之前,请参阅 [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__]。
发起同时发送
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
在调用 :cpp:member:`rmt_sync_manager_config_t::tx_channel_array` 中所有通道上的 :cpp:func:`rmt_transmit` 前,任何受管理的 TX 通道都不会启动发送机制而是处于待命状态。由于各通道事务不同TX 通道通常会在不同的时间完成相应事务,这可能导致无法同步。因此,在重新启动同时发送程序之前,应调用 :cpp:func:`rmt_sync_reset` 函数重新同步所有通道。
调用 :cpp:func:`rmt_del_sync_manager` 函数可以回收同步管理器,并使通道可以在将来独立启动发送程序。
.. code:: c
rmt_channel_handle_t tx_channels[2] = {NULL}; // 声明两个通道
int tx_gpio_number[2] = {0, 2};
// 依次安装通道
for (int i = 0; i < 2; i++) {
rmt_tx_channel_config_t tx_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 MHz 分辨率
.trans_queue_depth = 1, // 设置可以在后台挂起的事务数量
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &tx_channels[i]));
}
// 安装同步管理器
rmt_sync_manager_handle_t synchro = NULL;
rmt_sync_manager_config_t synchro_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[1] 的 rmt_transmit() 函数返回后tx_channels[0] 才会开始发送数据。
ESP_ERROR_CHECK(rmt_transmit(tx_channels[1], led_strip_encoders[1], led_data, led_num * 3, &transmit_config));
.. _rmt-initiate-rx-transaction:
发起 RX 事务
^^^^^^^^^^^^^^^^^^^^^^^
:ref:`rmt-enable-and-disable-channel` 一节所述,仅调用 :cpp:func:`rmt_enable`RX 信道无法接收 RMT 符号。为此,应在 :cpp:type:`rmt_receive_config_t` 中指明传入信号的基本特征:
- :cpp:member:`rmt_receive_config_t::signal_range_min_ns` 指定高电平或低电平有效脉冲的最小持续时间。如果脉冲宽度小于指定值,硬件会将其视作干扰信号并忽略。
- :cpp:member:`rmt_receive_config_t::signal_range_max_ns` 指定高电平或低电平有效脉冲的最大持续时间。如果脉冲宽度大于指定值,接收器会将其视作 **停止信号**,并立即生成接收完成事件。
根据以上配置调用 :cpp:func:`rmt_receive`RMT 接收器会启动 RX 机制。注意,以上配置均针对特定事务存在,也就是说,要开启新一轮的接收时,需要再次设置 :cpp:type:`rmt_receive_config_t` 选项。接收器会将传入信号以 :cpp:type:`rmt_symbol_word_t` 的格式保存在内部内存块或 DMA 缓冲区中。
.. only:: SOC_RMT_SUPPORT_RX_PINGPONG
由于内存块大小有限RMT 接收器会交替提醒驱动程序将累积的符号复制到外部处理。
.. only:: not SOC_RMT_SUPPORT_RX_PINGPONG
由于内存块大小有限RMT 接收器只能保存长度不超过内存块容量的短帧。硬件会将长帧截断,并由驱动程序报错:``hw buffer too small, received symbols truncated``
应在 :cpp:func:`rmt_receive` 函数的 ``buffer`` 参数中提供复制目标。如果由于缓冲区大小不足而导致缓冲区溢出,接收器仍可继续工作,但会丢弃溢出的符号,并报告此错误信息:``user buffer too small, received symbols truncated``。请注意 ``buffer`` 参数的生命周期,确保在接收器完成或停止工作前不会回收缓冲区。
当接收器完成工作,即接收到持续时间大于 :cpp:member:`rmt_receive_config_t::signal_range_max_ns` 的信号时,驱动程序将停止接收器。如有需要,应再次调用 :cpp:func:`rmt_receive` 重新启动接收器。在 :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` 的回调中可以获取接收到的数据。要获取更多有关详情,请参阅 :ref:`rmt-register-event-callbacks`
.. code:: c
static bool example_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{
BaseType_t high_task_wakeup = pdFALSE;
QueueHandle_t receive_queue = (QueueHandle_t)user_data;
// 将接收到的 RMT 符号发送到解析任务的消息队列中
xQueueSendFromISR(receive_queue, edata, &high_task_wakeup);
// 返回是否唤醒了任何任务
return high_task_wakeup == pdTRUE;
}
QueueHandle_t receive_queue = xQueueCreate(1, sizeof(rmt_rx_done_event_data_t));
rmt_rx_event_callbacks_t cbs = {
.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_t receive_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_t raw_symbols[64]; // 64 个符号应足够存储一个标准 NEC 帧的数据
// 准备开始接收
ESP_ERROR_CHECK(rmt_receive(rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config));
// 等待 RX 完成信号
rmt_rx_done_event_data_t rx_data;
xQueueReceive(receive_queue, &rx_data, portMAX_DELAY);
// 解析接收到的符号数据
example_parse_nec_frame(rx_data.received_symbols, rx_data.num_symbols);
.. _rmt-rmt-encoder:
RMT 编码器
^^^^^^^^^^^
RMT 编码器是 RMT TX 事务的一部分,用于在特定时间生成正确的 RMT 符号,并将其写入硬件内存或 DMA 缓冲区。对于编码函数,存在以下特殊限制条件:
- 由于目标 RMT 内存块无法一次性容纳所有数据,在单个事务中,须多次调用编码函数。为突破这一限制,可以采用 **交替** 方式,将编码会话分成多个部分。为此,编码器需要 **记录其状态**,以便从上一部分编码结束之处继续编码。
- 编码函数在 ISR 上下文中运行。为加快编码会话,建议将编码函数放入 IRAM这也有助于避免在编码过程中出现 cache 失效的情况。
为帮助用户更快速地上手 RMT 驱动程序,该程序默认提供了一些常用编码器,可以单独使用,也可以链式组合成新的编码器,有关原理请参阅 `组合模式 <https://en.wikipedia.org/wiki/Composite_pattern>`__。驱动程序在 :cpp:type:`rmt_encoder_t` 中定义了编码器接口,包含以下函数:
- :cpp:member:`rmt_encoder_t::encode` 是编码器的基本函数,编码会话即在此处进行。
- 在单个事务中,可能会多次调用 :cpp:member:`rmt_encoder_t::encode` 函数,该函数会返回当前编码会话的状态。
- 可能出现的编码状态已在 :cpp:type:`rmt_encode_state_t` 列出。如果返回结果中包含 :cpp:enumerator:`RMT_ENCODING_COMPLETE`,表示当前编码器已完成编码。
- 如果返回结果中包含 :cpp:enumerator:`RMT_ENCODING_MEM_FULL`,表示保存编码数据的空间不足,需要从当前会话中退出。
- :cpp:member:`rmt_encoder_t::reset` 会将编码器重置为初始状态(编码器有其特定状态)。
- 如果在未重置 RMT 发射器对应编码器的情况下,手动停止 RMT 发射器,随后的编码会话将报错。
- 该函数也会在 :cpp:func:`rmt_disable` 中隐式调用。
- :cpp:member:`rmt_encoder_t::del` 可以释放编码器分配的资源。
拷贝编码器
~~~~~~~~~~~~
调用 :cpp:func:`rmt_new_copy_encoder` 可以创建拷贝编码器,将 RMT 符号从用户空间复制到驱动程序层。拷贝编码器通常用于编码 ``const`` 数据,即初始化后在运行时不会发生更改的数据,如红外协议中的前导码。
调用 :cpp:func:`rmt_new_copy_encoder` 前,应预先提供配置结构体 :cpp:type:`rmt_copy_encoder_config_t`。目前,该配置保留用作未来的扩展功能,暂无具体用途或设置项。
字节编码器
~~~~~~~~~~~~~
调用 :cpp:func:`rmt_new_bytes_encoder` 可以创建字节编码器,将用户空间的字节流动态转化成 RMT 符号。字节编码区通常用于编码动态数据,如红外协议中的地址和命令字段。
调用 :cpp:func:`rmt_new_bytes_encoder` 前,应预先提供配置结构体 :cpp:type:`rmt_bytes_encoder_config_t`,具体配置如下:
- :cpp:member:`rmt_bytes_encoder_config_t::bit0`:cpp:member:`rmt_bytes_encoder_config_t::bit1` 为必要项,用于告知编码器如何以 :cpp:type:`rmt_symbol_word_t` 格式表示零位和一位。
- :cpp:member:`rmt_bytes_encoder_config_t::msb_first` 设置各字节的位编码。如果设置为真,编码器将首先编码 **最高有效位**,否则将首先编码 **最低有效位**
除驱动程序提供的原始编码器外,也可以将现有编码器链式组合成自定义编码器。常见编码器链如下图所示:
.. blockdiag:: /../_static/diagrams/rmt/rmt_encoder_chain.diag
:caption: RMT 编码器链
:align: center
自定义 NEC 协议的 RMT 编码器
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
本节将演示编写 NEC 编码器的流程。NEC 红外协议使用脉冲距离编码来发送消息位,每个脉冲突发的持续时间为 ``562.5 µs``,逻辑位发送详见下文。注意,各字节的最低有效位会优先发送。
- 逻辑 ``0````562.5 µs`` 的脉冲突发后有 ``562.5 µs`` 的空闲时间,总发送时间为 ``1.125 ms``
- 逻辑 ``1````562.5 µs`` 的脉冲突发后有 ``1.6875 ms`` 的空闲时间,总发送时间为 ``2.25 ms``
在遥控器上按下某个按键时,将按以下顺序发送有关信号:
.. figure:: /../_static/ir_nec.png
:align: center
:alt: 红外 NEC 帧
红外 NEC 帧
- ``9 ms`` 的引导脉冲发射,也称为 AGC 脉冲
- ``4.5 ms`` 的空闲时间
- 接收设备的 8 位地址
- 地址的 8 位逻辑反码
- 8 位命令
- 命令的 8 位逻辑反码
- 最后的 ``562.5 µs`` 脉冲突发,表示消息发送结束
随后可以按相同顺序构建 NEC :cpp:member:`rmt_encoder_t::encode` 函数,例如
.. code:: c
// 红外 NEC 扫码表示法
typedef struct {
uint16_t address;
uint16_t command;
} ir_nec_scan_code_t;
// 通过组合原始编码器构建编码器
typedef struct {
rmt_encoder_t base; // 基础类 "class" 声明了标准编码器接口
rmt_encoder_t *copy_encoder; // 使用拷贝编码器来编码前导码和结束码
rmt_encoder_t *bytes_encoder; // 使用字节编码器来编码地址和命令数据
rmt_symbol_word_t nec_leading_symbol; // 使用 RMT 表示的 NEC 前导码
rmt_symbol_word_t nec_ending_symbol; // 使用 RMT 表示的 NEC 结束码
int state; // 记录当前编码状态,即所处编码阶段
} rmt_ir_nec_encoder_t;
static size_t rmt_encode_ir_nec(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_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_t session_state = RMT_ENCODING_RESET;
rmt_encode_state_t state = RMT_ENCODING_RESET;
size_t encoded_symbols = 0;
ir_nec_scan_code_t *scan_code = (ir_nec_scan_code_t *)primary_data;
rmt_encoder_handle_t copy_encoder = nec_encoder->copy_encoder;
rmt_encoder_handle_t bytes_encoder = nec_encoder->bytes_encoder;
switch (nec_encoder->state) {
case 0: // 发送前导码
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;
goto out; // 如果没有足够的空间来存放其他编码相关的数据,程序会暂停当前操作,并跳转到指定位置继续执行。
}
// 继续执行
case 1: // 发送地址
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;
goto out; // 如果没有足够的空间来存放其他编码相关的数据,程序会暂停当前操作,并跳转到指定位置继续执行。
}
// 继续执行
case 2: // 发送命令
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;
goto out; // 如果没有足够的空间来存放其他编码相关的数据,程序会暂停当前操作,并跳转到指定位置继续执行。
}
// 继续执行
case 3: // 发送结束码
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;
goto out; // 如果没有足够的空间来存放其他编码相关的数据,程序会暂停当前操作,并跳转到指定位置继续执行。
}
}
out:
*ret_state = state;
return encoded_symbols;
}
完整示例代码存放在 :example:`peripherals/rmt/ir_nec_transceiver` 目录下。以上代码片段使用了 ``switch-case`` 和一些 ``goto`` 语句实现了一个 `有限状态机 <https://en.wikipedia.org/wiki/Finite-state_machine>`__,借助此模式可构建更复杂的红外协议。
.. _rmt-power-management:
电源管理
^^^^^^^^^^^^^^^^
通过 :ref:`CONFIG_PM_ENABLE` 选项启用电源管理时,系统会在进入 Light-sleep 模式前调整 APB 频率。该操作可能改变 RMT 内部计数器的分辨率。
然而,驱动程序可以通过获取 :cpp:enumerator:`ESP_PM_APB_FREQ_MAX` 类型的电源管理锁,防止系统改变 APB 频率。每当驱动创建以 :cpp:enumerator:`RMT_CLK_SRC_APB` 作为时钟源的 RMT 通道时,都会在通过 :cpp:func:`rmt_enable` 启用通道后获取电源管理锁。反之,调用 :cpp:func:`rmt_disable` 时,驱动程序释放锁。这也意味着 :cpp:func:`rmt_enable`:cpp:func:`rmt_disable` 应成对出现。
如果将通道时钟源设置为其他选项,如 :cpp:enumerator:`RMT_CLK_SRC_XTAL`,则驱动程序不会为其安装电源管理锁。对于低功耗应用程序来说,只要时钟源仍然可以提供足够的分辨率,不安装电源管理锁更为合适。
.. _rmt-iram-safe:
IRAM 安全
^^^^^^^^^
默认情况下,禁用 cache 时,写入/擦除主 flash 等原因将导致 RMT 中断延迟,事件回调函数也将延迟执行。在实时应用程序中,应避免此类情况。此外,当 RMT 事务依赖 **交替** 中断连续编码或复制 RMT 符号时,上述中断延迟将导致不可预测的结果。
因此,可以启用 Kconfig 选项 :ref:`CONFIG_RMT_ISR_IRAM_SAFE`,该选项:
1. 支持在禁用 cache 时启用所需中断
2. 支持将 ISR 使用的所有函数存放在 IRAM 中 [2]_
3. 支持将驱动程序实例存放在 DRAM 中,以防其意外映射到 PSRAM 中
启用该选项可以保证 cache 禁用时的中断运行,但会相应增加 IRAM 占用。
.. _rmt-thread-safety:
线程安全
^^^^^^^^^^^^^
RMT 驱动程序会确保工厂函数 :cpp:func:`rmt_new_tx_channel`:cpp:func:`rmt_new_rx_channel`:cpp:func:`rmt_new_sync_manager` 的线程安全。使用时,可以直接从不同的 RTOS 任务中调用此类函数,无需额外锁保护。
其他以 :cpp:type:`rmt_channel_handle_t`:cpp:type:`rmt_sync_manager_handle_t` 作为第一个位置参数的函数均非线程安全,在没有设置互斥锁保护的任务中,应避免从多个任务中调用这类函数。
.. _rmt-kconfig-options:
Kconfig 选项
^^^^^^^^^^^^^^^
- :ref:`CONFIG_RMT_ISR_IRAM_SAFE` 控制默认 ISR 处理程序能否在禁用 cache 的情况下工作。详情请参阅 :ref:`rmt-iram-safe`
- :ref:`CONFIG_RMT_ENABLE_DEBUG_LOG` 用于启用调试日志输出,启用此选项将增加固件的二进制文件大小。
应用示例
--------------------
* 基于 RMT 的 RGB LED 灯带自定义编码器::example:`peripherals/rmt/led_strip`
* RMT 红外 NEC 协议的编码与解码::example:`peripherals/rmt/ir_nec_transceiver`
* 队列中的 RMT 事务::example:`peripherals/rmt/musical_buzzer`
* 基于 RMT 的步进电机与 S 曲线算法:: :example:`peripherals/rmt/stepper_motor`
* 用于驱动 DShot ESC 的 RMT 无限循环::example:`peripherals/rmt/dshot_esc`
* 模拟 1-wire 协议的 RMT 实现(以 DS18B20 为例)::example:`peripherals/rmt/onewire`
FAQ
---
* RMT 编码器为什么会产生比预期更多的数据?
RMT 编码在 ISR 上下文中发生。如果 RMT 编码会话耗时较长(例如,记录调试信息),或者由于中断延迟导致编码会话延迟执行,则传输速率可能会超过编码速率。此时,编码器无法及时准备下一组数据,致使传输器再次发送先前的数据。由于传输器无法停止并等待,可以通过以下方法来缓解此问题:
- 增加 :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols` 的值,步长为 {IDF_TARGET_SOC_RMT_MEM_WORDS_PER_CHANNEL}。
- 将编码函数放置在 IRAM 中。
- 如果所用芯片支持 :cpp:member:`rmt_tx_channel_config_t::with_dma`,请启用该选项。
API 参考
-------------
.. include-build-file:: inc/rmt_tx.inc
.. include-build-file:: inc/rmt_rx.inc
.. include-build-file:: inc/rmt_common.inc
.. include-build-file:: inc/rmt_encoder.inc
.. include-build-file:: inc/components/driver/include/driver/rmt_types.inc
.. include-build-file:: inc/components/hal/include/hal/rmt_types.inc
.. [1]
不同 ESP 芯片系列可能具有不同数量的 RMT 通道,详情请参阅 [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__]。驱动程序不会禁止申请更多 RMT 通道,但会在可用硬件资源不足时报错。在进行 :ref:`rmt-resource-allocation` 时,请持续检查返回值。
.. [2]
回调函数,如 :cpp:member:`rmt_tx_event_callbacks_t::on_trans_done` 及回调函数所调用的函数也应位于 IRAM 中,用户需自行留意这一问题。