example: update ir nec example with new rmt driver

This commit is contained in:
morris 2022-04-07 13:10:57 +08:00
parent c5cd86ae8b
commit 977a2830dd
17 changed files with 574 additions and 281 deletions

View File

@ -31,6 +31,16 @@ example_test_pytest_esp32_generic:
TARGET: ESP32
ENV_MARKER: generic
example_test_pytest_esp32_ir_transceiver:
extends:
- .pytest_examples_dir_template
- .rules:test:example_test-esp32
needs:
- build_pytest_examples_esp32
variables:
TARGET: ESP32
ENV_MARKER: ir_transceiver
example_test_pytest_esp32s2_generic:
extends:
- .pytest_examples_dir_template
@ -462,12 +472,6 @@ example_test_011:
variables:
SETUP_TOOLS: "1"
example_test_012:
extends: .example_test_esp32_template
tags:
- ESP32
- Example_RMT_IR_PROTOCOLS
example_test_013:
extends: .example_test_esp32_template
tags:

View File

@ -3,4 +3,4 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ir_protocols)
project(ir_nec_transceiver)

View File

@ -0,0 +1,110 @@
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 |
| ----------------- | ----- | -------- | -------- | -------- |
# IR NEC Encoding and Decoding Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
[NEC](https://www.sbprojects.net/knowledge/ir/nec.php) is a common use IR protocol, this example creates a TX channel and sends out the IR NEC signals periodically. The signal is modulated with a 38KHz carrier. The example also creates an RX channel, to receive and parse the IR NEC signals into scan codes.
## How to Use Example
### Hardware Required
* A development board with supported SoC mentioned in the above `Supported Targets` table
* An USB cable for power supply and programming
* A 5mm infrared LED (e.g. IR333C) used to transmit encoded IR signals
* An infrared receiver module (e.g. IRM-3638T), which integrates a demodulator and AGC circuit
### Hardware Connection
```
IR Receiver (IRM-3638T) ESP Board IR Transmitter (IR333C)
+--------------------------+ +----------------------+ +---------------------------+
| RX+-------+IR_RX_GPIO IR_TX_GPIO+--------------+TX |
| | | | | |
| 3V3+-------+3V3 5V+--------------+VCC |
| | | | | |
| GND+-------+GND GND+--------------+GND |
+--------------------------+ +----------------------+ +---------------------------+
```
The TX and RX GPIO number used by this example can be changed in [the source file](main/ir_nec_transceiver_main.c) via `EXAMPLE_IR_TX_GPIO_NUM` and `EXAMPLE_IR_RX_GPIO_NUM`.
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
Run this example, you might see the following output log:
```
...
I (0) cpu_start: Starting scheduler on APP CPU.
I (306) example: create RMT RX channel
I (306) gpio: GPIO[19]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (316) example: register RX done callback
I (316) example: create RMT TX channel
I (326) gpio: GPIO[18]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (336) example: modulate carrier to TX channel
I (336) example: install IR NEC encoder
I (346) example: start RMT TX and RX channel
NEC frame start---
{0:9020},{1:4461}
{0:577},{1:577}
{0:577},{1:576}
{0:552},{1:601}
{0:552},{1:601}
{0:577},{1:576}
{0:578},{1:575}
{0:583},{1:572}
{0:579},{1:574}
{0:576},{1:1648}
{0:553},{1:1673}
{0:579},{1:1647}
{0:580},{1:1645}
{0:577},{1:1649}
{0:554},{1:1673}
{0:578},{1:1648}
{0:553},{1:1673}
{0:555},{1:1671}
{0:578},{1:577}
{0:553},{1:1673}
{0:554},{1:1671}
{0:555},{1:601}
{0:580},{1:574}
{0:551},{1:603}
{0:580},{1:574}
{0:553},{1:601}
{0:553},{1:1672}
{0:554},{1:602}
{0:552},{1:603}
{0:579},{1:1646}
{0:554},{1:1672}
{0:555},{1:1672}
{0:580},{1:1646}
{0:555},{1:0}
---NEC frame end: Address=FF00, Command=F20D
NEC frame start---
{0:9024},{1:2213}
{0:583},{1:0}
---NEC frame end: Address=FF00, Command=F20D, repeat
NEC frame start---
{0:584},{1:0}
---NEC frame end: Unknown NEC frame
...
```
In the example's main loop, the RX channel waits for any NEC frames, if nothing received within 1 second, the TX channel will send out a predefined NEC frame (address=0x0440, command=0x3003).
## Troubleshooting
For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "ir_nec_transceiver_main.c" "ir_nec_encoder.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,154 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "ir_nec_encoder.h"
static const char *TAG = "nec_encoder";
typedef struct {
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;
} 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 = 0;
rmt_encode_state_t state = 0;
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: // send leading code
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
}
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
}
// 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
}
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
}
// 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
}
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
}
// fall-through
case 3: // send ending code
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 = 0; // back to the initial encoding session
state |= RMT_ENCODING_COMPLETE;
}
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
}
}
out:
*ret_state = state;
return encoded_symbols;
}
static esp_err_t rmt_del_ir_nec_encoder(rmt_encoder_t *encoder)
{
rmt_ir_nec_encoder_t *nec_encoder = __containerof(encoder, rmt_ir_nec_encoder_t, base);
rmt_del_encoder(nec_encoder->copy_encoder);
rmt_del_encoder(nec_encoder->bytes_encoder);
free(nec_encoder);
return ESP_OK;
}
static esp_err_t rmt_ir_nec_encoder_reset(rmt_encoder_t *encoder)
{
rmt_ir_nec_encoder_t *nec_encoder = __containerof(encoder, rmt_ir_nec_encoder_t, base);
rmt_encoder_reset(nec_encoder->copy_encoder);
rmt_encoder_reset(nec_encoder->bytes_encoder);
nec_encoder->state = 0;
return ESP_OK;
}
esp_err_t rmt_new_ir_nec_encoder(const ir_nec_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{
esp_err_t ret = ESP_OK;
rmt_ir_nec_encoder_t *nec_encoder = NULL;
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
nec_encoder = calloc(1, sizeof(rmt_ir_nec_encoder_t));
ESP_GOTO_ON_FALSE(nec_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for ir nec encoder");
nec_encoder->base.encode = rmt_encode_ir_nec;
nec_encoder->base.del = rmt_del_ir_nec_encoder;
nec_encoder->base.reset = rmt_ir_nec_encoder_reset;
rmt_copy_encoder_config_t copy_encoder_config = {};
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(&copy_encoder_config, &nec_encoder->copy_encoder), err, TAG, "create copy encoder failed");
// construct the leading code and ending code with RMT symbol format
nec_encoder->nec_leading_symbol = (rmt_symbol_word_t) {
.level0 = 1,
.duration0 = 9000ULL * config->resolution / 1000000,
.level1 = 0,
.duration1 = 4500ULL * config->resolution / 1000000,
};
nec_encoder->nec_ending_symbol = (rmt_symbol_word_t) {
.level0 = 1,
.duration0 = 560 * config->resolution / 1000000,
.level1 = 0,
.duration1 = 0x7FFF,
};
rmt_bytes_encoder_config_t bytes_encoder_config = {
.bit0 = {
.level0 = 1,
.duration0 = 560 * config->resolution / 1000000, // T0H=560us
.level1 = 0,
.duration1 = 560 * config->resolution / 1000000, // T0L=560us
},
.bit1 = {
.level0 = 1,
.duration0 = 560 * config->resolution / 1000000, // T1H=560us
.level1 = 0,
.duration1 = 1690 * config->resolution / 1000000, // T1L=1690us
},
};
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &nec_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");
*ret_encoder = &nec_encoder->base;
return ESP_OK;
err:
if (nec_encoder) {
if (nec_encoder->bytes_encoder) {
rmt_del_encoder(nec_encoder->bytes_encoder);
}
if (nec_encoder->copy_encoder) {
rmt_del_encoder(nec_encoder->copy_encoder);
}
free(nec_encoder);
}
return ret;
}

View File

@ -0,0 +1,44 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "driver/rmt_encoder.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief IR NEC scan code representation
*/
typedef struct {
uint16_t address;
uint16_t command;
} ir_nec_scan_code_t;
/**
* @brief Type of IR NEC encoder configuration
*/
typedef struct {
uint32_t resolution; /*!< Encoder resolution, in Hz */
} ir_nec_encoder_config_t;
/**
* @brief Create RMT encoder for encoding IR NEC frame into RMT symbols
*
* @param[in] config Encoder configuration
* @param[out] ret_encoder Returned encoder handle
* @return
* - ESP_ERR_INVALID_ARG for any invalid arguments
* - ESP_ERR_NO_MEM out of memory when creating IR NEC encoder
* - ESP_OK if creating encoder successfully
*/
esp_err_t rmt_new_ir_nec_encoder(const ir_nec_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,233 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/rmt_tx.h"
#include "driver/rmt_rx.h"
#include "ir_nec_encoder.h"
#define EXAMPLE_IR_RESOLUTION_HZ 1000000 // 1MHz resolution, 1 tick = 1us
#define EXAMPLE_IR_TX_GPIO_NUM 18
#define EXAMPLE_IR_RX_GPIO_NUM 19
#define EXAMPLE_IR_NEC_DECODE_MARGIN 200 // Tolerance for parsing RMT symbols into bit stream
/**
* @brief NEC timing spec
*/
#define NEC_LEADING_CODE_DURATION_0 9000
#define NEC_LEADING_CODE_DURATION_1 4500
#define NEC_PAYLOAD_ZERO_DURATION_0 560
#define NEC_PAYLOAD_ZERO_DURATION_1 560
#define NEC_PAYLOAD_ONE_DURATION_0 560
#define NEC_PAYLOAD_ONE_DURATION_1 1690
#define NEC_REPEAT_CODE_DURATION_0 9000
#define NEC_REPEAT_CODE_DURATION_1 2250
static const char *TAG = "example";
/**
* @brief Saving NEC decode results
*/
static uint16_t s_nec_code_address;
static uint16_t s_nec_code_command;
/**
* @brief Check whether a duration is within expected range
*/
static inline bool nec_check_in_range(uint32_t signal_duration, uint32_t spec_duration)
{
return (signal_duration < (spec_duration + EXAMPLE_IR_NEC_DECODE_MARGIN)) &&
(signal_duration > (spec_duration - EXAMPLE_IR_NEC_DECODE_MARGIN));
}
/**
* @brief Check whether a RMT symbol represents NEC logic zero
*/
static bool nec_parse_logic0(rmt_symbol_word_t *rmt_nec_symbols)
{
return nec_check_in_range(rmt_nec_symbols->duration0, NEC_PAYLOAD_ZERO_DURATION_0) &&
nec_check_in_range(rmt_nec_symbols->duration1, NEC_PAYLOAD_ZERO_DURATION_1);
}
/**
* @brief Check whether a RMT symbol represents NEC logic one
*/
static bool nec_parse_logic1(rmt_symbol_word_t *rmt_nec_symbols)
{
return nec_check_in_range(rmt_nec_symbols->duration0, NEC_PAYLOAD_ONE_DURATION_0) &&
nec_check_in_range(rmt_nec_symbols->duration1, NEC_PAYLOAD_ONE_DURATION_1);
}
/**
* @brief Decode RMT symbols into NEC address and command
*/
static bool nec_parse_frame(rmt_symbol_word_t *rmt_nec_symbols)
{
rmt_symbol_word_t *cur = rmt_nec_symbols;
uint16_t address = 0;
uint16_t command = 0;
bool valid_leading_code = nec_check_in_range(cur->duration0, NEC_LEADING_CODE_DURATION_0) &&
nec_check_in_range(cur->duration1, NEC_LEADING_CODE_DURATION_1);
if (!valid_leading_code) {
return false;
}
cur++;
for (int i = 0; i < 16; i++) {
if (nec_parse_logic1(cur)) {
address |= 1 << i;
} else if (nec_parse_logic0(cur)) {
address &= ~(1 << i);
} else {
return false;
}
cur++;
}
for (int i = 0; i < 16; i++) {
if (nec_parse_logic1(cur)) {
command |= 1 << i;
} else if (nec_parse_logic0(cur)) {
command &= ~(1 << i);
} else {
return false;
}
cur++;
}
// save address and command
s_nec_code_address = address;
s_nec_code_command = command;
return true;
}
/**
* @brief Check whether the RMT symbols represent NEC repeat code
*/
static bool nec_parse_frame_repeat(rmt_symbol_word_t *rmt_nec_symbols)
{
return nec_check_in_range(rmt_nec_symbols->duration0, NEC_REPEAT_CODE_DURATION_0) &&
nec_check_in_range(rmt_nec_symbols->duration1, NEC_REPEAT_CODE_DURATION_1);
}
/**
* @brief Decode RMT symbols into NEC scan code and print the result
*/
static void example_parse_nec_frame(rmt_symbol_word_t *rmt_nec_symbols, size_t symbol_num)
{
printf("NEC frame start---\r\n");
for (size_t i = 0; i < symbol_num; i++) {
printf("{%d:%d},{%d:%d}\r\n", rmt_nec_symbols[i].level0, rmt_nec_symbols[i].duration0,
rmt_nec_symbols[i].level1, rmt_nec_symbols[i].duration1);
}
printf("---NEC frame end: ");
// decode RMT symbols
switch (symbol_num) {
case 34: // NEC normal frame
if (nec_parse_frame(rmt_nec_symbols)) {
printf("Address=%04X, Command=%04X\r\n\r\n", s_nec_code_address, s_nec_code_command);
}
break;
case 2: // NEC repeat frame
if (nec_parse_frame_repeat(rmt_nec_symbols)) {
printf("Address=%04X, Command=%04X, repeat\r\n\r\n", s_nec_code_address, s_nec_code_command);
}
break;
default:
printf("Unknown NEC frame\r\n\r\n");
break;
}
}
static bool example_rmt_rx_done_callback(rmt_channel_handle_t channel, rmt_rx_done_event_data_t *edata, void *user_data)
{
BaseType_t high_task_wakeup = pdFALSE;
TaskHandle_t task_to_notify = (TaskHandle_t)user_data;
// send the received RMT symbols to the parser task
xTaskNotifyFromISR(task_to_notify, (uint32_t)edata, eSetValueWithOverwrite, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
void app_main(void)
{
ESP_LOGI(TAG, "create RMT RX channel");
rmt_rx_channel_config_t rx_channel_cfg = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = EXAMPLE_IR_RESOLUTION_HZ,
.mem_block_symbols = 64, // amount of RMT symbols that the channel can store at a time
.gpio_num = EXAMPLE_IR_RX_GPIO_NUM,
};
rmt_channel_handle_t rx_channel = NULL;
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channel));
ESP_LOGI(TAG, "register RX done callback");
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
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, cur_task));
// 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
};
ESP_LOGI(TAG, "create RMT TX channel");
rmt_tx_channel_config_t tx_channel_cfg = {
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = EXAMPLE_IR_RESOLUTION_HZ,
.mem_block_symbols = 64, // amount of RMT symbols that the channel can store at a time
.trans_queue_depth = 4, // number of transactions that allowed to pending in the background, this example won't queue multiple transactions, so queue depth > 1 is sufficient
.gpio_num = EXAMPLE_IR_TX_GPIO_NUM,
};
rmt_channel_handle_t tx_channel = NULL;
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel));
ESP_LOGI(TAG, "modulate carrier to TX channel");
rmt_carrier_config_t carrier_cfg = {
.duty_cycle = 0.33,
.frequency_hz = 38000, // 38KHz
};
ESP_ERROR_CHECK(rmt_apply_carrier(tx_channel, &carrier_cfg));
// this example won't send NEC frames in a loop
rmt_transmit_config_t transmit_config = {
.loop_count = 0, // no loop
};
ESP_LOGI(TAG, "install IR NEC encoder");
ir_nec_encoder_config_t nec_encoder_cfg = {
.resolution = EXAMPLE_IR_RESOLUTION_HZ,
};
rmt_encoder_handle_t nec_encoder = NULL;
ESP_ERROR_CHECK(rmt_new_ir_nec_encoder(&nec_encoder_cfg, &nec_encoder));
ESP_LOGI(TAG, "enable RMT TX and RX channels");
ESP_ERROR_CHECK(rmt_enable(tx_channel));
ESP_ERROR_CHECK(rmt_enable(rx_channel));
// save the received RMT symbols
rmt_symbol_word_t raw_symbols[64]; // 64 symbols should be sufficient for a standard NEC frame
rmt_rx_done_event_data_t *rx_data = NULL;
// ready to receive
ESP_ERROR_CHECK(rmt_receive(rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config));
while (1) {
// wait for RX done signal
if (xTaskNotifyWait(0x00, ULONG_MAX, (uint32_t *)&rx_data, pdMS_TO_TICKS(1000)) == pdTRUE) {
// parse the receive symbols and print the result
example_parse_nec_frame(rx_data->received_symbols, rx_data->num_symbols);
// start receive again
ESP_ERROR_CHECK(rmt_receive(rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config));
} else {
// timeout, transmit predefined IR NEC packets
const ir_nec_scan_code_t scan_code = {
.address = 0x0440,
.command = 0x3003,
};
ESP_ERROR_CHECK(rmt_transmit(tx_channel, nec_encoder, &scan_code, sizeof(scan_code), &transmit_config));
}
}
}

View File

@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32
@pytest.mark.ir_transceiver
def test_ir_nec_example(dut: Dut) -> None:
dut.expect_exact('example: create RMT RX channel')
dut.expect_exact('example: register RX done callback')
dut.expect_exact('example: create RMT TX channel')
dut.expect_exact('example: modulate carrier to TX channel')
dut.expect_exact('example: install IR NEC encoder')
dut.expect_exact('example: enable RMT TX and RX channels')
dut.expect_exact('NEC frame start---')
dut.expect_exact('---NEC frame end: Address=0440, Command=3003')
dut.expect_exact('---NEC frame end: Address=0440, Command=3003')

View File

@ -1,76 +0,0 @@
| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- |
# IR Protocol Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example illustrates how to encode and decode RMT signals with/to common IR protocols (e.g. NEC and RC5).
[NEC](https://www.sbprojects.net/knowledge/ir/nec.php) and [RC5](https://www.sbprojects.net/knowledge/ir/rc5.php) have different encoding rules, but both can be compatible to RMT data format.
The example supports building and parsing both normal and extended NEC/RC5 protocol. And also supports `repeat code` which would be sent out if one remote key got pressed for a specific long time.
## How to Use Example
### Hardware Required
* A development board with supported SoC mentioned in the above `Supported Targets` table
* An USB cable for power supply and programming
* A 5mm infrared LED (e.g. IR333C) used to transmit encoded IR signals
* An infrared receiver module (e.g. IRM-3638T), which integrates a demodulator and AGC circuit.
Example connection :
| ESP chip | IR333C | IRM-3638T |
| --------------------------- | ------ | --------- |
| CONFIG_EXAMPLE_RMT_TX_GPIO | Tx | × |
| CONFIG_EXAMPLE_RMT_RX_GPIO | × | Rx |
| VCC 5V | √ | × |
| VCC 3.3V | × | √ |
| GND | GND | GND |
### Configure the Project
Open the project configuration menu (`idf.py menuconfig`).
In the `Example Configuration` menu:
* Select the infrared protocol used in the example under `Infrared Protocol` option.
* Set the GPIO number used for transmitting the IR signal under `RMT TX GPIO` option.
* Set the GPIO number used for receiving the demodulated IR signal under `RMT RX GPIO` option.
* Set the RMT TX channel number under `RMT TX Channel Number` option.
* Set the RMT RX channel number under `RMT RX Channel Number` option.
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
Run this example, you will see the following output log (for NEC protocol):
```
I (2000) example: Send command 0x20 to address 0x10
I (2070) example: Scan Code --- addr: 0x0010 cmd: 0x0020
I (2220) example: Scan Code (repeat) --- addr: 0x0010 cmd: 0x0020
I (4240) example: Send command 0x21 to address 0x10
I (4310) example: Scan Code --- addr: 0x0010 cmd: 0x0021
I (4460) example: Scan Code (repeat) --- addr: 0x0010 cmd: 0x0021
I (6480) example: Send command 0x22 to address 0x10
I (6550) example: Scan Code --- addr: 0x0010 cmd: 0x0022
I (6700) example: Scan Code (repeat) --- addr: 0x0010 cmd: 0x0022
I (8720) example: Send command 0x23 to address 0x10
I (8790) example: Scan Code --- addr: 0x0010 cmd: 0x0023
I (8940) example: Scan Code (repeat) --- addr: 0x0010 cmd: 0x0023
I (10960) example: Send command 0x24 to address 0x10
I (11030) example: Scan Code --- addr: 0x0010 cmd: 0x0024
I (11180) example: Scan Code (repeat) --- addr: 0x0010 cmd: 0x0024
```
## Troubleshooting
For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -1,28 +0,0 @@
from __future__ import print_function
import ttfw_idf
EXPECT_TIMEOUT = 20
@ttfw_idf.idf_example_test(env_tag='Example_RMT_IR_PROTOCOLS')
def test_examples_rmt_ir_protocols(env, extra_data):
dut = env.get_dut('ir_protocols_example', 'examples/peripherals/rmt/ir_protocols', app_config_name='nec')
print('Using binary path: {}'.format(dut.app.binary_path))
dut.start_app()
dut.expect('example: Send command 0x20 to address 0x10', timeout=EXPECT_TIMEOUT)
dut.expect('Scan Code --- addr: 0x0010 cmd: 0x0020', timeout=EXPECT_TIMEOUT)
dut.expect('Scan Code (repeat) --- addr: 0x0010 cmd: 0x0020', timeout=EXPECT_TIMEOUT)
env.close_dut(dut.name)
dut = env.get_dut('ir_protocols_example', 'examples/peripherals/rmt/ir_protocols', app_config_name='rc5')
print('Using binary path: {}'.format(dut.app.binary_path))
dut.start_app()
dut.expect('example: Send command 0x20 to address 0x10', timeout=EXPECT_TIMEOUT)
dut.expect('Scan Code --- addr: 0x0010 cmd: 0x0020', timeout=EXPECT_TIMEOUT)
dut.expect('Scan Code (repeat) --- addr: 0x0010 cmd: 0x0020', timeout=EXPECT_TIMEOUT)
env.close_dut(dut.name)
if __name__ == '__main__':
test_examples_rmt_ir_protocols()

View File

@ -1,2 +0,0 @@
idf_component_register(SRCS "ir_protocols_main.c"
INCLUDE_DIRS ".")

View File

@ -1,46 +0,0 @@
menu "Example Configuration"
choice EXAMPLE_IR_PROTOCOL
prompt "Infrared Protocol"
default EXAMPLE_IR_PROTOCOL_NEC
help
Choose the IR protocol used in the example.
config EXAMPLE_IR_PROTOCOL_NEC
bool "NEC"
help
NEC is a kind of Pulse Distance Protocol.
It uses ASK modulation and pulse distance encoding with a carrier frequency of 38 kHz.
config EXAMPLE_IR_PROTOCOL_RC5
bool "RC5"
help
The RC5 protocol was introduced by Philips.
It uses ASK modulation and Manchester encoding with carrier frequency fixed at 36 kHz.
endchoice
config EXAMPLE_RMT_TX_GPIO
int "RMT TX GPIO"
default 18
help
Set the GPIO number used for transmitting the RMT signal.
config EXAMPLE_RMT_RX_GPIO
int "RMT RX GPIO"
default 19
help
Set the GPIO number used for receiving the RMT signal.
config EXAMPLE_RMT_TX_CHANNEL
int "RMT TX Channel Number"
default 0
help
Set the RMT TX channel number.
config EXAMPLE_RMT_RX_CHANNEL
int "RMT RX Channel Number"
default 4 if IDF_TARGET_ESP32S3
default 2
help
Set the RMT RX channel number.
endmenu

View File

@ -1,118 +0,0 @@
/* IR protocols example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/rmt.h"
#include "ir_tools.h"
static const char *TAG = "example";
static rmt_channel_t example_tx_channel = CONFIG_EXAMPLE_RMT_TX_CHANNEL;
static rmt_channel_t example_rx_channel = CONFIG_EXAMPLE_RMT_RX_CHANNEL;
/**
* @brief RMT Receive Task
*
*/
static void example_ir_rx_task(void *arg)
{
uint32_t addr = 0;
uint32_t cmd = 0;
size_t length = 0;
bool repeat = false;
RingbufHandle_t rb = NULL;
rmt_item32_t *items = NULL;
rmt_config_t rmt_rx_config = RMT_DEFAULT_CONFIG_RX(CONFIG_EXAMPLE_RMT_RX_GPIO, example_rx_channel);
rmt_config(&rmt_rx_config);
rmt_driver_install(example_rx_channel, 1000, 0);
ir_parser_config_t ir_parser_config = IR_PARSER_DEFAULT_CONFIG((ir_dev_t)example_rx_channel);
ir_parser_config.flags |= IR_TOOLS_FLAGS_PROTO_EXT; // Using extended IR protocols (both NEC and RC5 have extended version)
ir_parser_t *ir_parser = NULL;
#if CONFIG_EXAMPLE_IR_PROTOCOL_NEC
ir_parser = ir_parser_rmt_new_nec(&ir_parser_config);
#elif CONFIG_EXAMPLE_IR_PROTOCOL_RC5
ir_parser = ir_parser_rmt_new_rc5(&ir_parser_config);
#endif
//get RMT RX ringbuffer
rmt_get_ringbuf_handle(example_rx_channel, &rb);
assert(rb != NULL);
// Start receive
rmt_rx_start(example_rx_channel, true);
while (1) {
items = (rmt_item32_t *) xRingbufferReceive(rb, &length, portMAX_DELAY);
if (items) {
length /= 4; // one RMT = 4 Bytes
if (ir_parser->input(ir_parser, items, length) == ESP_OK) {
if (ir_parser->get_scan_code(ir_parser, &addr, &cmd, &repeat) == ESP_OK) {
ESP_LOGI(TAG, "Scan Code %s --- addr: 0x%04x cmd: 0x%04x", repeat ? "(repeat)" : "", addr, cmd);
}
}
//after parsing the data, return spaces to ringbuffer.
vRingbufferReturnItem(rb, (void *) items);
}
}
ir_parser->del(ir_parser);
rmt_driver_uninstall(example_rx_channel);
vTaskDelete(NULL);
}
/**
* @brief RMT Transmit Task
*
*/
static void example_ir_tx_task(void *arg)
{
uint32_t addr = 0x10;
uint32_t cmd = 0x20;
rmt_item32_t *items = NULL;
size_t length = 0;
ir_builder_t *ir_builder = NULL;
rmt_config_t rmt_tx_config = RMT_DEFAULT_CONFIG_TX(CONFIG_EXAMPLE_RMT_TX_GPIO, example_tx_channel);
rmt_tx_config.tx_config.carrier_en = true;
rmt_config(&rmt_tx_config);
rmt_driver_install(example_tx_channel, 0, 0);
ir_builder_config_t ir_builder_config = IR_BUILDER_DEFAULT_CONFIG((ir_dev_t)example_tx_channel);
ir_builder_config.flags |= IR_TOOLS_FLAGS_PROTO_EXT; // Using extended IR protocols (both NEC and RC5 have extended version)
#if CONFIG_EXAMPLE_IR_PROTOCOL_NEC
ir_builder = ir_builder_rmt_new_nec(&ir_builder_config);
#elif CONFIG_EXAMPLE_IR_PROTOCOL_RC5
ir_builder = ir_builder_rmt_new_rc5(&ir_builder_config);
#endif
while (1) {
vTaskDelay(pdMS_TO_TICKS(2000));
ESP_LOGI(TAG, "Send command 0x%x to address 0x%x", cmd, addr);
// Send new key code
ESP_ERROR_CHECK(ir_builder->build_frame(ir_builder, addr, cmd));
ESP_ERROR_CHECK(ir_builder->get_result(ir_builder, &items, &length));
//To send data according to the waveform items.
rmt_write_items(example_tx_channel, items, length, false);
// Send repeat code
vTaskDelay(pdMS_TO_TICKS(ir_builder->repeat_period_ms));
ESP_ERROR_CHECK(ir_builder->build_repeat_frame(ir_builder));
ESP_ERROR_CHECK(ir_builder->get_result(ir_builder, &items, &length));
rmt_write_items(example_tx_channel, items, length, false);
cmd++;
}
ir_builder->del(ir_builder);
rmt_driver_uninstall(example_tx_channel);
vTaskDelete(NULL);
}
void app_main(void)
{
xTaskCreate(example_ir_rx_task, "ir_rx_task", 2048, NULL, 10, NULL);
xTaskCreate(example_ir_tx_task, "ir_tx_task", 2048, NULL, 10, NULL);
}

View File

@ -1 +0,0 @@
CONFIG_EXAMPLE_IR_PROTOCOL_NEC=y

View File

@ -1 +0,0 @@
CONFIG_EXAMPLE_IR_PROTOCOL_RC5=y

View File

@ -32,6 +32,7 @@ markers =
usb_host: usb host runners
ethernet_ota: ethernet OTA runners
flash_encryption: Flash Encryption runners
ir_transceiver: runners with a pair of IR transmitter and receiver
# log related
log_cli = True

View File

@ -2073,8 +2073,6 @@ examples/peripherals/mcpwm/mcpwm_brushed_dc_control/main/mcpwm_brushed_dc_contro
examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/mcpwm_capture_hc_sr04.c
examples/peripherals/mcpwm/mcpwm_servo_control/main/mcpwm_servo_control_example_main.c
examples/peripherals/mcpwm/mcpwm_sync_example/main/mcpwm_sync_example.c
examples/peripherals/rmt/ir_protocols/example_test.py
examples/peripherals/rmt/ir_protocols/main/ir_protocols_main.c
examples/peripherals/rmt/led_strip/main/led_strip_main.c
examples/peripherals/rmt/morse_code/main/morse_code_main.c
examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/include/musical_buzzer.h