欢迎光临东莞市飞江电子科技有限公司官网!
全国咨询热线

13926563901

18925580829

飞江淘宝店铺

首页>技术资料>其它单片机

STC的51单片机红外遥控器读码、发射程序,已试成功

发布时间: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
}
}

}