红外接收模块
模块来源
采购链接:
https://detail.tmall.com/item.htm?_u=72t4uge51318&id=548393997684&skuId=4361372496386
资料下载链接:
https://pan.baidu.com/s/1dEWVMIFDWb7k1NcsRy5hHA
资料提取码:uucv
规格参数
查看资料
在光谱中波长自760nm至400um的电磁波称为红外线,它是一种不可见光。红外线通信的例子我们每个人应该都很熟悉,目前常用的家电设备几乎都可以通过红外遥控的方式进行遥控,比如电视机、空调、投影仪等,都可以见到红外遥控的影子。这种技术应用广泛,相应的应用器件都十分廉价,因此红外遥控是我们日常设备控制的理想方式。
红外线的通讯原理
红外光是以特定的频率脉冲形式发射,接收端收到到信号后,按照约定的协议进行解码,完成数据传输。在消费类电子产品里,脉冲频率普遍采用 30KHz 到 60KHz 这个频段,NEC协议的频率就是38KHZ。 这个以特定的频率发射其实就可以理解为点灯,不要被复杂的词汇难住了,就是控制灯的闪烁频率(亮灭),和刚学单片机完成闪烁灯一样的意思,只不过是灯换了一种类型,都是灯。
接收端的原理: 接收端的芯片对这个红外光比较敏感,可以根据有没有光输出高低电平,如果发送端的闪烁频率是有规律的,接收端收到后输出的高电平和低电平也是有规律对应的,这样发送端和接收端只要约定好,那就可以做数据传输了。
红外线传输协议可以说是所有无线传输协议里成本最低,最方便的传输协议了,但是也有缺点,距离不够长,速度不够快;当然,每个传输协议应用的环境不一样,定位不一样,好坏没法比较,具体要看自己的实际场景选择合适的通信方式。
NEC协议介绍
NEC协议是众多红外线协议中的一种(这里说的协议就是他们数据帧格式定义不一样,数据传输原理都是一样的),我们购买的外能遥控器、淘宝买的mini遥控器、电视机、投影仪几乎都是NEC协议。 像格力空调、美的空调这些设备使用的就是其他协议格式,不是NEC协议,但是只要学会一种协议解析方式,明白了红外线传输原理,其他遥控器协议都可以解出来。
NEC协议一次完整的传输包含: 引导码、8位地址码、8位地址反码、8位命令码、8位命令反码。这里我们主要讲解如何接收红外发送端发送的NEC协议内容。
引导码: 由9ms的低电平+4.5ms的高电平组成。
4个字节的数据: 地址码+地址反码+命令码+命令反码。 这里的反码可以用来校验数据是否传输正确,有没有丢包。
重点: NEC协议传输数据位的时候,0和1的区分是依靠收到的高、低电平的持续时间来进行区分的。这是解码关键。
数据发送0码:0.56ms低电平+ 0.56ms的高电平。
数据发送1码:0.56ms低电平+1.68ms的高电平
所以,收到一个数据位的完整时间表示方法是这样的:
收到数据位0: 0.56ms低电平+ 0.56ms的高电平
收到数据位1: 0.56ms低电平+1.68ms的高电平
还有一个重复码,它是由一个 9ms 的低电平和一个 2.5ms 的高电平组成。当一个红外信号连续发送时,可以通过发送重复码的方式快速发送。
移植过程
引脚选择
当红外线接收头感应到有红外光就输出低电平,没有感应到红外光就输出高电平。因此我们配置红外引脚为外部中断下降沿触发方式,当红外引脚有下降沿时,我们马上进入处理函数并接收红外信号。
移植至工程
我们的目标是将例程移植至ESP32-S3开发板上。已经为大家提供了完整的驱动代码,按照以下步骤,即可完成移植。
具体新建文件夹和新建c和h文件在 【DHT11温湿度传感器】章节中的1.4.2小节中有详细的教学,这里就不再多说了。
只不过这里我们将文件名 bsp_dht11.c 和 bsp_dht11.h 换成 bsp_ir_receiver.c 和 bsp_ir_receiver.h,文件夹名字改为infraredReceiver。
代码写入
在 bsp_ir_receiver.c 写入:
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-01-12 LCKFB-lp first version
*/
#include "bsp_ir_receiver.h"
#include "stdio.h"
typedef struct INFRARED_DATA{
uint8_t AddressCode; //地址码
uint8_t AddressInverseCode; //地址反码
uint8_t CommandCode; //命令码
uint8_t CommandInverseCode; //命令反码
}_INFRARED_DATA_STRUCT_;
_INFRARED_DATA_STRUCT_ InfraredData;
static void delay_ms(unsigned int ms)
{
vTaskDelay(ms / portTICK_PERIOD_MS);
}
static void delay_us(unsigned int us)
{
ets_delay_us(us);
}
static void delay_1ms(unsigned int ms)
{
vTaskDelay(ms / portTICK_PERIOD_MS);
}
static void delay_1us(unsigned int us)
{
ets_delay_us(us);
}
//红外引脚初始化
void infrared_goio_config(void)
{
gpio_config_t lll_config = {
.pin_bit_mask = (1ULL<<IR_PIN), //配置引脚
.mode =GPIO_MODE_INPUT, //输入模式
.pull_up_en = GPIO_PULLUP_ENABLE, //使能上拉
.pull_down_en = GPIO_PULLDOWN_DISABLE, //不使能下拉
.intr_type = GPIO_INTR_DISABLE //不使能引脚中断
};
gpio_config(&lll_config);
}
//获取红外低电平时间
//以微秒us作为时间参考
void get_infrared_low_time( uint32_t *low_time )
{
uint32_t time_val = 0;
while( gpio_get_level(IR_PIN) == 0 )
{
if( time_val>= 500 )
{
*low_time = time_val;
return;
}
delay_1us(20);
time_val++;
}
*low_time = time_val;
}
//获取红外高电平时间
//以微秒us作为时间参考
void get_infrared_high_time(uint32_t *high_time)
{
uint32_t time_val = 0;
while( gpio_get_level(IR_PIN) == 1 )
{
if( time_val >= 250 )
{
*high_time = time_val;
return;
}
delay_1us(20);
time_val++;
}
*high_time = time_val;
}
/******************************************************************
* 函 数 名 称:guide_and_repeat_code_judgment
* 函 数 说 明:引导 和 重复 码 判断
* 函 数 形 参:无
* 函 数 返 回:1:不是引导码 2:重复码 0:引导码
* 作 者:LC
* 备 注:以20微秒us作为时间参考
引导码:由一个 9ms 的低电平和一个 4.5ms 的高电平组成
重复码:由一个 9ms 的低电平和一个 2.5ms 的高电平组成
******************************************************************/
uint8_t guide_and_repeat_code_judgment(void)
{
uint32_t out_time=0;
get_infrared_low_time(&out_time);
//time>10ms time <8ms
if((out_time > 500) || (out_time < 400))
{
return 1;
}
get_infrared_high_time(&out_time);
// x>5ms 或者 x<2ms
if((out_time > 250) || (out_time < 100))
{
return 1;
}
//如果是重复码 2ms < time < 3ms
if((out_time > 100) && (out_time < 150))
{
return 2;
}
return 0;
}
//红外数据是否正确判断
uint8_t infrared_data_true_judgment(uint8_t *value)
{
//判断地址码是否正确
if( value[0] != (uint8_t)(~value[1]) ) return 0;
//判断命令码是否正确
if( value[2] != (uint8_t)(~value[3]) ) return 1;
printf("%x %x %x %x\r\n",value[0],value[1],value[2],value[3]);
//保存正确数据
InfraredData.AddressCode = value[0];
InfraredData.AddressInverseCode = value[1];
InfraredData.CommandCode = value[2];
InfraredData.CommandInverseCode = value[3];
return 0;
}
//接收红外数据
void receiving_infrared_data(void)
{
uint16_t group_num = 0,data_num = 0;
uint32_t time=0;
uint8_t bit_data = 0;
uint8_t ir_value[5] = {0};
uint8_t guide_and_repeat_code = 0;
if( gpio_get_level( IR_PIN ) == 1 )
{
return;
}
//等待引导码
guide_and_repeat_code = guide_and_repeat_code_judgment();
//如果不是引导码则结束解析
if( guide_and_repeat_code == 1 )
{
return;
}
//共有4组数据
//地址码+地址反码+命令码+命令反码
for(group_num = 0; group_num < 4; group_num++ )
{
//接收一组8位的数据
for( data_num = 0; data_num < 8; data_num++ )
{
//接收低电平
get_infrared_low_time(&time);
//如果不在0.56ms内的低电平,数据错误
if((time > 60) || (time < 20))
{
return ;
}
time = 0;
//接收高电平
get_infrared_high_time(&time);
//如果是在1200us<t<2000us范围内则判断为1
if((time >=60) && (time < 100))
{
bit_data = 1;
}
//如果是在200us<t<1000us范围内则判断为0
else if((time >=10) && (time < 50))
{
bit_data = 0;
}
//groupNum表示第几组数据
ir_value[ group_num ] <<= 1;
//接收的第1个数为高电平;在第二个for循环中,数据会向右移8次
ir_value[ group_num ] |= bit_data;
//用完时间要重新赋值
time=0;
}
}
//判断数据是否正确,正确则保存数据
infrared_data_true_judgment(ir_value);
}
//获取红外发送过来的命令
uint8_t get_infrared_command(void)
{
return InfraredData.CommandCode;
}
//清除红外发送过来的数据
void clear_infrared_command(void)
{
InfraredData.CommandCode = 0x00;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
在 bsp_ir_receiver.h 写入:
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-01-12 LCKFB-lp first version
*/
#ifndef _BSP_IR_RECEIVER_H__
#define _BSP_IR_RECEIVER_H__
#include <stdio.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "freertos/queue.h"
#include <inttypes.h>
#include "sdkconfig.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "rom/ets_sys.h"
#include "esp_system.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "driver/spi_common.h"
#include "hal/gpio_types.h"
#include "string.h"
// OUT 引脚
#define IR_PIN 1
void infrared_goio_config(void);
uint8_t get_infrared_command(void);
void clear_infrared_command(void);
void receiving_infrared_data(void);
void clear_infrared_command(void);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
移植验证
在自己工程中的main主函数中,编写如下。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-01-12 LCKFB-lp first version
*/
#include <stdio.h>
#include "bsp_ir_receiver.h"
#include "string.h"
#include "esp_private/esp_task_wdt.h"
#include "esp_private/esp_task_wdt_impl.h"
int app_main(void)
{
esp_task_wdt_deinit();
//红外接收初始化
infrared_goio_config();
printf("Start......\r\n");
while(1)
{
receiving_infrared_data(); // 接收一次数据
//如果按下遥控的【1】键
if( get_infrared_command() == 0xA2 )
{
clear_infrared_command();
printf("Press the 1 button \r\n");
}
vTaskDelay(1 / portTICK_PERIOD_MS);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
上电效果:
驱动代码: