红外遥控二进制信号的编码
红外遥控器发射的信号由一串「0」和「1」的二进制代码组成,不同的芯片对「0」和「1」的编码有所不同,通常有曼彻斯特 (Manchester) 编码和脉冲宽度编码 (PWM)。家用电器使用的红外遥控器绝大部分都是脉冲宽度编码,如下图所示:
红外遥控二进制信号的调制
二进制信号的调制由发送单片机芯片或红外遥控发射专用芯片来完成,把编码后的二进制信号调制成频率为 38kHz 的间断脉冲串,相当于用二进制信号的编码乘以频率为38kHz 的脉冲信号得到的间断脉冲串,即是调制后用于红外发射二极管发送的信号。通用红外遥控器里面常用的红外遥控发射专用芯片载波频率为 38kHz,这是由发射端所使用的 455kHz 陶瓷晶振来决定的。在发射端对晶振进行整数分频,分频系数一般取 12 所以 455kHz ÷ 12 ≈ 37.9kHz ≈ 38kHz。
NEC编码协议
在红外开发中,可能最重要的就是发送二进制信号的编码协议了,各个厂家所使用的编码协议不同,所以遥控器也不能相互控制,而即使编码协议相同,使用的用户码不同,也不能被接收端接受。所以类似万能遥控器这种应用,第一个要解决的问题就是各类家用电器,各类厂家所使用的编码协议以及所使用的用户码等。但各类电器众多,这种应用是很难兼容全的,有的大厂家会将自家产品的编码及对应功能键的数据序列公布在网上,方便其他开发者开发,至于其他没有公布的,可能就要使用红外解码仪来破解它所使用的协议以及各功能键对应的数据码等。
在日常家用电器中,NEC 编码是比较常见的一种编码协议, 通用红外遥控器发出的一串二进制代码按功能可以分为分「引导码、用户码 16 位、数据码 8 位、数据反码 8 位和结束位」,编码共占 32 位,如下图所示:
其中引导码由一个 9ms 的 38kHz 载波起始码和一个 4.5ms 的无载波低电平结果码组成。用户码由低 8 位和高 8 位组成 (用户码高八位和低八位可采用原码与反码的方式,可用于纠错,但也可直接是 16 位的原码方式),不同的遥控器有不同的用户码,避免不同设备产生干扰,用户码又称为地址码或系统码。数据码采用原码和反码方式重复发送,编码时用于对数据的纠错,遥控器发射编码时,低位在前,高位在后。结束位是 0.56ms 的 38kHz 载波。而其中的「0」码由 0.56ms 的 38kHz 载波和 0.56ms 的无载波低电平组合而成,脉冲宽度为 1.125ms,「1」码由 0.56ms 的 38kHz 载波和 1.69ms 的无载波低电平组合而成,脉冲宽度为 2.25ms,如下图所示:
红外遥控器解码
红外遥控器的解码仪能够分析常见家电的红外遥控信号,下面两种除外:
- 空调遥控器,空调的控制比较复杂,光光温度就可能调节十几次,难以破解。
- 灯光遥控器,灯本身发光发热,同时也会散发大量红外线,势必对外部的红外信号造成严重干扰;所以灯只能采取射频遥控器。
红外解码仪是家电维修人员的必备仪器,常用于检测遥控器能否正常工作,开发者为了让手机实现遥控功能,也要利用解码仪捕捉每个按键对应的红外信号,红外信号由三部分组成,分别是:
- 用户码
表示厂商代号,每个厂家都有自己的唯一代号; - 数据码
表示按键的编号,不同的数据码代表不同的按键; - 电路格式
表示红外信号的编码协议,每种协议都有专门的指令格式。
比如说电路61212表示的是NEC6121协议,该协议的红外信号编码格式为:引导码+用户码+数据码+数据反码+结束码,其中引导码和结束码都是固定的,数据反码由数据码按位取反得来,真正变化的只有用户码和数据码。
Android红外遥控功能开发
- 权限
<!--红外遥控-->
<uses-permission android:name="android.permission.TRANSMIT_IR" />
<!-- 是否仅在支持红外的设备上运行 -->
<uses-feature android:name="android.hardware.ConsumerIrManager" android:required="false" />
红外接口:
- hasIrEmitter()
用于检查设备是否具有红外发射器。返回true表示有,返回false表示没有。 - getCarrierFrequencies()
获得红外发射器可用的载波频率范围。 - transmit (int carrierFrequency, int[] pattern)
用于发射红外信号。
- 第一个参数为信号频率,单位赫兹(Hz),家用电器的红外频率通常使用38000Hz
- 第二个参数为整型数组形式的信号格式。
码值编码
一般家用电器遥控器的频率是38000,核心就在transmit()方法的信号编码参数上,上文第5部分提到过,编码由
引导码(9ms+4.5ms) + 用户编码(高八位)+ 用户编码(低八位) + 键数据码 + 键数据反码 + 结束码
组成,然后按照一定的编码规则,合成数组的形式。以NEC6122协议举例,引导码都是固定的(9000+4500),结束码即停止位可以按照(560,2000),不同遥控器差别主要在于用户码和数据码,同一个遥控器的用户码是一样的,不同按键有不同的码值,码值可以转换出对应的数据码和数据反码,以下面的遥控器码值举例。
用户码是0X08E6,按键2对应的码值是0X41,码值都是十六进制表示的,转化为二进制,用户码高八位08为00001000,用户码低八位E6为11100110,数据码41为01000001,数据反码为10111110。
由于手机与遥控器的信号编码有区别,需要逆序编码。逆序编码后,用户码高八位为00010000,用户码低八位为01100111,数据码为10000010,数据反码为01111101。
编码转换完成,但是transmit方法,参数要传递整型数组形式的信号,并不是二进制数而是电平信号数据。电平是电路中某一点电压的高低状态,在数字电路中常用高电平表示“1”,用低电平表示“0”。根据上文第四部分图三可以看出,遥控器发射红外信号之时,通过“560us低电平+1690us高电平”代表“1”,通过“560us低电平+565us低电平”代表“0”。于是编写Android代码的时候,使用“560,1690”表示二进制的1,使用“560,565”表示二进制的0,具体数组值如下所示:
int[] pattern = {9000,4500, // 开头两个数字表示引导码
// 下面两行表示用户码
560,565, 560,565, 560,565, 560,1690, 560,565, 560,565, 560,565, 560,565,
560,565, 560,1690, 560,1690, 560,565, 560,565, 560,1690, 560,1690, 560,1690,
// 下面一行表示数据码
560,1690, 560,565, 560,565, 560,565, 560,565, 560,565, 560,1690, 560,565,
// 下面一行表示数据反码
560,565, 560,1690, 560,1690, 560,1690, 560,1690, 560,1690, 560,565, 560,1690,
560,2000}; // 末尾两个数字表示结束码
具体这些转换工作用代码写出,如下:
package com.startimes.home.irManager.ircode;
import java.util.ArrayList;
import java.util.List;
/**
* 构造NEC协议的pattern编码
*/
public class NecPattern {
//引导码
private static final int startH = 9000;
private static final int startL = 4500;
//结束码
private static final int endL = 560;
private static final int endH = 2000;
//高电平
private static final int high8 = 560;
//低电平0:1125
private static final int low0 = 565;
//低电平1:2250
private static final int low1 = 1690;
private static int[] pattern;
private static List<Integer> list = new ArrayList<>();
/**
* 正常发码:引导码(9ms+4.5ms)+用户编码(高八位)+用户编码(低八位)+键数据码+键数据反码+结束码
*/
public static int[] buildPattern(int userCodeH, int userCodeL, int keyCode) {
//用户编码高八位00
String userH = constructBinaryCode(userCodeH);
//用户编码低八位DF
String userL = constructBinaryCode(userCodeL);
//数字码
String key = constructBinaryCode(keyCode);
//数字反码
String keyReverse = constructBinaryCode(~keyCode);
list.clear();
//引导码
list.add(startH);
list.add(startL);
//用户编码
changeAdd(userH);
changeAdd(userL);
//键数据码
changeAdd(key);
//键数据反码
changeAdd(keyReverse);
//结束码
list.add(endL);
list.add(endH);
int size = list.size();
pattern = new int[size];
for (int i = 0; i < size; i++) {
pattern[i] = list.get(i);
}
return pattern;
}
/**
* 十六进制键值转化为二进制串,并逆转编码
* @param keyCode
* @return
*/
private static String constructBinaryCode(int keyCode) {
String binaryStr = convertToBinary(keyCode);
char[] chars = binaryStr.toCharArray();
StringBuffer sb = new StringBuffer();
for (int i = 7; i >= 4; i--) {
sb.append(chars[i]);
}
for (int i = 3; i >= 0; i--) {
sb.append(chars[i]);
}
return sb.toString();
}
/**
* 数字转换为长度为8位的二进制字符串
* @return
*/
private static String convertToBinary(int num) {
String binary = Integer.toBinaryString(num);
StringBuffer sb8 = new StringBuffer();
//每个元素长度为8位,不够前面补充0
if (binary.length() < 8) {
for (int i = 0; i < 8 - binary.length(); i++) {
sb8.append("0");
}
String binaryStr8 = sb8.append(binary).toString();
return binaryStr8;
}else{
String binaryStr8 = binary.substring(binary.length() - 8);
return binaryStr8;
}
}
/**
* 二进制转成电平
*
* @param code
*/
public static void changeAdd(String code) {
int len = code.length();
String part;
for (int i = 0; i < len; i++) {
list.add(high8);
part = code.substring(i, i + 1);
if (part.equals("0"))
list.add(low0);
else
list.add(low1);
}
}
}