一篇文章教会你红外接收模块接收红外遥控信号,附STM32代码示例

目录

一、红外线的通讯原理:

(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 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值