第二十七章 ESP32S3 INFRARED_TRANSMISSION 实验
本章将学习 ESP32-S3 的红外发射器。 ESP32-S3 板子上标配的红外发射头。将利用管脚输入功能,发送我们自己定义的红外编码信号,并将编码后的键值在 LCD 屏中显示出来。
本章分为如下几个小节:
27.1 红外发射器简介
27.2 硬件设计
27.3 程序设计
27.4 下载验证
27.1 红外发射器介绍
RMT 外设是一个红外发射和接收控制器。它的数据格式灵活,可进一步扩展为多功能的通用收发器,发送或者接收多种类型的信号。上一章学习红外接收控制器,本章学习红外发射控制器。
ESP32-S3 的 RMT 外设是一个高度可配置的脉冲信号生成器,用于红外发射时的主要特性包括:
- 多通道支持:ESP32-S3 的 RMT 通常提供 8 个独立通道(通道 0-7),其中 通道 0-3 专用于发送(TX);
- 精密时序控制:支持可编程的时钟分辨率(例如 1 MHz,即 1 tick = 1μs),能够精确控制脉冲的宽度和间隔,以满足各种红外协议严格的时序要求;
- 硬件载波调制:内置硬件支持,可直接生成 38kHz(或其他频率)的载波并进行调制,无需 CPU 干预,极大减轻了主控的负担;
- DMA 支持:ESP32-S3 的 RMT 支持 DMA(直接内存访问),这对于传输长数据序列(如控制大量 LED 的 WS2812 灯带)时尤为重要,可以有效避免因 Wi-Fi 或蓝牙等中断造成的信号时序错乱;
- 灵活的数据格式:将需要发送的数据(如 NEC 协议的地址和命令)转换成一系列高低电平脉冲序列(rmt_symbol_word_t),并由 RMT 硬件自动输出。
ESP32 的 RMT 发送通道(TX Channel)的数据路径以及控制器路径,如下图所示:
图 27.1.1 RMT 发射器概述
模块功能详解:
模块名称 | 功能描述 |
---|---|
Input | 用户输入。这是由程序员定义的、想要发送的原始波形序列。图中的 |
Waveform Generator | 波形发生器。这是 RMT 硬件的核心功能之一。它读取 Input 中的序列,并根据其描述,在硬件层面生成精确的基带波形(Baseband Waveform),即不包含载波的原始数字波形。 |
Enable Carrier | 载波使能。这是一个控制信号,用于开启或关闭载波发生器。通常通过软件配置 RMT 寄存器的相应位来实现。 |
Carrier Generator | 载波发生器。当被使能后,该模块会生成一个高频的载波信号。对于红外遥控,最常用的载波频率是 38kHz。 |
mod | 调制器(Modulator)。这是红外发射的关键一步。它接收来自 Waveform Generator 的基带波形和来自 Carrier Generator 的载波信号,并进行调制。调制方式通常是幅度调制(ASK) 或更具体地称为通断键控(OOK): |
GPIO | 通用输入/输出引脚。经过调制后的信号最终通过某个配置好的 GPIO 引脚输出到外部电路。 |
Output | 物理输出。指从 GPIO 引脚输出的、最终的电信号。这个信号可以直接驱动一个红外发射二极管(IRED),将其转换成红外光信号发射出去。 |
表 27.1.1 RMT 发射器模块功能描述
驱动程序将用户数据编码为 RMT 数据格式,随后有 RMT 发射器根据编码生成波形。再将波形发送至 GPIO 引脚,发射器还可以提供载波,并调制高频载波信号。
具体实现流程如下:
整个流程可以理解为数据在 RMT 发射器内部的处理阶段,从上至下大致分为三层:
- 数据准备层(上层):用户提供需要发送的原始波形序列;
- 核心处理层(中层):RMT 硬件根据用户序列生成波形,并与载波进行调制;
- 物理输出层(下层):生成最终的调制信号并通过 GPIO 输出。
27.2 硬件设计
27.2.1 例程功能
开机后在 LCD 上显示一些信息,然后等待红外接收触发解码,在死循环里面,利用红外发射头发送键值(每 200ms加 1),同时在 LCD上显示当前发送键值与接收到的键值,来观察自发自收情况。其中 LED 用来指示程序运行状态。
27.2.2 硬件资源
1. LED 灯
LED-IO1
2. USART0
U0TXD-IO43
U0RXD-IO44
3. XL9555
IIC_SDA-IO41
IIC_SCL-IO42
4. SPILCD
CS-IO21
SCK-IO12
SDA-IO11
DC-IO40(在 P5 端口,使用跳线帽将 IO_SET 和 LCD_DC 相连)
PWR- IO1_3(XL9555)
RST- IO1_2(XL9555)
5. 红外接收头
REMOTE_IN-IO2
6. 红外发送头
REMOTE_IN-IO8(需要在 P3 端口处用跳线帽将 AIN 与 RMT 两个引脚连接起来)
27.2.3 原理图
红外接收头的原理图请参照上一章节的内容。红外发送头相关原理图,如下图所示。
图 27.2.3.1 红外接收头原理图
需要注意: REMOTE_OUT 并没有直接与 ESP32-S3 芯片的引脚相连,而是需要在 P3 端口
处使用跳线帽将 REMOTE_OUT 和 ADC_IN 连接起来, P3 端口的原理图以及实物连接方式如下
所示:
图 27.2.3.2 P3 端口原理图
图 27.2.3.3 P3 实物连接方式
开发板上接收红外遥控器信号的红外管外观如图 27.2.3 所示。使用时需要遥控器有红外管的一端对准开发板上的红外管才能正确收到信号。
图 27.2.3.4 开发板上的红外发送管位置
27.3 程序设计
27.3.1 程序流程图
本实验的程序流程图:
图 27.3.1.1 INFRARED_RECEPTION 实验程序流程图
27.3.2 RMT 函数解析
ESP-IDF 提供了一套 API 来配置 RMT。要使用此功能,需要导入必要的头文件:
#include "driver/rmt_tx.h"
#include "driver/rmt_rx.h"
#include "ir_nec_encoder.h"
接下来将介绍一些常用的 ESP32-S3 中的 RMT 函数,这些函数的描述及其作用如下:
(1)安装 RMT 接收通道
该函数用于安装 RMT 接收通道,其函数原型如下所示:
esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config,rmt_channel_handle_t *ret_chan);
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
config | 指向配置 RMT 发送通道的指针 |
ret_chan | 返回的通用 RMT 通道句柄 |
表 27.3.2.1 函数 rmt_new_tx_channel()形参描述
该函数的返回值描述,如下表所示:
返回值 | 描述 |
---|---|
ESP_OK | 返回: 0,配置成功 |
ESP_ERR_INVALID_ARG | 无效参数, 创建 RMT 发送通道失败 |
ESP_ERR_NOT_FOUND | 创建 RMT发送通道失败,因为所有 RMT通道都已用 完,没有更多空闲通道 |
ESP_ERR_NO_MEM | 内存不足, 创建 RMT 接收通道失败 |
ESP_ERR_NOT_SUPPORTED | 创建 RMT 发送通道失败,因为硬件不支持某些功 能,例如硬件不支持 DMA 功能 |
ESP_FAIL | 由于其他错误,创建 RMT 发送通道失败 |
表 27.3.2.2 函数 rmt_new_tx_channel()返回值描述
(2)使能 RMT 发送通道
该函数用于使能 RMT 发送通道,其函数原型如下所示:
esp_err_t rmt_enable(rmt_channel_handle_t channel);
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
channel | 创建的 RMT 通用通道 |
表 27.3.2.3 函数 rmt_enable()形参描述
该函数的返回值描述,如下表所示:
返回值 | 描述 |
---|---|
ESP_OK | 返回: 0,配置成功 |
ESP_ERR_INVALID_ARG | 无效参数, 创建 RMT 发送通道失败 |
ESP_ERR_INVALID_STATE | 使能 RMT 发送通道失败,因为它已经启用 |
ESP_FAIL | 由于其他错误,使能 RMT 发送通道失败 |
表 27.3.2.4 函数 rmt_enable()返回值描述
(3)通过 RMT 发送通道传输数据
该函数用于启动 RMT 接收通道的接收任务,其函数原型如下所示:
esp_err_t rmt_transmit(rmt_channel_handle_t channel,rmt_encoder_t *encoder,const void *payload,size_t payload_bytes,const rmt_transmit_config_t *config)
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
tx_channel | 创建的 RMT 通道 |
encoder | 用户自己创建的编码器或者通过其它 API 构建的编码器 |
payload | 要编码为 RMT 符号的原始数据 |
payload_bytes | “有效负载”的大小(以字节为单位) |
config | 发送特定配置 |
表 27.3.2.5 函数 rmt_transmit()形参描述
该函数的返回值描述,如下表所示:
返回值 | 描述 |
---|---|
ESP_OK | 返回: 0,配置成功 |
ESP_ERR_INVALID_ARG | 无效参数, 使能发送任务失败 |
ESP_ERR_INVALID_STATE | 由于通道未启用,使能发送任务失败 |
ESP_ERR_NOT_SUPPORTED | 传输数据失败,因为硬件不支持某些功能,例如不 支持的循环计数 |
ESP_FAIL | 由于其他错误,使能发送任务失败 |
表 27.3.2.6 函数 rmt_transmit()返回值描述
(4)配置编码器
创建并初始化一个基于 NEC 协议 的 RMT 编码器。此编码器负责将用户提供的地址和命令数据,自动转换为包含引导码、地址码、地址反码、命令码、命令反码在内的完整 NEC 协议帧,并输出为精确的 RMT 符号序列(rmt_symbol_word_t
)。函数原型如下:
esp_err_t rmt_new_ir_nec_encoder(const ir_nec_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
该函数的形参描述,如下表所示:
参数 | 类型 | 说明 |
---|---|---|
|
| 指向 NEC 编码器配置结构体的指针。 |
|
| 输出参数。函数成功执行后,此句柄将指向新创建的 NEC 编码器。 |
表 27.3.2.7 函数 rmt_new_ir_nec_encoder()形参描述
返回 ESP_OK
表示成功,其他错误码(如 ESP_ERR_INVALID_ARG
, ESP_ERR_NO_MEM
)则表示失败。
函数使用示例如下:
#include "driver/rmt_tx.h"
#include "ir_nec_encoder.h" // 确保包含正确的头文件// 1. 配置并创建RMT发送通道(前提步骤)
rmt_tx_channel_config_t tx_chan_cfg = {.clk_src = RMT_CLK_SRC_DEFAULT,.gpio_num = GPIO_NUM_8, // 连接红外发射管(IRED)的GPIO.mem_block_symbols = 64,.resolution_hz = 1000000, // 通道分辨率1MHz,1 tick = 1us.trans_queue_depth = 4,
};
rmt_channel_handle_t tx_channel = NULL;
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_cfg, &tx_channel));// 2. 为发送通道配置载波(NEC协议通常为38kHz)
rmt_carrier_config_t carrier_cfg = {.frequency_hz = 38000, // NEC协议标准载波频率.duty_cycle = 0.33, // 常见占空比33%
};
ESP_ERROR_CHECK(rmt_apply_carrier(tx_channel, &carrier_cfg));// 3. 配置NEC编码器参数
ir_nec_encoder_config_t nec_encoder_cfg = {.resolution = 1000000, // 编码器分辨率1MHz,应与通道分辨率一致
};// 4. 创建NEC编码器句柄
rmt_encoder_handle_t nec_encoder = NULL;
ESP_ERROR_CHECK(rmt_new_ir_nec_encoder(&nec_encoder_cfg, &nec_encoder));// 5. 使能发送通道
ESP_ERROR_CHECK(rmt_enable(tx_channel));// ...(在需要发送红外信号的地方,例如某个任务或函数中)
// 6. 准备要发送的数据
ir_nec_scan_code_t scan_code = {.address = 0x00, // 设备地址码.command = 0x45, // 命令码
};// 7. 配置发送参数
rmt_transmit_config_t transmit_config = {.loop_count = 0, // 发送一次,不循环
};// 8. 发送数据!编码和波形生成由NEC编码器自动完成
ESP_ERROR_CHECK(rmt_transmit(tx_channel, nec_encoder, &scan_code, sizeof(scan_code), &transmit_config));
(5)配置载波与占空比
此函数为指定的 RMT 发送通道(TX Channel) 使能或禁用载波调制,并设置载波的频率和占空比等参数。函数原型如下:
esp_err_t rmt_apply_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config)
该函数的形参描述,如下表所示:
参数 | 类型 | 说明 |
---|---|---|
|
| RMT 发送通道的句柄。此句柄需通过 |
|
| 指向载波配置结构体的指针,用于设置载波的各项参数。 |
表 27.3.2.8 函数 rmt_apply_carrier()形参描述
返回值:返回 ESP_OK
表示成功,其他错误码(如 ESP_ERR_INVALID_ARG
)则表示失败。
载波配置结构体 rmt_carrier_config_t
定义如下:
typedef struct {uint32_t frequency_hz; // 载波频率(单位:Hz)。设为0则禁用载波输出。float duty_cycle; // 载波占空比(范围:0.0 ~ 1.0)。uint32_t polarity_active_low; // 载波极性:0-高电平有效,1-低电平有效(不常用)。
} rmt_carrier_config_t;
关键参数说明:
参数 | 含义 | 说明 |
---|---|---|
| 载波频率 |
|
| 占空比 |
|
polarity_active_low | 极性 |
|
表 27.3.2.9 rmt_carrier_config_t结构体描述
27.3.3 RMT 驱动解析
在 IDF版的17_infrared_transmission 例 程 中 , 在 17_infrared_transmission\components\BSP 路径下新增了一个 EMISSION 文件夹,分别用于存放 emission.c、 emission.h 和ir_nec_encoder.c 以及 ir_nec_encoder.h 这四个文件。其中,emission.h 文件负责声明 RMT 相关的函数和变量, ir_nec_encoder.h 存放用于 IR NEC 帧编码为 RMT 符号的 RMT 编码器的相关结构体成员,而 emission.c 和 ir_nec_encoder.c 文件则实现了 RMT 的驱动代码。
(1) remote.h 文件
/* 引脚定义 */
#define RMT_RX_PIN GPIO_NUM_2 /* 连接RX_PIN的GPIO端口 */
#define RMT_TX_PIN GPIO_NUM_8 /* 连接TX_PIN的GPIO端口 */
#define RMT_RESOLUTION_HZ 1000000 /* 1MHz, 1 tick = 1us */
#define RMT_NEC_DECODE_MARGIN 200 /* 判断NEC时序时长的容差值,小于(值+此值),大于(值-此值)为正确 *//* NEC 协议时序时间,协议头9.5ms 4.5ms 逻辑0两个电平时长,逻辑1两个电平时长,重复码两个电平时长 */
#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/* 函数声明 */
void emission_init(void);
void example_parse_nec_frame(rmt_symbol_word_t *rmt_nec_symbols, size_t symbol_num);
bool RMT_Rx_Done_Callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data);
(2)remote.c 文件
选择使用 IO8 作为红外接收管的引脚。发送初始化流程如下:
- 配置并创建RMT发送通道(前提步骤);
- 为发送通道配置载波(NEC协议通常为38kHz);
- 配置NEC编码器参数;
- 创建NEC编码器句柄;
- 使能发送通道;
- 准备要发送的数据;
- 配置发送参数;
- 发送数据!编码和波形生成由NEC编码器自动完成。
接下来,红外接收初始化函数 emission_init,代码如下:
/* 保存NEC解码的地址和命令字节 */
uint16_t s_nec_code_address;
uint16_t s_nec_code_command;QueueHandle_t receive_queue;
uint8_t tbuf[40];/*** @brief 初始化RMT* @param 无* @retval 无*/
void emission_init(void)
{uint8_t t = 0;/* 配置接收通道 */rmt_rx_channel_config_t rx_channel_cfg = {.clk_src = RMT_CLK_SRC_DEFAULT, /* RMT接收通道时钟源 */.resolution_hz = RMT_RESOLUTION_HZ, /* RMT接收通道时钟分辨率 */.mem_block_symbols = 64, /* 通道一次可以存储的RMT符号数量 */.gpio_num = RMT_RX_PIN, /* RMT接收通道引脚 */};rmt_channel_handle_t rx_channel = NULL;ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channel)); /* 创建一个RMT接收通道 *//* 创建消息队列,接收红外编码 */QueueHandle_t receive_queue = xQueueCreate(1, sizeof(rmt_rx_done_event_data_t)); /* 定义一个消息队列,用以处理RMT接收的回调函数 */assert(receive_queue);rmt_rx_event_callbacks_t cbs = {.on_recv_done = RMT_Rx_Done_Callback, /* 事件回调,当一个RMT通道接收事务完成时调用 */};/* 注册红外回调函数 */ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(rx_channel, &cbs, receive_queue)); /* 为RMT RX信道设置回调 *//* 以下时间要求基于NEC协议 */rmt_receive_config_t receive_config = {.signal_range_min_ns = 1250, /* NEC信号的最短持续时间为560us,1250ns<560us,有效信号不会被视为噪声 */.signal_range_max_ns = 12000000, /* NEC信号的最长持续时间为9000us,12000000ns>9000us,接收不会提前停止 */};/* 配置发送通道 */rmt_tx_channel_config_t tx_channel_cfg = {.clk_src = RMT_CLK_SRC_DEFAULT, /* RMT发送通道时钟源 */.resolution_hz = RMT_RESOLUTION_HZ, /* RMT发送通道时钟分辨率 */.mem_block_symbols = 64, /* 通道一次可以存储的RMT符号数量 */.trans_queue_depth = 4, /* 允许在后台挂起的事务数,本例不会对多个事务进行排队,因此队列深度>1就足够了 */.gpio_num = RMT_TX_PIN, /* RMT发送通道引脚 */};rmt_channel_handle_t tx_channel = NULL;ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel)); /* 创建一个RMT发送通道 *//* 配置载波与占空比,红外遥控的通用标准,接收端通常被设计为只对38kHz的载波敏感,占空比33%是常见的效率与稳定性的平衡点*/rmt_carrier_config_t carrier_cfg = {.frequency_hz = 38000, /* 载波频率,0表示禁用载波 */.duty_cycle = 0.33, /* 载波占空比 */};ESP_ERROR_CHECK(rmt_apply_carrier(tx_channel, &carrier_cfg)); /* 对发送信道应用调制功能 *//* 不会在循环中发送NEC帧 */rmt_transmit_config_t transmit_config = {.loop_count = 0, /* 0为不循环,-1为无限循环 */};/* 配置编码器 */ir_nec_encoder_config_t nec_encoder_cfg = {.resolution = RMT_RESOLUTION_HZ, /* 编码器分辨率 */};rmt_encoder_handle_t nec_encoder = NULL;// NEC编码器,自动将用户数据(地址、命令)转换为符合NEC协议格式的脉冲序列(包括引导码、地址、命令、反码等),极大简化开发ESP_ERROR_CHECK(rmt_new_ir_nec_encoder(&nec_encoder_cfg, &nec_encoder)); /* 配置编码器 *//* 使能发送、接收通道 */ESP_ERROR_CHECK(rmt_enable(tx_channel)); /* 使能发送通道 */ESP_ERROR_CHECK(rmt_enable(rx_channel)); /* 使能接收通道 *//* 保存接收到的RMT符号 */rmt_symbol_word_t raw_symbols[64]; /* 64个符号对于标准NEC框架应该足够 */rmt_rx_done_event_data_t rx_data;ESP_ERROR_CHECK(rmt_receive(rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config)); /* 准备接收 */while (1){if (xQueueReceive(receive_queue, &rx_data, pdMS_TO_TICKS(1000)) == pdPASS) /* 等待RX完成信号 */{example_parse_nec_frame(rx_data.received_symbols, rx_data.num_symbols); /* 解析接收符号并打印结果 */ESP_ERROR_CHECK(rmt_receive(rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config)); /* 重新开始接收 */}else /* 超时,传输预定义的IR NEC数据包 */{t++;if (t == 0){t = 1;}const ir_nec_scan_code_t scan_code = {.address = 0x00,.command = t,};lcd_fill(116, 110, 176, 150, WHITE);sprintf((char *)tbuf, "%d", scan_code.command);printf("TX KEYVAL = %d\n", scan_code.command);lcd_show_string(116, 110, 200, 16, 16, (char *)tbuf, BLUE);ESP_ERROR_CHECK(rmt_transmit(tx_channel, nec_encoder, &scan_code, sizeof(scan_code), &transmit_config)); /* 通过RMT发送信道传输数据 */}}
}
在 else 语句中定义了一个 t, t 的初始值为 0,通过 t 的自加我们能做到发送 t 自加后的数值,达到发送不同红外编码的目的,但也仅能发送 0~255 的数值。
接下来,介绍一下红外按键扫描函数 example_parse_nec_frame,代码如下:
/*** @brief 根据NEC编码解析红外协议并打印指令结果* @param 无* @retval 无*/
void example_parse_nec_frame(rmt_symbol_word_t *rmt_nec_symbols, size_t symbol_num)
{switch (symbol_num) /* 解码RMT接收数据 */{case 34: /* 正常NEC数据帧 */{if (nec_parse_frame(rmt_nec_symbols) ){lcd_fill(116, 130, 176, 150, WHITE);sprintf((char *)tbuf, "%d", s_nec_code_command);printf("RX KEYCNT = %d\n", s_nec_code_command);lcd_show_string(116, 130, 200, 16, 16, (char *)tbuf, BLUE);}break;}case 2: /* 重复NEC数据帧 */{if (nec_parse_frame_repeat(rmt_nec_symbols)){printf("RX KEYCNT = %d, repeat\n", s_nec_code_command);}break;}default: /* 未知NEC数据帧 */{printf("Unknown NEC frame\r\n\r\n");break;}}
}
该函数调用 rmt_nec_parse_frame()函数将 RMT结果解码出 NEC地址和命令,这里只需要处理解码出的命令即可,因为地址不会变。处理解码出来的命令我们可以直观的在 LCD 以及串口助手上看见。由于我们在程序中编写的功能是实现开发板的自发自收功能,所以当我们接上跳线帽的时候会看见发送端和接收端显示的数据是一样的。同样的,我们也对重复的 NEC 数据帧以及未知的 NEC 数据帧进行识别与处理,重复的 NEC 数据帧会通过串口打印键值以及十六进制的数据,并添加上“repeat”的标识以作区分。而对于未知数据帧会通过串口打印“Unknown NEC frame”的字样。
/*** @brief 将RMT接收结果解码出NEC地址和命令* @param 无* @retval 无*/
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++;}/* 保存数据地址和命令,用于判断重复按键 */s_nec_code_address = address;s_nec_code_command = command;return true;
}
该函数将接收到的电平数组解码成红外编码,也就是 NEC 地址以及命令。首先,通过布尔
型函数对比数据时序长度是否为逻辑 1或者逻辑 0,从而获取地址、地址反码以及命令、命令反码,并检查获取的数据是否正确,最后分别保存数据地址和命令到 s_nec_code_address 以及s_nec_code_command,用于判断重复按键。
(3)ir_nec_encoder.h 文件
/*** @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);
(4)ir_nec_encoder.c 文件
该文件是 ESP32 的一个红外编解码文件,该文件是作为 RMT 编解码过程中的一个辅助文件,由于代码过长,结合例程资料进行学习。这个函数rmt_new_ir_nec_encoder是ESP32库函数,但是包对应的ir_nec_encoder.h,会提示找不到,估计这块也是为啥将这个函数拷贝出来放到当前文件夹下原因吧。
27.3.4 CMakeLists.txt 文件
打开本实验 BSP 下的 CMakeLists.txt 文件,其内容如下所示:
set(src_dirsEMISSIONIICLCDLEDSPIXL9555)set(include_dirsEMISSIONIICLCDLEDSPIXL9555)set(requiresdriver)idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
27.3.5 实验应用代码
打开 main/main.c 文件,该文件定义了工程入口函数,名为 app_main。该函数代码如下。
i2c_obj_t i2c0_master;/*** @brief 程序入口* @param 无* @retval 无*/
void app_main(void)
{esp_err_t ret;ret = nvs_flash_init(); /* 初始化NVS */if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND){ESP_ERROR_CHECK(nvs_flash_erase());ret = nvs_flash_init();}led_init(); /* 初始化LED */i2c0_master = iic_init(I2C_NUM_0); /* 初始化IIC0 */spi2_init(); /* 初始化SPI2 */xl9555_init(i2c0_master); /* 初始化XL9555 */lcd_init(); /* 初始化LCD */lcd_show_string(30, 50, 200, 16, 16, "ESP32", RED);lcd_show_string(30, 70, 200, 16, 16, "REMOTE TEST", RED);lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);lcd_show_string(30, 110, 200, 16, 16, "TX KEYVAL:", RED);lcd_show_string(30, 130, 200, 16, 16, "RX KEYCNT:", RED);emission_init(); /* 初始化REMOTE */
}
27.4 下载验证
下载代码后,可以看到 LCD 显示如下图所示。
图 27.4.1 红外发射实验测试图
可以看到开发板发送的红外信号全部红外接收头接收到,说明已经实现了开发板自发自收红外信号的功能。 注意:由于开发板的红外接收头和红外发射头没有正对,有可能会造成接收不到数据情况,这样我们需要一个提供一个反射面,最简单的做法就是将手放在传感器正前方大约 10cm 左右的位置。