mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
example: update ir nec example with new rmt driver
This commit is contained in:
parent
c5cd86ae8b
commit
977a2830dd
@ -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:
|
||||
|
@ -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)
|
110
examples/peripherals/rmt/ir_nec_transceiver/README.md
Normal file
110
examples/peripherals/rmt/ir_nec_transceiver/README.md
Normal 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.
|
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "ir_nec_transceiver_main.c" "ir_nec_encoder.c"
|
||||
INCLUDE_DIRS ".")
|
@ -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(©_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;
|
||||
}
|
@ -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
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
19
examples/peripherals/rmt/ir_nec_transceiver/pytest_ir_nec.py
Normal file
19
examples/peripherals/rmt/ir_nec_transceiver/pytest_ir_nec.py
Normal 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')
|
@ -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.
|
@ -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()
|
@ -1,2 +0,0 @@
|
||||
idf_component_register(SRCS "ir_protocols_main.c"
|
||||
INCLUDE_DIRS ".")
|
@ -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
|
@ -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);
|
||||
}
|
@ -1 +0,0 @@
|
||||
CONFIG_EXAMPLE_IR_PROTOCOL_NEC=y
|
@ -1 +0,0 @@
|
||||
CONFIG_EXAMPLE_IR_PROTOCOL_RC5=y
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user