目录
一、红外线的通讯原理:
(1)发射端:
(2)接收端:
(3)红外线通信的脉冲频率:
(4)红外线通信:
二、NEC协议介绍:
(1)协议特征:
(2)数据格式:
(3)传输过程:
(4)连发码:
三、接收红外遥控信号:
(1)第一步:
(2)第二步:
(3)第三步:
(4)第四步:
(5)第五步:
(6) 第六步:
(7)第七步:
(8)第八步:
四、结果演示及完整示例:
一、红外线的通讯原理:
红外线通信是一种利用红外线传输信息的技术。红外线是电磁波谱中可见光与微波之间的部分,在光谱中波长自760nm至400um的电磁波称为红外线,它是一种不可见光。在通信中,红外线通常采用38kHz的调制频率,这是一种在自然光中很少出现的频率,因此可以很好地与环境光区分开来,减少干扰。
(1)发射端:
- 发射端使用红外LED灯,通过控制LED的闪烁频率来编码数据。
- 红外LED以特定的频率(如38kHz)发射脉冲信号,这些信号代表了二进制数据。
- 每个脉冲的持续时间和间隔时间可以表示不同的数据位,例如在NEC协议中,逻辑“0”和逻辑“1”通过高电平的不同持续时间来区分。
(2)接收端:
- 接收端使用红外光敏传感器,如光电二极管或光电晶体管,来检测发射端发出的红外光。
- 接收端芯片对红外光敏感,可以根据接收到的光的有无输出相应的高低电平。
- 接收端根据发送端的闪烁频率(即红外光的有无)来识别数据,并按照预定协议进行解码。
(3)红外线通信的脉冲频率:
- 在消费类电子产品中,红外通信的脉冲频率通常在30kHz到60kHz之间。
- NEC协议使用的脉冲频率是38kHz,这是一个非常常见的频率,可以有效地在家庭环境中进行通信,同时避免与自然环境光发生冲突。
(4)红外线通信:
- 就像控制LED灯的闪烁来传递信号一样,红外通信也是通过控制红外LED的闪烁来传递数据。
- 发射端和接收端通过约定的闪烁模式(即协议)来实现数据的发送和接收。
二、NEC协议介绍:
NEC协议是红外通信中非常流行的一种协议,广泛应用于各种消费电子产品,如电视、投影仪和许多迷你遥控器。虽然像格力和美的这样的空调制造商可能使用不同的红外协议,但红外通信的基本原理是相同的。一旦你理解了红外光的传输方式和如何解析NEC协议的数据包,你就可以将这些知识应用到其他协议上,通过观察和分析它们的信号特征,来逆向工程和解码它们。简而言之,掌握一种协议的解析方法,就好比掌握了打开所有红外通信理解之门的钥匙。
(1)协议特征:
- NEC协议使用脉冲位置调制(PPM)方式,以发射红外载波的占空比代表“0”和“1”。
- 载波频率为38kHz,通过高电平的持续时间来区分逻辑“0”和逻辑“1”。
- 地址和命令码均有8位长度,并且每个码都会传输两次,即地址码后跟一个地址反码,命令码后跟一个命令反码,以确保传输的可靠性。
(2)数据格式:
- NEC协议的数据格式包括一个起始位,后面跟着地址码、地址反码、命令码和命令反码,每个部分都是8位二进制数。
- 起始位:引导码由一个9ms的低电平后跟一个4.5ms的高电平组成,用于标识一次新的数据传输开始。
-
发送数据位0: 0.56ms低电平 + 0.56ms的高电平
-
发送数据位1: 0.56ms低电平 + 1.68ms的高电平
-
收到数据位0: 0.56ms低电平 + 0.56ms的高电平
-
收到数据位1: 0.56ms低电平 + 1.68ms的高电平
(3)传输过程:
- 当用户按下遥控器上的按键时,遥控器首先发送起始位,然后是地址码和地址反码,接着是命令码和命令反码。
- NEC协议一次完整的传输包含: 引导码、8位地址码、8位地址反码、8位命令码、8位命令反码。
- 如果用户长按按键,遥控器在发送完一次完整的数据帧后,会每隔110ms发送一次重复码,而不是重复发送完整的数据帧。
(4)连发码:
连发码(Repeat Code)或称重复码,是在用户长按按键时发送的简化信号,它是由一个 9ms 的低电平和一个 2.5ms 的高电平组成。当一个红外信号连续发送时,可以通过发送重复码的方式快速发送。
三、接收红外遥控信号:
(1)第一步:
初始化红外信号接收引脚,设置外部中断,用于接收和处理红外信号。
void receive_Init(void){
/* 开启时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 开启GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 开启GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 开启AFIO的时钟,用于外部中断线配置
/* GPIO初始化 */
GPIO_InitTypeDef GPIO_InitStructure; // 定义GPIO初始化结构体
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 设置GPIO模式为上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // 选择GPIOA的PA3引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置GPIO速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); // 根据GPIO_InitStructure初始化GPIOA的PA3引脚
/* 使能EXTI中断线路 */
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource3); // 将PA3引脚配置为外部中断线3
EXTI_InitTypeDef EXTI_InitStructure; // 定义外部中断初始化结构体
EXTI_InitStructure.EXTI_Line = EXTI_Line3; // 选择外部中断线3
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 设置外部中断模式为中断
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 设置中断触发方式为下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能外部中断线3
EXTI_Init(&EXTI_InitStructure); // 根据EXTI_InitStructure初始化外部中断
NVIC_InitTypeDef NVIC_InitStructure; // 定义中断控制器初始化结构体
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; // 选择外部中断3中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;// 设置中断抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; // 设置中断响应优先级为1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道
NVIC_Init(&NVIC_InitStructure); // 根据NVIC_InitStructure初始化中断控制器
}
(2)第二步:
定义Infrared_Data
结构体将地址码、地址反码、命令码和命令反码封装为一个统一的数据模型,简化红外通信数据的管理和操作。使得数据的存储和访问更加直观和方便。通过InfraredData
变量,程序能有效地保存和处理从红外信号中解析出的数据,同时,通过直接访问结构体成员的方式,如InfraredData.AddressCode
,快速读写具体的数据字段。
typedef struct Infrared_Data{
uint8_t AddressCode; //地址码
uint8_t AddressInverseCode; //地址反码
uint8_t CommandCode; //命令码
uint8_t CommandInverseCode; //命令反码
}Infrared_Data_Struct;
Infrared_Data_Struct InfraredData;
(3)第三步:
分析红外信号的时序,测量红外遥控器发送的信号中的高电平和低电平时间长度。Infrared_low
函数用来测量并返回低电平的持续时间,而 Infrared_high
函数用来测量并返回高电平的持续时间。
//获取红外低电平时间
//引导码低电平9ms,引导码:由9ms的低电平+4.5ms的高电平组成。
void Infrared_low(uint32_t *low_time) {
uint32_t time_val = 0;
while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3) == 0) {
if(time_val >= 500) { // 超过10ms,认为数据接收引导码9ms超时
*low_time = time_val; // low_time=500,跳出循环
return;
}
Delay_us(20); // 延时20微秒
time_val++;
}
*low_time = time_val;
}
//获取红外高电平时间
//引导码高电平4.5ms,引导码:由9ms的低电平+4.5ms的高电平组成。
void Infrared_high(uint32_t *high_time)
{
uint32_t time_val = 0;
while( GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3) == 1 )
{
if( time_val >= 250 ) // 超过5ms,认为数据接收引导码4.5ms超时
{
*high_time = time_val; // 跳出循环,high_time=250
return;
}
Delay_us(20);
time_val++;
}
*high_time = time_val;
}
(4)第四步:
对红外信号引导码和重复码进行判断。通过测量红外信号的低电平和高电平时间,编写函数Guide_Repeat_Judgment用于
区分接收到的信号是引导码还是重复码。如果低电平时间不在9ms左右或高电平时间不在4.5ms左右,则认为不是引导码;如果高电平时间在2.5ms左右,则认为是重复码;否则,如果条件符合,它将被识别为引导码。以实现红外通信中的数据同步和连续信号传输。
//重复码,它是由一个 9ms 的低电平和一个 2.5ms 的高电平组成。
//当一个红外信号连续发送时,可以通过发送重复码的方式快速发送。
//引导码和重复码判断
uint8_t Guide_Repeat_Judgment(void) {
uint32_t out_time=0;
Infrared_low(&out_time);
if((out_time > 500) || (out_time < 400)) { // 大于10ms或小于8ms 9ms
return 1; // 不是引导码
}
Infrared_high(&out_time);
if((out_time > 250) || (out_time < 100)) { // 大于5ms或小于2ms 4.5ms
return 1; // 不是引导码
}
if((out_time > 100) && (out_time < 150)) { // 大于2ms或小于3ms 2.5ms
return 2; // 重复码
}
return 0; // 引导码
}
(5)第五步:
验证接收到的红外数据的正确性,并存储正确的数据。使用Infrared_Data_True
函数检查地址码和地址反码、命令码和命令反码是否彼此是对方的二进制补码验证数据的准确性。如果校验通过,函数将数据打印出来,并保存到全局结构体InfraredData
中,以供后续使用。如果校验失败,则函数返回0,表示数据不正确,确保红外通信数据完整性和准确性。
//红外数据正确性判断
uint8_t Infrared_Data_True(uint8_t *value) {
if(value[0] != (uint8_t)(~value[1])) return 0; // 地址码与地址反码校验
if(value[2] != (uint8_t)(~value[3])) return 0; // 命令码与命令反码校验
/*将校验通过的数据使用串口打印出来*/
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 1; // 数据为真,返回1
}
(6)第六步:
定义函数 Receiving_Infrared_Data
,用于从红外信号中接收并解码数据。首先判断接收到的信号是否包含有效的引导码,然后逐个数据位地接收并识别是逻辑"0"还是逻辑"1"。通过测量红外信号的低电平和高电平时间来确定数据位的值,并将结果存储在数组 ir_value
中。最后,调用 Infrared_Data_True
函数来验证数据的正确性,如果数据正确,就将其保存到全局变量 InfraredData
中,对红外通信信号实现数据接收和解码。
//接收红外数据
/*
数据发送0码:0.56ms低电平+ 0.56ms的高电平
数据发送1码:0.56ms低电平+ 1.68ms的高电平
收到数据位0: 0.56ms低电平+ 0.56ms的高电平
收到数据位1: 0.56ms低电平+ 1.68ms的高电平
*/
void Receiving_Infrared_Data(void) {
uint16_t Group_num = 0, Data_num = 0; // 定义变量,用于存储数据组的索引和数据位的索引
uint32_t time=0; // 定义变量,用于存储时间测量值
uint8_t Bit_data = 0; // 定义变量,用于存储单个数据位的值(0或1)
uint8_t ir_value[5] = {0}; // 定义数组,用于存储接收到的红外数据(4个字节的数据 + 1个字节用于其他目的)
uint8_t Guide_Repeat_Code = 0; // 定义变量,用于存储引导码和重复码的判断结果
Guide_Repeat_Code = Guide_Repeat_Judgment(); // 调用函数判断接收到的是否是引导码或重复码
if(Guide_Repeat_Code == 1) { // 如果判断结果不是引导码,则打印错误信息并结束函数
//printf("err\r\n");
return;
}
for(Group_num = 0; Group_num < 4; Group_num++) { // 循环4次,每次处理一个字节的数据
for(Data_num = 0; Data_num < 8; Data_num++) { // 循环8次,每次处理一个数据位
Infrared_low(&time); // 调用函数获取红外低电平时间
if((time > 60) || (time < 20)) return; // 如果低电平时间不在0.4ms到1.2ms之间,说明数据错误,结束函数
time = 0; // 重置time变量,为下一次测量做准备
Infrared_high(&time); // 调用函数获取红外高电平时间
if((time >= 60) && (time < 100)) { // 如果高电平时间在1.2ms到2ms之间,说明接收到的是数据位1
Bit_data = 1;
}
else if((time >= 10) && (time < 50)) { // 如果高电平时间在0.2ms到1ms之间,说明接收到的是数据位0
Bit_data = 0;
}
ir_value[Group_num] <<= 1; // 将接收到的数据位左移1位,为下一个数据位腾出位置
ir_value[Group_num] |= Bit_data; // 将接收到的数据位写入数组
time=0; // 重置time变量,为下一次测量做准备
}
}
Infrared_Data_True(ir_value); // 调用函数判断接收到的数据是否正确,并保存正确数据
}
(7)第七步:
创建两个函数用于管理和操作红外通信中接收到的命令数据。Get_Infrared_Command
函数用于获取存储在 InfraredData
结构体中的命令码,而 Clear_Infrared_Command
函数则用于清除或重置该命令码,将其设置为0x00。程序在处理完红外命令后,能够读取最新的红外命令数据,并在需要时清除旧数据,为接收新的红外信号做准备。
//获取红外发送过来的命令
uint8_t Get_Infrared_Command(void)
{
return InfraredData.CommandCode;
}
//清除红外发送过来的数据
void Clear_Infrared_Command(void)
{
InfraredData.CommandCode = 0x00;
}
(8)第八步:
编写外部中断3(EXTI3)的中断服务函数。当EXTI3线路触发中断时,函数首先检查是否确实是EXTI3的中断,如果是,则清除中断标志位。接着调用Receiving_Infrared_Data
函数来接收和解析红外数据,然后使用Get_Infrared_Command
函数获取红外命令码,并在OLED屏幕上显示该命令码的字符、十六进制和十进制表示。最后,调用OLED_Update
函数来更新OLED显示。实现中断驱动的红外数据接收和显示机制,允许微控制器在接收到红外信号时立即做出响应并显示信息。
uint8_t temp2=0;
// 外部中断3的中断服务函数
void EXTI3_IRQHandler(void)
{
// 检查是否是EXTI3的中断
if(EXTI_GetITStatus(EXTI_Line3) != RESET)
{
// 清除EXTI3的中断标志位
EXTI_ClearITPendingBit(EXTI_Line3);
// 在这里编写中断处理代码
// 例如,可以判断接收到的信号实现切换一个LED的状态
// 接收一次红外数据
Receiving_Infrared_Data();
char temp1=Get_Infrared_Command();
temp2=Get_Infrared_Command();
OLED_ShowString(0, 16 ,&temp1, OLED_8X16);
OLED_ShowHexNum(0, 32, temp2, 2, OLED_8X16);
OLED_ShowNum(0, 48, temp2, 3, OLED_8X16);
OLED_Update();
// Clear_Infrared_Command();
// 其他中断处理代码...
}
}
四、结果演示及完整示例:
通过网盘分享的文件:12- 红外接收模块接收信号
链接: https://pan.baidu.com/s/1r40f6S2A_N_pLabWjx0zhQ?pwd=qshi 提取码: qshi