NEC 红外传输协议说明(附代码)
NEC红外传输协议详解(含代码)
注:这个代码是c语言编到keil的,51核的芯片,下面的所有波形图都是用逻辑分析器测量的。
首先写NEC接收的部分:
红外接受管有3脚,一个接地一个接VCC一个信号输出脚,输入端并电容(我用的104)滤波,VCC串了个电阻分压。接收管的输出脚在没接收到信号的时候输出的是高电平。
在红外发送器发送信号时,接收头会接收到信号,首先,接收到的该信号有一个头码(引导码):
首先接收到9ms的低电平,
然后是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的值,协议里是560us的低电平,然后再560us的高电平,周期是1.20ms;
↑这是1的值,协议里是560us的低电平,然后再1680us的高电平,周期是2.24ms;
最后就是0.56ms的低电平结束码了。这就是接受到的第一串数据。贴的这串数据是01FE48B7H。
上面那一串应该就能理解了吧~,然后继续,如果信号发送器发送的按键信息“长按”了,之后接受到的就是重复码了:
重复码的规则是这样的,从第一次接受到信号开始计时,到每一次接收重复码,间隔时间是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会简单很多。
这里就可以在高电平的时候判断高电平的持续时间了,到这里红外的接受模块已经结束了,有兴趣的读者可以自己问一下身边的高手,为什么这个延时程序是这样写的。