1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > ESP32实现红外遥控 发射与接收

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

时间:2022-12-17 00:30:28

相关推荐

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

文章目录

一,原理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个itemrmt_tx.clk_div = RMT_CLK_DIV; rmt_tx.tx_config.loop_en = false; //关闭循环发射,只发射一次rmt_tx.tx_config.carrier_duty_percent = 50; //载波占空比为50rmt_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");//安装红外发射通道 无需ringbuffrmt_driver_install(rmt_tx.channel, 0, 0);}

2.3 构建并发射item

rmt_item32_t *item;//发射itemsize_t size = 0;int item_num = 0;item_num = NEC_DATA_ITEM_NUM; //此处为70size = (sizeof(rmt_item32_t) * item_num); //计算70个item所需的字节空间item = (rmt_item32_t *)malloc(size);//记得freememset((void *)item, 0, size);nec_build_items(item, item_num, ir_msg->data0, ir_msg->data1); //根据要发射的数据,填充itemrmt_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; //分频系数 100rmt_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.5usrmt_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中数据写入ringbuffBaseType_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协议:/thread-46-1-1.html

esp32技术参考手册

由于作者才疏学浅,难免有错误,欢迎指正~

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。