当前位置: 代码迷 >> 单片机 >> 51单片机学习札记:红外接收(对原子哥的ALIENTEK遥控器进行解码)
  详细解决方案

51单片机学习札记:红外接收(对原子哥的ALIENTEK遥控器进行解码)

热度:89   发布时间:2016-04-28 15:30:13.0
51单片机学习笔记:红外接收(对原子哥的ALIENTEK遥控器进行解码)

正点原子的ALIENTEK遥控器

用户码00ff

16进制键值码表
45??? 46??? 47
44??? 40??? 43
07??? 15??? 09
16??? 19??? 0d
0c??? 18??? 5e
08??? 1c???? 5a
42????????? 4a

?

?

红外接收管? 通用型即可

?


?---------------------------------------

下面是类似的波形图,这个是网上找的,图片较大,缩小后看上去有些模糊,不过没关系,数据手册上一般都有



?

遥控器在发射红外信号之前,我们的mcu已经开启了定时器
在定时器中断函数中的全局变量irTimeCounts++ 一直在自加
irTimeCounts多长时间加一次? 或者说多长时间进入一次定时器中断函数呢?
在方式2时,t=256*12/11059200 约为277.78us
============================================================
1.对于1.125ms的时间,timer0会中断1.125/t=4.05次 也就是要5次,算上各种误差,(顶多5次,至少4次)
2.对于2.25ms 的时间,timer0会中断 2.25/t=8.1次, 也就是要9次,算上各种误差,(顶多9次,至少8次)
? 对于这里的次数,比N次小一点,就是N次,比N次大一点,就是N+1次,因为不会有半次,
? 误差给它正负0.4次足够了,给了误差后再算次数,直接舍入就行

那么我们就检测irTimeCounts的值,
如果小于6或7,那么接收到的数据为0
如果大于6或7,那么接收到的数据为1

从计算结果来看,我觉得0.56,1.125,2.25这些值的出现是比较合理的,
能有效避免由于器件误差和环境造成的数据误判,而且计算也方便

?

#include "my51.h"#include "ir.h"#include "smg.h"void main(){	timer0Init();	  //定时器0初始化	int0Init();		  //外部中断0初始化		while(1)	{		if(irTimeCountsArrProcess()) //如果成功接收并解析完成一帧数据		{							 //就让蜂鸣器响一下			beep=0;					 //蜂鸣器开启			led4=~led4;				 //4号灯反转一下		}										 		displaySMG(irCodeByteDataProcessForSmg());//显示		beep=1;			   			 			  //蜂鸣器关闭		//由于displaySMG()函数执行时间较短,故蜂鸣器响声时间也较短,听到滴了一下	}}

?

#ifndef _51IR_H_#define _51IR_H_#include "my51.h"extern u8 data smgWela[7];	  			 //数码管显示的数据extern void int0Init();	   	  			 //外部中断0初始化extern void timer0Init();	  			 //定时器0初始化extern bool irTimeCountsArrProcess();    //成功解析一帧中断数据返回TRUEextern u8* irCodeByteDataProcessForSmg();//将遥控器码值处理成数码管可显示数据#endif

?

#include "ir.h"u8 irTimeCounts=0;	  		  //定时器0在方式2下8位自动重装时的中断计数值u8 irTimeCountsArr[32]={0};	  //存放红外接收数据时的中断次数记录值,u8 bitNum=0;		  		  //标志当前接收的是第几个比特位u8 irReceFlag=0;	  		  //红外接收一帧数据未完成标志,为1时完成u8 irCodeByteData[4]={0}; 	  //保存接收到的4个字节的有效数据u8 irTimeCountsArrProcessOk=0;//对接收到的33位数据处理未完成标志,1完成void int0Init();	   			  //外部中断0初始化void timer0Init();	   			  //定时器0初始化bool irTimeCountsArrProcess();    //解析中断次数,即解码u8* irCodeByteDataProcessForSmg();//将遥控器码值处理成数码管可显示数据u8* irCodeByteDataProcessForSmg() //将解码的4字节数据处理成数码管可显示的数据{		if(irTimeCountsArrProcessOk)  //检测一帧数据是否解析完成	{		//这里的用户码只显示低八位,因为高八位反正都是00(手上2个遥控器都是00)		//然后还显示遥控键值及其反码,我们的数码管只有6位,只显示3字节数据		if(irCodeByteData[2]+irCodeByteData[3]!=0xff)//校验数据的完整性		{									 //最后一个字节是键码的反码			led6=0;	  //调试代码		}		else		{			smgWela[0]=irCodeByteData[1] >> 4 ;	   //取高4位			smgWela[1]=irCodeByteData[1] & 0x0f;   //取低4位			smgWela[2]=irCodeByteData[2] >> 4 ;			smgWela[3]=irCodeByteData[2] & 0x0f;			smgWela[4]=irCodeByteData[3] >> 4 ;			smgWela[5]=irCodeByteData[3] & 0x0f;		    smgWela[6]=0xff;	//小数点全不显			}		irTimeCountsArrProcessOk=0;//标志清零,下一次有未解析的数据时才会再解析	}	return 	 smgWela;}bool irTimeCountsArrProcess()	//对接收到的32位的中断次数数据进行解析{	u8 i,j,k,value=0;	if(irReceFlag)			   //检测是否已经接收到新的4字节的红外通信数据	{		for(j=0;j<4;j++)   		 //有4个字节		{			for(i=0;i<8;i++)	 //对每个字节的8位数据处理			{				value>>=1;				if(irTimeCountsArr[k++]>6)	 //这里我们用6或7都是可以的				{					 value|=0x80;			 //大于6的话该位数据是1				}			}			irCodeByteData[j]=value;//保存该字节,也就是遥控器的键码			}		irReceFlag=0;	//接收标志清零,这样就会等到下次收到数据后才会再解析		irTimeCountsArrProcessOk=1; //中断数据解析完毕标志置1		return TRUE;		  	    //解析完成	}	return FALSE;	//未进行解析,该返回值主要是为了方便外部文件调用时判断的	}void int0() interrupt 0//外部中断0{	     if(irTimeCounts>30) //9ms的话中断32.4次,30这个取值差不多就可以了,不用太精确   {	//这里9ms引导码需要timer0中断irTimeCounts=9*11059.2/(256*12)=32.4次    	bitNum =0;		irTimeCounts=0;//为接收第0位数据做准备		return;		   //丢弃引导码,反正不是有效数据0或1的都丢弃,直接返回  		   }				    irTimeCountsArr[bitNum]=irTimeCounts; //将中断次数数据存储起来   irTimeCounts=0;     //存好了就立即清零,这样不会影响下一位数据的接收   bitNum++;	       //继续下一位   if(32==bitNum)      //32位数据已经接收完成(0~31已经存储)   {   		bitNum=0;  	   //清零,这里不清也可以,反正引导时也会清		irReceFlag=1; //接收完成标志   }				  	}void timer0() interrupt 1  //定时器0中断函数{	 irTimeCounts++;	   //注:该值最大为255}void timer0Init()	//定时器0初始化{	//配置工作方式寄存器,且不影响定时器1的状态	TMOD &= 0xf0;   //保留定时器1的配置,并清除定时器0的配置	TMOD |= 0X02;   //使用定时器0的工作方式2	TH0=0X00;	TL0=0X00; 		//工作方式2是8位自动重装	ET0=1;	  		//打开定时器	EA=1;	  		//打开总中断	TR0=1;	  		//启动定时器0}void int0Init()	    //外部中断0初始化{	IT0=1; 			//配置外部中断0的触发方式为 跳变延触发	EX0=1; 			//打开外部中断0	EA=1;  			//打开总中断	}

?

#ifndef _51SMG_H_#define _51SMG_H_#include "my51.h"sbit dula =P2^6;  			//段选锁存器控制  控制笔段sbit wela =P2^7;  			//位选锁存器控制  控制位置extern u8 data smgWela[7];  //第一位到第六位,最后一个是小数点位置控制#define dark	0x11//0x11是第17号元素,0x00是低电平,数码管不亮,即table[17]#define dotDark 0xff		//小数点全暗void displaySMG(u8* pWela); //数码管显示函数,参数是数组指针#endif

?

#include "smg.h"#include "my51.h"static u8 code table[]= { 		//0~F外加小数点和空输出的数码管编码	0x3f , 0x06 , 0x5b , 0x4f , // 0 1 2 3	0x66 , 0x6d , 0x7d , 0x07 , // 4 5 6 7	0x7f , 0x6f , 0x77 , 0x7c , // 8 9 A B	0x39 , 0x5e , 0x79 , 0x71 , // C D E F	0x80 , 0x00 , 0x40          // . 暗 负号    暗即不显示是第17索引号 };								//负号为第18索引号元素/*  由于此表只能一次显示一个小数点,故已注释掉,仅供查询	例如想要第一个和第六个数码管小数点同时点亮,	则执行 pWela->dot = 0xfe & 0xdf  即可	u8 code dotTable[]={   //小数点位置,某一位置0时,小数点亮	0xff ,                 //那么全暗就是0xff	0xfe , 0xfd , 0xfb ,   //1 2 3	0xf7 , 0xef , 0xdf     //4 5 6                    };*/u8 data smgWela[7]={0,0,0,0,0,0,0}; //第一位到第六位,最后一个是小数点位置控制//P0口的数码管位选控制锁存器只用了低6位,我们保留高2位的数据,留作它用void displaySMG(u8* pWela){	u8 i=0;	    //控制6位数码管显示函数,不显示的位用参数dark    u8 preState=P0|0x3f;  //保存高2位状态,其中最高位是ADC0804的片选信号	wela=0;dula=0;_nop_();//先锁定数据,防止吴亮及位选锁存器高2位数据被改变			P0=0;  		 	      //由于数码管是共阴极的,阳极送低电平,灯不亮    dula=1;_nop_();    dula=0;	  	 	      //段选数据清空并锁定    P0=preState;  		  //共阴极数码管是阴极置高不亮,低6位置1,高2位保留	    wela=1;_nop_();		  //注:wela和dula上电默认为1		     wela=0;		  		  //位选锁定,初始保留高2位的数据,低6位置高不亮	for(i=0;i<6;i++)	  //显示6位数码管	{		P0=table[pWela[i]]|(((1<<i) & pWela[6])?0x00:0x80);	    dula=1;_nop_();	     //送段数据,叠加小数点的显示,0x00点亮小数点	    dula=0;	    	   	P0=preState&~(1<<i); //不影响高2位数据,低6位是数码管位选,低电平有效	    wela=1;	_nop_();	 //送位选号	    wela=0;		    delayms(1);			 //稍作延时,让灯管亮起来					{  //消除叠影及误亮,阴极置1不亮,低6位置1,高2位保留并锁定	        P0=preState;	        wela=1;	_nop_();				        wela=0;		    }	}}

?