前言

红外传感只占用了STM32的一个引脚(外部中断引脚)就可以实现无数的数据传输,单片机也就对不同的红外数据进行接收与程序存储的信息进行比较,再在执行主程序·····
下面是本人对红外接收的处理,以及相应的一些简单的主函数执行。
在这里插入图片描述

一、什么红外通信协议

1.1、NEC 协议

  • 8位地址码和8位用户码;
  • 增加8位地址反码和8位用户反码的传输(以确保可靠性);
  • PWM脉冲位置调制,以发射红外载波的占空比代表“0”和“1”;
  • 载波频率为38Khz;

1.1-1,NEC 协议 位 的定义

如何判断数据的一个位是 “1” 还是 “0” :

  • 一个逻辑 “1” 传输需要2.25ms(560us脉冲+1680us低电平);
  • 一个逻辑 “0” 的传输需要1.125ms(560us脉冲+560us低电平);
  • 所以,每位的周期为 2.25ms 或者 1.12ms ,(分别代表“1”和“0”)
    在这里插入图片描述

1.1-2,红外遥控以NEC 协议数据“单发”的格式

一个正常的数据包 :

  • 开头:发送的是9ms高电平+4.5ms低电平。为引导码。
  • 紧接着是:8bit的地址码+8bit地址反码+8bit用户码+8bit用户反码。为32位的数据。

单次发送时

1.1-3,红外遥控以NEC 协议“连发”数据格式

连续发送数据包 :

  • 长按红外遥控按键时,每隔 110ms 重复发送一次。但是命令只发送一次,重复发送的是 9ms 高电平+2.25ms 低电平+0.56ms 高电平+低电平

连续发送时

1.1-4捕获红外信号的思路

二、红外接收程序

2.1、由外部中断接收红外信号

2.1-1、GUA_Infrared_Receiver.c 文件

  • 针对大佬的代码进行了自己的理解与修改,增加了解译红外遥控发送的32位数据(地址码+地址反码+用户码+用户反码)的函数。
  • 这里最核心的,属红外接收处理时对高低电平时间的判断,经过不断的修正电平维持时间,就可以将红外遥控发送的数据捕获到。
  • 针对不同的遥控可能因为单片机频率和红外接收模块各因素,需要修改判断电平维持时间的范围。处理红外接收的函数
GUA_U8 GUA_Infrared_Receiver_Process(void)
  • 在红外接收处理函数处,我增加串口输出的功能,每到一个接收出错退出循环的同时将上次计时的值从串口打印出来,由此知道红外接收输入引脚上电平变化的时间间隔。对不同的遥控按键发送的协议解读有实用价值。
    红外接收处理出错时执行
/6		else if(GUA_Infrared_Receiver_Process() == GUA_INFRARED_RECEIVER_ERROR)
		{ 											//否则、串口显示已经累加的时间(uS)和 解码错误提示
			Serial_SendNumber(nGUA_Time_Num,4);			
			Serial_SendString(" GUA_INFRARED_RECEIVER_ERROR  \r\n");
			gGUA_InfraredReceiver_Data=0;
		}
  • 还有,红外发送过来的键码解读成功后,将其 地址码+地址反码+用户码+用户反码 都打印在串口上,由此,可以看到用户码(命令键)的十进制,再换算成十六进制后就可以进行比较与传递。 跳转到该子函数执行串口显示
/5			Show_Data();       		 //显示红外接收到的数据
  • 这里对于红外连续发送时的处理比较随便,但是效果还是达到了。不符合该函数判断的红外协议并不会被捕获到。

  • 四重对电平维持时间判断关卡决定了数据的保存与否。


#include "GUA_Infrared_Receiver.h"

/*********************外部变量************************/
GUA_U32 gGUA_InfraredReceiver_Data = 0;  //一个接收红外原始数据的变量(32位)

/********************保存红外数据的变量*************/
uint8_t IR_RECEIVE[5];   // 每一项保存一个字节数据
int down16,up16;         // 分别保存低十六位和高十六位数据

uint8_t IR_State;		
uint8_t IR_DataFlag;    //单发标志
uint8_t IR_RepeatFlag;  //连发标志

/*********************内部函数************************/
static void GUA_Infrared_Receiver_IO_Init(void);
static GUA_U16 GUA_Infrared_Receiver_GetHighLevelTime(void);
static GUA_U16 GUA_Infrared_Receiver_GetLowLevelTime(void);

//******************************************************************************        
//name:             GUA_Infrared_Receiver_IO_Init        
//introduce:        红外接收的IO初始化  
static void GUA_Infrared_Receiver_IO_Init(void)
{   
    GPIO_InitTypeDef GPIO_InitStructure;	//IO结构体
//	//失能JTAG和SWD在PB3上的功能使用
//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_Disable, ENABLE);
//	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
	//时钟使能
RCC_APB2PeriphClockCmd(GUA_INFRARED_RECEIVER_RCC | RCC_APB2Periph_AFIO, ENABLE);

	//红外接收IO配置
    GPIO_InitStructure.GPIO_Pin = GUA_INFRARED_RECEIVER_PIN;        
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 
    GPIO_Init(GUA_INFRARED_RECEIVER_PORT, &GPIO_InitStructure);  

    GPIO_EXTILineConfig(GUA_INFRARED_RECEIVER_PORTSOURCE, GUA_INFRARED_RECEIVER_PINSOURCE); 
}

//******************************************************************************            
//name:             GUA_Infrared_Receiver_Exti_Init
//introduce:        红外接收的IO中断初始化                             
//******************************************************************************
static void GUA_Infrared_Receiver_Exti_Init(void)
{   
	NVIC_InitTypeDef NVIC_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;
	//中断配置
    EXTI_InitStructure.EXTI_Line = GUA_INFRARED_RECEIVER_EXTI_LINE;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger =  EXTI_Trigger_Falling ;  
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}

//******************************************************************************        
//name:             GUA_Infrared_Receiver_Init 
//introduce:        红外接收初始化 
//******************************************************************************  
void GUA_Infrared_Receiver_Init(void)
{
	GUA_Infrared_Receiver_IO_Init();  //初始化IO
	GUA_Infrared_Receiver_Exti_Init();  //初始化中断配置
}

//******************************************************************************        
//name:             GUA_Infrared_Receiver_GetHighLevelTime  
//introduce:        红外接收获取高电平维持时间
//****************************************************************************** 
static GUA_U16 GUA_Infrared_Receiver_GetHighLevelTime(void)
{
    GUA_U16 nGUA_Num = 0;
	//判断是否一直为高电平
while(GPIO_ReadInputDataBit(GUA_INFRARED_RECEIVER_PORT, GUA_INFRARED_RECEIVER_PIN) == Bit_SET)
    {
		//超时超时溢出
        if(nGUA_Num >= 250) 
        {
            return nGUA_Num;
        }
        nGUA_Num++;	 	//计延时20us的次数
        Delay_us(20);	//该延时针对外部晶振8000的STM32C8T6核心板,对于不同类型不同外部晶振的开发板需要根据波形图调整延时,保证20us延时       
    }

    return nGUA_Num;
}

//******************************************************************************        
//name:             GUA_Infrared_Receiver_GetLowLevelTime  
//introduce:        红外接收获取低电平维持时间
//****************************************************************************** 
static GUA_U16 GUA_Infrared_Receiver_GetLowLevelTime(void)
{
    GUA_U16 nGUA_Num = 0;

	//判断是否一直为低电平
    while(GPIO_ReadInputDataBit(GUA_INFRARED_RECEIVER_PORT, GUA_INFRARED_RECEIVER_PIN) == Bit_RESET)
    {
        if(nGUA_Num >= 500) 
        {
            return nGUA_Num;
        }

        nGUA_Num++;

        delay_us(20);//该延时针对外部晶振8000的STM32C8T6核心板,对于不同类型不同外部晶振的开发板需要根据波形图调整延时,保证低电平延时                   
    }

    return nGUA_Num;
}

//******************************************************************************            
//name:             GUA_Infrared_Receiver_Process  
//introduce:        红外接收的处理函数 
// 该函数是关键所在:处理红外信号看接收管OUT引脚电平维持时间,高低电平一定时间后状态变化。
//                  依据电平维持时间,判断:引导码、32位数据···
//******************************************************************************
GUA_U16 nGUA_Time_Num;
GUA_U8 nGUA_Data;
GUA_U8 nGUA_Byte_Num;
GUA_U8 nGUA_Bit_Num;
GUA_U32 ContinuousReceiver_Data;  //保存上次捕获的32位原始的数据
//
GUA_U8 GUA_Infrared_Receiver_Process(void)
{
     nGUA_Time_Num = 0;
     nGUA_Data = 0;
     nGUA_Byte_Num = 0;
     nGUA_Bit_Num = 0;

	//接收引导码9ms的低电平,过滤无用信号>10ms或<8ms
    nGUA_Time_Num = GUA_Infrared_Receiver_GetLowLevelTime();
    if((nGUA_Time_Num > 500) || (nGUA_Time_Num < 400))
    {
//1		Serial_SendString("\r\n 1: error occurred \r\n");     //_________
        return GUA_INFRARED_RECEIVER_ERROR;
    }   
	//接收引导码4.5ms的高电平,过滤无用信号>5ms或<4ms     //250ms时是连续发送信号
    nGUA_Time_Num = GUA_Infrared_Receiver_GetHighLevelTime();
    if((nGUA_Time_Num > 250) || (nGUA_Time_Num < 100))                   //200<=  >=250
    {
//2		Serial_SendString("\r\n 2: error occurred \r\n");     //_________
        return GUA_INFRARED_RECEIVER_ERROR;
    }   
	
	//接收4字节数据(分别有:用户反码、用户码、地址反码、地址码)
    for(nGUA_Byte_Num = 0; nGUA_Byte_Num < 4; nGUA_Byte_Num++)
    {
		//接收位数据(每字节8位)
        for(nGUA_Bit_Num = 0; nGUA_Bit_Num < 8; nGUA_Bit_Num++)
        {
			//接收每bit的前0.56ms的低电平,过滤无用信号>1.2ms或<0.40ms
            nGUA_Time_Num = GUA_Infrared_Receiver_GetLowLevelTime();
            if((nGUA_Time_Num > 60) || (nGUA_Time_Num < 20))
            {
//3				Serial_SendString("\r\n 3: error occurred \r\n");     //_________
                return GUA_INFRARED_RECEIVER_ERROR;
            }
			//接收每bit的后高电平时长:高电平数据,1.68ms(1.2ms~2.0ms),低电平数据,0.56ms(0.2ms~1ms),过滤其他无用信号
			nGUA_Time_Num = GUA_Infrared_Receiver_GetHighLevelTime();
            if((nGUA_Time_Num >=60) && (nGUA_Time_Num < 100))   //25  50
            {
                nGUA_Data = 1;
            }
            else if((nGUA_Time_Num >=10) && (nGUA_Time_Num < 50))   //50 90
            {
                nGUA_Data = 0;
            }
            else
            {
				if(nGUA_Time_Num == 250)   //红外连续发送情况下,退出循环直接赋予上次的数据
				{
					IR_RepeatFlag = 1;     //连续收发标志置一
					IR_DataFlag = 0;		//单发标志置零
					gGUA_InfraredReceiver_Data = ContinuousReceiver_Data;
					return GUA_INFRARED_RECEIVER_OK;    
				}else{
//4					Serial_SendString("\r\n 4: error occurred \r\n");     //_________
					return GUA_INFRARED_RECEIVER_ERROR;
				}
            }
			//保存数据
            gGUA_InfraredReceiver_Data <<= 1;   
            gGUA_InfraredReceiver_Data |= nGUA_Data;                        
        }
    }
	
	IR_DataFlag = 1;     //单次收发标志置一
	IR_RepeatFlag = 0;   //连发标志置零
	ContinuousReceiver_Data = gGUA_InfraredReceiver_Data;
    return GUA_INFRARED_RECEIVER_OK;    
}

//*******************************************************************************
//》》》》》》》》》》》》》》中断处理函数《《《《《《《《《《《《《《《《《《
void EXTI9_5_IRQHandler(void)
{
	if(EXTI_GetITStatus(GUA_INFRARED_RECEIVER_EXTI_LINE) != RESET)
	{
		EXTI_ClearITPendingBit(GUA_INFRARED_RECEIVER_EXTI_LINE);
		EXTI->IMR &= ~(GUA_INFRARED_RECEIVER_EXTI_LINE);
	  
		if(GUA_Infrared_Receiver_Process() == GUA_INFRARED_RECEIVER_OK) //如果捕获到有效数据
		{
			down16=gGUA_InfraredReceiver_Data;
			up16=gGUA_InfraredReceiver_Data>>16;
			//-----------从上到下依次为用户反码、用户码、地址反码、地址码
			IR_RECEIVE[3]=down16;
			IR_RECEIVE[2]=(down16>>8);
			IR_RECEIVE[1]=up16;
			IR_RECEIVE[0]=(up16>>8);

//5			Show_Data();       		 //显示红外接收到的数据
		}	
/*6		else if(GUA_Infrared_Receiver_Process() == GUA_INFRARED_RECEIVER_ERROR)
		{ 											//否则、串口显示已经累加的时间(uS)和 解码错误提示
			Serial_SendNumber(nGUA_Time_Num,4);			
			Serial_SendString(" GUA_INFRARED_RECEIVER_ERROR  \r\n");
			gGUA_InfraredReceiver_Data=0;
		}
		*/
	}
	EXTI->IMR|=(GUA_INFRARED_RECEIVER_EXTI_LINE);
}

/***********************调试键码的函数(串口输出)*********************************
  *
  *函数: 显示出已经捕获得到的红外数据,
  *		  如果解码成功,可以获得红外数据(地址码+地址反码+用户码+用户反码)
  *		  (通过串口发送相关信息,使用Serial_SendNumber()可以在调试窗口文本类得到十进制格式,
  *		    再将数据十进制换算成十六进制就可以进行比较和传递。)
  *参数:无
**/
void Show_Data(void)
{
//	Serial_SendArray(IR_RECEIVE,32);
	Serial_SendString("\r\n GUA_INFRARED_RECEIVER_OK  ");
	Serial_SendString("\r\n 用户反码:  ");
	Serial_SendNumber( IR_RECEIVE[3],4);
	Serial_SendString("\r\n 用户码:  ");
	Serial_SendNumber( IR_RECEIVE[2],4);
	Serial_SendString("\r\n 地址反码:  ");
	Serial_SendNumber( IR_RECEIVE[1],4);
	Serial_SendString("\r\n 地址码:  ");
	Serial_SendNumber( IR_RECEIVE[0],4);
}
/************************************五个可获取红外相关数据的函数***********************************
  * @brief  红外遥控获取收到数据帧标志位
  * @param  无
  * @retval 是否收到数据帧,1为收到,0为未收到
  */
uint8_t IR_GetDataFlag(void)
{
	if(IR_DataFlag)
	{
		IR_DataFlag=0;
		return 1;
	}
	return 0;
}
/**
  * @brief  红外遥控获取收到连发帧标志位
  * @param  无
  * @retval 是否收到连发帧,1为收到,0为未收到
  */
uint8_t IR_GetRepeatFlag(void)
{
	if(IR_RepeatFlag)
	{
		IR_RepeatFlag=0;
		return 1;
	}
	return 0;
}
/**
  * @brief  红外遥控获取收到的地址数据
  * @param  无
  * @retval 收到的地址码数据
  */
uint8_t IR_GetAddress(void)
{
	return IR_RECEIVE[0];
}
/**
  * @brief  红外遥控获取收到的命令数据
  * @param  无
  * @retval 收到的用户码数据
  */
uint8_t IR_GetUserCode(void)
{
	return IR_RECEIVE[2];
}


/***
  * @brief  为获得的红外用户码(命令)数据赋予一个数字身份
  * @param  无
  * @retval 收到的用户码的新身份
  */
uint8_t IR_CommandSymbol(void)
{
	unsigned char Num;
	switch(IR_RECEIVE[2])
	{
		case IR_0: Num=0;break;            //0 代表 0键:
		case IR_1: Num= 1;break;           //1 代表 1键:	
		case IR_2: Num= 2;break;           //2 代表 2键:
		case IR_3: Num= 3;break;           //3 代表 3键: 	
		case IR_4: Num= 4;break;           //4 代表 4键:
		case IR_5: Num= 5;break;           //5 代表 5键:	
		case IR_6: Num= 6;break;           //6 代表 6键:
		case IR_7: Num= 7;break;           //7 代表 7键:
		case IR_8: Num= 8;break;           //8 代表 8键:
		case IR_9: Num= 9;break;           //9 代表 9键:
		case IR_POWER: Num= 11;break;       //11 代表 OnOff键:
		case IR_MODE:  Num= 12;break;       //12 代表 Mode键:		
		case IR_MUTE:  Num= 13;break;       //13 代表 Mute键:
		case IR_START_STOP: Num= 21;break;  //21 代表 Pause键:
		case IR_PREVIOUS:   Num= 22;break;  //22 代表 Left键:	
		case IR_NEXT: Num= 23;break;        //23 代表 Right键:
		case IR_EQ:   Num= 31;break;        //31 代表 EQ键:
		case IR_VOL_MINUS: Num= 32;break;   //32 代表 VolADD键	
		case IR_VOL_ADD:   Num= 33;break;   //33 代表 VolDOW键
		case IR_RPT: Num= 42;break;         //42 代表 RPT键:		
		case IR_USD: Num= 43;break;			//43 代表 U_SD键:
		default :break;
	}
	return Num;	
}


2.1-2、GUA_Infrared_Receiver.h 文件

  • 得到的红外遥控键码(用户码)进行了(十六进制的)宏定义。
  • 而主要利用的是,我将用户码进行了重新的命名,针对不同的按键__相应有代表数字。其由函数:uint8_t IR_CommandSymbol(void) 来实现。
  • 如何利用这些函数,请看主函数。
//******************************************************************************              
//name:             GUA_Infrared_Receiver.h               
//******************************************************************************    
#ifndef _GUA_INFRARED_RECEIVER_H_
#define _GUA_INFRARED_RECEIVER_H_

#include "main.h"
.............略了一些定义
.............
//红外接收引脚
#define GUA_INFRARED_RECEIVER_PORT               GPIOB
#define GUA_INFRARED_RECEIVER_PIN                GPIO_Pin_5
#define GUA_INFRARED_RECEIVER_RCC                RCC_APB2Periph_GPIOB

//中断
#define GUA_INFRARED_RECEIVER_EXTI_LINE          EXTI_Line5
#define GUA_INFRARED_RECEIVER_PORTSOURCE         GPIO_PortSourceGPIOB
#define GUA_INFRARED_RECEIVER_PINSOURCE          GPIO_PinSource5

#define TRUE            0
#define FALSE           1

#define GUA_INFRARED_RECEIVER_OK                0
#define GUA_INFRARED_RECEIVER_ERROR             1

/*********************外部变量************************/
extern GUA_U32 gGUA_InfraredReceiver_Data;          //一个接收红外原始数据的变量(32位)

/********************* 函数声明 ************************/ 
extern void GUA_Infrared_Receiver_Init(void);
extern GUA_U8 GUA_Infrared_Receiver_Process(void);
void Show_Data(void);


//**************************************************************
//》》》》》》》》》》能在主函数里调用的函数《《《《《《《《《《《
//**************************************************************

uint8_t IR_GetDataFlag(void);     	//  是否按一次	(单发)
uint8_t IR_GetRepeatFlag(void);   	//	是否为连按	(连发)
uint8_t IR_GetAddress(void);		// 	地址码
uint8_t IR_GetUserCode(void);		//	用户码(按键命令)
uint8_t IR_CommandSymbol(void);   	//	将用户码转换出一个对应的数字身份

//***********************************************************************
//***********************************************************************
//从“调试键码的函数”获得相应用户码,并转换位16进制后,对遥控发送的键码(用户码)进行宏定义
#define IR_POWER		0xA2
#define IR_MODE			0x62
#define IR_MUTE			0xE2
#define IR_START_STOP	0x22
#define IR_PREVIOUS		0x02
#define IR_NEXT			0xC2
#define IR_EQ			0xE0
#define IR_VOL_MINUS	0xA8
#define IR_VOL_ADD		0x90
#define IR_0			0x68
#define IR_RPT			0x98
#define IR_USD			0xB0
#define IR_1			0x30
#define IR_2			0x18
#define IR_3			0x7A
#define IR_4			0x10
#define IR_5			0x38
#define IR_6			0x5A
#define IR_7			0x42
#define IR_8			0x4A
#define IR_9			0x52

#endif

2.1-3、main.c 文件

  1. 很显然,IR_GetDataFlag()和 IR_GetRepeatFlag()两个函数是判断是否有接收到红外信号。
  2. IR_GetAddress()和IR_GetUserCode()两个函数就是分别获得红外数据中“地址码”和“用户码”。
  3. IR_CommandSymbol()函数是根据遥控器上按键对“用户码”进行了重新命名定义,就方便于使用与记忆。
#include "main.h"

uint8_t Num;
uint8_t Address;
uint8_t Command;
uint8_t One_More =1,Command_early;
uint8_t Information;

void Click_Button_(void);
void Continuous_Button_(void);

int main(void)
{
	OLED_Init();
	LED_Init();
	Serial_Init();
	GUA_Infrared_Receiver_Init(); //红外接收初始化
	
	Serial_SendString("\r\n Is OK \r\n");
	OLED_ShowString(1, 1,"User Add   CMD  ");
	OLED_ShowString(2, 1,"0000 0000  00   ");
	LED2_OFF();
	LED2_OFF();
	while (1)
	{
		if(IR_GetDataFlag())  //若红外单发
		{
			LED2_Turn();
			OLED_ShowNum(2,1,IR_GetUserCode(),4);
			OLED_ShowNum(2,6,IR_GetAddress(),4);
			OLED_ShowNum(2,12,IR_CommandSymbol(),2);
			if(((int )IR_CommandSymbol() >= 0) && ((int )IR_CommandSymbol() <= 9) )
			{
				Serial_SendString("\r\n 你按下了——键 ");   // 打印按键0到9
				Serial_SendNumber(IR_CommandSymbol(),1);
			}
			else if(IR_CommandSymbol() == 6)   //当然该判断是不会执行了,因为已经满足了前面一个的判断条件
			{
				Serial_SendString("\r\n 你按下的是键“6”");
			}
			else if(IR_CommandSymbol() == 11)
			{
				Serial_SendString("\r\n 你按了开关键");
			}
			else if(IR_CommandSymbol() == 12)
			{
				OLED_ShowString(3,1,"  Happy Happy");
				OLED_ShowString(4,2,"Happy every day");
			}
			else if(IR_CommandSymbol() == 22)
			{
				Serial_SendString("\r\n 上一曲");
			}
			else if(IR_CommandSymbol() == 23)
			{
				Serial_SendString("\r\n 下一曲");
			}
		}
		if(IR_GetDataFlag() || IR_GetRepeatFlag()) //若红外单发或连发
		{
			if(IR_CommandSymbol() == 32)
			{
				Serial_SendString("\r\n 音量- -");
			}else if(IR_CommandSymbol() == 33)
			{
				Serial_SendString("\r\n 音量+ +");
			}
		}
	}
}

在这里插入图片描述

效果演示

在这里插入图片描述

红外接收视频演示

相关参考资料链接

1、典型的 NEC 协议传输格式:红外遥控原理

3、该参考程序的原版来源: STM32之红外接收

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐