需要利用下面这个红外接收头,OUT口会发出红外信号对应的高低电平,由于发送的速度很快,所以需要把OUT引脚接在外部中断引脚上,当OUT一旦产生下降沿,马上进中断,这样响应会更及时。
外部中断引脚位于P3_2和P3_3,我的开发板把OUT接在了P3_2,利用的是下降沿触发。
外部中断比定时器中断和串口中断要简洁一些,这里使用外部中断0(Int0),当IT0=1就是下降沿触发,IT0=0就是低电平触发。
IE0为中断标志位,EX0为此中断的使能,EA为所有中断的使能,PX0设置优先级。
外部中断1(Int1)也是同理。
图中红框框出的才是控制外部中断的。
首先来配置外部中断:
void Int0_Init(void)
{
//配置外部中断
IT0 = 1; //选择下降沿触发
IE0 = 0; //中断标志位
EX0 = 1; //外部中断0使能
EA = 1; //中断使能
PX0 = 1; //优先级
}
/* @brief 外部中断函数
void Int0_Routine(void) interrupt 0
{
}
*/
高低电平的组合、持续时长构成了NEC编码:下面的波形就是OUT口会输出的波形。
Data中的反码可以进行数据验证。
在遥控器上按下一个按键之后,先发送一个start波形,然后发送Data,如果按住按键一直不放手,每过110ms就会发送一次repeat,相当于连续按键功能。
这里遥控器按键的命令码如下:
接下来编写红外解码的程序,主要的思路是:
建立一个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 <REGX52.H>
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 <REGX52.H>
#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 <REGX52.H>
#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);
}
}
}