目录
红外遥控简介
硬件电路
基本发送与接收
NEC通信协议
简单的叙述一下代码思路:
遥控建码
51单片机的外部中断
STC89C52的外部中断有两种触发方式:
? ? ? ? ? ? ? ? ??下降沿触发和低电平触发
外部中断寄存器??
?代码示例:
主函数:
IR红外函数:
定时器:
外部中断函数:
LCD1602函数:
红外遥控简介
红外遥控是利用红外光进行通信的设备,由红外LED将调制后的信号发出,由专用的红外接收头进行解调输出
通信方式:
单工(只能由一方发送数据到另一方,即遥控器到设备)
异步(双方各自约定通信频率) ? ? ? ? ? ? ? ?
红外LED波长:940nm(这个一定要精准,否则就是红外波)
通信协议标准:NEC标准(相当于之前的时序,较为重要)
硬件电路
基本发送与接收
注意区分空闲状态与发送低电平的区别:
NEC通信协议
这里红外接受器接收数据的时间是以us为单位的,证明它对于时间的要求还是很高的,所以我们之前用定时器扫描数码管,设置PWM等等是不能和红外接受器的计时器一起来使用的,因为红外发送的信号来的时间很短,如果定时器在这个时候调用到别的函数中了,那么这时就不会接受到信号了。所以在配置的时候一般将红外函数单独配置一个定时器来计时;
还有一点就是我们在计时的时候,要调用定时器才能进行计时,还有执行每一条语句的时候也是需要时间的,所以在对时间的范围进行判断的时候我们不能那么死板用“==”来判断,应该考虑一个范围在误差允许的范围内进行判断:
我们这个代码在写的时候就是遵循的上下500ms或者500us来考虑的
例如:逻辑“0”就是判断(IR_Time>1120-500 && IR_Time<1120+500)
然后逻辑“1”是判断(IR_Time>2250-500 && IR_Time<2250+500)
?简单的叙述一下代码思路:
1,设置3种状态:状态0(空闲状态)<--->状态1(判断开始或者重复)<--->状态2(接受32位数据)
2,从状态0开始,在状态0的时候,将计时器清零并进行计时,这时候变为状态1,并读出状态0到状态1的时间,进行判断时Start状态还是Repeat状态。
3,判断如果为Start状态,那就将状态置为状态2
4,判断如果为Repeat状态,那就将状态置为状态0,并关闭定时器
5,判断如果时间都不对,那么将状态重新置为1,继续执行次状态下的语句
6,进入状态2后,读取计时器时间进行判断是逻辑“0”还是逻辑“1”
如果时间在范围内那么将数据存入一个四个变量的数组,一个变量存储8位,刚刚好32位。
否则证明接受错误,将数组清零,然后状态置1
7,存储完毕后将数据进行验证:要求第一个变量和第二个变量的反码以及第三个变量和第四个变量的反码相同,验证才算成功
8,将数组第一个变量值存入地址码,第三个变量值存入命令码
9,最后将定时器停止,后将状态置0
利用红外线检测仪,检测了一段红外线波如下:
遥控建码
51单片机的外部中断
STC89C52有4个外部中断:
STC89C52的外部中断有两种触发方式:
下降沿触发和低电平触发
下降沿触发:数字电路中,数字电平从高电平(数字“1”)变为低电平(数字“0”)的那一瞬间叫作下降沿。下降沿触发是当信号有下降沿时的开关动作,当电位由高变低而触发输出变化的就叫下降沿触发。也就是当测到的信号电位是从高到低也就是下降时就触发,叫做下降沿触发。
简而言之指的是电平下降的时候触发,例如按键按下的时候,按下几次触发几次。
低电平触发:在低电平的时候会一直触发,直到电平转化为高电平的时候,例如按键按下不松手,那么一直是低电平,则一直会触发,直到松开按键
中断号:
外部中断寄存器?
?代码示例:
主函数:
#include <REGX52.H>
#include "Delay.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();
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) //如果遥控器VOL-按键按下
{
Num--; //Num自减
}
if(Command==IR_VOL_ADD) //如果遥控器VOL+按键按下
{
Num++; //Num自增
}
LCD_ShowNum(2,12,Num,3); //显示Num
}
}
}
IR红外函数:
#include <REGX52.H>
#include "Timer0.h"
#include "Int0.h"
unsigned int IR_Time;
unsigned char IR_State;
unsigned char IR_Data[4];//用long型的储存32位在实际操作中发现,long型数据在使用位移符时,位移大于20时会出现错误,所以用char类型定义4个变量,4x8一共构成32位
unsigned char IR_pData;//数据位置1--4,
unsigned char IR_DataFlag;
unsigned char IR_RepeatFlag;
unsigned char IR_Address;
unsigned char IR_Command;
/**
* @brief 红外遥控初始化
* @param 无
* @retval 无
*/
void IR_Init(void)
{
Timer0_Init();
Int0_Init();
}
/**
* @brief 红外遥控获取收到数据帧标志位
* @param 无
* @retval 是否收到数据帧,1为收到,0为未收到
*/
unsigned char IR_GetDataFlag(void)
{
if(IR_DataFlag)
{
IR_DataFlag=0;
return 1;
}
return 0;
}
/**
* @brief 红外遥控获取收到连发帧标志位
* @param 无
* @retval 是否收到连发帧,1为收到,0为未收到
*/
unsigned char IR_GetRepeatFlag(void)
{
if(IR_RepeatFlag)
{
IR_RepeatFlag=0;
return 1;
}
return 0;
}
/**
* @brief 红外遥控获取收到的地址数据
* @param 无
* @retval 收到的地址数据
*/
unsigned char IR_GetAddress(void)
{
return IR_Address;
}
/**
* @brief 红外遥控获取收到的命令数据
* @param 无
* @retval 收到的命令数据
*/
unsigned char IR_GetCommand(void)
{
return IR_Command;
}
//状态0初始化计时器。状态1,判断Start信号或Repeat信号。状态2接受信号出错后回到状态1
//状态0-->状态1<-->状态2-->状态0
//**外部中断0中断函数,*下降沿*触发执行**
void Int0_Routine(void) 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
//如果计时为13.5ms,(这里加减500us来允许可能存在的误差)则接收到了Start信号(判定值在12MHz晶振下为13500,在11.0592MHz晶振下为12442)
if(IR_Time>13500-500 && IR_Time<13500+500)
{
IR_State=2; //置状态为2,准备接受数据
}
//如果计时为11.25ms,则接收到了Repeat信号(判定值在12MHz晶振下为11250,在11.0592MHz晶振下为10368)
else if(IR_Time>11250-500 && IR_Time<11250+500)
{
IR_RepeatFlag=1; //置收到连发帧标志位为1
Timer0_Run(0); //定时器停止
IR_State=0; //置状态为0空闲状态
}
else //接收出错(也可以不写这一句,等待中断函数下一次执行即可)
{
IR_State=1; //置状态为1
}
}
else if(IR_State==2) //状态2,接收数据
{
IR_Time=Timer0_GetCounter(); //*获取上一次中断到此次中断的时间
Timer0_SetCounter(0); //*定时计数器清0
//如果计时为1120us,则接收到了数据0(判定值在12MHz晶振下为1120,在11.0592MHz晶振下为1032)
if(IR_Time>1120-500 && IR_Time<1120+500)//数据对应位置0
{
IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8));//除法与求余运算使得随着IR_pData++;可以实现自动遍历数组
IR_pData++; //数据位置指针自增
}
//如果计时为2250us,则接收到了数据1(判定值在12MHz晶振下为2250,在11.0592MHz晶振下为2074)
else if(IR_Time>2250-500 && IR_Time<2250+500)//数据对应位置1
{
IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8));//数据对应位清0(注意取反符号)
IR_pData++; //数据位置指针自增
}
else //接收出错
{
IR_pData=0; //出错后将数据位置指针清0以及状态置为1都是有必要的操作
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]; //转存数据(1,3位是反码,不转存)
IR_Command=IR_Data[2];
IR_DataFlag=1; //置收到连发帧标志位为1
}
Timer0_Run(0); //定时器停止
IR_State=0; //置状态为0
}
}
}
定时器:
#include <REGX52.H>
/**
* @brief 定时器0初始化
* @param 无
* @retval 无
*/
void Timer0_Init(void)
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0; //设置定时初值
TH0 = 0; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 0; //定时器0不计时
}
/**
* @brief 定时器0设置计数器值
* @param Value,要设置的计数器值,范围:0~65535
* @retval 无
*/
void Timer0_SetCounter(unsigned int Value)
{
TH0=Value/256;
TL0=Value%256;
}
/**
* @brief 定时器0获取计数器值
* @param 无
* @retval 计数器值,范围:0~65535
*/
unsigned int Timer0_GetCounter(void)
{
return (TH0<<8)|TL0;
}
/**
* @brief 定时器0启动停止控制
* @param Flag 启动停止标志,1为启动,0为停止
* @retval 无
*/
void Timer0_Run(unsigned char Flag)
{
TR0=Flag;//定时器0计时
}
外部中断函数:
#include <REGX52.H>
/**
* @brief 外部中断0初始化
* @param 无
* @retval 无
*/
void Int0_Init(void)
{
IT0=1;//设置为低电平触发
IE0=0;//中断标志位
EX0=1;
EA=1;
PX0=1;//中断系统中优先级设置为高(不能被打断)
}
LCD1602函数:
#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 无
* @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 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]);
}
}
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 要显示的数字,范围: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');
}
}
}
|