红外遥控

红外线介绍

​人的眼睛能看到的可见光按波长从长到短排列,依次为 红、橙、黄、绿、青、蓝、紫。其中红光的波长范围为 0.62~0.76μm; 紫光的波长范围为 0.38~0.46μm 。 比紫光波长还短的光叫紫外线,比红光波长还长的光叫红外线。红外线遥控就是利用波长为 0.76~1.5μm 之间的近红外线来传送控制信号的。 红外遥控通信系统一般由 红外发射装置红外接收设备 两大部分组成

红外发射装置

​红外发射装置,也就是通常我们说的红外遥控器是由键盘电路、红外编码电路、电源电路和红外发射电路组成。

  • 红外遥控为了提高抗干扰性能和降低电源消耗,红外遥控器常用 载波的方式传送二进制编码常用的载波频率为 38kHz,这是由发射端所使用的 455kHz晶振来决定的

  • 在发射端要对晶振进行整数分频, 分频系数一般取 12

455kHz1237.9kHz38kHz\frac{455kHz}{12}≈37.9kHz≈38kHz

也有一些遥控系统采用 36kHz、 40 kHz、 56 kHz等,一般 由发射端晶振的振荡频率来决定

  • 二进制脉冲码的形式有多种,我这个是使用的是 NEC 协议

8 位地址和 8 位指令长度;

②地址和命令 2 次传输(确保可靠性)

③PWM 脉冲位置调制,以发射红外载波的占空比代表 “0”和“1”

④载波频率为 38Khz

⑤位时间为 1.125ms 或 2.25ms

NEC 码的位定义

一个脉冲对应 560us 的连续载波,一个逻辑 1 传输需要2.25ms(560us 脉冲+1680us 低电平), 一个逻辑 0 的传输需要 1.125ms(560us脉冲+560us 低电平)。

而红外接收头在收到脉冲的时候为 低电平,在没有脉冲的时候为 高电平,这样,我们在接收头端收到的信号为: 逻辑 1 应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。所以可以 通过计算高电平时间判断接收到的数据是 0 还是 1

NEC 码位定义时序图如下图所示:

NEC 遥控指令的数据格式为引导码、地址码、地址反码、控制码、控制反码引导码由一个 9ms 的低电平和一个 4.5ms 的高电平组成,地址码、地址反码、控制码、控制反码 均是 8 位数据格式。按照低位在前,高位在后 的顺序发送。采用反码是为了增加传输的可靠性(可用于校验)数据格式如下:

NEC 码还规定了 连发码(由 9ms 低电平+2.5m 高电平+0.56ms 低电平+97.94ms 高电平组成), 如果在一帧数据发送完毕之后,红外遥控器按键仍然没有放开,则发射连发码,可以 通过统计连发码的次数来标记按键按下的长短或次数。

红外接收设备

红外接收设备是由红外接收电路、红外解码、电源和应用电路组成。红外遥控接收器的 主要作用是将遥控发射器发来的红外光信好转换成电信号,再放大、限幅、检波、整形,形成遥控指令脉冲,输出至遥控微处理器

红外接收头的封装大致有 两种: 一种采用铁皮屏蔽;一种是塑料封装。均有三只引脚,正对接收头的凸起处看,从左至右即 电源正( VDD)、电源负(GND)和数据输出(VOUT)

红外接收头 在没有脉冲的时候为高电平,当收到脉冲的时候为低电平。通过 外部中断的下降沿触发中断,在中断内 通过计算高电平时间来判断接收到的数据是 0还是1

注意

为了保证红外接收头输出管脚默认为 高电平,需 外接一个 10K 上拉电阻,51单片机 IO 口都默认增加了 10K 上拉电阻。

软件编写

流程

进入中断函数,表示已来下降沿, 然后判断管脚是否为低电平,如果为低电平则首先判断引导信号,根据前面 NEC 协议可知,引导信号有 9ms 的低电平和 4.5ms 的高电平,因此通过 time_cnt 赋值 1000,然后在 while 循环内判断,time_cnt 每递减一次约 10us,1000 次则为10ms, 在解码时,这个时间要适当放宽一点范围,因为不同传感器性能会有差异,所以此处以 10ms 的低电平为界限,如果超过 10ms 则强制退出,防止系统死机。判断完引导信号的低电平,接着判断高电平,实现方法一样。当引导信号判断完成后进入地址码、地址反码、控制码及控制反码共 4 个字节的数据判断,也就是数据 0 和 1 的判断,实现方法也是和前面判断引导信号一样,这里使用到了嵌套循环,外层循环次数是 4,表示读取 4 个字节,内层循环次数是 8,表示读取每个字节的 8 位。注意,红外遥控解码数据是从低位开始,最后是高位。最后将读取的 4 个字节数据存储在全局变量数组 gired_data 中,外部可直接使用这四个字节。

main.c

# include "smg.h"
# include "lred.h"


void main()
{
	u8 Ired_buf[3];
	ired_init();
	while(1)
	{
		Ired_buf[0]=gsmg_code[gired_data[2]/16];//将控制码高4位转换为数码管段码
		Ired_buf[1]=gsmg_code[gired_data[2]%16];//将控制码低4位转换为数码管段码
		Ired_buf[2]=0x76;//显示H
		smg_display(Ired_buf,6);
	}
}

smg.c

# include "smg.h"
# include "lred.h"

u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
				0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

void smg_display(u8 dat[],u8 pos)
{
	u8 i=0;
	u8 pos_temp=pos-1;
	
	for(i=pos_temp;i<8;i++)
	{
		switch(i)
		{
				case 0:A0=1;A1=1;A2=1;break;//Y7//板子从左边数第一个数码管,下面以此类推
				case 1:A0=0;A1=1;A2=1;break;//Y6
				case 2:A0=1;A1=0;A2=1;break;//Y5
				case 3:A0=0;A1=0;A2=1;break;//Y4
				case 4:A0=1;A1=1;A2=0;break;//Y3
				case 5:A0=0;A1=1;A2=0;break;//Y2
				case 6:A0=1;A1=0;A2=0;break;//Y1
				case 7:A0=0;A1=0;A2=0;break;//Y0
		}
			SMG_A0_F_PORT=dat[i-pos_temp];//传送段选数据
			delay_10us(100);//延时1毫秒左右
			SMG_A0_F_PORT=0x00;//消影
	}
}

smg.h

# ifndef _smg_H
# define _smg_H

# include "lred.h"

sbit A0=P2^2;
sbit A1=P2^3;
sbit A2=P2^4;
# define SMG_A0_F_PORT P0//宏定义数码管P0端口
extern u8 gsmg_code[17];  //注意要加extern 
void smg_display(u8 dat[],u8 pos);
# endif

lred.c(红外)

# include "lred.h"


u8 gired_data[4];//存储4个字节接收码(地址码+地址反码+控制码+控制反码)

void delay_10us(u16 ten_us)//当传入 Ten_us=1时,大约延时10us
{
	while(ten_us--);
}

void ired_init()
{
	IT0=1;//触发方式   1:下降沿触发  0:低电平触发
	EX0=1;//开启外部中断0
	EA=1;//开启总中断
	IRED=1;//上拉电阻拉高(可以省略)初始化端口
}

void ired() interrupt 0       //外部中断0服务函数
{
	u16 time_cnt=0;
	u8 i=0,j=0;
	u8 Ired_high_time=0;//要初始化为0不然按其他键没反应!!!!!踩坑
	if(IRED==0)
	{
		time_cnt=1000;
		//1.引导码9ms
		while((!IRED)&&(time_cnt))//等待引导信号9ms低电平结束,若超过10ms强制退出
		{
			delay_10us(1);//10*1000就是10ms(10us进来一次)
			time_cnt--;
			if(time_cnt==0)
				return;
		}
		
		
		if(IRED)//引导信号9ms低电平已过,进入4.5ms高电平
		{
			time_cnt=500;
			//2.高电平4.gms
			while(IRED&&time_cnt)
			{
				delay_10us(1);
				time_cnt--;
				if(time_cnt==0)
					return;
			}
			
			//3.4个字节的数据
			for(i=0;i<4;i++)
			{
				for(j=0;j<8;j++)//循环8次读取每位数据即一个字节
				{
					//判断高电平时间和低电平时间
					time_cnt=600;
					while((IRED==0)&&time_cnt)//等待数据1或0前面的0.56ms结束,若超过6ms强制退出
					{
						delay_10us(1);
						time_cnt--;
						if(time_cnt==0)
							return;
					}
					time_cnt=20;
					while(IRED)//等待数据1或0后面的高电平结束,若超过2ms强制退出
					{
						delay_10us(10);
						Ired_high_time++;
						if(Ired_high_time>20)//当大于2000us(2ms)也就是超过逻辑1的1690us,我们就说这信号是错误的
							return;//正常情况是不会进入return的
					}
					gired_data[i]>>=1;//右移//先读取的为低位,然后是高位
					if(Ired_high_time>=8)//选一个中间数,大于800us就是高电平,小于800us就是低电平
						gired_data[i]|=0x80;
					Ired_high_time=0;//清零
				}
			}
			
			
		}
		if(gired_data[2]!=~gired_data[3])//如果控制码不等于控制反码,就说接收的数据是错误的就不要这4字节数据
		{
			for(i=0;i<4;i++)
				gired_data[i]=0;//清零
			return;
		}
	}
}

lred.h

# ifndef _lred_H
# define _lred_H

# include "reg52.h"
typedef unsigned char u8;
typedef unsigned int u16;
sbit IRED=P3^2;
extern u8 gired_data[4];

void delay_10us(u16 ten_us);
void ired_init();
# endif

实验现象

红外遥控LED亮灭和蜂鸣器

main.c

/**************************************************************************************
实验名称:红外遥控LED和蜂鸣器
接线说明:	
实验现象:下载程序后,操作红外遥控器最上面第1个键和第2个键可控制D1和蜂鸣器开关,并且数码管
			上显示遥控器键值码
注意事项:红外接收头凸起处要与PCB板接口凸起丝印处对应																				  
***************************************************************************************/
# include "public.h"
# include "smg.h"
# include "ired.h"
# include "beep.h"

//定义端口
sbit LED1=P2^0;

//定义宏值
# define LED_KEY_VALUE		0X45
# define BEEP_KEY_VALUE		0X46

/*******************************************************************************
* 函 数 名       : main
* 函数功能		 : 主函数
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void main()
{	
	u8 ired_buf[3];
	u8 key_cnt_led=0;
	u8 key_cnt_beep=0;
	u8 ired_tempx=0;
	u8 ired_tempy=0;

	ired_init();//红外初始化
	
	while(1)
	{
		ired_tempx=gired_data[2];//保存键值
		if(ired_tempx!=0)ired_tempy=ired_tempx;//当不为0时则更新数码管显示键值
		ired_buf[0]=gsmg_code[ired_tempy/16];//将控制码高4位转换为数码管段码
		ired_buf[1]=gsmg_code[ired_tempy%16];//将控制码低4位转换为数码管段码
		ired_buf[2]=0X76;//显示H的段码
		smg_display(ired_buf,6);

		if(ired_tempx==LED_KEY_VALUE)//如果是第一键按下
		{
			gired_data[2]=0;//清零,等待下次按键按下
			key_cnt_led++;
			if(key_cnt_led==3)key_cnt_led=1;
		}
		else if(ired_tempx==BEEP_KEY_VALUE)//如果是第二键按下
		{
			gired_data[2]=0;//清零,等待下次按键按下
			key_cnt_beep++;
			if(key_cnt_beep==3)key_cnt_beep=1;
		}

		if(key_cnt_led==1)LED1=0;
		else LED1=1;
		if(key_cnt_beep==1)beep_alarm(100,10);
	}
}

beep.h

# include "beep.h"

/*******************************************************************************
* 函 数 名       : beep_alarm
* 函数功能		 : 蜂鸣器报警函数
* 输    入       : time:报警持续时间
				   fre:报警频率
* 输    出    	 : 无
*******************************************************************************/
void beep_alarm(u16 time,u16 fre)
{
	while(time--)
	{
		BEEP=!BEEP;
		delay_10us(fre);	
	}		
}

beep.h

# ifndef _beep_H
# define _beep_H

# include "public.h"

//管脚定义
sbit BEEP=P2^5;

//函数声明
void beep_alarm(u16 time,u16 fre);
# endif

ired.c(红外)

# include "ired.h"

u8 gired_data[4];//存储4个字节接收码(地址码+地址反码+控制码+控制反码)

/*******************************************************************************
* 函 数 名         : ired_init
* 函数功能		   : 红外端口初始化函数,外部中断0配置 
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
void ired_init(void)
{
	IT0=1;	//下降沿触发
	EX0=1;	//打开中断0允许
	EA=1;	//打开总中断
	IRED=1;	//初始化端口
}

void ired() interrupt 0	//外部中断0服务函数
{
	u8 ired_high_time=0;
	u16 time_cnt=0;
	u8 i=0,j=0;

	if(IRED==0)
	{
		time_cnt=1000;
		while((!IRED)&&(time_cnt))//等待引导信号9ms低电平结束,若超过10ms强制退出
		{
			delay_10us(1);//延时约10us
			time_cnt--;
			if(time_cnt==0)return;		
		}
		if(IRED)//引导信号9ms低电平已过,进入4.5ms高电平
		{
			time_cnt=500;
			while(IRED&&time_cnt)//等待引导信号4.5ms高电平结束,若超过5ms强制退出
			{
				delay_10us(1);
				time_cnt--;
				if(time_cnt==0)return;	
			}
			for(i=0;i<4;i++)//循环4次,读取4个字节数据
			{
				for(j=0;j<8;j++)//循环8次读取每位数据即一个字节
				{
					time_cnt=600;
					while((IRED==0)&&time_cnt)//等待数据1或0前面的0.56ms结束,若超过6ms强制退出
					{
						delay_10us(1);
						time_cnt--;
						if(time_cnt==0)return;	
					}
					time_cnt=20;
					while(IRED)//等待数据1或0后面的高电平结束,若超过2ms强制退出
					{
						delay_10us(10);//约0.1ms
						ired_high_time++;
						if(ired_high_time>20)return;	
					}
					gired_data[i]>>=1;//先读取的为低位,然后是高位
					if(ired_high_time>=8)//如果高电平时间大于0.8ms,数据则为1,否则为0
						gired_data[i]|=0x80;
					ired_high_time=0;//重新清零,等待下一次计算时间
				}
			}
		}
		if(gired_data[2]!=~gired_data[3])//校验控制码与反码,错误则返回
		{
			for(i=0;i<4;i++)
				gired_data[i]=0;
			return;	
		}
	}		
}

ired.h

# ifndef _ired_H
# define _ired_H

# include "public.h"

//管脚定义
sbit IRED=P3^2;

//声明变量
extern u8 gired_data[4];


//函数声明
void ired_init(void);

# endif

smg.c

# include "smg.h"

//共阴极数码管显示0~F的段码数据
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
				0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

/*******************************************************************************
* 函 数 名       : smg_display
* 函数功能		 : 动态数码管显示
* 输    入       : dat:要显示的数据
				   pos:从左开始第几个位置开始显示,范围1-8
* 输    出    	 : 无
*******************************************************************************/
void smg_display(u8 dat[],u8 pos)
{
	u8 i=0;
	u8 pos_temp=pos-1;

	for(i=pos_temp;i<8;i++)
	{
	   	switch(i)//位选
		{
			case 0: LSC=1;LSB=1;LSA=1;break;
			case 1: LSC=1;LSB=1;LSA=0;break;
			case 2: LSC=1;LSB=0;LSA=1;break;
			case 3: LSC=1;LSB=0;LSA=0;break;
			case 4: LSC=0;LSB=1;LSA=1;break;
			case 5: LSC=0;LSB=1;LSA=0;break;
			case 6: LSC=0;LSB=0;LSA=1;break;
			case 7: LSC=0;LSB=0;LSA=0;break;
		}
		SMG_A_DP_PORT=dat[i-pos_temp];//传送段选数据
		delay_10us(100);//延时一段时间,等待显示稳定
		SMG_A_DP_PORT=0x00;//消音
	}
}

smg.h

# ifndef _smg_H
# define _smg_H

# include "public.h"


# define SMG_A_DP_PORT	P0	//使用宏定义数码管段码口

//定义数码管位选信号控制脚
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;

extern u8 gsmg_code[17];

void smg_display(u8 dat[],u8 pos);

# endif

public.c

# include "public.h"

/*******************************************************************************
* 函 数 名       : delay_10us
* 函数功能		 : 延时函数,ten_us=1时,大约延时10us
* 输    入       : ten_us
* 输    出    	 : 无
*******************************************************************************/
void delay_10us(u16 ten_us)
{
	while(ten_us--);	
}

/*******************************************************************************
* 函 数 名       : delay_ms
* 函数功能		 : ms延时函数,ms=1时,大约延时1ms
* 输    入       : ms:ms延时时间
* 输    出    	 : 无
*******************************************************************************/
void delay_ms(u16 ms)
{
	u16 i,j;
	for(i=ms;i>0;i--)
		for(j=110;j>0;j--);
}

public.h

# ifndef _public_H
# define _public_H

# include "reg52.h"

typedef unsigned int u16;	//对系统默认数据类型进行重定义
typedef unsigned char u8;


void delay_10us(u16 ten_us);
void delay_ms(u16 ms);

# endif