STC的51单片机红外遥控器读码、发射程序,已试成功 |
|
|
有一台DVD机没有遥控器,正好别的遥控器有的键可以用,但功能不一样。 |
|
于是开展本工程,程序原为网上摘的,经过修改均已全部成功。 |
|
采用STC的51单片机,STC12C5A60S2,可以直接串口编程,而且是1T的,非常方便。 |
|
一、红外遥控器读码 |
读码程序没怎么修改就成功了。 |
注意:这里的延时程序是STC12C5A60S2的,如果用别的单片机,需要修改。 |
|
#include <STC\STC12C5A60S2.H> |
#include <INTRINS.h> |
|
//采用1T周期的STC12C5A60S2单片机,11.0592MHZ |
//WXL:一体化接收头默认是输出高电平,有信号时输出低电平;接P3.2脚。 |
//WXL:这里按“低位在先” |
|
/******************************************************************/ |
/* 本程序的蓝本从网上搜集,经修改并注释,万能遥控器解码成功 */ |
/* 晶振:11.0592MHz */ |
/* 整理与测试:单片机教程网 胡琴 2012.5.15 */ |
/************************* 说 明 *********************************/ |
/* 以一个9ms的低电平和4.5ms的高电平为引导码,后跟32位二进制代码 */ |
/* 前16位为8位用户码及其反码,后16位为8位的操作码及其反码 */ |
/* 以脉宽为低电平0.565ms、间隔高电平0.56ms、周期为1.125ms的组合表示"0"; */ |
/* 以脉宽为低电平0.565ms、间隔高电平1.685ms、周期为2.25ms的组合表示"1"。 */ |
/* 注意:接收码的脉宽与间隔是对发射码取反的,即间隔是0.565ms */ |
/* 解码后共有四个十六进制码,本程序取第三个作为识别码 */ |
/*******************************************************************/ |
|
#define uchar unsigned char |
uchar data IRcode[4]; //定义一个4字节的数组用来存储代码 |
uchar CodeTemp; //编码字节缓存变量 |
uchar i,j,k; //延时用的循环变量 |
sbit IRsignal=P3^2; //HS0038接收头OUT端直接连P3.2(INT0) |
sbit P0_0=P0^0; //P0连接到 LED 上 |
sbit P0_1=P0^1; |
sbit P0_2=P0^2; |
|
|
|
/**************************延时0.6ms子程序**********************/ |
void Delay0_6ms(void) //@11.0592MHz |
{ |
unsigned char i, j; |
|
_nop_(); |
_nop_(); |
i = 7; |
j = 112; |
do |
{ |
while (--j); |
} while (--i); |
|
} |
|
|
/**************************延时0.9ms子程序**********************/ |
void Delay0_9ms(void) //@11.0592MHz |
{ |
unsigned char i, j; |
_nop_(); |
_nop_(); |
_nop_(); |
i = 10; |
j = 170; |
do |
{ |
while (--j); |
} while (--i); |
} |
|
|
|
|
/***************************延时1ms子程序**********************/ |
void Delay1ms(void) |
{ |
unsigned char i, j; |
|
_nop_(); |
i = 11; |
j = 190; |
do |
{ |
while (--j); |
} while (--i); |
} |
|
/***************************延时4ms子程序**********************/ |
void Delay4ms(void) |
{ |
unsigned char i, j; |
|
_nop_(); |
_nop_(); |
_nop_(); |
i = 44; |
j = 3; |
do |
{ |
while (--j); |
} while (--i); |
} |
|
/**************************** 延时子程序 ************************/ |
void Delay(void) |
{ |
uchar i,j,k; |
for(i=200;i>0;i--) |
for(j=200;j>0;j--) |
for(k=3;k>0;k--) ; |
} |
|
/******************** 中断0解码服务子程序 ********************/ |
void int0(void) interrupt 0 using 2 |
{ |
EA = 0; //??? 可以这样,跳入中断,但仍可对P3.2(INT0)进行电平变化的读取 |
for(k=0;k<10;k++) |
{ |
Delay0_9ms(); |
if (IRsignal==1) //如果0.9ms后IRsignal=1,说明不是引导码,退出中断 |
{ |
k=10; |
break; |
} |
|
else if(k==9) //如果 持续了10×0.9ms=9ms的低电平,说明是引导码。WXL:一定是从引导码开始 |
{ |
while(IRsignal==0); // WXL:因为红外头默认输出是高电平,故用while(IRsignal==0)很安全,而用while(IRsignal==1)则可能会进入死循环 |
Delay4ms(); //跳过持续4.5ms的高电平 WXL:要超过4.5ms更好 |
Delay0_6ms(); |
|
for(i=0;i<4;i++) //分别读取4个字节 |
{ |
for(j=1;j<=8;j++) //每个字节8个bit的判断 |
{ |
while(IRsignal==0); //等待上升沿,此处用得很好:因为0.56ms的低电平(接收时)是代码0与1的相同部分 |
Delay0_9ms(); //从上升沿那一时刻开始延时0.9ms(因为0.9介于0.56(=1.125-0.56)与1.69(=2.25-0.56)之间),再判断IRsignal |
if(IRsignal==1) // 如果IRsignal是"1",高位置"1",并向右移一位 |
{ |
Delay1ms(); //为什么要延时1ms呢?因为要使IRsignal跳至低电平(即0.56ms的0与1相同部分上) |
CodeTemp=CodeTemp | 0x80; //此处的算法很好 |
if(j<8) CodeTemp=CodeTemp>>1; |
} |
else // 如果IRsignal是"0",高位置"0",并向右移一位 |
if(j<8) CodeTemp=CodeTemp>>1; //如果IRsignal是"0",则直接向右移一位,自动补"0" |
} |
IRcode=CodeTemp; |
CodeTemp=0; |
} //end for |
|
for(i=0;i<4;i++) //通过串口将代码发出 |
{ |
SBUF=IRcode; |
while(!TI); //等待一个字节发送完毕 |
TI=0; |
} |
|
Delay(); |
} //end else |
} //END for |
EA = 1; |
} |
|
/***********************串口初始化程序*********************/ |
void initUart(void) |
{ |
TMOD | = 0x20; // |
SCON = 0x50; // |
PCON | = 0x80; // |
TH1 = 250; // 9600 bps @ 11.0592MHz |
TL1 = 250; |
TR1 = 1; |
} |
|
/**************************主程序*************************/ |
void main() |
{ |
//P0=0XFF; |
initUart(); |
IT0 = 1; //INT0为负边沿触发, (1:负边沿触发,0:低电平触发) |
EX0 = 1; //外部中断INT0开, (1:开, 0:关 ) |
EA = 1; //开所有中断 |
|
CodeTemp = 0; //初始化红外编码字节缓存变量 |
Delay(); |
while(1) |
{ |
|
} |
} |
|
|
|
|
|
|
二、红外遥控发射 |
|
网上的程序是http://gudeng614.blog.163.com/blog/static/818017420101545648734/ |
|
做发射程序费了很大波折,因为网上的程序不好用。 |
|
后来不得不用计算机的并口采集了发射数据,发现数据有异常,终于找到了问题所在。 |
|
原因是count变量是int的,对其赋值或比较时,汇编语句一句完不成,会被中断服务程序中断,造成count变量赋值或比较出现问题。 |
解决方法是必须在操作时屏蔽中断。而flag变量是bit的,一句汇编即可完成赋值,故不会有问题。 |
|
其间还发现别的遥控器会在起始码前加一个前脉冲,以为是这个问题,其实不是。 |
|
注意:由于13us会中断一次,这里是采用1T的单片机。如果采用普通的51单片机,由于是12T的,不知道能不能成功。 |
|
//程序从网上修改而来 |
//由于中断需要13us中断一次,即中断要在几us处理完,因此需要单片机速度比较快,用24MHZ晶振才能保证正常 |
//但24MHZ晶振,用串口不方便 |
//这里采用1T周期的STC12C5A60S2单片机,11.0592MHZ,可以兼顾。 |
//STC12C5A60S2 引脚可灌入20mA电流,直接从正电源→红外LED→串1K电阻→P0.0脚。 |
//串口1默认选T1作为波特率发生器 |
//TO用于中断 |
//发送时,低比特位优先 |
|
#include <STC\STC12C5A60S2.H> |
#include <INTRINS.h> |
|
sbit P0_0 = P0^0; |
|
static bit g_OP; //红外发射管的亮灭 |
static unsigned int g_count; //延时计数器 |
static unsigned int g_endcount; //终止延时计数 |
static bit g_flag; //红外发送标志 |
unsigned char g_iraddr1; //十六位地址的第一个字节 |
unsigned char g_iraddr2; //十六位地址的第二个字节 |
|
|
//定时器0中断处理 |
void timeint(void) interrupt 1 |
{ |
g_count++; |
if (g_flag) g_OP=~g_OP; |
else g_OP = 1; //LED不点亮 |
P0_0 = g_OP; |
} |
|
|
///////////////////////////////////////////////////// |
void SendIRdata_38KHZ(unsigned int temp1, bit temp2) |
{ |
g_endcount=temp1; |
g_flag=temp2; |
EA=0; g_count=0; EA=1; //避免中断影响count置数 |
while(1) |
{ |
EA=0; |
if( g_count < g_endcount ) EA=1; //避免中断影响count比较 |
else |
{ |
EA=1; |
break; |
} |
} |
} |
|
///////////////////////////////////////////////////// |
void SendIRdata_BYTE(unsigned char irdata) |
{ |
unsigned char i; |
for(i=0;i<8;i++) |
{ |
//先发送0.56ms的38KHZ红外波(即编码中0.56ms的高电平) |
SendIRdata_38KHZ(43, 1); //13.02*43=0.56ms |
|
//停止发送红外信号(即编码中的低电平) |
if(irdata & 1) //判断最低位为1还是0。 低位先发送!! |
SendIRdata_38KHZ(130, 0); //1为宽电平,13.02*130=1.693ms |
else SendIRdata_38KHZ(43, 0); //0为窄电平,13.02*43=0.560ms |
|
irdata=irdata>>1; |
} |
} |
|
///////////////////////////////////////////////////// |
void SendIRdata(unsigned char p_irdata) |
{ |
//有的遥控器会发一个前脉冲,如果不灵,可试试加上前脉冲 |
//发送起始码前脉冲,高电平有38KHZ载波 |
//SendIRdata_38KHZ(18, 1); |
//发送起始码前脉冲,低电平无38KHZ载波 |
//SendIRdata_38KHZ(18, 0); |
|
//发送9ms的起始码,高电平有38KHZ载波 |
SendIRdata_38KHZ(692, 1); //13.02*692=9.010ms |
|
//发送4.5ms的结果码,低电平无38KHZ载波 |
SendIRdata_38KHZ(346, 0); //13.02*346=4.505ms |
|
//发送十六位地址的前八位 |
SendIRdata_BYTE(g_iraddr1); |
|
//发送十六位地址的后八位 |
SendIRdata_BYTE(g_iraddr2); |
|
//发送八位数据 |
SendIRdata_BYTE(p_irdata); |
|
//发送八位数据的反码 |
SendIRdata_BYTE(~p_irdata); |
|
//发送总的结束位1bit |
SendIRdata_38KHZ(43, 1); //13.02*43=0.56ms |
|
|
/* //后面这些可以不用发 |
g_endcount=1766; |
g_flag=0; |
EA=0; g_count=0; EA=1; |
while(1){EA=0; if(g_count<g_endcount) EA=1; else { EA=1; break; } } |
|
//发送9ms的起始码,高电平有38KHZ载波 |
g_endcount=692; //13.02*692=9.010ms |
g_flag=1; |
EA=0; g_count=0; EA=1; |
while(1){EA=0; if(g_count<g_endcount) EA=1; else { EA=1; break; } } |
|
//发送4.5ms的结果码,低电平无38KHZ载波 |
g_endcount=346; //13.02*346=4.505ms |
g_flag=0; |
EA=0; g_count=0; EA=1; |
while(1){EA=0; if(g_count<g_endcount) EA=1; else { EA=1; break; } } |
|
//发送总的结束位1bit |
g_endcount=43; //13.02*43=0.56ms |
g_flag=1; |
EA=0; g_count=0; EA=1; |
while(1){EA=0; if(g_count<g_endcount) EA=1; else { EA=1; break; } } |
|
*/ |
|
g_flag=0; |
|
|
} |
|
|
|
/////////////////////////////////////////////////////////// |
void main(void) |
{ |
unsigned char com_data; //数据字节 |
|
g_count = 0; |
g_flag = 0; |
g_OP = 1; |
P0_0 = g_OP; //LED接电源正极,不点亮 |
|
SCON=0x50; //串口方式1 01 0 1 0 0 00 模式1,非多机,允许接收,无数据位8,清中断标识TI和RI |
|
TMOD = 0x22; //(定时器0和1:方式2,自动重装,8位) |
TH1=253; //11.0592MHZ,9600bps。没有设置SMOD,故波特率没有加倍。即:11.0592/12/3/32=9600bps |
TL1=253; |
TR1=1; //启动定时器 |
|
TH0 = 244; |
TL0 = 244; //(WXL:即计数12次中断一次,即11.0592MHZ晶振,机器周期是1.085us,12次*1.085=13.02us,这样达38KHZ。 13us一次中断,时间太短了,所以单片机要快) |
ET0 = 1; //定时器0中断允许 |
EA = 1; //允许CPU中断 |
TR0 = 1; //开始计数 |
|
g_iraddr1=0; //地址码 |
g_iraddr2=255; //地址反码 |
|
RI=0; |
while(1) |
{ |
if(RI==1) |
{ |
com_data =SBUF; |
RI=0; //要人工清RI |
SendIRdata(com_data); //发送红外数据 |
TI=0; |
SBUF = com_data; //输出字符 |
while(!TI) ; //空语句判断字符是否发完,TI=1表示发完 |
TI = 0; //要人工清TI |
} |
} |
|
} |