51单片机之红外通信
在写代码之前需要了解一下红外遥控的原理
红外遥控通信系统一般由红外发射装置和红外接收设备两大部分组成。
(一):红外发射装置
通常红外遥控为了提高抗干扰性能和降低电源消耗,红外遥控器常用载波的 方式传送二进制编码,常用的载波频率为 38kHz,这是由发射端所使用的 455kHz 晶振来决定的。在发射端要对晶振进行整数分频,分频系数一般取 12,所以 455kHz÷12≈37.9kHz≈38kHz。也有一些遥控系统采用 36kHz、 40 kHz、 56 kHz 等,一般由发射端晶振的振荡频率来决定。所以,通常的红外遥控器是将遥控信 号(二进制脉冲码)调制在 38KHz 的载波上,经缓冲放大后送至红外发光二极 管,转化为红外信号发射出去的
二进制脉冲码的形式有多种,其中最为常用的是 ==NEC Protocol 的 PWM 码 (脉冲宽度调制)==和 Philips RC-5 Protocol 的 PPM 码(脉冲位置调制码,脉冲 串之间的时间间隔来实现信号调制)。如果要开发红外接收设备,一定要知道红 外遥控器的编码方式和载波频率,我们才可以选取一体化红外接收头和制定解码 方案。
由于我使用的是普中科技的板子,配套的红外遥控器使用的是 NEC 协议
所以此处只介绍NEC
NEC特征:
1、8 位地址和 8 位指令长度;
2、地址和命令 2 次传输(确保可靠性)
3、PWM 脉冲位置调制,以发射红外载波的占空比代表“0”和“1”;
4、载波频率为 38Khz;
5、位时间为 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 高电平组成),如果在一帧数据发送完毕之后,红外遥控器按键仍然没 有放开,则发射连发码,可以通过统计连发码的次数来标记按键按下的长短或次数
(二):红外接收设备
红外接收设备是由红外接收电路、红外解码、电源和应用电路组成。红外遥 控接收器的主要作用是将遥控发射器发来的红外光信好转换成电信号,再放大、 限幅、检波、整形,形成遥控指令脉冲,输出至遥控微处理器。
由于红外接收头在没有脉冲的时候为高电平,当收到脉冲的时候为低电平, 所以可以通过外部中断的下降沿触发中断,在中断内通过计算高电平时间来判断 接收到的数据是 0 还是 1。
直接分析代码
该代码由几个函数组成。
1:延时函数 delay
2:数码显示函数 DigDisplay
3:初始化红外接收函数 IrInit
4:读取红外数值的中断函数 ReadIr
5:主函数 main
延时函数和数码显示函数不做赘述,先将整体的代码放出来,取重要的部分再加注释
//红外中断
#include "reg52.h"
typedef unsigned int u16;
typedef unsigned char u8;
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
sbit IRIN=P3^2;
u8 IrValue[6];
u8 Time;
u8 DisplayData[8];
u8 code smgduan[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07, 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0X76};
//显示 0 1 2 3 4 5 6 7 8 9 A b C d E F H
void delay(u16 i)//延时函数
{
while(i--);
}
void DigDisplay()//数码管显示函数
{
u8 i;
for(i=0;i<3;i++)
{
switch(i)//位选,选择点亮的数码管
{
case(0):
LSA=1;LSB=1;LSC=1;break;// 111
case(1):
LSA=0;LSB=1;LSC=1;break;// 110
case(2):
LSA=1;LSB=0;LSC=1;break;// 101
}
P0=DisplayData[i];//发送数据
delay(100);//间隔一段时间
P0=0X00;//消隐
}
}
void IrInit()//初始化红外接收 函数
{
IT0=1;//下降沿触发
EX0=1;//打开中断0允许
EA=1;//打开总中断
IRIN=1;//初始化端口
}
void main()
{
ItInit();
while(1);
{
DisplayData[0]=smgduan[Irvalue[2]/16];
DisplayData[1]=smgduan[Irvalue[2]%16];
DisplayData[2]=smgduan[16];
Digdisplay();
}
}
void ReadIr() interrupt 0//读取红外数值的中断函数
{
u8 j,k;
u16 err;
Time=0;
delay(700);
if(IRIN==0)
{
err=1000;
while((IRIN==0)&&(err>0))
{
delay(1);
err--;
}
if(IRIN==1)
{
err=500;
while((IRIN==1)&&(err>0))
{
delay(1);
err--;
}
for(k=0;k<4;k++)
{
for(j=0;j<8;j++)
{
err=60;
while((IRIN==0)&&(err>0))
{
delay(1);
err--;
}
err=500;
while((IRIN==1)&&(err>0))
{
delay(10);
Time++;
err--;
if(Time>30)
{
return;
}
}
IrValue[k]>>1;
if(Time>=8)
{
IrValue[k]|=0x80;
}
Time=0;
}
}
}
if(IrValue[2]!=~IrValue[3])
{
return;
}
}
}
分析初始化红外接收函数
P3^2接口具有外部中断0的功能用来红外解码
void IrInit()//初始化红外接收 函数 进行起始码的判断
{
IT0=1;//下降沿触发
EX0=1;//打开中断0允许
EA=1;//打开总中断
IRIN=1;//初始化端口 默认情况为高电平 下降沿触发 起始码由高电平变为低电平,出现下降沿触发外部中断
}
分析读取红外数值的中断函数
下面是我个人的理解,一开始定义err变量的作用,err=1000;1000*10us=10ms,这10ms就是等待起始码由低电平变为高电平的时间,此时间上图中说为9ms,如果到10ms还没变为高电平,则说明不对,如果是高电平则说明正确进入了。
总的来说err变量就是判断是否进入下一阶段的条件
void ReadIr() interrupt 0//读取红外数值的中断函数
{
u8 j,k;
u16 err;
Time=0;//Time变量用来保存高电平的时间 此处是在清零
delay(700);//延时用来起始码下降沿低电平 7ms
if(IRIN==0)//确认是否真的接受到正确的信号 如果还是低电平
{
err=1000;//1000*10Us0ms,超过说明接收到错误的信号
/*当两个条件都为真进入循环 如果有一个条件为假则跳出循环*/
while((IRIN==0)&&(err>0))//等待前面9ms过去,为什么是9ms起始码低电平的时间就是9ms
{
delay(1);//10us
err--;//变量自减一
}//
if(IRIN==1)//正确等到了高电平
{
err=500;//500*10us=5ms,为什么定义5ms,因为5ms肯定是已经超过4.5ms的时间
while((IRIN==1)&&(err>0))//等待4.5ms起始高电平过去
{
delay(1);
err--;
}//高电平完成后,退出while循环
for(k=0;k<4;k++)//四组数据,用户码,用户码,数据码,数据反码
{
for(j=0;j<8;j++)//接受一组数据是8个码,一位一位接受
{
err=60;
while((IRIN==0)&&(err>0))//等待信号前面560us低电平过去
{
delay(1);
err--;
}
err=500;
while((IRIN==1)&&(err>0))//计算高电平时间的长度
{
delay(10);//延时0.1ms
Time++;//高电平过0.1ms
err--;
if(Time>30)//若高电平时间超过3ms,则通信失败
{
return;//强制退出循环
}
}
IrValue[k]>>1;//k移位
if(Time>=8)//如果高电平出现大于565us则是1
{
IrValue[k]|=0x80;//数据保存至数组
}
Time=0;//时间重新赋值
}
}
}
/*校验*/if(IrValue[2]!=~IrValue[3])//判断读取的数据码和数据反码是否正确
{
return;//强制退出
}
}
}
分析main函数
void main()
{
ItInit();
while(1);
{
DisplayData[0]=smgduan[Irvalue[2]/16];//获得16进制高位数据
DisplayData[1]=smgduan[Irvalue[2]%16];//获得16进制低位数据
DisplayData[2]=smgduan[16];
Digdisplay();//显示
}
}
大概是写完了,看一遍看不太懂。。。真麻烦