《ESP32-P4开发指南—V1.0》第二十九章 INFRARED_TRANSMISSION实验

第二十九章 INFRARED_TRANSMISSION实验


       上一章中,我们通过红外接收头来接收遥控器发过来的红外信号。在本章,我们将介绍如何通过红外发射头来发射红外信号,同时来模拟我们的红外遥控器。ESP32-P4开发板上板载一个红外发射头,我们可以通过软件编程来直接实现红外信号的发送,同时我们利用上一章红外接收实验来实现开发板的红外信号自发自收功能。

       本章分为如下几个小节:

       29.1 红外发射简介

       29.2 硬件设计

       29.3 程序设计

       29.4 下载验证


        29.1 红外发射简介

       在上一章中,我们已经讲解了红外遥控器的原理以及ESP32-P4红外遥控(RMT)外设,这里请大家参考上一章的内容进行学习。


        29.2 硬件设计


       29.2.1 例程功能

       在LCD上显示一些信息后,然后等待红外接收触发解码,在死循环中,我们利用红外发射头发送键值(每100ms加1),同时在LCD上显示当前发送键值与接收到的键值,来观察自发自收情况。


       29.2.2 硬件资源


       1)LED灯

              LED 0 - IO51


       2)RGBLCD / MIPILCD(引脚太多,不罗列出来)


       3)红外接收头

              REMOTE_IN - IO2


       4)红外发射头

              REMOTE_OUT – IO19(需要在P4端口处用跳线帽将AIN与RMT连接起来)


       29.2.3 原理图

       红外接收头的原理图请参考上一章节的内容,在此不再贴出。红外发射头相关原理图如下图所示。


图29.2.3.1 红外接收头原理图


       需要注意:REMOTE_OUT并没有直接与ESP32-P4芯片的引脚连接,而是需要在P4端口处使用跳线帽将REMOTE_OUT和ADC_IN连接起来,P4端口的原理图及实物连接如下图所示


 

图29.2.3.2 P4端口原理图及实物连接图


       ESP32-P4开发板上的红外发射头如下图所示。


图29.2.3.3 底板上的红外发射管位置


        29.3 程序设计


       29.3.1 RMT的IDF驱动

       RMT外设驱动位于ESP-IDF下的components/esp_driver_rmt目录下。使用RMT功能,必须先导入以下头文件:

#include "driver/rmt_tx.h"

       本例程主要实现的是RMT的发送功能。接下来,作者将介绍一些常用的函数,这些函数的描述及其作用如下:


       1,创建RMT发送通道函数rmt_new_tx_channel

       该函数用于创建RMT发送通道,其函数原型如下:

esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, 
  rmt_channel_handle_t *ret_chan);

       函数形参:


表29.3.1.1 rmt_new_tx_channel函数形参描述


       函数返回值:

       ESP_OK表示创建RMT发送通道成功。

       ESP_ERR_INVALID_ARG表示错误参数。

       ESP_ERR_NO_MEM表示内存不足。

       ESP_ERR_NOT_FOUND表示所有发送通道已经用完。

       ESP_ERR_NOT_SUPPORTED表示某些功能不被硬件支持,比如DMA。

       ESP_FAIL表示其他错误。

       config为指向RMT发送通道配置结构体指针。接下来,笔者将介绍rmt_tx_channel_config_t结构体中各个成员,如下代码所示:

typedef struct {
    gpio_num_t gpio_num;        /* 发 射 器使用的GPIO编号 */
    rmt_clock_source_t clk_src; /* RMT通道的时钟源 */
    uint32_t resolution_hz;     /* 内部滴答计数器的分辨率 */
size_t mem_block_symbols;   /* 专用内存块大小(启动DMA时,内部DMA缓冲区大小) */
    size_t trans_queue_depth;   /* 内部传输队列深度 */
    int intr_priority;          /* 设置中断优先级 */
    struct {
        uint32_t invert_out: 1;  /* 在输出信号传递到RMT发 射 器前对其进行反转 */
        uint32_t with_dma: 1;      /* 为通道启动DMA后端 */
        uint32_t io_loop_back: 1;  /* 用于调试/测试 */
        uint32_t io_od_mode: 1;    /* 配置GPIO模式为开漏输出 */
    } flags;                       /* 接收通道配置标志 */
} rmt_tx_channel_config_t;

       rmt_tx_channel_config_t结构体用于配置RMT发送通道参数,以下对各个成员做简单介绍。

       1)gpio_num:

       设置发 射 器使用的GPIO编号。

       2)clk_src:

       选择RMT通道时钟源,可选RMT_CLK_SRC_APB或RMT_CLK_SRC_REF_TICK,也可以选择RMT_CLK_SRC_DEFAULT,默认指的RMT_CLK_SRC_APB。注意:其他通道需要使用同一所选的时钟源。

       3)resolution_hz:

       设置内部滴答计数器的分辨率。基于此滴答,可计算出RMT信号的定时参数。

       4)mem_block_symbols:

       在启动DMA后端和未启用DMA后端,含义稍有不同。若with_dma启用DMA,这里的含义是最大化控制内部DMA缓冲区大小。若未启用DMA,这里的含义是控制通道专用内存块大小,至少为48。

       5)trans_queue_depth:

       设置内部事务队列深度。队列越深,待处理队列中可准备的的事务越多。

       6)intr_priority:

       设置中断优先级。如果设置为0,系统将会使用一个中低优先级的中断。

       7)invert_out:

       在RMT信号发送到GPIO引脚前翻转RMT信号。

       8)with_dma:

       为通道启用DMA后端。启动DMA后端可以释放CPU上的大部分通道工作负载,显著减轻CPU负担。

       9)io_loop_back:

       从GPIO输出的信号也将会被反馈到输入路径。

       10) io_od_mode:

       配置GPIO为开漏模式。

       ret_chan为指向RMT通道句柄结构体指针,rmt_channel_handle_t结构体成员比较多,可以不需要了解。


       2,载波调制函数rmt_apply_carrier

       该函数用于RMT发 射 器生成载波信号,并将其调制到消息信号上,其函数原型如下:

esp_err_t rmt_apply_carrier(rmt_channel_handle_t channel, const 
rmt_carrier_config_t *config)

       函数形参:


表29.3.1.2 rmt_apply_carrier函数形参描述


       函数返回值:

       ESP_OK表示配置成功。

       ESP_ERR_INVALID_ARG表示参数有误。

       ESP_FAIL表示其他错误。

       channel为RMT通道句柄,在rmt_new_tx_channel函数中创建的。

       config为指向载波相关配置结构体指针。接下来,介绍rmt_carrier_config_t结构体中各个成员,如下代码所示:

typedef struct {
    uint32_t frequency_hz;      /* 载波频率,单位为Hz */
    float duty_cycle;           /* 设置载波占空比 */
    struct {
        uint32_t polarity_active_low: 1;  /* 设置载波极性,应用载波的电平 */
        uint32_t always_on: 1;            /* 是否在数据发送完成后仍输出载波 */
    } flags;                              /* 载波配置标志 */
} rmt_carrier_config_t;

       在这里主要关注frequency_hz和duty_cycle两个成员,若要实现28.1.2中的红外载波信号,这里frequency_hz设置38KHz,而duty_cycle设置为33%。


       3,使能RMT发送通道函数rmt_enable

       该函数用于使能RMT发送通道,其函数原型如下:

esp_err_t rmt_enable(rmt_channel_handle_t channel);

       函数形参:


表29.3.1.3 rmt_enable函数形参描述


       函数返回值:

       ESP_OK表示读取数据成功。

       ESP_ERR_INVALID_ARG表示参数有误。

       ESP_ERR_INVALID_STATE表示通道已经使能。

       ESP_FAIL表示其他错误。

       注意:发送或接收RMT符号前,都应先调用rmt_enable。


       4,启动RMT发送通道发送任务函数rmt_transmit

       该函数用于启动RMT发送通道的发送任务,其函数原型如下:

esp_err_t rmt_transmit( rmt_channel_handle_t tx_channel, 
rmt_encoder_handle_t encoder, 
const void *payload, 
size_t payload_bytes, 
const rmt_transmit_config_t *config);

       函数形参:


表29.3.1.4 rmt_transmit函数形参描述


       函数返回值:

       ESP_OK表示发送数据成功。

       ESP_ERR_INVALID_ARG表示参数有误。

       ESP_ERR_INVALID_STATE表示通道未启用。

       ESP_ERR_NOT_SUPPORTED表示硬件不支持。

       ESP_FAIL表示其他错误。

       tx_channel为RMT通道句柄,在调用rmt_new_tx_channel函数时将返回RMT通道句柄。

       encoder为RMT编码器,在例程中由rmt_new_bytes_encoder函数创建,简单理解就是逻辑0和逻辑1的表示时序,结合28.1.3小节内容理解即可。

       payload为要编码成RMT符号的数据,按照RMT编码器的配置去生成。

       payload_bytes为payload的数据长度。

       config为指向专用于发送的配置结构体指针,rmt_transmit_config_t结构体成员如下所示。

typedef struct {
    int loop_count;       /* 设置发送的循环次数 */
    struct {
        uint32_t eot_level : 1;          /* 设置发 射 器完成工作时的输出电平 */
        uint32_t queue_nonblocking : 1;  /* 设置当传输队列满时该函数是否需要等待 */
    } flags;                             /* 发送特定配置的标志 */
} rmt_transmit_config_t;

       loop_count设置发送的循环次数。在发 射 器完成一轮发送后,如果该值未设置为零,则再次启动相同的发送程序。由于循环由硬件控制,RMT通道可以在几乎不需要CPU干预的情况下,生成许多周期性序列。

       若loop_count设置为-1,表示会启用无限循环发送机制,此时,除非手动调用rmt_disable函数,否则通道不会停止,也不会生成“完成发送”事件。

       若loop_count设置为正数,表示迭代次数有限。此时,“完成发送”事件在指定的迭代次数完成后发生。

       eot_level和queue_nonblocking中可使用默认配置即可。


       5,创建字节编码器函数rmt_new_bytes_encoder

       该函数用于创建字节编码器,将用户控件的字节流动态转换成RMT符号,其函数原型如下:

esp_err_t rmt_new_bytes_encoder(const rmt_bytes_encoder_config_t *config, 
rmt_encoder_handle_t *ret_encoder);

       函数形参:


表29.3.1.5 rmt_new_bytes_encoder函数形参描述


       函数返回值:

       ESP_OK表示创建成功。

       ESP_ERR_INVALID_ARG表示参数有误。

       ESP_ERR_NO_MEM表示内存不足。

       ESP_FAIL表示其他错误。

       config为指向字节编码器配置结构体指针,rmt_bytes_encoder_config_t结构体成员如下所示。

typedef struct {
    rmt_symbol_word_t bit0;  /* 如何在RMT符号中表示bit0 */
    rmt_symbol_word_t bit1;  /* 如何在RMT符号中表示bit1 */
    struct {
        uint32_t msb_first: 1; /* MSB 高位先发 */
    } flags;                   /* 编码器配置标志 */
} rmt_bytes_encoder_config_t;

       bit0和bit1都是rmt_symbol_word_t类型的变量,也就是表示逻辑0和逻辑1 的时序构成。rmt_symbol_word_t结构体类型已在前面有说明,这里就不罗列了。

       ret_encoder为指向编码器句柄结构体指针,rmt_encoder_handle_t结构体成员如下所示。

struct rmt_encoder_t {
/* 将用户数据编码为RMT符号并写入RMT内存 */
    size_t (*encode)(rmt_encoder_t *encoder, rmt_channel_handle_t tx_channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state);
    esp_err_t (*reset)(rmt_encoder_t *encoder); /* 重置编码 */
    esp_err_t (*del)(rmt_encoder_t *encoder);  /* 删除编码对象 */
};

       rmt_encoder_t定义了编码器的接口。encode是编码器的基础函数,编码会话在此处进行。reset是将编码器重置为初始状态。del可以释放编码器分配的资源。


       29.3.2 程序流程图


图28.3.2.1 红外接收实验程序流程图


       29.3.3 程序解析

       在20_infrared_transmission例程中,作者在20_infrared_transmission\components\BSP路径下新建了1个文件夹RMT_TX,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。


       1. RMT_TX驱动代码

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。RMT_TX驱动源码包括两个文件:rmt_nec_tx.c、rmt_nec_tx.h、ir_nec_encoder.c和ir_nec_encoder.h。

       其中,rmt_nec_tx.h文件负责声明RMT发送相关的宏以及函数声明,rmt_nec_tx.c文件存放用于实现RMT发送初始化函数。ir_nec_encoder.h主要存放RMT编码器的相关结构体类型以及函数声明,而ir_nec_encoder.c存放的是关于RMT编码器相关的函数接口。

       下面先解析rmt_nec_tx.h的程序,了解一下在该文件中定义了哪些宏。

#define RMT_TX_PIN                  GPIO_NUM_19    /* 连接RMT_TX的IO */
#define RMT_TX_HZ                   1000000       /* 1MHz 频率, 1 tick = 1us */

       开发板上使用的是IO19作为RMT发送通道,内部滴答计数器的分辨率设置为1MHz。

       接下来,解析rmt_nec_tx.c程序。该文件只有RMT发送初始化函数rmt_nec_tx_init,代码如下。

/**
 * @brief      RMT红外发送初始化
 * @param     无
 * @retval    ESP_OK:初始化成功
 */
esp_err_t rmt_nec_tx_init(void)
{
    /* 配置发送通道 */
    rmt_tx_channel_config_t tx_channel_cfg = {
        .gpio_num           = RMT_TX_PIN,          /* RMT发送通道引脚 */
        .clk_src            = RMT_CLK_SRC_DEFAULT, /* RMT发送通道时钟源 */
        .resolution_hz      = RMT_TX_HZ,         /* RMT发送通道时钟分辨率 */
        .mem_block_symbols  = 64,                /* 通道一次可存储的RMT符号数量 */
        .trans_queue_depth  = 4,       /* 允许在后台挂起的事务数 */
};
/* 创建一个RMT发送通道 */
    ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel));  
 
    /* 配置载波与占空比 */
    rmt_carrier_config_t carrier_cfg = {
        .frequency_hz = 38000,   /* 载波频率,0表示禁用载波 */
        .duty_cycle = 0.33,       /* 载波占空比33% */
};
/* 对发送信道应用调制功能 */
    ESP_ERROR_CHECK(rmt_apply_carrier(tx_channel, &carrier_cfg));
 
    /* 不会在循环中发送NEC帧 */
    transmit_config.loop_count = 0; /* 0为不循环,-1为无限循环 */
 
    /* 配置编码器 */
    ir_nec_encoder_config_t nec_encoder_cfg = {
        .resolution = RMT_TX_HZ,      /* 编码器分辨率 */
};
/* 配置编码器 */
    ESP_ERROR_CHECK(rmt_new_ir_nec_encoder(&nec_encoder_cfg, &nec_encoder));    
 
    ESP_ERROR_CHECK(rmt_enable(tx_channel));   /* 使能发送通道 */
 
    return ESP_OK;
}

       在RMT发送初始化函数中,首先对tx_channel_cfg变量的成员进行赋值,设置好红外发送通道的引脚为IO19,RMT时钟源为默认,分辨率为1Mz,通道容量大小为64和传输队列深度为4,然后调用rmt_new_tx_channel函数创建RMT发送通道。接着,对carrier_cfg变量的成员进行赋值,载波频率设置38K,而载波占空比为33%,然后调用rmt_apply_carrier函数对发送通道应用调制功能。接下来,便是配置RMT编码器,用到乐鑫官方的rmt_new_ir_nec_encoder函数。完成以上操作以后,最后调用rmt_enable函数使能发送通道,后续便可以通过rmt_transmit函数发送红外数据。

       接下来,解析ir_nec_encoder.c和ir_nec_encoder.h,这两个文件是乐鑫公开的关于RMT编码器的代码。

       首先看一下ir_nec_encoder.h文件,了解一下在该文件中定义了两个结构体类型。

typedef struct {
    uint16_t address;   /* 地址码 */
    uint16_t command;   /* 命令码 */
} ir_nec_scan_code_t;    /* NEC扫描码的表达 */
 
typedef struct {
    uint32_t resolution;   /* 编码器分辨率 */
} ir_nec_encoder_config_t;  /* NEC编码器配置类型 */

       ir_nec_scan_code_t结构体类型主要用来存放RMT帧的内容,而ir_nec_encoder_config_t结构体类型主要用来存放NEC编码器配置类型。

       ir_nec_encoder.c文件中,主要就是编码器的驱动框架代码,在前面RMT的IDF驱动中有说明到主要调用rmt_new_bytes_encoder函数实现的。这里先看一下,rmt_nec_tx_init函数中涉及的rmt_new_ir_nec_encoder函数,代码如下。

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 = rmt_alloc_encoder_mem(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;
}

       想要理解这个代码逻辑,还是得结合NEC遥控指令去理解。NEC遥控指令的数据格式:同步码头、地址码、地址反码、控制码、控制反码、结束码。也就是编码器需要按照这个NEC遥控指令数据格式发送波形数据。函数中,主要就是对字节编码、引导码编码和结束码相关做了配置,后面在函数中调用rmt_new_bytes_encoder函数创建好字节编码器,发送逻辑0和1就会出现符合NEC时序的信号。该函数中会涉及到rmt_encode_ir_nec,rmt_del_ir_nec_encoder和rmt_ir_nec_encoder_reset函数,这些函数都是在ir_nec_encoder.c中实现。

       rmt_encode_ir_nec函数用于编码NEC红外信号,其中会根据状态机编码发送引导码、地址码、命令码和结束码。

       rmt_del_ir_nec_encoder函数用于删除或释放一个NEC红外信号编码器的资源。

       rmt_ir_nec_encoder_reset函数用于重置NEC红外信号编码器的状态。

       以上三个函数,可自行查看,这里就不罗列出来了。红外发送实验代码讲解到这里。


       2. CMakeLists.txt文件

       本例程的功能实现主要依靠RMT_TX驱动。要在main函数中,成功调用RMT_RX文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:

set(src_dirs
            LED
LCD
RMT_RX
RMT_TX)
 
set(include_dirs
           LED
LCD
RMT_RX
RMT_TX)
 
set(requires
            driver
            esp_lcd
esp_common)
 
idf_component_register( SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})
 
component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)


       3. main.c驱动代码

       在main.c里面编写如下代码。

void rmt_rx_scan(rmt_symbol_word_t *rmt_nec_symbols, size_t symbol_num)
{
    switch (symbol_num)    /* 解码RMT接收数据 */
    {
        case 34:             /* 正常NEC数据帧 */
        {
            if (rmt_nec_parse_frame(rmt_nec_symbols) )
            {
                lcd_fill(110, 130, 200, 150, WHITE);
                sprintf((char *)tbuf, "%d", s_nec_code_command);
                ESP_LOGI(TAG,"RX KEYCNT = %d", s_nec_code_command);
                lcd_show_string(110, 130, 200, 16, 16, (char *)tbuf, BLUE);
            }
            break;
        }
        case 2:           /* 重复NEC数据帧 */
        {
            if (rmt_nec_parse_frame_repeat(rmt_nec_symbols))
            {
                ESP_LOGI(TAG,"RX KEYCNT = %d, repeat", s_nec_code_command);
            }
            break;
        }
        default:          /* 未知NEC数据帧 */
        {
            ESP_LOGI(TAG,"Unknown NEC frame");
            break;
        }
    }
}
 
void app_main(void)
{
    esp_err_t ret;
    uint8_t t = 0;
    rmt_rx_done_event_data_t rx_data;
 
    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());
        ESP_ERROR_CHECK(nvs_flash_init());
    }
 
    led_init();              /* LED初始化 */
    lcd_init();              /* LCD屏初始化 */
    rmt_nec_rx_init();       /* 红外接收器件初始化 */
    rmt_nec_tx_init();       /* 红外发送器件初始化 */
 
    lcd_show_string(30,  50, 200, 16, 16, "ESP32-P4", 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);
 
    while (1)
    {
        if (xQueueReceive(receive_queue,&rx_data,pdMS_TO_TICKS(1000)) == pdPASS)
        {
/* 解析接收符号并打印结果 */
            rmt_rx_scan(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 = {
                .command = t,
            };
 
            lcd_fill(110, 110, 200, 150, WHITE);
            sprintf((char *)tbuf, "%d", scan_code.command);
            ESP_LOGI(TAG, "TX KEYVAL = %d", scan_code.command);
            lcd_show_string(110, 110, 200, 16, 16, (char *)tbuf, BLUE);
            ESP_ERROR_CHECK(rmt_transmit(tx_channel, nec_encoder, &scan_code, 
            sizeof(scan_code), &transmit_config)); /* 通过RMT发送信道传输数据 */
        }
    
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

       rmt_rx_scan函数用来扫描解码结果的,相当于我们的按键扫描函数。相对红外发送实验,该函数缺少了数据解析功能,因为这时候用的并不是红外遥控器发送红外数据。

       在app_main函数,调用rmt_nec_rx_init和rmt_nec_tx_init函数初始化RMT发送和接收之后,在while循环中消息队列有数据,便可调用rmt_tx_scan对数据进行解析并在LCD上显示。解析完数据后,还需要继续调用rmt_receive函数重新开始接收数据。若消息队列没有数据时,便通过rmt_transmit函数发送红外数据,数据范围为1~255,LCD也会显示发送的数据。


        29.4 下载验证

       在代码编译成功后,下载代码到开发板上,可以看到LCD显示如下图所示。


图29.4.1 红外发送实验测试图


       我们可以看到开发板发送的红外信号全部被红外接收头接收到,说明我们已经实现了开发板自发自收红外信号的功能。温馨提示:由于开发板的红外接收头和红外发射头没有正对,有可能会造成接收不到数据情况,这种情况下我们需要一个物体提供反射面,最简单的做法就是将手放在传感器正前方大约10cm左右的位置。


请使用浏览器的分享功能分享到微信等