關于我們
18925580829
13926563901
發(fā)布時間:2018-04-09 瀏覽量:
STC的51單片機紅外遙控器讀碼、發(fā)射程序,已試成功
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"。 */
/* 注意:接收碼的脈寬與間隔是對發(fā)射碼取反的,即間隔是0.565ms */
/* 解碼后共有四個十六進制碼,本程序取第三個作為識別碼 */
/*******************************************************************/
#define uchar unsigned char
uchar data IRcode[4]; //定義一個4字節(jié)的數組用來存儲代碼
uchar CodeTemp; //編碼字節(jié)緩存變量
uchar i,j,k; //延時用的循環(huán)變量
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) //如果 持續(xù)了10×0.9ms=9ms的低電平,說明是引導碼。WXL:一定是從引導碼開始
{
while(IRsignal==0); // WXL:因為紅外頭默認輸出是高電平,故用while(IRsignal==0)很安全,而用while(IRsignal==1)則可能會進入死循環(huán)
Delay4ms(); //跳過持續(xù)4.5ms的高電平 WXL:要超過4.5ms更好
Delay0_6ms();
for(i=0;i<4;i++) //分別讀取4個字節(jié)
{
for(j=1;j<=8;j++) //每個字節(jié)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++) //通過串口將代碼發(fā)出
{
SBUF=IRcode;
while(!TI); //等待一個字節(jié)發(fā)送完畢
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為負邊沿觸發(fā), (1:負邊沿觸發(fā),0:低電平觸發(fā))
EX0 = 1; //外部中斷INT0開, (1:開, 0:關 )
EA = 1; //開所有中斷
CodeTemp = 0; //初始化紅外編碼字節(jié)緩存變量
Delay();
while(1)
{
}
}
二、紅外遙控發(fā)射
網上的程序是http://gudeng614.blog.163.com/blog/static/818017420101545648734/
做發(fā)射程序費了很大波折,因為網上的程序不好用。
后來不得不用計算機的并口采集了發(fā)射數據,發(fā)現數據有異常,終于找到了問題所在。
原因是count變量是int的,對其賦值或比較時,匯編語句一句完不成,會被中斷服務程序中斷,造成count變量賦值或比較出現問題。
解決方法是必須在操作時屏蔽中斷。而flag變量是bit的,一句匯編即可完成賦值,故不會有問題。
其間還發(fā)現別的遙控器會在起始碼前加一個前脈沖,以為是這個問題,其實不是。
注意:由于13us會中斷一次,這里是采用1T的單片機。如果采用普通的51單片機,由于是12T的,不知道能不能成功。
//程序從網上修改而來
//由于中斷需要13us中斷一次,即中斷要在幾us處理完,因此需要單片機速度比較快,用24MHZ晶振才能保證正常
//但24MHZ晶振,用串口不方便
//這里采用1T周期的STC12C5A60S2單片機,11.0592MHZ,可以兼顧。
//STC12C5A60S2 引腳可灌入20mA電流,直接從正電源→紅外LED→串1K電阻→P0.0腳。
//串口1默認選T1作為波特率發(fā)生器
//TO用于中斷
//發(fā)送時,低比特位優(yōu)先
#include <STC\STC12C5A60S2.H>
#include <INTRINS.h>
sbit P0_0 = P0^0;
static bit g_OP; //紅外發(fā)射管的亮滅
static unsigned int g_count; //延時計數器
static unsigned int g_endcount; //終止延時計數
static bit g_flag; //紅外發(fā)送標志
unsigned char g_iraddr1; //十六位地址的第一個字節(jié)
unsigned char g_iraddr2; //十六位地址的第二個字節(jié)
//定時器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++)
{
//先發(fā)送0.56ms的38KHZ紅外波(即編碼中0.56ms的高電平)
SendIRdata_38KHZ(43, 1); //13.02*43=0.56ms
//停止發(fā)送紅外信號(即編碼中的低電平)
if(irdata & 1) //判斷最低位為1還是0。 低位先發(fā)送!!
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)
{
//有的遙控器會發(fā)一個前脈沖,如果不靈,可試試加上前脈沖
//發(fā)送起始碼前脈沖,高電平有38KHZ載波
//SendIRdata_38KHZ(18, 1);
//發(fā)送起始碼前脈沖,低電平無38KHZ載波
//SendIRdata_38KHZ(18, 0);
//發(fā)送9ms的起始碼,高電平有38KHZ載波
SendIRdata_38KHZ(692, 1); //13.02*692=9.010ms
//發(fā)送4.5ms的結果碼,低電平無38KHZ載波
SendIRdata_38KHZ(346, 0); //13.02*346=4.505ms
//發(fā)送十六位地址的前八位
SendIRdata_BYTE(g_iraddr1);
//發(fā)送十六位地址的后八位
SendIRdata_BYTE(g_iraddr2);
//發(fā)送八位數據
SendIRdata_BYTE(p_irdata);
//發(fā)送八位數據的反碼
SendIRdata_BYTE(~p_irdata);
//發(fā)送總的結束位1bit
SendIRdata_38KHZ(43, 1); //13.02*43=0.56ms
/* //后面這些可以不用發(fā)
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; } }
//發(fā)送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; } }
//發(fā)送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; } }
//發(fā)送總的結束位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; //數據字節(jié)
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); //發(fā)送紅外數據
TI=0;
SBUF = com_data; //輸出字符
while(!TI) ; //空語句判斷字符是否發(fā)完,TI=1表示發(fā)完
TI = 0; //要人工清TI
}
}
}