目录

红外遥控简介

红外发射二极管

红外接收二极管

红外遥控原理

红外发射二极管原理

红外接收二极管原理 

基本的发送与接收

NEC编码

基本原理

DATA数据的格式

DATA中的1与0的表示

遥控器的键码值

按键波形案例

仿真案例

前言

keil文件

红外遥控简介

  • 红外遥控是利用红外光进行通信的设备,由红外LED将调制后的信号发出,由专用的红外接收头进行解调输出
  • 通信方式:单工、异步
  • 红外LED波长:940nm
  • 通信协议标准:NEC标准

红外发射二极管

注意:红外发射二极管和一般的LED灯很相似,但是红外发射二极管用来发射红外光,红外光是不可见光,人眼几乎看不到,虽然我们看不到现象,但他也在发射红外光。

红外接收二极管

注意:

  • 红外接收二极管的颜色一般是黑色的,它用于接收红外发射二极管发射的光线,不接受红外线时,红外接受二极管反向导通阻抗无穷大,接收红外线时,红外接收二极管反向导通阻抗无穷小。
  • 这种三脚的红外接收二极管电路如右,当他没有接收到红外信号时,他的out引脚输出的是高电平,而只有当他收到38KHz的高频脉冲红外信号时他才会输出低电平,他就能把38KHz的脉冲信号转变为静态的低电平信号,否则为高电平信号。

红外遥控原理

红外发射二极管原理

理解:

  • 由电路图观之,只有当两个三极管同时打开时,那么红外发射二极管才会发光。
  • 第一个三极管左端的连线一直发送38KHz的方波
  • 第二个三极管左端的连线发送的就是我们的真正波形
  • 这样的设计导致的结果:当in端口为高电平时,灯不亮,当in端口为低电平时,灯以38KHz的频率闪着亮(38Khz的端口始终输出)
  • 因为自然界中没有以38KHz频率闪着亮的红外光,所以传递用38KHz的频率红外光,最终接收头用滤波器把38KHz的波形提出来,把38KHz滤掉,之后进行放大就能够在自然界连续不断地很强的红外光中传递38KHz的通讯信号

红外接收二极管原理 

前言:红外发射二极管通常用于红外遥控,若要遥控设备,则还需要对应的红外接收二极管。

原理:红外接收二极管在电路中需要给他反向供电,当红外发射二极管开关断开时,这个二极管是截止状态,此时二极管阻抗无穷大,所以右侧点位电压输出的接近5V;而当开关闭合之后,该红外接收二极管被红光照射时他的阻抗很小,所以此时输出的电压接近0V;把该引脚连接到单片机声,我们就可以通过红外光是否发射来通过单片机控制外部设备了。

注意:

  • 这里的右侧引脚连接的是单片机中外部中断,外部中断下降沿有效。
  • 接收部分主要用于将红外接收部分输出的波形进行接收并解码,然后读取里面的键码值
  • 我们使用的是可以解调38KHz载波的接收二极管,具体原理请见红外接收二极管注意事项2

基本的发送与接收

  • 空闲状态:红外LED不亮,接收头输出高电平
  • 发送低电平:红外LED以38KHz的频率闪烁发光,接收头输出低电平
  • 发送高电平:红外LED不亮,接收头输出高电平

理解:给红外发射二极管输送含有38KHz载波的波形信号(含有38KHz载波的波形地方为低电平,而整个波形里面封装了地址码与命令码)那么红外发射的二极管就会在输入波形为低电平的时刻以38KHz的频率一直闪烁,这样就能表示将要发的数据,当红外接收二极管接收到这个闪烁的红外光时,就会将对应的闪烁信号转化为波(接收到红光时波形显示为波的低电平,不接受红外光时展示波形显示为波的高电平),并把38KHz的载波信号进行解码,高电平的地方还为高电平,将含有38KHz载波的波形转化为低电平,进而得到完整的波形,然后将该波形传输到单片机中作为控制信号。

NEC编码

基本原理

前言:38KHz的载波频率只是针对于底层通信,而NEC编码中并不会出现38KHz的调制(底层做好了基本的发射高低电平,然后把他封装在一个模块中)

理解:上面的波形就是我们的遥控器按键按下之后接收头发射器out引脚输出的波形,首先没有按键按下那么波形就是前面蓝色的高电平代表空闲状态,一旦有按键按下,那么out就会输出一个start信号,这个start信号由9ms的低电平和4.5ms的高电平组成.主要用于告诉单片机这些接收设备有按键过来了进而做出准备;start信号发送完之后就会有一长段的数据区(就表示我们遥控器的地址码和控制码波形部分)数据区发送格式见下,数据发送完之后需要在跟一个下降沿(该下降沿560us后上升,其作用为终止最后一位)升到高电平后一帧数据就发完了,总共110ms;

注意:

  • 发送完数据帧之后,红外接收器采集到out信号发送来的波形,然后对他的时间长度进行分析就可以得到我们的地址码以及命令码。
  • 当我们的红外遥控按键按下之后那么他就会先发送一个带有数据的波形,若不放手,就一直按,那么他就会每隔110ms发送一次repeat(见图)就相当于连续按键功能
  • repeat信号和start信号差不多,它是由9ms的低电平和2.25ms的高电平组成(其也会跟终止信号也就是1)

DATA数据的格式

注意:

  • data格式中的data总共4个字节,每个字节8位,4个字节一共32bit
  • 数据中的每个字节都是低位在前,高位在后
  • 地址码就是遥控器的一个标识符,防止不同品牌的遥控器/电器相互作用;命令码就是我们的键码,代表我们按下了哪个按键
  • 地址反码与命令反码主要用于确定接受的数据是否正确,用于校检。
  • 通过命令码是8位我们就可以得出,红外遥控按键的个数最多为256个

DATA中的1与0的表示

前言:NEC的data并不像我们传统模式高电平表示1,低电平表示0.

  • 0:先是低电平560us,然后高电平560us
  • 1:先是低电平560us,然后高电平1690us

遥控器的键码值

理解:按按键后就会使红外发射器按一定的频率闪烁,进而等待红外接收器的接收。

按键波形案例

前言:通过红外发射器按键2后发射红外光,发射后通过红外接收器接收调制后得到波形。

理解:

  • key2就是按下遥控器mode后所发出来的波形,通过这里我们可以更完整的看到数据的结构,并且也可以看到在数据发送完之后以及repeat之后都会接一个1至高电平,代表终止最后一位。
  • 因为是低位在前,高位在后,所以读键码时应该反过来读,例如命令码为:01000110(46)

仿真案例

需求:通过红外遥控的按键控制单片机将自己按键的地址码和命令码显示在LCD1602液晶屏上,用音量+/-键控制Num+/-,并将Num也显示在LCD上(按键长按多次生效)

前言

  • 笔记上面的延时时间判定是晶振频率为12MHz下的延时时间,我的普中单片机的晶振是11.0592MHz的,所以延时大小可能有些不同
  • 我的LCD1602中RS接的是P2.6,RW接的是P2.5,e接的是P2.7,其中数据接口用的是P0口
  • 我的红外接收器中的Vout引脚接到了外部中断0(P3.1)的引脚(起初外部中断0为高电平),并且他是反向供电的,具体接法请看红外接收二极管

keil文件

#include "regx52.h"
//LCD1602模块——————————————————————————————————————————————————————————
sbit RS=P2^6;
sbit RW=P2^5;
sbit E=P2^7;
//延时n毫秒
void delay(unsigned int n)
{
	while(n--){
	unsigned char i, j;
	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
	}
}
//写指令
void writecom(unsigned char com){
	RS=0;
	RW=0;
	E=0;
	P0=com;
	delay(5);
	E=1;
	E=0;
}
//写数据
void writedat(unsigned char dat){
	RS=1;
	RW=0;
	E=0;
	P0=dat;
	delay(5);
	E=1;
	E=0;
}
//初始化液晶屏
void initlcd(){
	writecom(0x38);
	writecom(0x0c);
	writecom(0x06);
	writecom(0x01);
}
int LCD_Pow(int X,int Y){
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++){
		Result*=X;
	}
	return Result;
}
//展示16进制数字
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length){
	unsigned char i;
	unsigned char SingleNumber;
	if(Line==1){
		writecom(0x80|(Column-1));
	}else{
		writecom(0x80|(Column-1)+0x40);
	}
	for(i=Length;i>0;i--){
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10){
			writedat('0'+SingleNumber);
		}else{
			writedat('A'+SingleNumber-10);
		}
	}
}
//外部中断与定时器模块—————————————————————————————————————————————————
//外部中断初始化
void Int0_Init(){
	IT0=1; //下降沿有效
	IE0=0; //中断请求标志位
	EX0=1; //外部中断0允许
	EA=1; //开启总中断
	PX0=1; //中断优先级高
}
//定时器0初始化
void Timer0_Init(){
	TMOD &= 0xF0; //将定时器0清零
	TMOD |= 0x01; //设置为定时器0,工作方式16位
	TL0=0; 
	TH0=0;
	TF0=0; //定时器请求中断标志
	TR0=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;
}
//红外模块—————————————————————————————————————————————————————————————
unsigned int IR_Time; //获得到时序中对应的时间
unsigned char IR_State; //NEC中时序的状态
unsigned char IR_Data[4]; //获取按键的Data数据容器
unsigned char IR_pData; //IR_Data的下标
unsigned char IR_DataFlag;
unsigned char IR_RepeatFlag;
unsigned char IR_Address;
unsigned char IR_Command;
unsigned char Address;
unsigned char Command;
unsigned char Num;
void IR_Init(){
	Timer0_Init();
	Int0_Init();
}
//获得数据标志
unsigned char IR_GetDataFlag(){
	if(IR_DataFlag==1){
		IR_DataFlag=0;
		return 1;
	}
	return 0;
}
//获得重发标志
unsigned char IR_GetRepeatFlag(){
	if(IR_RepeatFlag==1){
		IR_RepeatFlag=0;
		return 1;
	}
	return 0;
}
unsigned char IR_GetAddress(){
	return IR_Address;
}
unsigned char IR_GetCommand(){
	return IR_Command;
}
//main函数模块—————————————————————————————————————————————————————————
void main(){
	initlcd();
	IR_Init();
	while(1){
		//若接收到数据标志或重发标志,那么都可以获得之前的数据
		if(IR_GetDataFlag() || IR_GetRepeatFlag()){
			Address=IR_GetAddress();
			Command=IR_GetCommand();
			LCD_ShowHexNum(2,1,Address,2); //将地址码展示在LCD屏幕的2行1列
			LCD_ShowHexNum(2,5,Command,2); //将命令码展示在LCD屏幕的2行5列
			if(Command==0x15){
				//若按键为0x15(音量-键),那么Num数字-1
				Num--;
			}
			if(Command==0x09){
				//若按键为0x09(音量+键),那么Num数字+1
				Num++;
			}
			LCD_ShowHexNum(2,10,Num,2); //将Num变量展示在LCD屏幕的2行10列
		}
	}
}
//外部中断触发函数—————————————————————————————————————————————————————
void Int0_Routine() interrupt 0
{
	if(IR_State==0){
		//首次下降沿开始计数(时间)
		Timer0_SetCounter(0);
		Timer0_Run(1);
		IR_State=1;
	}else if(IR_State==1){
		IR_Time=Timer0_GetCounter();
		Timer0_SetCounter(0);
		//如果计时为13.5ms,则接收到了Start信号(判定值在12MHz晶振下为13500,在11.0592MHz晶振下为12442)
		if(IR_Time>(12442-500) && IR_Time<(12442+500)){
			IR_State=2;
		}
		//如果计时为11.25ms,则接收到了Repeat信号(判定值在12MHz晶振下为11250,在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);
		//如果计时为1120us,则接收到了数据0(判定值在12MHz晶振下为1120,在11.0592MHz晶振下为1032)
		if(IR_Time>(1032-500) && IR_Time<(1032+500)){
			IR_Data[IR_pData/8] &= ~(0x01<<(IR_pData%8));
			IR_pData++;
		}
		//如果计时为2250us,则接收到了数据1(判定值在12MHz晶振下为2250,在11.0592MHz晶振下为2074)
		else if(IR_Time>(2074-500) && IR_Time<(2074+500)){
			IR_Data[IR_pData/8] |= (0x01<<(IR_pData%8));
			IR_pData++;
		}else{
			//获取数据失败,那么重新接收
			IR_pData=0;
			IR_State=1;
		}
		if(IR_pData>=32){
			//数据接收完成
			IR_pData=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;
		}
	}
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐