红外遥控的介绍
红外遥控是一种无线、非接触控制技术,通过使用红外线来传送控制信号。它具有抗干扰能力强、信息传输可靠、功耗低、成本低、易实现等显著优点,因此被广泛应用于各种电子设备和家用电器,也越来越多地应用于计算机和手机系统中。
硬件电路
红外遥控系统一般由发射和接收两个部分组成。发射部分包括红外发光二极管,它是一种特殊的发光二极管,能够发射调制后的红外光波。接收部分通常采用红外接收模块,用于接收来自遥控器发射的红外信号。当遥控器处于学习状态时,接收模块会接收外来红外信号,并将其转换成电信号。经过检波、整形和放大等处理后,这些电信号会被传送到中央处理单元(CPU)进行解码和执行相应的控制动作。
发射端:
IN是输入信号端口,输入的信号会通过38kHz的频率在三极管中放大,到发光二极管中,二极管发出闪烁着的特制红外光线;
在现实生活中有很多种类型的红外线,为了让接收方只接收这一类型的红外线,就在电路中加入了频率,让这个信号能有一定频率的闪烁;
当我们没有输入时,默认为高电平,而当我们输入信号时,就会在低电平发出有频率波动的信号;
接收端:
这里将接收的发光二极管内置在了电路中,电路也很容易,电源、接地、以及信号输出;
在这里我们会让OUT端口接上外部中断,当产生下降沿就进入中断;
NEC编码
NEC编码是一种红外遥控协议,常用于遥控器与设备之间的通信。它是一种常用的编码格式;将遥控发送过来的信号进行一定形式的编码,转换为对应的信息;
NEC编码的数据格式通常由以下几部分组成:
- 引导码(Leader Code):持续低电平(通常为9ms)和持续高电平(通常为4.5ms),用于标识开始传输数据。
- 系统码(Customer Code):8位二进制代码,用于识别具体的设备或品牌。
- 数据码(Data Code):8位二进制代码,表示具体的指令或按键信息。在传输数据时一般持续低电平(560us)和持续高电平(560us)表示传输‘0’,持续低电平(560us)和持续高电平(1690us)表示传输‘1’;
- 取反码(Inverted Data Code):与数据码和系统码相反的二进制代码,用于检验数据的正确性。
- 结束码(Stop Bit):持续高电平(通常为0.56ms),表示数据传输结束。在这里,我们没有引用到;
- 重复码(Repeat Code):数据传输之后,若持续低电平(9ms)和持续高电平(2.25ms),那么将会重复这个命令信息;
NEC编码使用38kHz的载波频率进行传输,通过在红外光线的调制和解调实现遥控功能。在发射时,数据按照引导码、系统码、数据码和取反码的顺序发送;在接收端,通过解码器将红外信号还原为对应的系统码和数据码,从而实现遥控器与设备的交互。
外部中断
51单片机的外部中断是指通过外部信号触发单片机进行中断处理的一种机制。单片机通过外部引脚接收到特定的信号后,会立即暂停当前的任务,转而执行与该中断相关的程序。
在51单片机中,有两个外部中断源,分别为外部中断0(INT0)和外部中断1(INT1)。这两个中断源分别对应单片机的引脚P3.2和P3.3。
外部中断可以通过两种触发方式来进行中断处理:电平触发和边沿触发。电平触发是指当外部信号维持在某个电平上时触发中断,可以是低电平触发或高电平触发;边沿触发是指当外部信号的跳变沿(上升沿或下降沿)出现时触发中断。
中断号:
此次操作相关寄存器以及原理:
使用外部中断前,需要进行相应的初始化操作。
初始化:
void Interrupt0_Init() { IT0=1;//外部中断0选择边沿触发 IE0=1;//外部中断0请求标志 EX0=1;//外部中断0请求允许位 EA=1;//总中断允许 PX0=1;//中断优先级控制 }
红外遥控实例代码
接下来,我们要实现的是在屏幕上显示地址,命令码,数字增减,通过遥控的控制,让命令码随之改变,数字达到增减;
我们将通过定时器和外部中断0来进行操控,写出NEC编码;
利用不同的状态表示到达不同的编码阶段;
Timer0.h
#ifndef __TIMER0_H__ #define __TIMER0_H__ void Timer0Init(); void Timer0_SetCounter(unsigned int Value); unsigned int Timer0_GetCounter(); void Timer0_Run(unsigned char Flag); #endif
Timer0.c
#include <REGX52.H> /** * @brief 定时器0初始化 * @param 无 * @reval 无 */ void Timer0Init(void) //1毫秒@11.0592MHz { TMOD &= 0xF0; //设置定时器模式 TMOD |=0x01; TL0 = 0; //设置定时初值 TH0 = 0; //设置定时初值 TF0 = 0; //清除TF0标志 TR0 = 0; //定时器0不计时 } //定时器0设置计数器值,范围0-65535 void Timer0_SetCounter(unsigned int Value) { TH0=Value/256; TL0=Value%256; } //定时器0获取计数器值 unsigned int Timer0_GetCounter() { return (TH0<<8)|TL0; } //定时器0启动停止控制 void Timer0_Run(unsigned char Flag) { TR0=Flag; }
将定时器初始化为0,让定时器实现计时器的功能;
Interrupt.h
#ifndef __INTERRUPT_H__ #define __INTERRUPT_H__ void Interrupt0_Init(); #endif
Interrupt.c
#include <REGX52.H> //外部中断0初始化 void Interrupt0_Init() { IT0=1; IE0=1; EX0=1; EA=1; PX0=1; }
按键对应命令码:
IR.h
#ifndef __IR_H__ #define __IR_H__ #define IR_POWER 0x45 #define IR_MODE 0x46 #define IR_MUTE 0x47 #define IR_START_STOP 0x44 #define IR_PREVIOUS 0x40 #define IR_NEXT 0x43 #define IR_EQ 0x07 #define IR_VOL_MINUS 0x15 #define IR_VOL_ADD 0x09 #define IR_0 0x16 #define IR_RPT 0x19 #define IR_USD 0x0D #define IR_1 0x0C #define IR_2 0x18 #define IR_3 0x5E #define IR_4 0x08 #define IR_5 0x1C #define IR_6 0x5A #define IR_7 0x42 #define IR_8 0x52 #define IR_9 0x4A void IR_Init(); unsigned char IR_GetDataFlag(); unsigned char IR_GetRepeatFlag(); unsigned char IR_GetAddress(); unsigned char IR_GetCommand(); #endif
IR.c
#include <REGX52.H> #include"Timer0.h" #include"Interrupt.h" unsigned int IR_Time; unsigned char IR_State; unsigned char IR_Data[4]; unsigned char IR_pData; unsigned char IR_DataFlag; unsigned char IR_RepeatFlag; unsigned char IR_Address; unsigned char IR_Command; //红外遥控初始化 void IR_Init() { Timer0Init(); Interrupt0_Init(); } //红外遥控获取收到数据帧标志位 unsigned char IR_GetDataFlag() { if(IR_DataFlag) { IR_DataFlag=0; return 1; } return 0; } //红外遥控获取收到连发标志位 unsigned char IR_GetRepeatFlag() { if(IR_DataFlag) { IR_RepeatFlag=0; return 1; } return 0; } //红外遥控获取收到的地址数据 unsigned char IR_GetAddress() { return IR_Address; } //红外遥控获取收到的命令数 unsigned char IR_GetCommand() { return IR_Command; } //外部中断0函数 void Int0_Routine() interrupt 0 { if(IR_State==0) //状态0,空闲状态 { Timer0_SetCounter(0); //计数器清0 Timer0_Run(1); //定时器启动 IR_State=1; //变为状态1 } else if(IR_State==1) //状态1,等待Start信号或Repeat信号 { IR_Time=Timer0_GetCounter(); //获取开始的时间或重复的时间 Timer0_SetCounter(0); //计时器清0 if(IR_Time>12442-500&&IR_Time<12442+500)//表示是开始信号,进入下一阶段 { IR_State=2; } else if(IR_Time>10368-500&&IR_Time<10368+500)//是重复信号,回到最初的起点 { IR_RepeatFlag=1; //连发帧标志位置1 Timer0_Run(0); //定时器停止 IR_State=0; //状态置0 } else { IR_State=1; } } else if(IR_State==2) //状态2,接收数据 { IR_Time=Timer0_GetCounter();//获取接收数据的时间段 Timer0_SetCounter(0); if(IR_Time>1032-500&&IR_Time<1032+500)//数据接到为0 { IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); IR_pData++; } else if(IR_Time>2074-500&&IR_Time<2074+500)//数据接收到为1 { IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8));//除8是将32位下标分为4个元素,模8将32位下标从0~7有序循环 IR_pData++; } else //接收错误 { IR_pData=0; IR_State=1; } if(IR_pData>=32) //如果收到了32位 { IR_pData=0; //下标清0 if((IR_Data[0]==~IR_Data[1])&&(IR_Data[2]==~IR_Data[3])) //数据验证 { IR_Address=IR_Data[0]; //存储数据 IR_Command=IR_Data[2]; IR_DataFlag=1; //数据帧标志位置1 } Timer0_Run(0); //定时器停止 IR_State=0; //状态置0 } } }
LCD1602.c
#include <REGX52.H> //引脚配置: sbit LCD_RS=P2^6; sbit LCD_RW=P2^5; sbit LCD_EN=P2^7; #define LCD_DataPort P0 //函数定义: /** * @brief LCD1602延时函数,12MHz调用可延时1ms * @param 无 * @retval 无 */ void LCD_Delay() { unsigned char i, j; i = 2; j = 239; do { while (--j); } while (--i); } /** * @brief LCD1602写命令 * @param Command 要写入的命令 * @retval 无 */ void LCD_WriteCommand(unsigned char Command) { LCD_RS=0; LCD_RW=0; LCD_DataPort=Command; LCD_EN=1; LCD_Delay(); LCD_EN=0; LCD_Delay(); } /** * @brief LCD1602写数据 * @param Data 要写入的数据 * @retval 无 */ void LCD_WriteData(unsigned char Data) { LCD_RS=1; LCD_RW=0; LCD_DataPort=Data; LCD_EN=1; LCD_Delay(); LCD_EN=0; LCD_Delay(); } /** * @brief LCD1602设置光标位置 * @param Line 行位置,范围:1~2 * @param Column 列位置,范围:1~16 * @retval 无 */ void LCD_SetCursor(unsigned char Line,unsigned char Column) { if(Line==1) { LCD_WriteCommand(0x80|(Column-1)); } else if(Line==2) { LCD_WriteCommand(0x80|(Column-1+0x40)); } } /** * @brief LCD1602初始化函数 * @param 无 * @retval 无 */ void LCD_Init() { LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵 LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关 LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动 LCD_WriteCommand(0x01);//光标复位,清屏 } /** * @brief 在LCD1602指定位置上显示一个字符 * @param Line 行位置,范围:1~2 * @param Column 列位置,范围:1~16 * @param Char 要显示的字符 * @retval 无 */ void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char) { LCD_SetCursor(Line,Column); LCD_WriteData(Char); } /** * @brief 在LCD1602指定位置开始显示所给字符串 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param String 要显示的字符串 * @retval 无 */ void LCD_ShowString(unsigned char Line,unsigned char Column,char *String) { unsigned char i; LCD_SetCursor(Line,Column); for(i=0;String[i]!='\0';i++) { LCD_WriteData(String[i]); } } /** * @brief 返回值=X的Y次方 */ int LCD_Pow(int X,int Y) { unsigned char i; int Result=1; for(i=0;i<Y;i++) { Result*=X; } return Result; } /** * @brief 在LCD1602指定位置开始显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~65535 * @param Length 要显示数字的长度,范围:1~5 * @retval 无 */ void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0'); } } /** * @brief 在LCD1602指定位置开始以有符号十进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:-32768~32767 * @param Length 要显示数字的长度,范围:1~5 * @retval 无 */ void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length) { unsigned char i; unsigned int Number1; LCD_SetCursor(Line,Column); if(Number>=0) { LCD_WriteData('+'); Number1=Number; } else { LCD_WriteData('-'); Number1=-Number; } for(i=Length;i>0;i--) { LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0'); } } /** * @brief 在LCD1602指定位置开始以十六进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~0xFFFF * @param Length 要显示数字的长度,范围:1~4 * @retval 无 */ void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i,SingleNumber; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { SingleNumber=Number/LCD_Pow(16,i-1)%16; if(SingleNumber<10) { LCD_WriteData(SingleNumber+'0'); } else { LCD_WriteData(SingleNumber-10+'A'); } } } /** * @brief 在LCD1602指定位置开始以二进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~1111 1111 1111 1111 * @param Length 要显示数字的长度,范围:1~16 * @retval 无 */ void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0'); } }
LCD1602.h
#ifndef __LCD1602_H__ #define __LCD1602_H__ //用户调用函数: void LCD_Init(); void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char); void LCD_ShowString(unsigned char Line,unsigned char Column,char *String); void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length); void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); #endif
main.c
#include <REGX52.H> #include"LCD1602.h" #include"IR.h" unsigned char Num; unsigned char Address; unsigned char Command; void main() { LCD_Init(); LCD_ShowString(1,1,"ADDR CMD NUM"); LCD_ShowString(2,1,"00 00 000"); IR_Init(); //NEC编码初始化 while(1) { if(IR_GetDataFlag()||IR_GetRepeatFlag()) //数据帧或者重复帧 { Address=IR_GetAddress(); //获取遥控器地址吗 Command=IR_GetCommand(); //获取遥控器命令吗 LCD_ShowHexNum(2,1,Address,2); LCD_ShowHexNum(2,7,Command,2); if(Command==IR_VOL_MINUS) { Num--; } if(Command==IR_VOL_ADD) { Num++; } LCD_ShowNum(2,12,Num,3); } } }
通过判断标志位是否为1来表示是否有遥控信息传输