ESP32 IDF库中的RMT驱动
RMT(Remote Control Module)驱动是ESP-IDF库中的一个重要组成部分,它主要用于处理远程控制编码和解码。
红外遥控器介绍
一、红外遥控技术介绍
红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点。它被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计算机和手机系统中。
红外遥控系统由发射和接收两大部分组成,应用编码/解码专用集成电路芯片来进行控制操作。发射部分包括键盘矩阵、编码调制、LED红外发送器;接收部分包括光、电转换放大器、解调、解码电路。
二、红外器件特性
红外遥控器主要由红外发射器和红外接收器两部分组成。
-
红外发射器:红外发射器通常是一个红外LED,它可以发射红外光。当电流通过LED时,它会发射出红外光。红外LED的发射波长通常在940nm左右,这是人眼无法看到的红外光。
-
红外接收器:红外接收器是一个光电二极管,它可以将接收到的红外光转换为电信号。红外接收器通常有一个滤波器,可以过滤掉大部分的环境光,只接收特定频率的红外光。
红外发射管也是属于二极管类,红外发射电路通常使用三极管控制红外发射器的导通或者截至,在导通的时候,红外发射管会发射出红外光,反之,就不会发射出红外光。虽然我们用肉眼看不到红外光,但是我们借助手机摄像头就能看到红外光。但是红外接收管的特性是当接收到红外载波信号时,OUT 引脚输出低电平;假如没有接收到红外载波信号时,OUT 引脚输出高电平。
红外载波信号其实就是由一个个红外载波周期组成。在频率为 ( 38 K H z ) (38KHz ) (38KHz)下,红外载波周期约等于 26.3 u s ( 1 s / 38 K H z ≈ 26.3 u s ) 26.3us(1s / 38KHz ≈ 26.3us) 26.3us(1s/38KHz≈26.3us)。在一个红外载波发射周期里,发射红外光时间 8.77 u s 8.77us 8.77us 和不发射红外光 17.53 u s 17.53us 17.53us,发射红外光的占空比一般为 1 / 3 1/3 1/3。相对的,整个周期内不发射红外光,就是载波不发射周期。在红外遥控器内已经把载波和不载波信号处理好,我们需要做的就是识别遥控器按键发射出的信号,信号也是遵循某种协议的。
这里推荐一个视频可以快速了解红外控制器件:
📻一帧红外遥控信号,竟如此复杂,超乎你的想象!红外遥控的工作原理!
三、红外编解码协议介绍
红外遥控器通常使用特定的编解码协议来发送和接收数据。目前广泛使用的是:PWM(脉冲宽度调制)的NEC 协议和Philips PPM(脉冲位置调制)的 RC-5 协议的。这里以NEC为主讲解ESP32 红外遥控 (RMT)
NEC 协议,其特征如下:
- 8 位地址和 8 位指令长度
- 地址和命令 2 次传输(确保可靠性)
- PWM 脉冲位置调制,以发射红外载波的占比代表“0”和“1”
- 载波频率为 38Khz
- 位时间为 1.125ms 或 2.25ms
在 NEC 协议中,如何为协议中的数据‘0’或者‘1’这里分开红外接收器和红外发射器
红外发射器:
- 发送协议数据‘0’ = 发射载波信号560us + 不发射载波信号 560us
- 发送协议数据‘1’ = 发射载波信号 560us + 不发射载波信号 1680us
红外发射器的位定义如下图所示。
🚨需要注意的是红外编码发送的时候,并不单单是通过高低电平发送的,是在38khz的载波下进行发送的
红外接收器:
- 接收到协议数据‘0’ = 560us 低电平 + 560us 高电平
- 接收到协议数据‘1’ = 560us 低电平 + 1680us 高电平
红外接收器的位定义如下图所示
NEC协议的数据格式包括以下几个部分:
- 同步码:这是一个9ms的低电平+4.5ms的高电平,用于同步。
- 地址码和地址反码:这是一个8位数据格式,低位在前,高位在后。
- 控制码和控制反码:这部分用于表示具体的控制命令。
如果你长时间按住遥控按钮,使用NEC协议的红外遥控器将会发射一个以110ms为周期的重复码。每一次用户按下遥控器按钮,遥控器在发送一次指令码后,就不会再发送指令码了,而是发送一段重复码。
RMT驱动
简介
ESP32-S3的RMT 是一个红外发送和接收控制器,可通过软件加解密多种红外协议。RMT 模块可以实现模块内置 RAM 中的脉冲编码转换为信号输出,或将模块的输入信号转换为脉冲编码存入 RAM 中。此外,RMT 模块可以选择是否对输出信号进行载波调制,也可以选择是否对输入信号进行滤波和去噪处理。
RMT 共有八个通道,编码为 0 ~ 7,各通道可独立用于发送或接收信号:
- 0 ~ 3 通道专门用于发送信号;
- 4 ~ 7 通道专门用于接收信号。
每个发送通道和接收通道分别有一组功能相同的寄存器。另外,发送通道 3 和接收通道 7 对应的 RAM 支持 DMA访问,因此还有 DMA 相关的控制和状态寄存器。
RMT 符号的内存布局
RMT 硬件定义了自己的数据模式,称为 RMT 符号。对于一个 RMT 符号的位字段:每个符号由两对两个值组成,每对中的第一个值是一个 15 位的值,表示信号持续时间,以 RMT 滴答计。每对中的第二个值是一个 1 位的值,表示信号的逻辑电平,即高电平或低电平。
RMT RX驱动的接收解码
1️⃣安装 RMT 接收通道
该函数用于安装 RMT 接收通道,其函数原型如下所示:
esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_handle_t *ret_chan);
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
config | 指向配置 RMT 接收通道的指针 |
ret_chan | 返回的通用 RMT 通道句柄 |
该函数的使用示例,如下所示:
#include "driver/gpio.h" void example_fun(void)
{ // 初始化 RMT 接收通道句柄为 NULLrmt_channel_handle_t rx_chan = NULL; // 配置 RMT 接收通道参数rmt_rx_channel_config_t rx_chan_config = { .clk_src = RMT_CLK_SRC_DEFAULT, // 使用默认时钟源.gpio_num = 0, // 使用 GPIO0.mem_block_symbols = 64, // 内存块大小为 64.resolution_hz = 1 * 1000 * 1000, // 分辨率为 1MHz.trans_queue_depth = 4, // 传输队列深度为 4.flags.invert_out = false, // 不反转输出信号.flags.with_dma = false, // 不使用 DMA}; // 安装 RMT 接收通道,并检查错误ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_chan));
}
2️⃣配置 RMT 接收通道的回调函数
该函数用于配置 RMT 接收通道的回调函数,其函数原型如下所示:
esp_err_t rmt_rx_register_event_callbacks(rmt_channel_handle_t rx_channel, const rmt_rx_event_callbacks_t *cbs, void *user_data);
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
rx_channel | 创建的 RMT 通道句柄 |
cbs | RMT 接收事件回调函数结构体指针 |
user_data | 用户数据,将直接传递给回调函数 |
该函数的使用示例,如下所示:
#include "driver/gpio.h" void example_fun(void)
{ QueueHandle_t receive_queue; rmt_channel_handle_t rx_chan = NULL; rmt_rx_channel_config_t rx_chan_config = { .clk_src = RMT_CLK_SRC_DEFAULT, // 使用默认时钟源.gpio_num = 0, // 使用 GPIO0.mem_block_symbols = 64, // 内存块大小为 64.resolution_hz = 1 * 1000 * 1000, // 分辨率为 1MHz.trans_queue_depth = 4, // 传输队列深度为 4.flags.invert_out = false, // 不反转输出信号.flags.with_dma = false, // 不使用 DMA}; ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_chan)); // 创建用于存储接收完成事件数据的队列receive_queue = xQueueCreate(1, sizeof(rmt_rx_done_event_data_t)); assert(receive_queue); //检查队列是否创建成功rmt_rx_event_callbacks_t cbs = { .on_recv_done = RMT_Rx_Done_Callback, // 设置接收完成回调函数}; ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(rx_chan, &cbs, receive_queue));
}
3️⃣创建一个基于 NEC 协议的 RMT 编码器
该函数用于创建一个基于 NEC 协议的 RMT 编码器,其函数原型如下所示:
esp_err_t rmt_new_ir_nec_encoder(const ir_nec_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder);
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
config | 指向 RMT 编码器配置的指针 |
ret_encoder | 返回的 RMT 编码器句柄 |
该函数的使用示例,如下所示:
#include "driver/gpio.h" void example_fun(void)
{ // 配置 NEC 编码器参数ir_nec_encoder_config_t nec_encoder_cfg = { .resolution = 1000000, // 分辨率为 1MHz};// 初始化 NEC 编码器句柄rmt_encoder_handle_t nec_encoder = NULL; // 创建基于 NEC 协议的 RMT 编码器,并检查错误ESP_ERROR_CHECK(rmt_new_ir_nec_encoder(&nec_encoder_cfg, &nec_encoder));
}
4️⃣使能 RMT 接收通道
该函数用于使能 RMT 接收通道,其函数原型如下所示:
esp_err_t rmt_enable(rmt_channel_handle_t channel);
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
channel | 创建的 RMT 通道句柄 |
该函数的使用示例,如下所示:
#include "driver/gpio.h" void example_fun(void)
{ // 初始化 RMT 接收通道句柄为 NULLrmt_channel_handle_t rx_channel = NULL; //省略中间过程.....// 使能 RMT 接收通道,并检查错误rmt_enable(rx_channel);
}
5️⃣启动 RMT 接收通道的接收任务
该函数用于启动 RMT 接收通道的接收任务,其函数原型如下所示:
esp_err_t rmt_receive(rmt_channel_handle_t rx_channel, void *buffer, size_t buffer_size, const rmt_receive_config_t *config);
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
rx_channel | 创建的 RMT 通道句柄 |
buffer | 用于存储接收到的 RMT 符号的缓冲区 |
buffer_size | 缓冲区大小 |
config | 接收特定配置 |
该函数的使用示例,如下所示:
#include "driver/gpio.h" void example_fun(void)
{ // 声明用于存储接收到的 RMT 符号的缓冲区rmt_symbol_word_t raw_symbols[64]; // 配置接收任务特定配置rmt_receive_config_t receive_config; // 启动 RMT 接收通道的接收任务,并检查错误rmt_receive(rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config);
}
RMT TX驱动的发送
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 通道句柄 |
该函数的使用示例,如下所示:
#include "driver/gpio.h" // 定义示例函数
void example_fun(void)
{ // 声明 RMT 通道句柄rmt_channel_handle_t tx_chan = NULL; // 定义 RMT 发送通道配置rmt_tx_channel_config_t tx_chan_config = { .clk_src = RMT_CLK_SRC_DEFAULT, // 使用默认时钟源.gpio_num = 0, // GPIO0 用于发送.mem_block_symbols = 64, // 内存块大小为 64.resolution_hz = 1 * 1000 * 1000, // 分辨率为 1 MHz.trans_queue_depth = 4, // 传输队列深度为 4.flags.invert_out = false, // 输出不反转.flags.with_dma = false, // 不使用 DMA}; // 调用函数安装 RMT 发送通道,并检查返回错误ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &tx_chan));
}
2️⃣使能 RMT 发送通道
该函数用于使能 RMT 发送通道,其函数原型如下所示:
esp_err_t rmt_enable(rmt_channel_handle_t channel);
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
channel | 创建的 RMT 通用通道 |
#include "driver/gpio.h" // 定义示例函数
void example_fun(void)
{ rmt_channel_handle_t tx_channel = NULL; // 声明 RMT 通道句柄rmt_enable(tx_channel); // 调用函数使能 RMT 发送通道
}
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)
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
channel | 创建的 RMT 通道 |
encoder | 用户自己创建的编码器或者通过其它 API 构建的编码器 |
payload | 要编码为 RMT 符号的原始数据 |
payload_bytes | “有效负载”的大小(以字节为单位) |
config | 发送特定配置 |
发送示例
#include "driver/gpio.h"
#include "driver/rmt.h"// 定义示例函数
void example_fun(void)
{ // 1. 安装 RMT 发送通道// 声明 RMT 通道句柄rmt_channel_handle_t tx_chan = NULL; // 定义 RMT 发送通道配置rmt_tx_channel_config_t tx_chan_config = { .clk_src = RMT_CLK_SRC_DEFAULT, // 使用默认时钟源.gpio_num = GPIO_NUM_0, // GPIO0 用于发送.mem_block_symbols = 64, // 内存块大小为 64.resolution_hz = 1 * 1000 * 1000, // 分辨率为 1 MHz.trans_queue_depth = 4, // 传输队列深度为 4.flags.invert_out = false, // 输出不反转.flags.with_dma = false, // 不使用 DMA}; // 调用函数安装 RMT 发送通道,并检查返回错误ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &tx_chan)); // 2. 使能 RMT 发送通道rmt_enable(tx_chan); // 调用函数使能 RMT 发送通道// 3. 通过 RMT 发送通道传输数据// 创建数据数组uint8_t data[4] = {0x12, 0x34, 0x56, 0x78};// 创建 RMT 编码器rmt_encoder_t encoder;ESP_ERROR_CHECK(rmt_translator_init(&encoder, sizeof(data), false)); // 数据长度为4字节,不进行反转// 创建 RMT 发送配置rmt_transmit_config_t transmit_config = {.carrier_freq_hz = 38000, // 载波频率为38kHz.carrier_duty_percent = 50, // 载波占空比为50%.idle_level = RMT_IDLE_LEVEL_LOW, // 高电平空闲.carrier_level = RMT_CARRIER_LEVEL_HIGH, // 高电平为载波.loop_en = false, // 关闭循环发送.loop_count = 0, // 循环次数为0};// 使用 RMT 发送通道传输数据ESP_ERROR_CHECK(rmt_transmit(tx_chan, &encoder, data, sizeof(data), &transmit_config));
}
总结
RMT驱动是ESP32的一个强大功能,它可以方便地处理各种远程控制协议,对于开发物联网和嵌入式系统具有很大的帮助。
参考资料
ESP-IDF 红外遥控 (RMT)
正点原子DNESP32S3 开发板教程-IDF 版