赞
踩
红外是设备间通讯的一种方案,一般在空调、电视、机顶盒等设备中被使用。其原理是通过定义好设备接收端和发送端的编码协议,按照特定的脉冲,由发射管通过控制亮灭发送红外信息。然后由接收管进行接收。
在设备间通讯的方案中,红外具有使用简单的优点,但也存在使用距离短,中间不能由物体阻隔等缺点。
这里简单介绍一下NEC红外编码,一般NEC编码包括引导码和数据码两部分。下面的NEC编码引导码和数据码是基于一种海尔空调可用的编码协议写的。
引导码预示着后面是有效信息,引导码由一段比较长时间的高低电平构成,比如下图
而数据码通过特定的协议编码0和1,比如500us低电平,跟着1600us高电平编码数据1,而500us低电平跟着600us高电平意味着数据0
同时,红外编码发送的时候,并不单单是通过高低电平发送的,是在38khz的载波下进行发送的。
就比如如下案例,引导码和数据码高电平区域,是由38khz的脉冲构成的。
一般不同设备(尤其是空调)的红外编码协议会又所不同,所以进行红外设备控制的时候,需要进行测试。
emp32的RMT组件一共由8个通道,每个通道能够独立完成红外发射或者红外接收的工作,但是这两种功能不能同时进行。8个通道共用同一个RAM空间,具有完成载波调制、输入捕获、滤波等功能。
esp32的RMT模块功能框图如下,包括四个部分
RMT模块的时钟源经过分频后可以给红外接收和发送的计数器提供时钟。
RMT模块的时钟源可以是APB_CLK(默认是80MHZ),也可以是REF_TICK时钟。
经过分频以后的每个时钟,将会在后面被称为1个tick。
比如默认时钟是80MHZ,如果进行80分频,用于计数的时钟就是1MHZ,也就是每次数一个数字都是1us,1个tick就是1us。
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起始内存和终止内存地址计算公式为:
从框图中可以看出,发送模块包括两个部分,包括发射信号输出和调制信号输出两部分。
发射信号输出部分就是把给定的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的脉冲构成的
接收器做的事情与发送器相反,是把引脚出的电平转换为item数据结构进行存储。并且每次在电平变化的时候,记录上一个电平的周期数和电平高低。当定时器超过给定的最大超时时间,接收器会在item中写入0,表示接收结束。
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;
该实验讲解使用RMT进行发送的具体方法
完成该实验可以不使用任何外部电路。该实验使用GPIO_4作为输出示范,使用示波器检测电平变化即可
首先配置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; //配置发送模式
结构体的该部分最重要的是配置载波。
红外遥控器的话,一般是使用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; //关闭持续发送
与结构体配置载入有关的两个函数是
rmt_config()
rmt_driver_install()
//03 进行配置
rmt_config(&rmt);
//04 加载配置
rmt_driver_install(RMT_CHANNEL_0, 0, 0); //发送不需要缓冲区,中断级别默认
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 }}} };
发送数据主要使用函数为rmt_write_items,配置的参数包括使用哪个通道发送数据,发送数据地址,以及发送数据的大小。是否进行阻塞状态,即发送后进行等待,一直等待数据发送完成后,从阻塞状态退出。
如果阻塞状态为false,可以使用 rmt_wait_tx_done函数进行阻塞,等待发送完成。
rmt_write_items(RMT_CHANNEL_0, morse_esp, sizeof(morse_esp) / sizeof(morse_esp[0]), true);
/* 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() { }
该使用RMT接收功能,接收外遥控器发送的红外码,然后通过串口发送到电脑。
通过红外接收管接收红外信号,然后把输出引脚接到GPIO_15
首先配置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
接收结构体最主要的是配置滤波,这里配置的是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认为接收信号完成
//02 写入配置
rmt_config(&rmt);
//03 rmt驱动
rmt_driver_install(RMT_CHANNEL_0, 2000, 0); //第二个数字如果不是0,就是定义了ringbuff的大小,可以通过乒乓操作的方法,定义接收缓冲区大小
与接收数据有关的函数主要为
/ 定义接收变量 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); //关闭接收
/* 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。