ESP32实现红外遥控 红外发射与接收实现原理

对万能遥控有兴趣的同学可以参考:esp32实现万能红外遥控器 基于开源红外码库IREXT,推荐看完本文再去看万能遥控,实现效果:

esp32实现语音万能遥控

一,原理

1.1 概括

esp32系列芯片集成了红外发送与接收控制器,可用于多种类型的红外通信。esp32一共有8个通道,每个通道都可以独立的进行发射和接收,一个通道不能同时进行发射和接收。

发射红外时,从内部特定地址的RAM中读取红外编码数据,并对这些数据进行38khz的载波调制;接收时,将接收的电平和电平时间经过转换后存放于该RAM。

1.2,时钟

每个通道独立配置时钟,时钟源有两个,APB = 80MHZ,REF_TICK 。官方建议使用APB时钟源。
时钟可由8位的DIVIDER进行预分频,将分频后的信号作为发射/接收计数器的时钟源接下来的例程中我将分频系数设置为100,计数器的时钟频率为tick = 0.8MHZ,也就是1.25us。APB时钟还作为载波的时钟源,用于发射红外时需要用到的载波信号,如38Khz的典型红外载波。
在这里插入图片描述

1.3,认识 item

item是esp32红外中用于描述一个脉冲信号的概念,一个item包含:电平以及电平时间信息

存放红外数据的RAM分为8个block,默认一个通道对应一个block,一个block的大小是64×32bit,一个32bit的RAM就是一个item,所以一个block最多存放64个item,也就是64个脉冲信号。

item在内存中的情况如下图,level记录电平的高低,period记录电平的时间(period内存放的是计数器计数的数值,还需要经过换算才能得到真实的时间)。所以用一个item可以表示一个周期的信号。如果将多个item连起来,就是一帧的数据。
计算公式:真实时间=period*divider/80M;

在这里插入图片描述
rmt_struct.h中定义的结构体rmt_item32_t,就是指向以上说的item。

typedef struct {
    union {
        struct {
            uint32_t duration0 :15;
            uint32_t level0 :1;
            uint32_t duration1 :15;
            uint32_t level1 :1;
        };
        uint32_t val;
    };
} rmt_item32_t;

1.4,发射/接收器

1,发射器从内部的RAM中读取数据。每次读取32位数据,高位地址先发射,低位地址后发射。

2,当接收器使能时,输入gpio检查到电平变化时,开始计数,当又一次检查到电平变化时,将上次电平的高低和持续时间写入RAM中,以此持续检测,直到发射器接收的电平变化时间大于设置的退出检测时间,接收器才会停止接收。

3,通过设置RMT_IDLE_THRES_CH寄存器设置退出检测时间,该寄存器的计数频率与接收计数器频率相同,tick = 100/80M=1.25us;

4,接收器还支持简单的滤波,滤波器可以帮助我们滤除一些持续时间过短的信号,比如一个几us的噪声,通过设置RMT_RX_FILTER_THRES_CH寄存器来设置滤除的噪声信号的时间宽度。注意RMT_RX_FILTER_THRES_CH寄存器的时钟源是APB时钟。所以时间宽度:fliter = RMT_RX_FILTER_THRES_CH*0.0125us;

1.5 电路原理图

1.5.1,发射电路

在1.3节的时钟中,信号输出口sig_out连接到下图三极管的基极,从而控制三极管的导通,间接控制红外发光管的亮灭,当输出38khz的载波时,LED就会以38khz的频率闪烁。
在这里插入图片描述

1.5.2 ,接收电路

一般的接收管是一个集成的元器件,具备光信号到电信号的转换,信号放大,解码等功能。当二极管接收到红外线时导通,光信号转为电信号,再经过放大电路,最后解码,解码的作用是滤除非38khz的信号,当接收的信号是38khz时,OUT脚输出低电平。
在这里插入图片描述

二,红外发射

2.1 整体的思路

初始化外设,填充item的数据。然后调用以下函数将item中的内容写入RAM,发射。这个过程中填充item数据是灵活多样的,根据你要发射的数据不同,有不同的填充方法。本文就先介绍使用格力红外编码去填充,参考:

该函数将变量item中的数据写入特定的RAM中。

esp_err_t rmt_write_items(rmt_channel_t channel, const rmt_item32_t* rmt_item, int item_num, bool wait_tx_done);

该函数使能发射控制器,将对应通道的RAM中的数据发射出去,该操作会使任务进入阻塞。

esp_err_t rmt_wait_tx_done(rmt_channel_t channel, TickType_t wait_time);

2.2 发射器初始化

/*
 * @brief RMT transmitter initialization
 */
static void nec_tx_init()
{
    rmt_config_t rmt_tx;
    rmt_tx.channel = tx_channel;
    rmt_tx.gpio_num = RMT_TX_GPIO_NUM;
    rmt_tx.mem_block_num = 2;   //由于格力红外有70个item,使用2个块,64×2=128个item
    rmt_tx.clk_div = RMT_CLK_DIV;   
    rmt_tx.tx_config.loop_en = false;   //关闭循环发射,只发射一次
    rmt_tx.tx_config.carrier_duty_percent = 50; //载波占空比为50
    rmt_tx.tx_config.carrier_freq_hz = 38000;   //载波频率38khz 红外
    rmt_tx.tx_config.carrier_level = 1; //载波高电平
    rmt_tx.tx_config.carrier_en = RMT_TX_CARRIER_EN;    //使能载波
    rmt_tx.tx_config.idle_level = 0;    //空闲状态低电平
    rmt_tx.tx_config.idle_output_en = true; //输出使能
    rmt_tx.rmt_mode = RMT_MODE_TX;  //发射模式
    ESP_LOGI(TAG, "[ 1.1 ] config rmt");
    rmt_config(&rmt_tx);
    ESP_LOGI(TAG, "[ 1.2 ] install rmt driver");

    //安装红外发射通道 无需ringbuff
    rmt_driver_install(rmt_tx.channel, 0, 0);
}

2.3 构建并发射item

rmt_item32_t *item;	//发射item
size_t size = 0;
int item_num = 0;

item_num = NEC_DATA_ITEM_NUM;   //此处为70
size = (sizeof(rmt_item32_t) * item_num); //计算70个item所需的字节空间
item = (rmt_item32_t *)malloc(size);	//记得free
memset((void *)item, 0, size);
nec_build_items(item, item_num, ir_msg->data0, ir_msg->data1); //根据要发射的数据,填充item

rmt_write_items(tx_channel, item, item_num, true); //将item写入通道对应的RAM并进入阻塞

rmt_wait_tx_done(tx_channel, portMAX_DELAY); //等待发送完成 进入阻塞

free(item);

nec_build_items();就是填充item的代码,在本例子中,实现的是填充格力红外编码,具体代码在文末提供。

三,红外接收

3.1 思路

如果你理解了发射的部分,那么接收就更加容易了。你可能猜想如果发射是构建item,那么接收就应该是获得item。恭喜你,老伙计,你答对了!这里我假设需要接收格力红外。

在这里插入图片描述

接收器会自动把某个通道接收到的数据写入对应的RAM中,并封装成item,再放入指定的ringbuff中,我们的程序只要拿到对应通道的ringbuff,就能从ringbuff中读取出脉冲序列。

关于啥是ringbuff,ringbuff就是一个缓存区buff。废话。详细可以看看 ringbuff简单实现

关于接收的几个重要函数:

获取指定通道的ringbuff

rmt_get_ringbuf_handle(rx_channel, &rb);    //获取红外接收器对应通道的ringbuff

从ringbuff读取items 会进入阻塞 直到ringbuff中有新的数据(也就是接收到信号)

rmt_item32_t* item = (rmt_item32_t*) xRingbufferReceive(rb, &rx_size, portMAX_DELAY);

使用完ringbuff和item后要释放内存

vRingbufferReturnItem(rb, (void*) item);

开启或关闭接收通道,停止后ringbuff中不再有新的数据

rmt_rx_stop(rx_channel);

rmt_rx_start(rx_channel);

3.2 接收器初始化

/*
 * @brief RMT receiver initialization
 */
static void nec_rx_init()
{
    rmt_config_t rmt_rx;
    rmt_rx.channel = rx_channel;
    rmt_rx.gpio_num = RMT_RX_GPIO_NUM;
    rmt_rx.clk_div = RMT_CLK_DIV;   //分频系数 100
    rmt_rx.mem_block_num = 2;   
    rmt_rx.rmt_mode = RMT_MODE_RX;
    rmt_rx.rx_config.filter_en = true;  //开启滤波器
    rmt_rx.rx_config.filter_ticks_thresh = 100; //滤波信号宽度100*80M=12.5us
    rmt_rx.rx_config.idle_threshold = rmt_item32_tIMEOUT_US / 10 * (RMT_TICK_10_US);    //退出接收时间:21000us后接收不到信号变化,退出接收
    rmt_config(&rmt_rx);
    ESP_LOGI(TAG,"rmt rx config");
    //安装红外接收通道,rinbuff大小1000字节
    rmt_driver_install(rmt_rx.channel, 1000, 0);
    ESP_LOGI(TAG,"rx driver initialization ok");
}

3.3 接收获取item

接收代码就更加的简单了,只要调用这三个函数就够了。item的解析与对上文item的构建一样是最重要的,这里以格力红外为例,不同情况下有不同的解析代码。

rmt_rx_start(rx_channel);
rmt_item32_t* item = (rmt_item32_t*) xRingbufferReceive(rb, &rx_size, portMAX_DELAY);
rmt_rx_stop(rx_channel);

四,item的构建与解析

红外编码的构建:

4.1 item的构建

该步骤的目标,是构建item集合,因为item对应的就是RAM中的32位的数据。根据具体的红外协议的要求,将电平,逻辑0,1的电平时间长度等写入item,并根据协议的长度设置item的数量。
例如,格力红外协议组成:

起始码(S)+35位数据码+连接码(C)+32位数据码
1、各种编码的电平宽度:
数据码由“0”“1”起始码、连接码组成:
0的电平宽度为:600us低电平+600us高电平,
1的电平宽度为:600us低电平+1600us高电平
起始码S电平宽度为:9000us低电平+4500us高电平
连接码C电平宽度为:600us低电平+20000us高电平

例如item构建函数:构建70个item,item的内容由使用的协议决定。

/*
 * @brief Build NEC 32bit waveform.
 */
static void nec_build_items( rmt_item32_t* item, int item_num, uint64_t ir_data0,uint32_t ir_data1)
{
    int j = 0;
    nec_fill_item_header(item++);   //构建起始信号

    //35位数据码
    for(j = 0; j < 35; j++) {
        if(ir_data0 & 0x1) {
            //ESP_LOGI(TAG, "item =1");
            nec_fill_item_bit_one(item);
        } else {
            //ESP_LOGI(TAG, "item =0");
            nec_fill_item_bit_zero(item);
        }
        item++;

        ir_data0 >>= 1;
    }

    //连接信号
    nec_fill_item_connect(item);
    item++;
    //32位数据码
    for(j = 0; j < 32; j++) {
        if(ir_data1 & 0x1) {
            //ESP_LOGI(TAG, "item =1");
            nec_fill_item_bit_one(item);
        } else {
            //ESP_LOGI(TAG, "item =0");
            nec_fill_item_bit_zero(item);
        }
        item++;
        
        ir_data1 >>= 1;
    }
    nec_fill_item_end(item);
}
/*
 * @brief 填充item的电平和电平时间 需要将时间转换成计数器的计数值 /10*RMT_TICK_10_US
 */
static inline void nec_fill_item_level(rmt_item32_t* item, int high_us, int low_us)
{
    item->level0 = 1;
    item->duration0 = (high_us) / 10 * RMT_TICK_10_US;
    item->level1 = 0;
    item->duration1 = (low_us) / 10 * RMT_TICK_10_US;
}

/*
 * @brief Generate NEC header value: active 9ms + negative 4.5ms
 */
static void nec_fill_item_header(rmt_item32_t* item)
{
    nec_fill_item_level(item, NEC_HEADER_HIGH_US, NEC_HEADER_LOW_US);
}

/*
 * @brief 
 */
static void nec_fill_item_connect(rmt_item32_t* item)
{
    nec_fill_item_level(item, NEC_CONNECT_HIGH_US, NEC_CONNECT_LOW_US);
}


/*
 * @brief Generate NEC data bit 1: positive 0.56ms + negative 1.69ms
 */
static void nec_fill_item_bit_one(rmt_item32_t* item)
{
    nec_fill_item_level(item, NEC_BIT_ONE_HIGH_US, NEC_BIT_ONE_LOW_US);
}

/*
 * @brief Generate NEC data bit 0: positive 0.56ms + negative 0.56ms
 */
static void nec_fill_item_bit_zero(rmt_item32_t* item)
{
    nec_fill_item_level(item, NEC_BIT_ZERO_HIGH_US, NEC_BIT_ZERO_LOW_US);
}
/*
 * @brief Generate NEC end signal: positive 0.56ms
 */
static void nec_fill_item_end(rmt_item32_t* item)
{
    nec_fill_item_level(item, NEC_BIT_END, 0x7fff);
}

4.2 item解析

红外编码的解析是构建的逆过程。首先了解红外接收器的工作原理:

在初始化时我们就开启了中断,红外模块的中断寄存器就只有三个,如下。在初始化过程中开启了发送和接收完成中断,在接收完成中断服务函数中,将RAM中接收到的数据写入ringbuff,所以我们需要在只需要从ringbuff中就能读取RAM中接收的数据,此部分代码如下:

在这里插入图片描述

接收中断:

						RMT.conf_ch[channel].conf1.rx_en = 0;
                        int item_len = rmt_get_mem_len(channel);
                        //change memory owner to protect data.
                        RMT.conf_ch[channel].conf1.mem_owner = RMT_MEM_OWNER_TX;
                        if(p_rmt->rx_buf) {
                        	//将RAM中数据写入ringbuff
                            BaseType_t res = xRingbufferSendFromISR(p_rmt->rx_buf, (void*) RMTMEM.chan[channel].data32, item_len * 4, &HPTaskAwoken);
                            if(res == pdFALSE) {
                                ESP_EARLY_LOGE(RMT_TAG, "RMT RX BUFFER FULL");
                            } else {

                            }
                        } else {
                            ESP_EARLY_LOGE(RMT_TAG, "RMT RX BUFFER ERROR\n");
                        }
                        RMT.conf_ch[channel].conf1.mem_wr_rst = 1;
                        RMT.conf_ch[channel].conf1.mem_owner = RMT_MEM_OWNER_RX;
                        RMT.conf_ch[channel].conf1.rx_en = 1;
                        break;

在接收完成后解析items:

/*
 * 解析从item中的信息
*/
static int nec_parse_items(rmt_item32_t* item, int item_num, uint64_t* data0, uint32_t* data1)
{
    int i;
    uint64_t temp0 = 0;
    uint32_t temp1 = 0;

    //接收的数据长度不小于一次传输的长度
    if(item_num < NEC_DATA_ITEM_NUM)
    {
        return -1;
    }

    //检查起始位
    if(!nec_header_if(item))
    {
        return -2;
    }
    item++;

    //检查数据位
    for(i = 0; i < 35; i++)
    {
        ESP_LOGI(IR_TAG, "item data0 %u, %u, %u, %u", NEC_ITEM_DURATION(item->level0), NEC_ITEM_DURATION(item->duration0), NEC_ITEM_DURATION(item->level1), NEC_ITEM_DURATION(item->duration1));

        if(nec_bit_one_if(item))
        {
            temp0 |= (1<<i);
        }else if(nec_bit_zero_if(item))
        {
            temp0 &= (0<<i);
        }else 
        {
            ESP_LOGI(IR_TAG, "item i= %d", i);
            return -3;
        }
        item++;
    }

    //检查连接段信号
    if(nec_bit_connect_if(item))
    {
        return -4;
    }
    item ++;

    //检查第二段信号
    for(i = 0; i < 35; i++)
    {
        ESP_LOGI(IR_TAG, "item data1 %u, %u, %u, %u", NEC_ITEM_DURATION(item->level0), NEC_ITEM_DURATION(item->duration0), NEC_ITEM_DURATION(item->level1), NEC_ITEM_DURATION(item->duration1));
        if(nec_bit_one_if(item))
        {
            temp1 |= (1<<i);
        }else if(nec_bit_zero_if(item))
        {
            temp1 &= (0<<i);
        }else 
        {
            ESP_LOGI(IR_TAG, "item i= %d", i);
            return -5;
        }
        item++;
    }
    *data0 = temp0;
    *data1 = temp1;
    return 0;
}

五,进阶—万能遥控

esp32 的rmt外设不仅可以用来处理红外,他可以广泛用于电平信号的接收与产生,他的分辨率可以达到微秒级别,使用rmt可以与其他的模块进行通信等。总之rmt的功能还是非常的强大的。

如果打算实现万能遥控的功能,可用参考我的博客:esp32实现万能红外遥控器,这篇博客是在本文的基础上增加了一个开源红外码库的使用

格力红外编码
YB0F2协议:http://bbs.eeworld.com.cn/thread-462015-1-1.html
esp32技术参考手册

由于作者才疏学浅,难免有错误,欢迎指正~
在这里插入图片描述

  • 44
    点赞
  • 213
    收藏
    觉得还不错? 一键收藏
  • 29
    评论
2022 / 01/ 30: 新版esptool 刷micropython固件指令不是 esptool.py cmd... 而是 esptool cmd... 即可;另外rshell 在 >= python 3.10 的时候出错解决方法可以查看:  已于2022年发布的: 第二章:修复rshell在python3.10出错 免费内容: https://edu.csdn.net/course/detail/29666 micropython语法和python3一样,编写起来非常方便。如果你快速入门单片机玩物联网而且像轻松实现各种功能,那绝力推荐使用micropython。方便易懂易学。 同时如果你懂C语音,也可以用C写好函数并编译进micropython固件里然后进入micropython调用(非必须)。 能通过WIFI联网(2.1章),也能通过sim卡使用2G/3G/4G/5G联网(4.5章)。 为实现语音控制,本教程会教大家使用tensorflow利用神经网络训练自己的语音模型并应用。为实现通过网页控制,本教程会教大家linux(debian10 nginx->uwsgi->python3->postgresql)网站前后台入门。为记录单片机传输过来的数据, 本教程会教大家入门数据库。  本教程会通过通俗易懂的比喻来讲解各种原理与思路,并手把手编写程序来实现各项功能。 本教程micropython版本是 2019年6月发布的1.11; 更多内容请看视频列表。  学习这门课程之前你需要至少掌握: 1: python3基础(变量, 循环, 函数, 常用库, 常用方法)。 本视频使用到的零件与淘宝上大致价格:     1: 超声波传感器(3)     2: MAX9814麦克风放大模块(8)     3: DHT22(15)     4: LED(0.1)     5: 8路5V低电平触发继电器(12)     6: HX1838红外接收模块(2)     7:红外发射管(0.1),HX1838红外接收板(1)     other: 电表, 排线, 面包板(2)*2,ESP32(28)  

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值