51单片机入门:红外遥控

红外遥控系统简介

红外遥控系统是利用红外光进行通信的设备,通常由发射和接收两大部分组成,即:由红外LED将调制后的信号发出,再由专门的红外接收头进行解调输出。

红外LED:外表与普通的LED没有什么不同,发射940nm的红外光,人眼看不到。也有850nm的红外LED,可以看见微微的红光。

通讯方式单工,只能一方发送一方接受,不能反过来;异步,没有单独的时钟线,通信需要双方约定时间。

通信协议标准:NEC标准(我国常用)、RC5、SONY、REC80、SAMSWNG等,主要是欧洲和日本生产厂家所使用的编码格式。

生活中,红外LED被广泛应用,比如在电视、空调和电扇的遥控里都有找到红外LED的身影。这些设备之所以能被控制,是因为内部有与之匹配的红外接收二极管,红外接收二极管的颜色一般是黑色的,当红外接收二极管被红外光照射阻值会变小


硬件电路

1、红外发送部分

主要由如下两种电路结构所示:

对于第一个电路,只有当两个三极管开关同时打开时,红外LED才发光。其中第一个接口的38KHz为调制频率,在发送信号时需要一直给这个接口输入一个频率为38KHz的方波;第二个接口就输入我们想要传输的波形。

将两个接口互相联系起来,会发现:在高电平时,红外LED不会发光;在低电平时,红外LED以38KHz频率闪着亮。(这是为了抵抗干扰,在自然界中有很多红外光,例如太阳光,而调制的目的就是为了区分自然光与作为信号的红外光。)

对于第二个电路,没有了38KHz接口,只是一个纯粹的三极管开关(给低电平亮、给高电平不亮),在发送的时候需要程序来实现38KHz调制的功能。

2、红外接收部分

接收部分有单独的一个红外接收LED(黑色),如果直接用红外接收管,则接受到的信号就会有很多成分(自然光等),因此后面还需要进行电路操作(例如滤除自然光、放大发送的信号、滤除38KHz部分的信号,使其输出信号看上去和IN端输入信号一致)。一体化红外接收管就包含了红外接收LED和这些功能电路。

注意,需要将输出信号OUT接在外部中断上。


基本发送与接收

空闲状态:红外LED不亮,接收头输出高电平。

发送低电平:红外LED以38KHz频率闪烁发光,接收头输出低电平。

发送高电平:红外LED不亮,接收头输出高电平。

接收装置如何区分空闲状态和发送高电平?

接收装置以第一个下降沿为开始,在此之后的一段时间内其输出波形都是发送装置所发送的波形(此时红外LED不亮为高电平输出),波形结束后又一直输出高电平(此时红外LED不亮为空闲状态)。

注意,此处的高电平和低电平都不代表逻辑1或0,仅仅是指物理现象本身。


NEC编码

NEC编码是一种红外遥控协议,常用于遥控器与设备之间的通信。它是一种常用的编码格式;将遥控发送过来的信号进行一定形式的编码,转换为对应的信息。

如上所示,这是一帧完整的红外信号(注意是红外信号,即这是从发送装置来看,对应输出信号波形高低电平与之相反),其中黄色区域既不是高电平也不是低电平,而是38KHz的高频脉冲信号

可以看出,一帧完整的红外信号包括:起始位、地址码、地址反码、数据码、数据反码、结束位。

1、起始位:持续低电平(高频脉冲)9ms后持续高电平(不亮)4.5ms,表示要开始传输数据了。

2、地址码:由8位0或1表示,用于确定(选定)设备(比如家里面有很多红外设备,不同的红外设备其地址码是不同的。)其逻辑0或1表示如下:

逻辑0是由562.5us的高频脉冲和562.5us的不亮表示;逻辑1是由562.5us的高频脉冲和1687.5us的不亮表示。对于上上图,其地址码为:11110000。

3、地址反码:对地址码进行取反,因而地址反码为:00001111。地址反码是为了保证传输的准确性,一旦有一个地址反码和地址码对不上号,这这一帧数据都将作废。

4、数据码:同样由8个0或1组成,对于上上图,其数据码为:1111 1111。其包含的信息为所匹配红外设备需要执行的功能。通过8个0或1的排列组合,有256个不同的结果,也就是说红外遥控器最多有256个按键。

5、数据反码:对数据码进行取反。

6、结束位:持续高频脉冲562.5us,表示数据传输结束。

7、重复码:持续低电平9ms后持续不亮4.5ms,在持续562.5us的高频脉冲,在然后又是持续不亮。每个一帧数据的时间约是110ms。(重复码通常用于调音量时的长按)


51单片机的外部中断

以STC89C52为例,有4个外部中断(传统51单片机只有两个)。其外部中断有两种触发方式:下降沿触发和低电平触发。

其外部中断0(INT0)和外部中断1(INT1)外部中断号分别为0、2。


代码设计

Timer0.c

#include <REGX52.H>

//定时器初始化
void Timer0_Init()
{
	TMOD&=0xF0;    //设置定时器模式
	TMOD|=0x01;    //设置定时器模式
	TL0=0x18;      //设置定时器初值
	TH0=0xFC;      //设置定时器初值
	TF0=0;         //清除TF0标志
	TR0=0;         //定时器0不计时
}

//设置定时器初始值
void Timer0_SetCounter(unsigned int Value)
{
	TH0=Value/256;
	TL0=Value%256;
}

//把定时器里变化后的数据拿出来,得到时间差
unsigned int Timer0_GetCounter()
{
	return (TH0<<8)|TL0;
}

//定时器启动开关
void Timer0_Run(unsigned char Flag)
{
	TR0=Flag;     //Flag为1,开始计时;为0,停止计时
}

Int0.c

#include <REGX52.H>

//外部中断0初始化
void Int0_Init()
{
	IT0=1;
	IE0=0;
	EX0=1;
	EA=1;
	PX0=1;   //优先级设为高优先级
}

此处需要将外部中断0的中断优先级设置为高优先级,保证在同时触发多个中断时优先执行此中断,使红外信号被及时接收。

IR.c

#include <REGX52.H>
#include "Timer0.h"
#include "Int0.h"

unsigned int IR_Time;      //计时(时间差)
unsigned char IR_State;    //状态

unsigned char IR_Data[4];  //存储数据
unsigned char IR_pData;    //0~31,即一个完整的信号有32位

unsigned char IR_DataFlag;   //数据帧标志位
unsigned char IR_RepeatFlag; //连发帧标志位
unsigned char IR_Address;    //地址码
unsigned char IR_Command;    //数据码

//红外遥控初始化
void IR_Init()
{
	Timer0_Init();
	Int0_Init();
}


/**
  *功能:红外遥控获取收到数据帧标志位
  *参数:无
	*返回值:是否收到数据帧,1为收到,0为未收到
  */
unsigned char IR_GetDataFlag()
{
	if(IR_DataFlag)
	{
		IR_DataFlag=0;
		return 1;
	}
	return 0;
}

/**
  *功能:红外遥控获取收到连发帧标志位
  *参数:无
	*返回值:是否收到连发帧,1为收到,0为未收到
  */
unsigned char IR_GetRepeatFlag()
{
	if(IR_RepeatFlag)
	{
		IR_RepeatFlag=0;
		return 1;
	}
	return 0;
}

/**
  *功能:红外遥控获取收到的命令数据
  *参数:无
	*返回值:收到的命令数据
  */
unsigned char IR_GetCommand()
{
	return IR_Command;
}

/**
  *功能:红外遥控获取收到的地址数据
  *参数:无
	*返回值:收到的地址数据
  */
unsigned char IR_GetAddress()
{
	return IR_Address;
}


//外部中断0函数,下降沿触发
void Int0_Routine() interrupt 0
{
	if(IR_State==0)          //状态为0,说明是空闲状态
	{
		Timer0_SetCounter(0);     //设置定时器初始值
		Timer0_Run(1);            //开始计时
		IR_State=1;               //进入状态1,即准备接收信号
	}
	else if(IR_State==1)
	{
		IR_Time=Timer0_GetCounter();   //读取计时的时间(时间差)
		Timer0_SetCounter(0);          //定时器清0,重新计时间差
		
		//计时为13.5ms,则收到了起始位(注意,11.0592MHz下为12442)
		if(IR_Time>12442-500&&IR_Time<12442+500) //如果是起始位
		{
			IR_State=2;   //进入状态2,即开始接收数据
		}
		
		//计时为11.25ms,则收到了重复位(同样,11.0592MHz下为10368)
		else if(IR_Time>10368-500&&IR_Time<10368+500)
		{
			IR_RepeatFlag=1;    //说明这一帧已经结束
			Timer0_Run(0);      //结束计时
			IR_State=0;         //回到空闲状态
		}
		
		//接收出错
		else
		{
			IR_State=1;
		}
	}
	else if(IR_State==2)
	{
		IR_Time=Timer0_GetCounter();  //读取计时的时间(时间差)
		Timer0_SetCounter(0);         //定时器清0
		
		//计时为1120us,则收到了逻辑0(注意,11.0592MHz下为1032us)
		if(IR_Time>1032-500&&IR_Time<1032+500) 
		{
			IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); //数据对应位  置0
			IR_pData++;
		}
		
		//计时为2250us,则收到了逻辑1(注意,11.0592MHz下为2074us)
		else if(IR_Time>2074-500&&IR_Time<2074+500)
		{
			IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8));  //数据对应位  置1
			IR_pData++;
		}
		
		//如果接受失败,这一帧作废
		else
		{
			IR_pData=0;
			IR_State=1;
		}
		
		if(IR_pData>=32)   //表示32位数据接受完毕
		{
			IR_pData=0; //重新置0
			if((IR_Data[0]==~IR_Data[1])&&(IR_Data[2]==~IR_Data[3]))
			{
				IR_Address=IR_Data[0];  //数据转移
				IR_Command=IR_Data[2];  //数据转移
				IR_DataFlag=1;
			}
			Timer0_Run(0);  //定时器停止
			IR_State=0;     //置状态为0
		}
	}
}

此代码主要通过下降沿之间的时间差来判断逻辑0、逻辑1、起始位、重复位。

对于重复位直接返回状态0即可(不需要再进入状态2中改变地址码和数据码),并且在下一次下降沿到来时在进行判断时起始位还是重复位。

对于起始位,则让其进入状态2,记录地址码与数据码。

  • 28
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值