🎀 文章作者:二土电子
🐸 期待大家一起学习交流!
一、红外遥控简介
红外遥控,顾名思义,就是利用红外线实现遥控。这里就不单独对红外线做介绍了,红外线的波长再可见光范围外,所以人眼是看不到的。红外遥控的原理就是利用红外线进行通讯,比如生活中常用的电视遥控器,空调遥控器等,大多都是红外通讯实现的遥控功能。
二、红外遥控的原理
红外遥控是一种非接触,无线控制技术。具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等特点。红外遥控系统一般有红外发射装置和红外接收设备两大部分组成。
- 红外发射装置就是我们常见的遥控器,由键盘电路,红外编码电路,电源电路和红外发射电路组成。
- 红外接收设备是由红外接收电路,红外解码,电源和应用电路组成。红外接收装置有三个引脚,VDD,GND和数据输出VOUT。通常正对接收头凸起处看,从左到右引脚顺序为VOUT,GND,VDD
通常红外遥控为了提高抗干扰性能和降低电源消耗,红外遥控器常用载波的方式传送二进制编码,常用的载波频率为38kHz,这是由发射端所使用的455kHz晶振来决定的。在发射端要对晶振进行整数分频,分频系数一般取12,所以为38kHz。也有一些遥控系统采用36kHz、40 kHz、56 kHz等,一般由发射端晶振的振荡频率来决定。所以,通常的红外遥控器是将遥控信号(二进制脉冲码) 调制在38KHz的载波上,经缓冲放大后送至红外发光二极管,转化为红外信号发射出去的。
三、二进制脉冲编码
二进制脉冲码的形式有多种,其中最为常用的是NEC Protocol 的PWM码(脉冲宽度调制)和 Philips RC-5 Protoco 的 PPM码(脉冲位置调制码,脉冲串之间的时间间隔来实现信号调制)。如果要开发红外接收设备,一定要知道红外遥控器的编码方式和载波频率,才可以选取一体化红外接收头和制定解码方案。这里针对NEC编码形式做一个详细介绍。NEC编码形式有以下特点
- 8 位地址和 8 位指令长度
- 地址和命令 2次传输(确保可靠性)
- PWM 脉冲位置调制,以发射红外载波的占空比代表“ 0”和“1”
- 载波频率为 38Khz
- 位时间为 1.125ms 或 2.25ms
3.1 NEC码的位定义
一个脉冲对应 560us 的连续载波,一个逻辑1传输需要 2.25ms(560us脉冲+1680us 低电平) ,一个逻辑 0的传输需要 1.125ms (560us脉冲+560us 低电平)。而红外接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,我们在接收头端收到的信号为: 逻辑 1应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。所以可以通过计算高电平时间判断接收到的数据是0还是1。NEC码位定义时序图如下
3.2 NEC遥控指令的数据格式
NEC遥控指令的数据格式为:引导码、地址码、地址反码、控制码控制反码。引导码由一个 9ms 的低电平和一个 4.5ms 的高电平组成地址码、地址反码、控制码、控制反码均是8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性 (可用于校验)。数据格式如下
NEC码还规定了连发码(由 9ms 低电平+2.5m 高电平+0.5ms 低电平+97.94ms 高电平组成),如果在一帧数据发送完毕之后,红外遥控器按键仍然没有放开,则发射连发码,可以通过统计连发码的次数来标记按键按下的长短或次数。
四、红外遥控程序设计思路
红外发射装置只需要按键按下即可产生红外信号,我们只需要针对红外接收设备编写程序即可。上面介绍了,红外接收设备在收到脉冲的时候为低电平,在没有脉冲的时候为高电平。根据“0”和“1”的时序图可知,我们只需要监测红外接收设备的数据输出引脚的高电平持续时间就可以判断接收到的是“0”还是“1”。
另外,没有按键按下时,也就是没有发红外信号,没有脉冲,红外接收设备的数据输出引脚一直为高电平。只有接收到脉冲时,说明有按键按下,此时红外接收设备的数据输出引脚为低电平。因此,可以利用外部中断的下降沿出发来判断是否有按键按下,在中断中测量高电平持续时间来判断接收到的是“0”还是“1”。
五、红外遥控程序设计
5.1 红外遥控初始化程序
需要初始化GPIO和外部中断,GPIO配置为上拉输入模式
/*
*==============================================================================
*函数名称:Drv_Hw_Init
*函数功能:初始化红外遥控模块
*输入参数:无
*返回值:无
*备 注:红外端口初始化函数,时钟端口及外部中断初始化
*==============================================================================
*/
void Drv_Hw_Init (void)
{
// 结构体定义
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);
// 配置GPIO结构体
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11; // 红外接收
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; // 上拉输入模式
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource11); // 选择GPIO管脚用作外部中断线路
EXTI_ClearITPendingBit(EXTI_Line11);
// 配置外部中断
EXTI_InitStructure.EXTI_Line=EXTI_Line11;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 配置NVIC结构体
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级为0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 响应优先级为1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能
NVIC_Init(&NVIC_InitStructure);
}
5.2 记录高电平持续时间函数
利用记录延时次数的方法测量高电平持续时间
/*
*==============================================================================
*函数名称:Drv_Hw_RecordHightTime
*函数功能:记录高电平持续时间并返回
*输入参数:无
*返回值:t:高电平持续时间
*备 注:无
*==============================================================================
*/
u8 Drv_Hw_RecordHightTime (void)
{
u8 t = 0;
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == 1) // 高电平
{
t ++;
delay_us(20);
if(t >= 250)
{
return t; // 超时溢出
}
}
return t;
}
5.3 中断服务函数
中断服务函数中接收数据,接收完成后对应的接收完成标志位置1
/*
*==============================================================================
*函数名称:EXTI15_10_IRQHandler
*函数功能:外部中断服务函数
*输入参数:无
*返回值:无
*备 注:无
*==============================================================================
*/
void EXTI15_10_IRQHandler(void) // 红外遥控外部中断
{
u8 Tim = 0,Ok = 0,Data,Num = 0;
while(1)
{
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == 1)
{
Tim = Drv_Hw_RecordHightTime(); //获得此次高电平时间
if(Tim>=250)
{
break; // 不是有用的信号
}
if(Tim >= 200 && Tim < 250)
{
Ok = 1; // 收到起始信号
}
else if(Tim >= 60 && Tim < 90)
{
Data = 1; // 收到数据 1
}
else if(Tim >= 10 && Tim < 50)
{
Data = 0; // 收到数据 0
}
if(Ok == 1)
{
hw_jsm <<= 1;
hw_jsm += Data;
// 接收完成
if(Num >= 32)
{
hw_jsbz = 1;
break;
}
}
Num ++;
}
}
// 清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line11);
}
5.4 读取键值
判断键盘上按下的是哪个按键。不同的遥控器,按键对应的红外编码可能不同,可以通过串口打印接收到的键值的方法来读出每一个按键的键值。
/*
*==============================================================================
*函数名称:Med_Hw_ReadKeyValue
*函数功能:读取键值
*输入参数:无
*返回值:红外键值
*备 注:每一个键值是测试出来的,不同遥控器键值可能不同
*==============================================================================
*/
u8 Med_Hw_ReadKeyValue (void)
{
if(hw_jsm == 0x00FF9867)
{
return 0;
}
else if(hw_jsm == 0x00FFA25D)
{
return 1;
}
else if(hw_jsm == 0x00FF629D)
{
return 2;
}
else if(hw_jsm == 0x00FFE21D)
{
return 3;
}
else if(hw_jsm == 0x00FF22DD)
{
return 4;
}
else if(hw_jsm == 0x00FF02FD)
{
return 5;
}
else if(hw_jsm == 0x00FFC23D)
{
return 6;
}
else if(hw_jsm == 0x00FFE01F)
{
return 7;
}
else if(hw_jsm == 0x00FFA857)
{
return 8;
}
else if(hw_jsm == 0x00FF906F)
{
return 9;
}
else if(hw_jsm == 0x00FF6897) // 按键*按下
{
return 10;
}
else if(hw_jsm == 0x00FFB04F) // 按键#按下
{
return 11;
}
else if(hw_jsm == 0x00FF38C7) // 按键OK按下
{
return 12;
}
else if(hw_jsm == 0x00FF18E7) // 按键"上"按下
{
return 13;
}
else if(hw_jsm == 0x00FF4AB5) // 按键"下"按下
{
return 14;
}
else if(hw_jsm == 0x00FF10EF) // 按键"左"按下
{
return 15;
}
else if(hw_jsm == 0x00FF5AA5) // 按键"右"按下
{
return 16;
}
return 17;
}
5.5 参数定义
定义了以下两个参数
u32 hw_jsm; // 定义一个32位数据变量,保存接收码
u8 hw_jsbz; // 定义一个8位数据的变量,用于指示接收标志
六、应用实例
利用红外遥控控制LED的亮灭,按下“1”点亮,按下“0”熄灭。每次执行完对应按键的内容后,需要清除接收完成标志位和接收码。main函数如下
u8 gHwKeyValue = 0; // 红外键值
int main(void)
{
Med_Mcu_Iint(); // 系统初始化
while(1)
{
if (hw_jsbz == 1)
{
// 获取红外键值
gHwKeyValue = Med_Hw_ReadKeyValue();
// 按下按键“1”
if (gHwKeyValue == 1)
{
// 点亮LED
Med_Led_StateCtrl(LED1,LED_ON);
}
// 按下按键“0”
if (gHwKeyValue == 0)
{
// 熄灭LED
Med_Led_StateCtrl(LED1,LED_OFF);
}
hw_jsbz = 0; // 清除接收完成标志位
hw_jsm = 0; // 清零接收码
}
}
}