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

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

esp32实现语音万能遥控

一,原理

1.1 概括

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

发射红外时,从特定的RAM中读取红外编码数据;接收时,将接收的电平和电平时间经过转换后存放于该RAM。

1.2,时钟

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

1.3,认识 item

item是esp32红外中经常使用的用于描述一个电平信号的概念

存放红外数据的RAM分为8个block,默认一个通道对应一个block,一个block的大小是64×32bit,一个32bit的RAM就是一个item。

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连接到下图三极管的基极,从而控制三极管的导通,间接控制红外发光管的亮灭。
在这里插入图片描述

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中读取出item。

关于啥是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技术参考手册

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

猜你喜欢

转载自blog.csdn.net/weixin_44821644/article/details/108592858