发布时间:2018-04-09 浏览量:
STC的51单片机红外遥控器读码、发射程序,已试成功
wxleasyland@sina.com
2014.11.21
有一台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
}
}
}