热度 20
2014-11-21 21:25
10938 次阅读|
3 个评论
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字节的数组用来存储代码 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;i0;i--) for(j=200;j0;j--) for(k=3;k0;k--) ; } /******************** 中断0解码服务子程序 ********************/ void int0(void) interrupt 0 using 2 { EA = 0; //??? 可以这样,跳入中断,但仍可对P3.2(INT0)进行电平变化的读取 for(k=0;k10;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;i4;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(j8) CodeTemp=CodeTemp1; } else // 如果IRsignal是"0",高位置"0",并向右移一位 if(j8) CodeTemp=CodeTemp1; //如果IRsignal是"0",则直接向右移一位,自动补"0" } IRcode =CodeTemp; CodeTemp=0; } //end for for(i=0;i4;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;i8;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=irdata1; } } ///////////////////////////////////////////////////// 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_countg_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_countg_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_countg_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_countg_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 } } }