ESP32实现红外遥控 红外发射与接收实现原理 您所在的位置:网站首页 红外智能音响怎么用 ESP32实现红外遥控 红外发射与接收实现原理

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

2024-07-13 09:14| 来源: 网络整理| 查看: 265

文章目录 一,原理1.1 概括1.2,时钟1.3,认识 item1.4,发射/接收器1.5 电路原理图1.5.1,发射电路1.5.2 ,接收电路 二,红外发射2.1 整体的思路2.2 发射器初始化2.3 构建并发射item 三,红外接收3.1 思路3.2 接收器初始化3.3 接收获取item 四,item的构建与解析4.1 item的构建4.2 item解析 五,进阶---万能遥控

对万能遥控有兴趣的同学可以参考: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 //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 //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 return -2; } item++; //检查数据位 for(i = 0; i temp0 |= (1 ESP_LOGI(IR_TAG, "item i= %d", i); return -3; } item++; } //检查连接段信号 if(nec_bit_connect_if(item)) { return -4; } item ++; //检查第二段信号 for(i = 0; i temp1 |= (1 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技术参考手册

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



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有