@TOC
一、红外遥控简介
红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点,被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计算机系统中。由于红外线遥控不具有像无线电遥控那样穿过障碍物去控制被控对象的能力,所以,在设计红外线遥控器时,不必要像无线电遥控器那样,每套(发射器和接收器)要有不同的遥控频率或编码(否则就会隔墙控制或干扰邻居的家用电器),所以同类产品的红外线遥控器,可以有相同的遥控频率或编码,而不会出现遥控信号“串门”的情况。这对于大批量生产以及在家用电器上普及红外线遥控提供了极大的方便。由于红外线为不可见光,因此对环境影响很小,再由红外光波动波长远小于无线电波的波长,所以红外线遥控不会影响其他家用电器,也不会影响临近的无线电设备。
二、红外遥控特性
红外遥控的情景中,必定会有一个红外发射端和红外接收端。要使两者通信成功,收/发红外波长与载波频率需一致,在这里波长就是940nm,载波频率就是38kHz。 红外发射管也是属于二极管类,红外发射电路通常使用三极管控制红外发射器的导通或者截至,在导通的时候,红外发射管会发射出红外光,反之,就不会发射出红外光。虽然用肉眼看不到红外光,但是可以借助手机摄像头就能看到红外光。但是红外接收管的特性是当接收到红外载波信号时,OUT引脚输出低电平;假如没有接收到红外载波信号时,OUT引脚输出高电平。 红外载波信号其实就是由一个个红外载波周期组成。在频率为38KHz下,红外载波周期约等于26.3us(1s/38KHz≈26.3us)。在一个红外载波发射周期里,发射红外光时间8.77us和不发射红外光17.53us,发射红外光的占空比一般为1/3。相对的,整个周期内不发射红外光,就是载波不发射周期。在红外遥控器内已经把载波和不载波信号处理好,只需要做的就是识别遥控器按键发射出的信号,信号也是遵循某种协议的。
三、红外编解码协议介绍
红外遥控的编码方式目前广泛使用的是:PWM(脉冲宽度调制)的NEC协议和PhilipsPPM(脉冲位置调制)的RC-5协议的。以NEC协议为例,其特征如下:
1、8位地址和8位指令长度; 2、地址和命令2次传输(确保可靠性); 3、PWM脉冲位置调制,以发射红外载波的占空比代表“0”和“1”; 4、载波频率为38Khz; 5、位时间为1.125ms或2.25ms;
在NEC协议中,如何为协议中的数据‘0’或者‘1’?这里分开红外接收器和红外发射器。
红外发射器:
发送协议数据‘0’=发射载波信号560us+不发射载波信号560us
发送协议数据‘1’=发射载波信号560us+不发射载波信号1680us
红外发射器的位定义如下图所示:
红外接收器:
接收到协议数据‘0’=560us低电平+560us高电平
接收到协议数据‘1’=560us低电平+1680us高电平
红外接收器的位定义如下图所示:
NEC遥控指令的数据格式为:同步码头、地址码、地址反码、控制码、控制反码。同步码由一个9ms的低电平和一个4.5ms的高电平组成,地址码、地址反码、控制码、控制反码均是8位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性 (可用于校验)。除了上面的数据格式,NEC还规定了一个连发码(由9ms低电平+2.5ms高电平+0.56ms低电平+97.94ms高电平组成),如果在一帧数据发送完毕之后,按键仍然没有放开,则发射重复码,即连发码可以通过统计连发码的次数来标记按键按下的长短/次数。红外NEC协议编码如图所示:
当遥控器的按键按下时,从红外接收头端收到的波形如图所示,从图中可以看到,其地址码为0,控制码为21(正确解码后00010101)。
四、STM32F103解码接收红外信号
准备工作
STM32F103C8T6开发板,940nm波长 38kHz载波频率的红外遥控器,HX1838接收头,OLED屏幕
接线说明
| STM32F103C8T6 | HX1838接收头 |
|---|---|
| 3.3V | 接收头的+ 和 OLED的VCC |
| GND | 共地 |
| PA0 | S |
| PB8 | OLED->SCL |
| PB9 | OLED->SDA |
代码示例
HX1838.c
#include "IR.h"
#include "timer.h"
// 遥控器接收状态
//[7]:收到了引导码标志
//[6]:得到了一个按键的所有信息
//[5]:保留
//[4]:标记上升沿是否已经被捕获
//[3:0]:溢出计时器
uint8_t RmtSta = 0;
uint16_t Dval; // 下降沿时计数器的值
uint32_t RmtRec = 0; // 红外接收到的数据
uint8_t RmtCnt = 0; // 按键按下的次数
void IR_Init(void)
{
Timer_Init();
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
if (RmtSta & 0x80) // 上次有数据被接收到了
{
RmtSta &= ~0X10; // 取消上升沿已经被捕获标记
if ((RmtSta & 0X0F) == 0X00)
RmtSta |= 1 << 6; // 标记已经完成一次按键的键值信息采集
if ((RmtSta & 0X0F) < 14)
RmtSta++;
else
{
RmtSta &= ~(1 << 7); // 清空引导标识
RmtSta &= 0XF0; // 清空计数器
}
}
}
if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET)
{
if (RDATA) // 上升沿捕获
{
TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Falling); // CC4P=1 设置为下降沿捕获
TIM_SetCounter(TIM2, 0); // 清空定时器值
RmtSta |= 0X10; // 标记上升沿已经被捕获
}
else // 下降沿捕获
{
Dval = TIM_GetCapture1(TIM2); // 读取CCR4也可以清CC4IF标志位
TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Rising); // CC4P=0 设置为上升沿捕获
if (RmtSta & 0X10) // 完成一次高电平捕获
{
if (RmtSta & 0X80) // 接收到了引导码
{
if (Dval > 300 && Dval < 800) // 560为标准值,560us
{
RmtRec <<= 1; // 左移一位.
RmtRec |= 0; // 接收到0
}
else if (Dval > 1400 && Dval < 1800) // 1680为标准值,1680us
{
RmtRec <<= 1; // 左移一位.
RmtRec |= 1; // 接收到1
}
else if (Dval > 2200 && Dval < 2600) // 得到按键键值增加的信息 2500为标准值2.5ms
{
RmtCnt++; // 按键次数增加1次
RmtSta &= 0XF0; // 清空计时器
}
}
else if (Dval > 4200 && Dval < 4700) // 4500为标准值4.5ms
{
RmtSta |= 1 << 7; // 标记成功接收到了引导码
RmtCnt = 0; // 清除按键次数计数器
}
}
RmtSta &= ~(1 << 4);
}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update | TIM_IT_CC1);
}
// 处理红外键盘
// 返回值:
// 0,没有任何按键按下
// 其他,按下的按键键值.
uint8_t Remote_Scan(void)
{
uint8_t sta = 0;
uint8_t t1, t2;
if (RmtSta & (1 << 6)) // 得到一个按键的所有信息了
{
t1 = RmtRec >> 24; // 得到地址码
t2 = (RmtRec >> 16) & 0xff; // 得到地址反码
if ((t1 == (uint8_t)~t2) && t1 == REMOTE_ID) // 检验遥控识别码(ID)及地址
{
t1 = RmtRec >> 8;
t2 = RmtRec;
if (t1 == (uint8_t)~t2)
sta = t1; // 键值正确
}
RmtSta &= ~(1 << 6); // 清除接收到有效按键标识
RmtCnt = 0; // 清除按键次数计数器
}
return sta;
}
main.c
#include "stm32f10x.h" // Device header
#include "oled.h"
#include "delay.h"
#include "IR.h"
uint8_t IR_Address;
uint8_t IR_Command;
uint8_t IR_Count;
uint8_t *str = 0;
uint8_t key;
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
OLED_Init();
IR_Init();
OLED_ShowString(1,1,"ADDR HEX CMD NUM");
OLED_ShowString(2,1,"00 00 0 000");
while(1)
{
IR_Address = (RmtRec >> 24) & 0xFF;
IR_Command = (RmtRec >> 8) & 0xFF;
key = Remote_Scan();
if (key) {
switch (key) {
case IR_1:
str = "1";
break;
case IR_2:
str = "2";
break;
case IR_3:
str = "3";
break;
case IR_4:
str = "4";
break;
case IR_5:
str = "5";
break;
case IR_6:
str = "6";
break;
case IR_7:
str = "7";
break;
case IR_8:
str = "8";
break;
case IR_9:
str = "9";
break;
case IR_0:
str = "0";
break;
case IR_SP1:
str = "*";
break;
case IR_SP2:
str = "#";
break;
case IR_UP: //"↑"
IR_Count++;
break;
case IR_LEFT: //"←"
//
break;
case IR_DOWN: //"↓"
IR_Count--;
break;
case IR_RIGHT: //"→"
//
break;
case IR_CONFIRM:
str = "OK ";
break;
default:
str = "EMOURE";
break;
}
OLED_ShowHexNum(2, 1, IR_Address, 2);
OLED_ShowHexNum(2, 6, IR_Command, 2);
OLED_ShowString(2,12," ");
OLED_ShowString(2,11,str);
OLED_ShowNum(2, 14, IR_Count, 3);
}
Delay_ms(10);
}
}