51单片机-红外遥控
红外遥控
红外线介绍
人的眼睛能看到的可见光按波长从长到短排列,依次为 红、橙、黄、绿、青、蓝、紫。其中红光的波长范围为 0.62~0.76μm; 紫光的波长范围为 0.38~0.46μm 。 比紫光波长还短的光叫紫外线,比红光波长还长的光叫红外线。红外线遥控就是利用波长为 0.76~1.5μm 之间的近红外线来传送控制信号的。 红外遥控通信系统一般由 红外发射装置 和 红外接收设备 两大部分组成
红外发射装置
红外发射装置,也就是通常我们说的红外遥控器是由键盘电路、红外编码电路、电源电路和红外发射电路组成。

-
红外遥控为了提高抗干扰性能和降低电源消耗,红外遥控器常用
载波的方式传送二进制编码,常用的载波频率为 38kHz,这是由发射端所使用的455kHz晶振来决定的。 -
在发射端要对晶振进行整数分频,
分频系数一般取 12。
也有一些遥控系统采用 36kHz、 40 kHz、 56 kHz等,一般 由发射端晶振的振荡频率来决定。
- 二进制脉冲码的形式有多种,我这个是使用的是
NEC 协议:
①8 位地址和 8 位指令长度;
②地址和命令 2 次传输(确保可靠性)
③PWM 脉冲位置调制,以发射红外载波的占空比代表 “0”和“1”;
④载波频率为 38Khz;
⑤位时间为 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 高电平组成), 如果在一帧数据发送完毕之后,红外遥控器按键仍然没有放开,则发射连发码,可以 通过统计连发码的次数来标记按键按下的长短或次数。
红外接收设备
红外接收设备是由红外接收电路、红外解码、电源和应用电路组成。红外遥控接收器的 主要作用是将遥控发射器发来的红外光信好转换成电信号,再放大、限幅、检波、整形,形成遥控指令脉冲,输出至遥控微处理器。
红外接收头的封装大致有 两种: 一种采用铁皮屏蔽;一种是塑料封装。均有三只引脚,正对接收头的凸起处看,从左至右即 电源正( VDD)、电源负(GND)和数据输出(VOUT)

红外接收头 在没有脉冲的时候为高电平,当收到脉冲的时候为低电平。通过 外部中断的下降沿触发中断,在中断内 通过计算高电平时间来判断接收到的数据是 0还是1
注意
为了保证红外接收头输出管脚默认为 高电平,需 外接一个 10K 上拉电阻,51单片机 IO 口都默认增加了 10K 上拉电阻。
软件编写
流程
进入中断函数,表示已来下降沿, 然后判断管脚是否为低电平,如果为低电平则首先判断引导信号,根据前面 NEC 协议可知,引导信号有 9ms 的低电平和 4.5ms 的高电平,因此通过 time_cnt 赋值 1000,然后在 while 循环内判断,time_cnt 每递减一次约 10us,1000 次则为10ms, 在解码时,这个时间要适当放宽一点范围,因为不同传感器性能会有差异,所以此处以 10ms 的低电平为界限,如果超过 10ms 则强制退出,防止系统死机。判断完引导信号的低电平,接着判断高电平,实现方法一样。当引导信号判断完成后进入地址码、地址反码、控制码及控制反码共 4 个字节的数据判断,也就是数据 0 和 1 的判断,实现方法也是和前面判断引导信号一样,这里使用到了嵌套循环,外层循环次数是 4,表示读取 4 个字节,内层循环次数是 8,表示读取每个字节的 8 位。注意,红外遥控解码数据是从低位开始,最后是高位。最后将读取的 4 个字节数据存储在全局变量数组 gired_data 中,外部可直接使用这四个字节。
main.c
# include "smg.h"
# include "lred.h"
void main()
{
u8 Ired_buf[3];
ired_init();
while(1)
{
Ired_buf[0]=gsmg_code[gired_data[2]/16];//将控制码高4位转换为数码管段码
Ired_buf[1]=gsmg_code[gired_data[2]%16];//将控制码低4位转换为数码管段码
Ired_buf[2]=0x76;//显示H
smg_display(Ired_buf,6);
}
}
smg.c
# include "smg.h"
# include "lred.h"
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
void smg_display(u8 dat[],u8 pos)
{
u8 i=0;
u8 pos_temp=pos-1;
for(i=pos_temp;i<8;i++)
{
switch(i)
{
case 0:A0=1;A1=1;A2=1;break;//Y7//板子从左边数第一个数码管,下面以此类推
case 1:A0=0;A1=1;A2=1;break;//Y6
case 2:A0=1;A1=0;A2=1;break;//Y5
case 3:A0=0;A1=0;A2=1;break;//Y4
case 4:A0=1;A1=1;A2=0;break;//Y3
case 5:A0=0;A1=1;A2=0;break;//Y2
case 6:A0=1;A1=0;A2=0;break;//Y1
case 7:A0=0;A1=0;A2=0;break;//Y0
}
SMG_A0_F_PORT=dat[i-pos_temp];//传送段选数据
delay_10us(100);//延时1毫秒左右
SMG_A0_F_PORT=0x00;//消影
}
}
smg.h
# ifndef _smg_H
# define _smg_H
# include "lred.h"
sbit A0=P2^2;
sbit A1=P2^3;
sbit A2=P2^4;
# define SMG_A0_F_PORT P0//宏定义数码管P0端口
extern u8 gsmg_code[17]; //注意要加extern
void smg_display(u8 dat[],u8 pos);
# endif
lred.c(红外)
# include "lred.h"
u8 gired_data[4];//存储4个字节接收码(地址码+地址反码+控制码+控制反码)
void delay_10us(u16 ten_us)//当传入 Ten_us=1时,大约延时10us
{
while(ten_us--);
}
void ired_init()
{
IT0=1;//触发方式 1:下降沿触发 0:低电平触发
EX0=1;//开启外部中断0
EA=1;//开启总中断
IRED=1;//上拉电阻拉高(可以省略)初始化端口
}
void ired() interrupt 0 //外部中断0服务函数
{
u16 time_cnt=0;
u8 i=0,j=0;
u8 Ired_high_time=0;//要初始化为0不然按其他键没反应!!!!!踩坑
if(IRED==0)
{
time_cnt=1000;
//1.引导码9ms
while((!IRED)&&(time_cnt))//等待引导信号9ms低电平结束,若超过10ms强制退出
{
delay_10us(1);//10*1000就是10ms(10us进来一次)
time_cnt--;
if(time_cnt==0)
return;
}
if(IRED)//引导信号9ms低电平已过,进入4.5ms高电平
{
time_cnt=500;
//2.高电平4.gms
while(IRED&&time_cnt)
{
delay_10us(1);
time_cnt--;
if(time_cnt==0)
return;
}
//3.4个字节的数据
for(i=0;i<4;i++)
{
for(j=0;j<8;j++)//循环8次读取每位数据即一个字节
{
//判断高电平时间和低电平时间
time_cnt=600;
while((IRED==0)&&time_cnt)//等待数据1或0前面的0.56ms结束,若超过6ms强制退出
{
delay_10us(1);
time_cnt--;
if(time_cnt==0)
return;
}
time_cnt=20;
while(IRED)//等待数据1或0后面的高电平结束,若超过2ms强制退出
{
delay_10us(10);
Ired_high_time++;
if(Ired_high_time>20)//当大于2000us(2ms)也就是超过逻辑1的1690us,我们就说这信号是错误的
return;//正常情况是不会进入return的
}
gired_data[i]>>=1;//右移//先读取的为低位,然后是高位
if(Ired_high_time>=8)//选一个中间数,大于800us就是高电平,小于800us就是低电平
gired_data[i]|=0x80;
Ired_high_time=0;//清零
}
}
}
if(gired_data[2]!=~gired_data[3])//如果控制码不等于控制反码,就说接收的数据是错误的就不要这4字节数据
{
for(i=0;i<4;i++)
gired_data[i]=0;//清零
return;
}
}
}
lred.h
# ifndef _lred_H
# define _lred_H
# include "reg52.h"
typedef unsigned char u8;
typedef unsigned int u16;
sbit IRED=P3^2;
extern u8 gired_data[4];
void delay_10us(u16 ten_us);
void ired_init();
# endif
实验现象
红外遥控LED亮灭和蜂鸣器
main.c
/**************************************************************************************
实验名称:红外遥控LED和蜂鸣器
接线说明:
实验现象:下载程序后,操作红外遥控器最上面第1个键和第2个键可控制D1和蜂鸣器开关,并且数码管
上显示遥控器键值码
注意事项:红外接收头凸起处要与PCB板接口凸起丝印处对应
***************************************************************************************/
# include "public.h"
# include "smg.h"
# include "ired.h"
# include "beep.h"
//定义端口
sbit LED1=P2^0;
//定义宏值
# define LED_KEY_VALUE 0X45
# define BEEP_KEY_VALUE 0X46
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void main()
{
u8 ired_buf[3];
u8 key_cnt_led=0;
u8 key_cnt_beep=0;
u8 ired_tempx=0;
u8 ired_tempy=0;
ired_init();//红外初始化
while(1)
{
ired_tempx=gired_data[2];//保存键值
if(ired_tempx!=0)ired_tempy=ired_tempx;//当不为0时则更新数码管显示键值
ired_buf[0]=gsmg_code[ired_tempy/16];//将控制码高4位转换为数码管段码
ired_buf[1]=gsmg_code[ired_tempy%16];//将控制码低4位转换为数码管段码
ired_buf[2]=0X76;//显示H的段码
smg_display(ired_buf,6);
if(ired_tempx==LED_KEY_VALUE)//如果是第一键按下
{
gired_data[2]=0;//清零,等待下次按键按下
key_cnt_led++;
if(key_cnt_led==3)key_cnt_led=1;
}
else if(ired_tempx==BEEP_KEY_VALUE)//如果是第二键按下
{
gired_data[2]=0;//清零,等待下次按键按下
key_cnt_beep++;
if(key_cnt_beep==3)key_cnt_beep=1;
}
if(key_cnt_led==1)LED1=0;
else LED1=1;
if(key_cnt_beep==1)beep_alarm(100,10);
}
}
beep.h
# include "beep.h"
/*******************************************************************************
* 函 数 名 : beep_alarm
* 函数功能 : 蜂鸣器报警函数
* 输 入 : time:报警持续时间
fre:报警频率
* 输 出 : 无
*******************************************************************************/
void beep_alarm(u16 time,u16 fre)
{
while(time--)
{
BEEP=!BEEP;
delay_10us(fre);
}
}
beep.h
# ifndef _beep_H
# define _beep_H
# include "public.h"
//管脚定义
sbit BEEP=P2^5;
//函数声明
void beep_alarm(u16 time,u16 fre);
# endif
ired.c(红外)
# include "ired.h"
u8 gired_data[4];//存储4个字节接收码(地址码+地址反码+控制码+控制反码)
/*******************************************************************************
* 函 数 名 : ired_init
* 函数功能 : 红外端口初始化函数,外部中断0配置
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void ired_init(void)
{
IT0=1; //下降沿触发
EX0=1; //打开中断0允许
EA=1; //打开总中断
IRED=1; //初始化端口
}
void ired() interrupt 0 //外部中断0服务函数
{
u8 ired_high_time=0;
u16 time_cnt=0;
u8 i=0,j=0;
if(IRED==0)
{
time_cnt=1000;
while((!IRED)&&(time_cnt))//等待引导信号9ms低电平结束,若超过10ms强制退出
{
delay_10us(1);//延时约10us
time_cnt--;
if(time_cnt==0)return;
}
if(IRED)//引导信号9ms低电平已过,进入4.5ms高电平
{
time_cnt=500;
while(IRED&&time_cnt)//等待引导信号4.5ms高电平结束,若超过5ms强制退出
{
delay_10us(1);
time_cnt--;
if(time_cnt==0)return;
}
for(i=0;i<4;i++)//循环4次,读取4个字节数据
{
for(j=0;j<8;j++)//循环8次读取每位数据即一个字节
{
time_cnt=600;
while((IRED==0)&&time_cnt)//等待数据1或0前面的0.56ms结束,若超过6ms强制退出
{
delay_10us(1);
time_cnt--;
if(time_cnt==0)return;
}
time_cnt=20;
while(IRED)//等待数据1或0后面的高电平结束,若超过2ms强制退出
{
delay_10us(10);//约0.1ms
ired_high_time++;
if(ired_high_time>20)return;
}
gired_data[i]>>=1;//先读取的为低位,然后是高位
if(ired_high_time>=8)//如果高电平时间大于0.8ms,数据则为1,否则为0
gired_data[i]|=0x80;
ired_high_time=0;//重新清零,等待下一次计算时间
}
}
}
if(gired_data[2]!=~gired_data[3])//校验控制码与反码,错误则返回
{
for(i=0;i<4;i++)
gired_data[i]=0;
return;
}
}
}
ired.h
# ifndef _ired_H
# define _ired_H
# include "public.h"
//管脚定义
sbit IRED=P3^2;
//声明变量
extern u8 gired_data[4];
//函数声明
void ired_init(void);
# endif
smg.c
# include "smg.h"
//共阴极数码管显示0~F的段码数据
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
/*******************************************************************************
* 函 数 名 : smg_display
* 函数功能 : 动态数码管显示
* 输 入 : dat:要显示的数据
pos:从左开始第几个位置开始显示,范围1-8
* 输 出 : 无
*******************************************************************************/
void smg_display(u8 dat[],u8 pos)
{
u8 i=0;
u8 pos_temp=pos-1;
for(i=pos_temp;i<8;i++)
{
switch(i)//位选
{
case 0: LSC=1;LSB=1;LSA=1;break;
case 1: LSC=1;LSB=1;LSA=0;break;
case 2: LSC=1;LSB=0;LSA=1;break;
case 3: LSC=1;LSB=0;LSA=0;break;
case 4: LSC=0;LSB=1;LSA=1;break;
case 5: LSC=0;LSB=1;LSA=0;break;
case 6: LSC=0;LSB=0;LSA=1;break;
case 7: LSC=0;LSB=0;LSA=0;break;
}
SMG_A_DP_PORT=dat[i-pos_temp];//传送段选数据
delay_10us(100);//延时一段时间,等待显示稳定
SMG_A_DP_PORT=0x00;//消音
}
}
smg.h
# ifndef _smg_H
# define _smg_H
# include "public.h"
# define SMG_A_DP_PORT P0 //使用宏定义数码管段码口
//定义数码管位选信号控制脚
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
extern u8 gsmg_code[17];
void smg_display(u8 dat[],u8 pos);
# endif
public.c
# include "public.h"
/*******************************************************************************
* 函 数 名 : delay_10us
* 函数功能 : 延时函数,ten_us=1时,大约延时10us
* 输 入 : ten_us
* 输 出 : 无
*******************************************************************************/
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
/*******************************************************************************
* 函 数 名 : delay_ms
* 函数功能 : ms延时函数,ms=1时,大约延时1ms
* 输 入 : ms:ms延时时间
* 输 出 : 无
*******************************************************************************/
void delay_ms(u16 ms)
{
u16 i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
public.h
# ifndef _public_H
# define _public_H
# include "reg52.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
void delay_10us(u16 ten_us);
void delay_ms(u16 ms);
# endif



