实现一个红外遥控系统的基本要素包括红外发射器、红外接收器、解码器和单片机。
其中,红外发射器负责发送红外信号,红外接收器用来接收红外信号,解码器将红外信号解码成数字信号,而单片机则是用来处理数字信号并控制外设的。
本文章主要是实现红外遥控功能与红外遥控控制电机调速。
红外发射信号
首先,红外发射器发送的红外信号,是以38KHz的频率发送,用于区分外界自然持续发出的红外光。根据发送的数据内容不同,进行区分按下的按钮。
发送的红外信号遵循NEC红外协议编码
NEC红外编码协议具体来说,一个NEC遥控信号包含了以下内容:
- 帧头: 9ms的空闲时间,后面跟着4.5ms的载波信号,表示信号开始。
- 地址码: 由8位组成,表示接收设备的地址。可以用来区分不同的设备。
- 地址码的反码: 地址码的每一位都取反,用于校验
- 命令码: 由8位组成,表示具体的操作命令。比如开关机、音量调节等。
- 命令码的反码: 命令码的每一位都取反,用于校验。
- 结束位: 一个1.5ms的高电平,表示信号传输结束。
红外信号接收与解调
发送的红外信号被HS0038接收与解调。HS0038是一种常用的红外接收器芯片,它具有以下特点:
- 1. 工作频率为38kHz,在NEC红外协议中得到广泛应用。
- 2. 强抗干扰能力,能够滤除大部分的杂波干扰。
- 3. 高灵敏度,能够接收到较远距离的红外信号。
- 4. 具有内置解调器,能够自动将解调后的数字信号输出到引脚上,方便后续处理。
- 5. 与常用的微控制器(如8051、AVR、PIC等)兼容。
被HS0038解调后的数据被放在P32线上,发送给单片机,所以我们要通过编写程序分析在P32线上的数据,进行对遥控信号的处理,进而转为其他程序的驱动指令。
因为解调后的数据遵循NEC红外协议,所以我们只需要对信号的时间长短进行分析,则可以区分不同的信号内容。对信号时间长短进行分析,就需要使用到定时器与外部中断,定时器是记录中断前的信号时间长短,外部中断主要是在发完了一段信号后进行中断
(外部中断触发方式分为下降沿触发与低电平触发,在此处设为下降沿触发,因为在发完一段信号时,都伴随着一段下降沿,则可以在该下降沿进行中断,进而对中断前的一段信号发送的总时间进行记时,达到分析不同信号内容的目的)。
通过定时器与外部中断,就可以实现对不同段信号内容的读取。
代码实现(STC89C52)
所以我们先要分别对定时器与外部中断进行设置,以下是代码实现
定时器对一段时间的记录
#include <REGX52.H>
//定时器初始化
void Timer0Init(void)
{
TMOD &= 0xF0;
TMOD |= 0x01;
TL0 = 0;
TH0 = 0;
TF0 = 0;
TR0 = 0;
}
//设置定时器初值函数
void Timer0_SetCounter(unsigned int Value)
{
TH0 = Value/256;
TL0 = Value%256;
}
//取得在该点处的记录的时间
unsigned int Timer0_GetCounter(void)
{
return (TH0 << 8) | TL0;
}
//启动定时器
void Timer0_Run(unsigned char Flag)
{
TR0 = Flag;
}
外部中断
#include <REGX52.H>
//外部中断0初始化
void Int0_Init(void)
{
IT0 = 1; //1为下降沿驱动,0为低电平驱动
IE0 = 0;
EX0 = 1;
EA = 1;
PX0 = 1;
}
//中断函数
/*
void Int0_Routine(void) interrupt 0
{
}
*/
接下来就是在中断后对前段的信号内容进行处理,将定时器与外部中断集成在一个模块中
#include <REGX52.H>
#include "Int0.h"
#include "Timer0.h"
unsigned int IR_Time; //计时
unsigned char IR_State; //状态位
unsigned char IR_Data[4]; //零时存放32位Data数据
unsigned char IR_pData; //指向32位数据的哪个位置
unsigned char IR_DataFlag; //数据标志位
unsigned char IR_RepeatFlag; //结束标志位
unsigned char IR_Address; //存放数据
unsigned char IR_Command; //存放指令
//初始化
void IR_Init(void)
{
Timer0Init();
Int0_Init();
}
//用于判断是否取得数据
unsigned char IR_GetDataFlag(void)
{
if(IR_DataFlag)
{
IR_DataFlag = 0;
return 1;
}
return 0;
}
//判断是否结束
unsigned char IR_GetRepeat(void)
{
if(IR_RepeatFlag)
{
IR_RepeatFlag = 0;
return 1;
}
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) //计数完成后开始分析
{
IR_Time = Timer0_GetCounter();
Timer0_SetCounter(0);
if(IR_Time > 13500 - 600 && IR_Time < 13500 + 600) //检测到Start
{
//转为对Data记录处理
IR_State = 2;
}
else if(IR_Time > 11250 - 600 && IR_Time < 11250 + 600) //检测到Repeat
{
IR_RepeatFlag = 1;
Timer0_Run(0);
IR_State = 0;
}
else
{
IR_State = 1;
}
}
else if(IR_State == 2)
{
IR_Time = Timer0_GetCounter();
Timer0_SetCounter(0);
if(IR_Time > 1120 - 600 && IR_Time < 1120 + 600) //检测到0
{
//IR_Data[4]将原本的32位数据用四个八位存放,
所以要精确的找到对应数组元素的对应位数进行改变
//因为IR_pData最大只能到达32,所以IR_pData/8的范围只有0~4,,IR_pData%8的范围为0~7
IR_Data[IR_pData/8] &= ~(0x01 << (IR_pData%8)); //对应位清零
IR_pData++;
}
else if(IR_Time > 2250 - 600 && IR_Time < 2250 + 600) //检测到1
{
IR_Data[IR_pData/8] |= (0x01 << (IR_pData%8)); //对应位清零
IR_pData++;
}
else
{
IR_pData = 0;
IR_State = 1;
}
if(IR_pData >= 32)
{
IR_pData = 0;
if((IR_Data[0] == ~IR_Data[1]) && (IR_Data[2] == ~IR_Data[3]))
//如果满足NEC红外协议中的Data格式
{
IR_Address = IR_Data[0];
IR_Command = IR_Data[2];
IR_DataFlag = 1;
}
Timer0_Run(0);
IR_State = 0;
}
}
}
综上就实现了最简单的红外遥控功能
下面是通过红外遥控控制电机速度(前置知识PWM)
//定时器1初始化
#include <REGX52.H>
void Timer1Init(void)
{
TMOD &= 0x0F;
TMOD |= 0x10;
TL1 = 0xF4; //设置定时初值
TH1 = 0xFF; //设置定时初值
TF1 = 0;
TR1 = 1;
ET1 = 1;
EA = 1;
PT1 = 0;
}
//
//main.c
#include <REGX52.H>
#include "IR.h"
#include "Timer1.h"
unsigned char Counter,Compare;
unsigned char Command;
void main()
{
P1 = 0;
IR_Init();
Timer1Init();
while(1)
{
if(IR_GetDataFlag())
{
//通过接收不同的指令去执行不同的速度
Command = IR_GetCommand();
if(Command == 0x16)
{
Compare = 0;
}
if(Command == 0x0C)
{
Compare = 50;
}
if(Command == 0x18)
{
Compare = 75;
}
if(Command == 0x5E)
{
Compare = 100;
}
}
}
}
void Timer1_Rountine(void) interrupt 3
{
TL1 = 0xF4; //设置定时初值
TH1 = 0xFF; //设置定时初值
Counter++;
Counter %= 100;
if(Counter >= Compare)
{
P1_0 = 0;
}
if(Counter < Compare)
{
P1_0 = 1;
}
}
遥控器键码