51单片机学习--红外遥控(外部中断)

51单片机学习--红外遥控(外部中断)_第1张图片


需要利用下面这个红外接收头,OUT口会发出红外信号对应的高低电平,由于发送的速度很快,所以需要把OUT引脚接在外部中断引脚上,当OUT一旦产生下降沿,马上进中断,这样响应会更及时。
51单片机学习--红外遥控(外部中断)_第2张图片


51单片机学习--红外遥控(外部中断)_第3张图片
51单片机学习--红外遥控(外部中断)_第4张图片
外部中断引脚位于P3_2和P3_3,我的开发板把OUT接在了P3_2,利用的是下降沿触发。
外部中断比定时器中断和串口中断要简洁一些,这里使用外部中断0(Int0),当IT0=1就是下降沿触发,IT0=0就是低电平触发。
IE0为中断标志位,EX0为此中断的使能,EA为所有中断的使能,PX0设置优先级。
外部中断1(Int1)也是同理。
图中红框框出的才是控制外部中断的。

51单片机学习--红外遥控(外部中断)_第5张图片
首先来配置外部中断:

void Int0_Init(void)
{
	//配置外部中断
	IT0 = 1;      //选择下降沿触发
	IE0 = 0;      //中断标志位
	EX0 = 1;      //外部中断0使能
	EA = 1;       //中断使能
	PX0 = 1;      //优先级
}




/* @brief 外部中断函数
void Int0_Routine(void) interrupt 0
{
	
}
*/


51单片机学习--红外遥控(外部中断)_第6张图片


高低电平的组合、持续时长构成了NEC编码:下面的波形就是OUT口会输出的波形。
Data中的反码可以进行数据验证。
在遥控器上按下一个按键之后,先发送一个start波形,然后发送Data,如果按住按键一直不放手,每过110ms就会发送一次repeat,相当于连续按键功能。

51单片机学习--红外遥控(外部中断)_第7张图片
这里遥控器按键的命令码如下:
51单片机学习--红外遥控(外部中断)_第8张图片


接下来编写红外解码的程序,主要的思路是:
建立一个IR.c红外解码模块,这个模块中利用Int0.c和Timer0.c来进行解码。
具体的解码方式:
首先定义一个变量表示当前的状态,用0来表示空闲状态,当收到下降沿时,转为1状态(1状态定义为:分辨是start还是repeat)并将定时器打开,当1状态又收到一个下降沿时,读出定时器的时长判断是start还是repeat。
如果判断为start,转为2状态(2状态定义为:开始解码32bit的Data)2状态会执行32次,数据读完之后回到0状态
如果判断为repeat,则把重发标志位的变量置1并转回0状态



所以首先要对之前写的Timer0模块进行一些改写。这次的目的不是让定时器计数进中断了,而是让它单纯的计时,下面就是改写后的Timer0.c,可以设定计时器的初始值,返回计时器的实时值,以及控制计时器的开关,计时器的返回值每多1就代表多走过了1us

#include 


void Timer0_Init(void)		//1毫秒@11.0592MHz
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	
	TF0 = 0;		//清除TF0标志
	TR0 = 0;		//定时器0不计时
}


//把16位值放入计数器
void Timer0_SetCounter(unsigned int Value)
{
	TH0 = Value/256;
	TL0 = Value%256;
}

//返回计数器的值
unsigned int Timer0_GetCounter(void)
{
	return (TH0<<8) | TL0;
}

//选择计时器是否启动,Flag为1则启动
void Timer0_Run(unsigned char Flag)
{
	TR0 = Flag;
}


接下来就可以编写IR.c了,要注意IR.c中包含了Int0的外部中断函数:
参考之前说的具体的解码方式:
首先定义一个变量表示当前的状态,用0来表示空闲状态,当收到下降沿时,转为1状态(1状态定义为:分辨是start还是repeat)并将定时器打开,当1状态又收到一个下降沿时,读出定时器的时长判断是start还是repeat。
如果判断为start,转为2状态(2状态定义为:开始解码32bit的Data)2状态会执行32次,数据读完之后回到0状态
如果判断为repeat,则把重发标志位的变量置1并转回0状态

#include 
#include "Timer0.h"
#include "Int0.h"

unsigned int IR_Time;  //计时时长
unsigned char IR_State; //状态

unsigned char IR_Data[4]; //分别表示Data的四个字节
unsigned char IR_pData;   //代表每个个字节的对应位 0~31

unsigned char IR_DataFlag; //数据接收完成标志位
unsigned char IR_RepeatFlag; //重发标志位
unsigned char IR_Address;
unsigned char IR_Command;


void IR_Init(void)
{
	Timer0_Init();
	Int0_Init();
}

unsigned char IR_GetDataFlag(void)
{
	if(IR_DataFlag)
	{
		IR_DataFlag = 0;
		return 1;
	}
	else return 0;
}

unsigned char IR_GetRepeatFlag(void)
{
	if(IR_RepeatFlag)
	{
		IR_RepeatFlag = 0;
		return 1;
	}
	else return 0;
}

unsigned char IR_GetAddress(void)
{
	return IR_Address;
}

unsigned char IR_GetCommand(void)
{
	return IR_Command;
}



void Int0_Routine(void) interrupt 0
{
	if(IR_State == 0) //空闲
	{
		Timer0_SetCounter(0);
		Timer0_Run(1);
		IR_State = 1;
	}
	else if(IR_State == 1) //判断是start/repeat
	{
		IR_Time = Timer0_GetCounter();
		Timer0_SetCounter(0);
		
		if(IR_Time > 13500-500 && IR_Time < 13500+500)
		{//start
			IR_State = 2;
		}
		else if(IR_Time > 11250-500 && IR_Time < 11250+500)
		{//repeat
			IR_RepeatFlag = 1;
			Timer0_Run(0);
			IR_State = 0;
		}
	}
	else if(IR_State == 2) //读取Data
	{
		IR_Time = Timer0_GetCounter();
		Timer0_SetCounter(0);
		
		if(IR_Time > 1120-500 && IR_Time < 1120+500)
		{//对应位为0
			IR_Data[(IR_pData/8)] &= ~(0x01<<(IR_pData%8));
			IR_pData ++;
		}
		else if(IR_Time > 2250-500 && IR_Time < 2250+500)
		{//对应位为1
			IR_Data[IR_pData/8] |= (0x01<<(IR_pData%8));
			IR_pData ++;
		}
		else
		{//接收到错误信号则重新接收
			IR_pData = 0;
			IR_State = 1;
		}
		
		if(IR_pData >= 32)
		{//Data已收完
			IR_pData = 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;
			}
			Timer0_Run(0);
			IR_State = 0;
		}
		
	}
	
	
}

Command和Address要想在main中调用,就得封装成相应的Get函数,然后对应红外遥控器按键的键码可以用宏定义,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(void);
unsigned char IR_GetDataFlag(void);
unsigned char IR_GetRepeatFlag(void);
unsigned char IR_GetAddress(void);
unsigned char IR_GetCommand(void);

#endif


最后在main.c中调用这些函数即可,

#include 
#include "Delay.h"
#include "LCD1602.h"
#include "IR.h"


unsigned char Num;
unsigned char Address, 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) Num --;
			if(Command == IR_VOL_ADD) Num ++;
			
			LCD_ShowNum(2, 12, Num, 3);
		}
	}
}

51单片机学习--红外遥控(外部中断)_第9张图片

你可能感兴趣的:(51单片机,单片机,51单片机,学习,嵌入式硬件)