【ESP32-IDF】02-1 外设-RMT

RMT

1. RMT简介

1.1 概述

   红外是设备间通讯的一种方案,一般在空调、电视、机顶盒等设备中被使用。其原理是通过定义好设备接收端和发送端的编码协议,按照特定的脉冲,由发射管通过控制亮灭发送红外信息。然后由接收管进行接收。

   在设备间通讯的方案中,红外具有使用简单的优点,但也存在使用距离短,中间不能由物体阻隔等缺点。

1.2 红外编码

1.2.1 编码组成

   这里简单介绍一下NEC红外编码,一般NEC编码包括引导码和数据码两部分。下面的NEC编码引导码和数据码是基于一种海尔空调可用的编码协议写的。

   引导码预示着后面是有效信息,引导码由一段比较长时间的高低电平构成,比如下图

在这里插入图片描述
   而数据码通过特定的协议编码0和1,比如500us低电平,跟着1600us高电平编码数据1,而500us低电平跟着600us高电平意味着数据0
在这里插入图片描述

1.2.2 载波

   同时,红外编码发送的时候,并不单单是通过高低电平发送的,是在38khz的载波下进行发送的。

  就比如如下案例,引导码和数据码高电平区域,是由38khz的脉冲构成的。

在这里插入图片描述
   一般不同设备(尤其是空调)的红外编码协议会又所不同,所以进行红外设备控制的时候,需要进行测试。

1.3 RMT组件概述

   emp32的RMT组件一共由8个通道,每个通道能够独立完成红外发射或者红外接收的工作,但是这两种功能不能同时进行。8个通道共用同一个RAM空间,具有完成载波调制、输入捕获、滤波等功能。

2. RMT框图剖析

  esp32的RMT模块功能框图如下,包括四个部分

在这里插入图片描述

  • (1) 时钟源
  • (2) RAM存储空间
  • (3) 发射模块
  • (4) 接收模块

2.1 时钟

   RMT模块的时钟源经过分频后可以给红外接收和发送的计数器提供时钟。

   RMT模块的时钟源可以是APB_CLK(默认是80MHZ),也可以是REF_TICK时钟。

  经过分频以后的每个时钟,将会在后面被称为1个tick。

  比如默认时钟是80MHZ,如果进行80分频,用于计数的时钟就是1MHZ,也就是每次数一个数字都是1us,1个tick就是1us。

2.2 RAM

   RMT组件8个通道共用同一段RAM,RAM分为0-7 一共8个block,每个block大小为64x32 bit。默认情况下每个通道分配一个block。主要用于红外接收和发送的时候存储数据

   RMT的空间结构如下图

在这里插入图片描述
  RAM的数据结构由32位组成,分为高低两个16位。其中level表示电平的高低,period表示分频时钟持续的周期数。当period为0的时候,发送停止。如果接收超时,也会往period中写入0,表示接收结束。这1个32位,用于表示电平高低和时钟持续周期数的数据结构定义,将在后面被称为item

  RAM可以通过数据总结,直接使用地址进行访问。其起始地址为 0x3FF56800。如果一个通道接收或者发送红外数据的时候一个block大小不够(因为block大小为64x32bit,意味着最多能存储128个数据),可以在配置的时间增加block数量。通道n使用的RMA起始内存和终止内存地址计算公式为:

在这里插入图片描述

2.3 发送器

  从框图中可以看出,发送模块包括两个部分,包括发射信号输出和调制信号输出两部分。

  发射信号输出部分就是把给定的item数据结构中对电平高低和周期数的描述,转化为实际的电平输出。

  如我们定义如下数据


static const rmt_item32_t morse_esp[] = {

	{{{ 3276, 1, 3276, 0 }}}, 
	{{{ 3276, 0, 3276, 0 }}}, 

	{{{ 3276, 1, 3276, 0 }}}, 
	{{{ 3276, 1, 3276, 0 }}}, 
	{{{ 3276, 1, 3276, 0 }}}, 
	{{{ 3276, 0, 3276, 0 }}},

	{{{ 3276, 1, 3276, 0 }}}, 
	{{{ 3276, 1, 3276, 1 }}},
	{{{ 3276, 1, 3276, 0 }}}, 
	{{{ 3276, 1, 3276, 1 }}},
	{{{ 3276, 1, 3276, 0 }}}, 
	{{{ 3276, 1, 3276, 0 }}}, 

	{{{ 0, 1, 0, 0 }}}
};

  就会产生这样的电信号输出
在这里插入图片描述
  另外一部分是信号的调制,用于对输出的信号增加载波,变成红外支持的编码格式。比如我们在配置中,给上文中的发送信号增加载波,就会变成下图

在这里插入图片描述
  放大后,发现原来的每个高电平,都是由38khz的脉冲构成的

在这里插入图片描述

在这里插入图片描述

2.4 接收器

   接收器做的事情与发送器相反,是把引脚出的电平转换为item数据结构进行存储。并且每次在电平变化的时候,记录上一个电平的周期数和电平高低。当定时器超过给定的最大超时时间,接收器会在item中写入0,表示接收结束。

3. RMT结构体配置说明

   RMT配置是通过配置rmt_config_t结构体实现的,rmt_config_t可以分为公共配置部分和特有配置部分,该结构体定义为:

typedef struct {
    rmt_mode_t rmt_mode;        //  配置RMT模块是发射或接收
    rmt_channel_t channel;       // 使用第几个通道       
    uint8_t clk_div;         //  对时钟源进行多少分频,可以配置0-255,其中0表示256分频             
    gpio_num_t gpio_num;       //使用第几个gpio完成该工作        
    uint8_t mem_block_num;        // 使用多少个block的RAM进行收发数据
    union{
        rmt_tx_config_t tx_config;     // 如果模式是接收,就配置这部分
        rmt_rx_config_t rx_config;     // 如果模式是发送,就配置这部分
    };
} rmt_config_t;

4. RMT发送实验

4.1 功能描述

   该实验讲解使用RMT进行发送的具体方法

4.2 硬件设计

   完成该实验可以不使用任何外部电路。该实验使用GPIO_4作为输出示范,使用示波器检测电平变化即可

4.3 软件设计

4.3.1 配置结构体的公共部分

   首先配置RMT模块结构体的公共部分,包括配置好使用的通道,对APB时钟进行多少分频,使用哪个引脚,block通道是多少,以及配置该通道的工作模式
   示例代码

//01 rmt driver共有部分
	rmt_config_t rmt;
	rmt.channel = RMT_CHANNEL_0; //RMT有0-7一共8个通道
	rmt.clk_div = 80;  //默认的时钟是80MHZ,分频器是8位的,这个时钟与发送信号时候电平长度以及接收信号时候计数有关系。 n个时钟周期就是电平长度.如果80分频,数一个数就是1us
	rmt.gpio_num = GPIO_NUM_4;
	rmt.mem_block_num = 1; //默认每个通道使用1个block。一共block是64x32bit 这么大。 也就是能储存128个数据
	rmt.rmt_mode = RMT_MODE_TX; //配置发送模式


  • 在该案例中,我使用通道0进行试验,并且把通道0的工作模式设置为发送模式。
  • 对时钟进行80分频,这样的话,对item的解析中,每个period都代表1us。
  • 使用GPIO_NUM_4作为输出。
  • 配置该通道占用1个block,1个block大小为64x32 bit,因为每个item可以存储2个电平,因此1个block,64个item,最多能够发送128个电平数据
4.3.2 配置结构体的发射部分

   结构体的该部分最重要的是配置载波。
   红外遥控器的话,一般是使用38khz载波

//02 配置tx独有的部分
	rmt.tx_config.carrier_en = true; //打开载波
	rmt.tx_config.carrier_freq_hz = 38000; //38khz载波
	rmt.tx_config.carrier_duty_percent = 50; //占空比50%
	rmt.tx_config.carrier_level = RMT_CARRIER_LEVEL_HIGH; //载波默认为高电平
	rmt.tx_config.idle_output_en = true; //空闲输出打开
	rmt.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; //空闲时候为低电平
	rmt.tx_config.loop_en = false; //关闭持续发送


  • 在该发射案例中,我打开了载波,并设置载波频率为38
  • 然后设置载波的占空比为50%
  • 载波在高电平部分有效
  • 打开空闲状态下的电平输出,并设置为低电平
  • 关闭持续发送,意味着,每次发送都只发送一次数据。如果开启发送,会重复的发送RAM中的数据
4.3.3 载入结构体配置

   与结构体配置载入有关的两个函数是

rmt_config()

rmt_driver_install()

  • rmt_config()函数的功能是载入rmt_config_t结构体的数据
  • rmt_driver_install()函数的功能是,为特定通道配置环形缓冲区。该缓冲区只在配置红外接收的时候有用。因为发射的时候是直接把自己定义的内存地址中的数据写入发射block即可。而接收的时候是把RAM的数据搬运到环形缓冲区ringbuff,然后把该缓冲区中的数据解析为item的数组。
  • 配置rmt_driver_install()函数后,系统会对RMT自动生成一个中断服务函数,用于处理ringbuff缓冲区中的数据,因此,一旦定义了这个函数以后,请不要使用RMT模块中的接收数据完成中断、发送数据完成中断,以及RAM访问冲突等中断。
//03 进行配置

	rmt_config(&rmt);
	//04 加载配置
	rmt_driver_install(RMT_CHANNEL_0, 0, 0); //发送不需要缓冲区,中断级别默认


4.3.4 定义要发送的数据
static const rmt_item32_t morse_esp[] = {
	// E : dot
	{{{ 3270, 1, 3270, 0 }}}, // dot
	{{{ 3270, 0, 3270, 0 }}}, // SPACE
	// S : dot, dot, dot
	{{{ 3270, 1, 3270, 0 }}}, // dot
	{{{ 3270, 1, 3270, 0 }}}, // dot
	{{{ 3270, 1, 3270, 0 }}}, // dot
	{{{ 3270, 0, 3270, 0 }}}, // SPACE
	// P : dot, dash, dash, dot
	{{{ 3270, 1, 3270, 0 }}}, // dot
	{{{ 3270, 1, 3270, 1 }}},
	{{{ 3270, 1, 3270, 0 }}}, // dash
	{{{ 3270, 1, 3270, 1 }}},
	{{{ 3270, 1, 3270, 0 }}}, // dash
	{{{ 3270, 1, 3270, 0 }}}, // dot
	// RMT end marker
	{{{ 0, 1, 0, 0 }}}
};
4.3.5 发送数据

   发送数据主要使用函数为rmt_write_items,配置的参数包括使用哪个通道发送数据,发送数据地址,以及发送数据的大小。是否进行阻塞状态,即发送后进行等待,一直等待数据发送完成后,从阻塞状态退出。

   如果阻塞状态为false,可以使用 rmt_wait_tx_done函数进行阻塞,等待发送完成。

rmt_write_items(RMT_CHANNEL_0, morse_esp, sizeof(morse_esp) / sizeof(morse_esp[0]), true);
4.3.6 完整发送数据的程序

/*
 Name:		Sketch1.ino
 Created:	2021/4/12 18:46:05
 Author:	hp
*/
// the setup function runs once when you press reset or power the board
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "led.h"
#include "uart.h"
#include "driver/rmt.h"


//红外发送模拟
static const rmt_item32_t morse_esp[] = {
	// E : dot
	{{{ 3270, 1, 3270, 0 }}}, // dot
	{{{ 3270, 0, 3270, 0 }}}, // SPACE
	// S : dot, dot, dot
	{{{ 3270, 1, 3270, 0 }}}, // dot
	{{{ 3270, 1, 3270, 0 }}}, // dot
	{{{ 3270, 1, 3270, 0 }}}, // dot
	{{{ 3270, 0, 3270, 0 }}}, // SPACE
	// P : dot, dash, dash, dot
	{{{ 3270, 1, 3270, 0 }}}, // dot
	{{{ 3270, 1, 3270, 1 }}},
	{{{ 3270, 1, 3270, 0 }}}, // dash
	{{{ 3270, 1, 3270, 1 }}},
	{{{ 3270, 1, 3270, 0 }}}, // dash
	{{{ 3270, 1, 3270, 0 }}}, // dot
	// RMT end marker
	{{{ 0, 1, 0, 0 }}}
};

static void rmt_tx_init(void)
{
	//01 rmt driver共有部分
	rmt_config_t rmt;
	rmt.channel = RMT_CHANNEL_0; //RMT有0-7一共8个通道
	rmt.clk_div = 80;  //默认的时钟是80MHZ,分频器是8位的,这个时钟与发送信号时候电平长度以及接收信号时候计数有关系。 n个时钟周期就是电平长度.如果80分频,数一个数就是1us
	rmt.gpio_num = GPIO_NUM_4;
	rmt.mem_block_num = 1; //默认每个通道使用1个block。一共block是64x32bit 这么大。 也就是能储存128个数据
	rmt.rmt_mode = RMT_MODE_TX; //配置发送模式


	//02 配置tx独有的部分
	rmt.tx_config.carrier_en = true; //打开载波
	rmt.tx_config.carrier_freq_hz = 38000; //38khz载波
	rmt.tx_config.carrier_duty_percent = 50; //占空比50%
	rmt.tx_config.carrier_level = RMT_CARRIER_LEVEL_HIGH; //载波默认为高电平
	rmt.tx_config.idle_output_en = true; //空闲输出打开
	rmt.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; //空闲时候为低电平
	rmt.tx_config.loop_en = false; //关闭持续发送

	//03 进行配置

	rmt_config(&rmt);
	//04 加载配置
	rmt_driver_install(RMT_CHANNEL_0, 0, 0); //发送不需要缓冲区,中断级别默认
}

void setup() {
	rmt_tx_init();
	while (1)
	{
		rmt_write_items(RMT_CHANNEL_0, morse_esp, sizeof(morse_esp) / sizeof(morse_esp[0]), true);
		vTaskDelay(100 / portTICK_PERIOD_MS);

	}
}
void loop() 
{
}


5. RMT接收实验

5.1 功能描述

   该使用RMT接收功能,接收外遥控器发送的红外码,然后通过串口发送到电脑。

5.2 硬件设计

在这里插入图片描述

  通过红外接收管接收红外信号,然后把输出引脚接到GPIO_15

5.3 软件设计

5.3.1 配置结构体的公共部分

   首先配置RMT模块结构体的公共部分,包括配置好使用的通道,对APB时钟进行多少分频,使用哪个引脚,block通道是多少,以及配置该通道的工作模式
   示例代码


//01 配置rmt
	//共有部分
	rmt_config_t rmt;
	rmt.channel = RMT_CHANNEL_0; //一共与0-7 8个通道
	rmt.clk_div = 80; //80分频,记一次数字1us
	rmt.gpio_num = GPIO_NUM_15; //引脚15作为输入捕获引脚
	rmt.mem_block_num = 3; //占用的block个数,一个block是64x32bit

  • 在该案例中,我使用通道0进行试验,并且把通道0的工作模式设置为发送模式。
  • 对时钟进行80分频,这样的话,对item的解析中,每个period都代表1us。
  • 使用GPIO_NUM_15作为输入。
  • 配置该通道占用3个block,因为红外输入码长度可能比较长,这个可以自行调整
5.3.2 配置结构体的接收部分

  接收结构体最主要的是配置滤波,这里配置的是150us以下的脉冲都进行忽略。
   同时设置了超时时间为50ms,如果50ms仍然没有电平变化,就判定超时,中断接收。


	//rx私有部分
	rmt.rmt_mode = RMT_MODE_RX; //接收模式
	rmt.rx_config.filter_en = true; //开启滤波
	rmt.rx_config.filter_ticks_thresh = 150; //150us以下的信号不接受
	rmt.rx_config.idle_threshold = 50000; //超出时间设置为50ms,超过50ms认为接收信号完成

5.3.3 载入结构体配置

//02 写入配置
	rmt_config(&rmt);

	//03 rmt驱动
	rmt_driver_install(RMT_CHANNEL_0, 2000, 0); //第二个数字如果不是0,就是定义了ringbuff的大小,可以通过乒乓操作的方法,定义接收缓冲区大小

  • rmt_config()函数的功能是载入rmt_config_t结构体的数据
  • rmt_driver_install()函数的功能是,为特定通道配置环形缓冲区。该缓冲区只在配置红外接收的时候有用。接收的时候是把RAM的数据搬运到环形缓冲区ringbuff,然后把该缓冲区中的数据解析为item的数组。这个ringbuff大小单位是byte,至少要大于红外码占用空间的两倍。
  • 我们举个例子,如红外码的item占用空间为400byte,则占用50个item空间,因此至少需要占用2个block,同时,ringbuff空间至少为占用RAM大小的两倍,也就是不能小于800
  • 配置rmt_driver_install()函数后,系统会对RMT自动生成一个中断服务函数,用于处理ringbuff缓冲区中的数据,因此,一旦定义了这个函数以后,请不要使用RMT模块中的接收数据完成中断、发送数据完成中断,以及RAM访问冲突等中断。
5.3.4 接收数据

  与接收数据有关的函数主要为

  • rmt_get_ringbuf_handle():之前通过rmt_driver_install()函数设置好了,让RAM中的数据完成接收后,存放到ringbuff中,这个函数就是用来获取这段ringbuff内存地址的
  • rmt_rx_start():打开接收,这个函数使用之后,就好不断的进行接收红外数据
  • xRingbufferReceive():这个函数用于获”取ringbuff中的数据,并转换为item格式进行返回。。这个函数最后一个参数是超时时间,也就是系统运行到这个函数的时候会进入阻塞状态,一直等待数据。实际测试下来,这个超时时间的单位似乎是ms
  • vRingbufferReturnItem():释放自己使用的ringbuff和item数据
  • rmt_rx_stop():停止接收数据
/ 定义接收变量
	RingbufHandle_t rb = NULL; //循环缓冲区指针
	rmt_item32_t* items = NULL; //items数据域指针
	size_t length = 0; //items占用字节数,一个items占4字节

	// 把ringbuf数据区域绑定到rb
	rmt_get_ringbuf_handle(RMT_CHANNEL_0, &rb);

	//04 打开接收
	rmt_rx_start(RMT_CHANNEL_0, true); 

	while (1) {
		// 获取items数据
		items = (rmt_item32_t*)xRingbufferReceive(rb, &length, portMAX_DELAY);
		

		// 如果获得到了数据
		if (items)
		{
		
			//07 打印数据
			printf("\n");
			printf("\n");
			printf("length=%d\n", length/4);
			for (size_t t = 0; t < length / 4; t++)
			{
				printf("%d %d ", items[t].duration0, items[t].duration1);
				if (t % 5 == 0)
				{
					printf("\n");
				}
			}

			// 完成一次数据接收要清空循环缓冲区和items数据域
			vRingbufferReturnItem(rb, (void*)items);
		}

	}
	rmt_rx_stop(RMT_CHANNEL_0); //关闭接收
5.3.5 完整数据接收程序
/*
 Name:		Sketch1.ino
 Created:	2021/4/12 18:46:05
 Author:	hp
*/



// the setup function runs once when you press reset or power the board



#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/rmt.h"
#include "led.h"
#include "uart.h"




//配置红外接收
static void rmt_rx_init(void)
{
	//01 配置rmt
	//共有部分
	rmt_config_t rmt;
	rmt.channel = RMT_CHANNEL_0; //一共与0-7 8个通道
	rmt.clk_div = 80; //80分频,记一次数字1us
	rmt.gpio_num = GPIO_NUM_15; //引脚15作为输入捕获引脚
	rmt.mem_block_num = 3; //占用的block个数,一个block是64x32bit

	//rx私有部分
	rmt.rmt_mode = RMT_MODE_RX; //接收模式
	rmt.rx_config.filter_en = true; //开启滤波
	rmt.rx_config.filter_ticks_thresh = 150; //150us以下的信号不接受
	rmt.rx_config.idle_threshold = 50000; //超出时间设置为50ms,超过50ms认为接收信号完成

	//02 写入配置
	rmt_config(&rmt);

	//03 rmt驱动
	rmt_driver_install(RMT_CHANNEL_0, 2000, 0); //第二个数字如果不是0,就是定义了ringbuff的大小,可以通过乒乓操作的方法,定义接收缓冲区大小


}



void setup() {
	//01 初始化红外接收器
	rmt_rx_init(); 




	//02 定义接收变量
	RingbufHandle_t rb = NULL; //循环缓冲区指针
	rmt_item32_t* items = NULL; //items数据域指针
	size_t length = 0; //items占用字节数,一个items占4字节

	//03 把ringbuf数据区域绑定到rb
	rmt_get_ringbuf_handle(RMT_CHANNEL_0, &rb);

	//04 打开接收
	rmt_rx_start(RMT_CHANNEL_0, true); 

	while (1) {
		//05 获取items数据
		items = (rmt_item32_t*)xRingbufferReceive(rb, &length, portMAX_DELAY);
		

		//06 如果获得到了数据
		if (items)
		{
		
			//07 打印数据
			printf("\n");
			printf("\n");
			printf("length=%d\n", length/4);
			for (size_t t = 0; t < length / 4; t++)
			{
				printf("%d %d ", items[t].duration0, items[t].duration1);
				if (t % 5 == 0)
				{
					printf("\n");
				}
			}

			//08 完成一次数据接收要清空循环缓冲区和items数据域
			vRingbufferReturnItem(rb, (void*)items);
		}

	}
	rmt_rx_stop(RMT_CHANNEL_0); //关闭接收
}


void loop()
{

}


/*
 Name:		Sketch1.ino
 Created:	2021/4/12 18:46:05
 Author:	hp
*/
// the setup function runs once when you press  or power the board

6. 参考资料

  • 11
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值