前言
这是在看完江科大的51单片机的红外遥控那部分写的一些心得笔记,这一部分是关于红外遥控的。我们都知道,按下遥控器,电视台就会有相应的频道转换,那他究竟是如何实现的呢,让我们来一探究竟吧。
红外遥控的简介
红外遥控是利用红外光进行通信的设备,由红外LED将调制后的信号发出,由专用的红外接收头进行解调输出
· 通信方式:单工,异步
· 红外LED波长:940nm(日常使用的还有800多的)
· 通信协议标准:NEC标准
总共需要用到什么?
unsigned int IR_Time; //计数
unsigned char IR_State; //显示当前状态
unsigned char IR_Data[4]; //将总共32位的data分成4个8位的(char是一个字节,占8位)
unsigned char IR_pData; //数据位(总共有32位)
unsigned char IR_DataFlag; //
unsigned char IR_RepeatFlag; //repeat已完成标志位
unsigned char IR_Address; //地址
unsigned char IR_Command; //命令
红外遥控基本流程
初始化
发送数据
接收数据
1.初始化
外部中断初始化
要用到外部中断,因为红外遥控的信号太快了,如果不给中断的话,MCU根本不会接收到这个信息
我们就使用外部中断0,下面是外部中断的代码
void Int0_Init(void)
{
IT0=1;
IE0=0;
EX0=1;
EA=1;
PX0=1;
}
计数初始化
然后启用定时器/计数器0的计数功能,通过计算本次中断到下次中断的时间判断是Start信号还是Repeat信号
#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;
}
模块化初始化的过程
void IR_Init(void)
{
Timer0_Init();
Int0_Init();
}
2.发送数据
先明确一点:
空闲状态:红外LED不亮,接收头输出高电平
发送低电平:红外LED以38KHz频率闪烁发光,接收头输出低电平
发送高电平:红外LED不亮,接收头输出高电平
开始(Start)
用if判断一下,目前是否是空闲状态,是则从初始的空闲状态(高电平)变成低电平
if(IR_State==0) //状态0,空闲状态
{
Timer0_SetCounter(0); //定时计数器清0
Timer0_Run(1); //定时器启动
IR_State=1; //置状态为1
}
基于晶振为12MHZ(下面会用到)
start信号是9ms的低电平+4.5ms的高电平,总时长为13.5ms=13500us
repeat信号是9ms的低电平+2.25ms的高电平,总时长为11.25ms=11250us
因为计数器是每1us计数一次,所以我们需要把ms转换成us
发送Data(地址位,地址校验位,命令位,命令校验位)
用if判断一下,看看是否处于低电平的状态,然后再进行发送data的操作
else if(IR_State==1)
已经进入到发送数据的操作,但我们需要判断一下我们收到的是Start信号还是Repeat信号,这样我们才能进行相应的操作,上面我们提到过使用定时器/计数器0来进行计数,作用就在这里,但是这个信号的时间段不一定就是13.5ms/11.25ms,它会有误差,所以我们应该取个在这个时间的一小段区间,而不是它的准确值,这里我们选择领域值为500us。为了下次信号发送的方便,我们在得到计数器的计数值后要记得清零。
else if(IR_State==1) //状态1,等待Start信号或Repeat信号
{
IR_Time=Timer0_GetCounter(); //获取上一次中断到此次中断的时间
Timer0_SetCounter(0); //定时计数器清0
//如果计时为13.5ms,则接收到了Start信号(判定值在12MHz晶振下为13500,在11.0592MHz晶振下为12442)
if(IR_Time>12442-500 && IR_Time<12442+500)
{
IR_State=2; //置状态为2
}
//如果计时为11.25ms,则接收到了Repeat信号(判定值在12MHz晶振下为11250,在11.0592MHz晶振下为10368)
else if(IR_Time>10368-500 && IR_Time<10368+500)
{
IR_RepeatFlag=1; //置收到连发帧标志位为1
Timer0_Run(0); //定时器停止
IR_State=0; //置状态为0
}
这一步之后,如果收到的是Start信号,IR_State=2。
如果收到的是Repeat信号,IR_RepeatFlag=1,表明要进入repeat这个流程,把定时器置0是为了表明已经接收到一帧数据,也就是一个分段的信号嘛,让自己能懂,当然不加也没有关系,最后IR_State=0,回到空闲状态。
这一步是防止程序出错的,如果出错了,那就让他继续搜寻起始信号,即回到else if那里
else //接收出错
{
IR_State=1; //置状态为1
}
注:这个图来自别的博主,懒得找了,如有侵权联系我,会删
3.接收数据
数据帧
/**
* @brief 红外遥控获取收到数据帧标志位
* @param 无
* @retval 是否收到数据帧,1为收到,0为未收到
*/
unsigned char IR_GetDataFlag(void)
{
if(IR_DataFlag)
{
IR_DataFlag=0;
return 1;
}
return 0;
}
数据连发帧(repeat)
/**
* @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信号一样。
接收0:560us+560us=1120us
接收1:560us+1690us=2250us
容易知道,二者相差1690-560=1130us,所以我们还是像之前一样取500为区间的话还是可以的(500+500<1130)
接收0
if(IR_Time>1032-500 && IR_Time<1032+500)
{
IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); //数据对应位清0
IR_pData++; //数据位置指针自增
}
在这里“&=~”是将数据对应位清0的操作,我们现在不是要接收数据嘛,总共有32位,我们想着用4个IR_Data来接收,我们把IR_Data定义为了char类型(一个字节8位),这样每一个就装8位,4个就能装32位了。
当这个数据位是数据0时,我们就把它给清0。
接收1
//如果计时为2250us,则接收到了数据1(判定值在12MHz晶振下为2250,在11.0592MHz晶振下为2074)
else if(IR_Time>2074-500 && IR_Time<2074+500)
{
IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8)); //数据对应位置1
IR_pData++; //数据位置指针自增
}
跟之前一样,接收也有可能有错误,为了预防这种情况,我们把这种例外也写一下。如果有问题,那这个Data就有问题,就不收这个Data了,它作废了,改收另外的Data
else //接收出错
{
IR_pData=0; //数据位置指针清0
IR_State=1; //置状态为1
}
1
2
3
4
5
收到了数据之后
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
}
最后的程序代码如下
main.c
#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.c
#include <REGX52.H>
#include "Timer0.h"
#include "Int0.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;
/**
* @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中断函数,下降沿触发执行
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,则接收到了Start信号(判定值在12MHz晶振下为13500,在11.0592MHz晶振下为12442)
if(IR_Time>12442-500 && IR_Time<12442+500)
{
IR_State=2; //置状态为2
}
//如果计时为11.25ms,则接收到了Repeat信号(判定值在12MHz晶振下为11250,在11.0592MHz晶振下为10368)
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; //置状态为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>1032-500 && IR_Time<1032+500)
{
IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); //数据对应位清0
IR_pData++; //数据位置指针自增
}
//如果计时为2250us,则接收到了数据1(判定值在12MHz晶振下为2250,在11.0592MHz晶振下为2074)
else if(IR_Time>2074-500 && IR_Time<2074+500)
{
IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8)); //数据对应位置1
IR_pData++; //数据位置指针自增
}
else //接收出错
{
IR_pData=0; //数据位置指针清0
IR_State=1; //置状态为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
}
}
}
Timer0.c
#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;
}
Int0.c
#include <REGX52.H>
/**
* @brief 外部中断0初始化
* @param 无
* @retval 无
*/
void Int0_Init(void)
{
IT0=1;
IE0=0;
EX0=1;
EA=1;
PX0=1;
}
/*外部中断0中断函数模板
void Int0_Routine(void) interrupt 0
{
}
*/
学习之路道阻且长,一个人可以走的更快,一群人可以走的更远,如果你对本篇文章有所想法,都可以提出来,很开心可以和大家一块交流
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/blow_fine/article/details/144670320
|