NEC红外传输协议详解(含代码)

NEC红外传输协议详解(含代码)

注:这个代码是c语言编到keil的,51核的芯片,下面的所有波形图都是用逻辑分析器测量的。

首先写NEC接收的部分:

红外接受管有3脚,一个接地一个接VCC一个信号输出脚,输入端并电容(我用的104)滤波,VCC串了个电阻分压。接收管的输出脚在没接收到信号的时候输出的是高电平

在红外发送器发送信号时,接收头会接收到信号,首先,接收到的该信号有一个头码(引导码):9ms的低电平
首先接收到9ms的低电平,
4.5ms的高电平
然后是4.5ms的高电平,

到这里程序就应该进入收码阶段了,接收到的码值是32位的,具体分布如下:
含义
其中,1、引导码;2、用户码;3、用户反码;4、数据码;5、数据反码;6、结束码

前面讲的9ms低电平和4.5ms高电平就是引导码的阶段,在2~5阶段中,每个阶段收到8位数据,一共是32位数据,建议还是4个8位寄存器保存(因为2和3的值是取反的,4和5的值是取反的,所以4个寄存器可以拿来比较),不过我的程序是32位保存的。好的现在我们看一下0和1这两个数据的特征。

0
↑这是0的值,协议里是560us的低电平,然后再560us的高电平,周期是1.20ms;

![1](https://img-blog.csdnimg.cn/20200724152733582.png

↑这是1的值,协议里是560us的低电平,然后再1680us的高电平,周期是2.24ms;

结束码

最后就是0.56ms的低电平结束码了。这就是接受到的第一串数据。贴的这串数据是01FE48B7H。

上面那一串应该就能理解了吧~,然后继续,如果信号发送器发送的按键信息“长按”了,之后接受到的就是重复码了:
1
重复码的规则是这样的,从第一次接受到信号开始计时,到每一次接收重复码,间隔时间是110ms:
在这里插入图片描述
↑也就是这三个间隔都为110ms。

然后,重复码是这样的:
在这里插入图片描述
首先接收到的是9ms的低电平,然后是2.25ms的高电平,然后是560us的低电平。

ok波形讲完了,那么代码中要注意到的有哪些呢?

贴代码:

unsigned char sum,sum0,sum1,b1,b2,b3,c1,c2,c3,d,j;
unsigned long number,number_out;
void main()
{
/************************************系统初始化****************************************/
	CLKSWR = 0x51;						//选择内部高频RC为系统时钟,内部高频RC 2分频,Fosc=16MHz
	CLKDIV = 0x01;						//Fosc 1分频得到Fcpu,Fcpu=16MHz 
/**********************************相关配置初始化**************************************/
	P1M6 = 0xC1;
	P1M5 = 0xC1;
	P1M4 = 0xC1;
	P1M7 = 0x61;						//P17非斯密特带上拉输入;
	
/**********************************TIM0配置初始化**************************************/
	TCON1 = 0x00;						//Tx0定时器时钟为Fosc
	TMOD = 0x00;						//16位重装载定时器/计数器

	//Tim0计算时间 	= (65536 - 0xFFBD) * (1 / (Fosc /Timer分频系数))
	//				= 67 / (16000000 / 12)
	//				= 50us

	//定时1ms
	//反推初值 	= 65536 - ((50/10000000) / (1/(Fosc / Timer分频系数)))
	//		   	= 65536 - ((50/10000000) / (1/(16000000 / 12)))
	//			= 65536 - 67
	//			= 0xFFBD
	
	TH0 = 0xFF;
	TL0 = 0xBD;							//T0定时时间50us
	IE |= 0x02;							//打开T0中断
	TCON = 0x10;						//使能T0
    
	EA = 1;								//打开总中断
	
	sum = sum0 = sum1 = b1 = b2 = b3 = c1 = c2 = c3 = d = j = 0;	//标志位初始化
	
	while(1)
	{
		if(b2 == 0)	//判断头码是否正确,正确b2置1
		{
			if(P1_7 == 1)
			{
				c1 = 0;
				if((160 <= sum0)&&(sum0 <= 200))	b1 = 1,sum1 = 0;	//头码9ms低电平
				sum0 = 0;
			}
			if(P1_7 == 0)
			{
				if(c1 == 0)	sum0 = 0,c1 = 1;
				if((70 <= sum1)&&(sum1 <= 110)&&(b1 == 1))	b1 = 0,b2 = 1,sum1 = 0;	//头码4.5ms高电平
				if((34 <= sum1)&&(sum1 <= 54)&&(b1 == 1))	b1 = 0,b2 = 0,b3 = 1,sum1 = 0;
			}
		}
		if(b2 == 1)
		{
			if(P1_7 == 0)
			{
				if(j <= 31)
				{
					if((18 <= sum)&&(sum <= 27))	d = 0,c3 = 1,j++;
					else if ((40 <= sum)&&(sum <= 50))	d = 1,c3 = 1,j++;
				}
			}
			if(c3 == 1)
			{
				if(d == 0)	number |= 0x00;
				else if(d == 1)	number |= 0x01;
				if(j <= 31)	number = number<<1;
				c3 = 0;
				c2 = 0;
				sum = 0;
			}
			if(j == 32)		//串码判断结束
			{
				b1 = b2 = 0;
				c1 = c2 = c3 = 0;
				sum = sum0 = sum1 = 0;
				j = 0;
				
				switch(number)
				{
					case 0x01FE48B7: number_out = 1;break;	//判断按键按下
					case 0X01FE58A7: number_out = 2;break;
					case 0x01FE7887: number_out = 3;break;
					default:number_out = 0;break;
				}
				number = 0;
			}
		}
		else if((b3 == 1)&&(P1_7 == 1))	//检测到按键重复的编码
		{
			c1 = 0;
			if((10 <= sum0)&&(sum0 <= 17))	P1_4 = 1;	//上次按键长按了,灯3亮
			sum0 = 0;
		}

		if(number_out == 1)	P1_6 = 1;	//按键1被按下,灯1亮
		if(number_out == 2)	P1_5 = 1;	//按键2被按下,灯2亮
		if(number_out == 3)	P1_4 = P1_5 = P1_6 = 0;	//按键3被按下,三个灯灭
		
	}
}
void TIMER0_Rpt(void) interrupt 1	//T0中断,每50us一次
{
	if(P1_7 == 1)	sum1++;
	else	sum0++;
	if(b2 == 1) sum++;
}

代码不是很精简,用的标志位也很多,是因为这个代码我没有用while,需要大家注意的是,我们必须在接受的信号为高/低时去判断上次低/高电平的持续时间,如果持续时间不符合要求,要把sumx置0,其他也没什么要注意的了。

然后写NEC发送的部分:

在这里插入图片描述
大家可以明显看到发送的信号中:原本应该是持续高电平的地方变成了“脉冲”。

先说一下,红外发送器在未发送的时候是低电平,并且发送出去的信号与上面接收进来的信号高低电平是相反的,就是说如果发送出去是低电平,接收器接收到的是高电平。那么每个阶段的间隔时间应该不用我多说了吧。

但是有一点是必须要注意的:当发送器发送高电平时,实际上发送的是38khz的载波,也就是周期约为26.31us(我程序里是26.25us),占空比为1/3(或1/4,有时候可以2/3)的波:
在这里插入图片描述
红框框的一整个才是一个“0”的信号,大家可以看到,发送载波的时候一个周期(26.25us)内高电平的时间约为8us。

但是我的定时器周期为26.25us,我要如何在26.25us中让信号出8us的高电平,然后再变为低电平呢?
用中断,详细可以看一下程序的定时器周期部分:

#define	ALLOCATE_EXTERN
#include "HC89F303B.h"

void Delay_us(unsigned int fui_i);

unsigned int sum,sum0,sum1,sum3;
unsigned char b1,b2,b3,c1,c2,c3,c4,c5,c6,c7,c8,d,e,j;
unsigned long number,number_out;
void main()
{
/************************************系统初始化****************************************/
	CLKSWR = 0x51;						//选择内部高频RC为系统时钟,内部高频RC 2分频,Fosc=16MHz
	CLKDIV = 0x01;						//Fosc 1分频得到Fcpu,Fcpu=16MHz 
/**********************************相关配置初始化**************************************/
	P1M6 = 0xC1;
	P1M5 = 0xC1;
	P1M4 = 0xC1;
	
	P2M1 = 0xC1;		//P21输出
	
	P1M7 = 0x61;						//P17非斯密特带上拉输入;
	
	P3M5 = 0x61;		//按键1	低电平触发
	P2M4 = 0x61;		//按键2
	P0M2 = 0x61;		//按键3
	
/**********************************TIM0配置初始化**************************************/
	TCON1 = 0x00;						//Tx0定时器时钟为Fosc
	TMOD = 0x00;						//16位重装载定时器/计数器

	//Tim0计算时间 	= (65536 - 0xFFBD) * (1 / (Fosc /Timer分频系数))
	//				= 35 / (16000000 / 12)
	//				= 26.25us

	//定时1ms
	//反推初值 	= 65536 - ((26.25/1000000) / (1/(Fosc / Timer分频系数)))
	//		   	= 65536 - ((26.25/1000000) / (1/(16000000 / 12)))
	//			= 65536 - 35
	//			= 0xFFDD
	
	TH0 = 0xFF;
	TL0 = 0xDD;							//T0定时时间26.25us
	IE |= 0x02;							//打开T0中断
	TCON = 0x10;						//使能T0
    
	EA = 1;								//打开总中断
	
	sum = sum0 = sum1 = b1 = b2 = b3 = c1 = c2 = c3 = c4 = d = j = 0;	//标志位初始化
	number = number_out = 0;
	while(1)
	{
		if(P3_5 == 0) j = 1;
		else if(P2_4 == 0) j = 2;
		else if(P0_2 == 0) j = 3;
		else j = 0;
		switch(j)
		{
			case 1:	number = 0x01FE48B7;break;
			case 2:	number = 0X01FE58A7;break;
			case 3:	number = 0x01FE7887;break;
			default:	number = 0;break;
		}
		while(number != 0)
		{
			if(c5 == 1)	c4 = 2;//执行过一次后判断
			number_out = number;
			if(c4 == 0)
			{
				if(b1 == 0)
				{
					if(c2 == 0)	sum1 = 0,c2 = 1;
					if(c3 == 0)	e = 1,sum0 = 0;
					else e = 0;
					if(sum1 >= 342)	c3 = 1;//9ms之后
					if(sum0 >= 171)	b1 = 1,c3 = 0,c2 = 0,sum1 = sum0 = 0,e = 1,j = 31;
				}
				else
				{
					if(b2 == 0)
					{
						if((sum1 >= 21)&&(c3 == 0))	c3 = 1,sum1 = 0,e = 0;	//高电平之后
						else if(c3 == 1)
						{
							if(j != 0)	d = (number >> j) & 0X00000001;
							else d = number & 0X00000001;
							b2 = 1;
							sum0 = 0;
						}else ;
					}
					else if(b2 == 1)
					{
						e = 0;
						if(sum0 >= (21+42*d))
						{
							b2 = 0;
							if(j == 0)	c4 = 1,sum0 = sum1 = 0;
							j--;
							sum1 = 0;
							sum0 = 0;
							c3 = 0;
							e = 1;
						}
					}
				}
			}
			else if(c4 == 1)
			{
				e = 1;
				if(sum1 >= 21)	//一串码结束,给标志位计时。
				{
					e = 0;
					number = 0;
					c5 = 1;
					c2 = 0;
					c3 = 0;
				}
			}
			else if(c4 == 2)	//长按的函数
			{
				if(c2 == 0)	e = 0,c2 = 1;
				if(sum0 >= (1600 + c6*2663))		//时间判定到
				{
					e = 1;
					c3 = 1;		
					sum1 = 0;						
				}
				if(c3 == 1)				//开始重复码
				{
					if(c7 == 0)	sum1 = 0,c7 = 1;
					if(c8 == 0)	e = 1,sum0 = 0;
					else e = 0;
					if(sum1 >= 342)	c8 = 1;//9ms之后
					if(sum0 >= 86)	c8 = 0,c7 = 0,c3 = 2,sum1 = 0,e = 1;	//一段时间的低电平之后
				}
				if((c3 == 2)&&(sum1 >= 21)) number = 0,e = 0,c6 = 1;
			}
		}
	}
}


void TIMER0_Rpt(void) interrupt 1	//T0中断,每26.25us一次
{
	if(e == 1)//高电平
	{
		sum1++;	
		P2_1 = 1;
		Delay_us(9);		//高电平时间在7.750us(周期26.250us)~8us(周期26.5us)之间 占空比约为1/3
		P2_1 = 0;
	}
	else sum0++,P2_1 = 0;
	
	if(c5 == 1) sum3++;
	if((P3_5 == 0)||(P2_4 == 0)||(P0_2 == 0))	sum++;
	if((P3_5 != 0)&&(P2_4 != 0)&&(P0_2 != 0))	//当按键全部不按下时,复位所有标志位
	{
		e = 0;
		b1 = b2 = b3 = c1 = c2 = c3 = c4 = c5 = c6 = c7 = c8 = d = 0;
		number = 0;
		j = 31;
	}
} 

void Delay_us(unsigned int fui_i)	//依cpu频率而定,fui_1 = 1时,延时约为1~2us
{
	while(fui_i--);	
}

也是为了避免while……所以用了很多标志位(整个程序里就一个似乎必要的while),大家看看就好,不要学,很憨的,用while会简单很多。
这里就可以在高电平的时候判断高电平的持续时间了,到这里红外的接受模块已经结束了,有兴趣的读者可以自己问一下身边的高手,为什么这个延时程序是这样写的。

  • 22
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值