打印
[经验分享]

红外遥控(基于51单片机)

[复制链接]
101|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2025-1-7 11:19 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
前言
这是在看完江科大的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

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2048

主题

15996

帖子

15

粉丝