一、紅外遙控
紅外遙控技術(shù)是通過紅外技術(shù)、紅外通信技術(shù)和遙控技術(shù)的結(jié)合實(shí)現(xiàn)的一種無線控制技術(shù)。由于紅外線的波長(zhǎng)較短,對(duì)障礙物的衍射能力較差,無法穿透墻壁,所以紅外遙控術(shù)更適合應(yīng)用在短距離直線控制的場(chǎng)合,也正是這樣,放置在不同房間的家用電器可使用通用的遙控器而不會(huì)產(chǎn)生相互干擾。
紅外遙控所需傳輸?shù)臄?shù)據(jù)量較小,一般僅為幾個(gè)至幾十個(gè)字節(jié)的控制碼,傳輸距離一般小于 10 米,因其功耗小、成本低、易實(shí)現(xiàn)等諸多優(yōu)點(diǎn),被廣泛應(yīng)用于電視機(jī)、機(jī)頂盒、DVD 播放器、功放、空調(diào)等家用電器的遙控。
二、手機(jī)紅外遙控功能
部分智能手機(jī)都配置了是紅外遙控功能(即安裝紅了外發(fā)射器)。那么,安裝了紅外發(fā)射器的智能手機(jī),可以拿來當(dāng)遙控器使用,還能一部手機(jī)遙控許多家電。具有紅外功能的智能手機(jī)的頂部,有的鑲嵌一個(gè)或多個(gè)小燈泡,有的是一小片黑色蓋子,這個(gè)黑蓋子對(duì)紅外線來說可是透明的,只是人的肉眼看不穿它。紅外遙控帶著燈泡就像一支手電筒,紅外光照到哪里,哪里的電器才會(huì)接收響應(yīng),這決定了紅外遙控的三個(gè)特性:
- 遙控器要對(duì)準(zhǔn)電器才有反應(yīng)。
- 遙控器不能距離電器太遠(yuǎn),最好是五米之內(nèi)。
- 遙控器與電器之間不能有障礙物。
安裝了紅外發(fā)射器的手機(jī),可以拿來當(dāng)遙控器使用,還能一部手機(jī)遙控許多家電,這就需要破解電器的信號(hào)編碼了。
三、紅外發(fā)射原理簡(jiǎn)介
通用紅外遙控系統(tǒng)主要由發(fā)射和接收兩大部分組成。發(fā)射部分包括單片機(jī)芯片或紅外遙控發(fā)射專用芯片實(shí)現(xiàn)編碼和調(diào)制,紅外發(fā)射電路實(shí)現(xiàn)發(fā)射;接收部分包括一體化紅外接收頭電路實(shí)現(xiàn)接收和解調(diào),單片機(jī)芯片實(shí)現(xiàn)解碼。紅外遙控發(fā)射專用芯片非常多,編碼及調(diào)制頻率也不完全一樣。手機(jī)實(shí)現(xiàn)紅外遙控功能,主要就是發(fā)射紅外信號(hào)部分,這就需要了解下紅外信號(hào)的編碼和調(diào)制原理。
3.1.紅外遙控二進(jìn)制信號(hào)的編碼
紅外遙控器發(fā)射的信號(hào)由一串「0」和「1」的二進(jìn)制代碼組成,不同的芯片對(duì)「0」和「1」的編碼有所不同,通常有曼徹斯特 (Manchester) 編碼和脈沖寬度編碼 (PWM)。家用電器使用的紅外遙控器絕大部分都是脈沖寬度編碼,如下圖所示:

3.2.紅外遙控二進(jìn)制信號(hào)的調(diào)制
二進(jìn)制信號(hào)的調(diào)制由發(fā)送單片機(jī)芯片或紅外遙控發(fā)射專用芯片來完成,把編碼后的二進(jìn)制信號(hào)調(diào)制成頻率為 38kHz 的間斷脈沖串,相當(dāng)于用二進(jìn)制信號(hào)的編碼乘以頻率為38kHz 的脈沖信號(hào)得到的間斷脈沖串,即是調(diào)制后用于紅外發(fā)射二極管發(fā)送的信號(hào)。通用紅外遙控器里面常用的紅外遙控發(fā)射專用芯片載波頻率為 38kHz,這是由發(fā)射端所使用的 455kHz 陶瓷晶振來決定的。在發(fā)射端對(duì)晶振進(jìn)行整數(shù)分頻,分頻系數(shù)一般取 12 所以 455kHz ÷ 12 ≈ 37.9kHz ≈ 38kHz。
四、 NEC編碼協(xié)議
在紅外開發(fā)中,可能最重要的就是發(fā)送二進(jìn)制信號(hào)的編碼協(xié)議了,各個(gè)廠家所使用的編碼協(xié)議不同,所以遙控器也不能相互控制,而即使編碼協(xié)議相同,使用的用戶碼不同,也不能被接收端接受。所以類似萬能遙控器這種應(yīng)用,第一個(gè)要解決的問題就是各類家用電器,各類廠家所使用的編碼協(xié)議以及所使用的用戶碼等。但各類電器眾多,這種應(yīng)用是很難兼容全的,有的大廠家會(huì)將自家產(chǎn)品的編碼及對(duì)應(yīng)功能鍵的數(shù)據(jù)序列公布在網(wǎng)上,方便其他開發(fā)者開發(fā),至于其他沒有公布的,可能就要使用紅外解碼儀來破解它所使用的協(xié)議以及各功能鍵對(duì)應(yīng)的數(shù)據(jù)碼等。
在日常家用電器中,NEC 編碼是比較常見的一種編碼協(xié)議, 通用紅外遙控器發(fā)出的一串二進(jìn)制代碼按功能可以分為分「引導(dǎo)碼、用戶碼 16 位、數(shù)據(jù)碼 8 位、數(shù)據(jù)反碼 8 位和結(jié)束位」,編碼共占 32 位,如下圖2所示:

其中引導(dǎo)碼由一個(gè) 9ms 的 38kHz 載波起始碼和一個(gè) 4.5ms 的無載波低電平結(jié)果碼組成。用戶碼由低 8 位和高 8 位組成 (用戶碼高八位和低八位可采用原碼與反碼的方式,可用于糾錯(cuò),但也可直接是 16 位的原碼方式),不同的遙控器有不同的用戶碼,避免不同設(shè)備產(chǎn)生干擾,用戶碼又稱為地址碼或系統(tǒng)碼。數(shù)據(jù)碼采用原碼和反碼方式重復(fù)發(fā)送,編碼時(shí)用于對(duì)數(shù)據(jù)的糾錯(cuò),遙控器發(fā)射編碼時(shí),低位在前,高位在后。結(jié)束位是 0.56ms 的 38kHz 載波。而其中的「0」碼由 0.56ms 的 38kHz 載波和 0.56ms 的無載波低電平組合而成,脈沖寬度為 1.125ms,「1」碼由 0.56ms 的 38kHz 載波和 1.69ms 的無載波低電平組合而成,脈沖寬度為 2.25ms,如下圖所示:

五、紅外遙控器解碼
紅外遙控器的解碼儀能夠分析常見家電的紅外遙控信號(hào),下面兩種除外:
1、空調(diào)遙控器,空調(diào)的控制比較復(fù)雜,光光溫度就可能調(diào)節(jié)十幾次,難以破解。
2、燈光遙控器,燈本身發(fā)光發(fā)熱,同時(shí)也會(huì)散發(fā)大量紅外線,勢(shì)必對(duì)外部的紅外信號(hào)造成嚴(yán)重干擾;所以燈只能采取射頻遙控器。
紅外解碼儀是家電維修人員的必備儀器,常用于檢測(cè)遙控器能否正常工作,開發(fā)者為了讓手機(jī)實(shí)現(xiàn)遙控功能,也要利用解碼儀捕捉每個(gè)按鍵對(duì)應(yīng)的紅外信號(hào),紅外信號(hào)由三部分組成,分別是:
- 用戶碼,表示廠商代號(hào),每個(gè)廠家都有自己的唯一代號(hào);
- 數(shù)據(jù)碼,表示按鍵的編號(hào),不同的數(shù)據(jù)碼代表不同的按鍵;
- 電路格式,表示紅外信號(hào)的編碼協(xié)議,每種協(xié)議都有專門的指令格式。
比如說電路61212表示的是NEC6121協(xié)議,該協(xié)議的紅外信號(hào)編碼格式為:引導(dǎo)碼+用戶碼+數(shù)據(jù)碼+數(shù)據(jù)反碼+結(jié)束碼,其中引導(dǎo)碼和結(jié)束碼都是固定的,數(shù)據(jù)反碼由數(shù)據(jù)碼按位取反得來,真正變化的只有用戶碼和數(shù)據(jù)碼。
六、 Android紅外遙控功能開發(fā)
1、紅外權(quán)限配置
在App工程的AndroidManifest.xml中補(bǔ)充紅外權(quán)限配置
<!--紅外遙控-->
<uses-permission android:name="android.permission.TRANSMIT_IR" />
<!-- 是否僅在支持紅外的設(shè)備上運(yùn)行 -->
<uses-feature android:name="android.hardware.ConsumerIrManager" android:required="false" />
2、紅外功能API
紅外遙控功能從Android4.4之后才開始支持,對(duì)應(yīng)的管理類名叫ConsumerIrManager,常用的三個(gè)方法分別是:
hasIrEmitter (): 用于檢查設(shè)備是否具有紅外發(fā)射器。返回true表示有,返回false表示沒有。
getCarrierFrequencies() : 獲得紅外發(fā)射器可用的載波頻率范圍。
transmit (int carrierFrequency, int[] pattern) : 用于發(fā)射紅外信號(hào)。
第一個(gè)參數(shù)為信號(hào)頻率,單位赫茲(Hz),家用電器的紅外頻率通常使用38000Hz;第二個(gè)參數(shù)為整型數(shù)組形式的信號(hào)格式。
下邊是一個(gè)封裝好的紅外遙控管理類
package com.startimes.home.irManager.manager;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
@TargetApi(Build.VERSION_CODES.KITKAT)
public class ConsumerIrManagerApi {
private static ConsumerIrManagerApi instance;
private static android.hardware.ConsumerIrManager service;
private ConsumerIrManagerApi(Context context) {
//Android4.4才開始支持紅外功能
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
// 獲取系統(tǒng)的紅外遙控服務(wù)
service = (android.hardware.ConsumerIrManager) context.getApplicationContext().getSystemService(Context.CONSUMER_IR_SERVICE);
}
}
public static ConsumerIrManagerApi getConsumerIrManager(Context context){
if(instance == null){
instance = new ConsumerIrManagerApi(context);
}
return instance;
}
/**
* 手機(jī)是否有紅外功能
* @return
*/
public static boolean hasIrEmitter() {
//android4.4及以上版本&有紅外功能
if(service!=null){
return service.hasIrEmitter();
}
//android4.4以下及4.4以上沒紅外功能
return false;
}
/**
* 發(fā)射紅外信號(hào)
* @param carrierFrequency 紅外頻率
* @param pattern
*/
public static void transmit(int carrierFrequency, int[] pattern) {
if(service!=null){
service.transmit(carrierFrequency, pattern);
}
}
/**
* 獲取可支持的紅外信號(hào)頻率
* @return
*/
public static android.hardware.ConsumerIrManager.CarrierFrequencyRange[] getCarrierFrequencies() {
if(service!=null){
return service.getCarrierFrequencies();
}
return null;
}
}
3、碼值編碼
上文提到過,一般家用電器遙控器的頻率是38000,核心就在transmit()方法的信號(hào)編碼參數(shù)上,上文第5部分提到過,編碼由“引導(dǎo)碼(9ms+4.5ms)+用戶編碼(高八位)+用戶編碼(低八位)+鍵數(shù)據(jù)碼+鍵數(shù)據(jù)反碼+結(jié)束碼”組成,然后按照一定的編碼規(guī)則,合成數(shù)組的形式。以NEC6122協(xié)議舉例,引導(dǎo)碼都是固定的(9000+4500),結(jié)束碼即停止位可以按照(560,2000),不同遙控器差別主要在于用戶碼和數(shù)據(jù)碼,同一個(gè)遙控器的用戶碼是一樣的,不同按鍵有不同的碼值,碼值可以轉(zhuǎn)換出對(duì)應(yīng)的數(shù)據(jù)碼和數(shù)據(jù)反碼,以下面的遙控器碼值舉例。

用戶碼是0X08E6,按鍵2對(duì)應(yīng)的碼值是0X41,碼值都是十六進(jìn)制表示的,轉(zhuǎn)化為二進(jìn)制,用戶碼高八位08為00001000,用戶碼低八位E6為11100110,數(shù)據(jù)碼41為01000001,數(shù)據(jù)反碼為10111110。
由于手機(jī)與遙控器的信號(hào)編碼有區(qū)別,需要逆序編碼。逆序編碼后,用戶碼高八位為00010000,用戶碼低八位為01100111,數(shù)據(jù)碼為10000010,數(shù)據(jù)反碼為01111101。
編碼轉(zhuǎn)換完成,但是transmit方法,參數(shù)要傳遞整型數(shù)組形式的信號(hào),并不是二進(jìn)制數(shù)而是電平信號(hào)數(shù)據(jù)。電平是電路中某一點(diǎn)電壓的高低狀態(tài),在數(shù)字電路中常用高電平表示“1”,用低電平表示“0”。根據(jù)上文第四部分圖三可以看出,遙控器發(fā)射紅外信號(hào)之時(shí),通過“560us低電平+1690us高電平”代表“1”,通過“560us低電平+565us低電平”代表“0”。于是編寫Android代碼的時(shí)候,使用“560,1690”表示二進(jìn)制的1,使用“560,565”表示二進(jìn)制的0,具體數(shù)組值如下所示:
int[] pattern = {9000,4500, // 開頭兩個(gè)數(shù)字表示引導(dǎo)碼
// 下面兩行表示用戶碼
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,
// 下面一行表示數(shù)據(jù)碼
560,1690, 560,565, 560,565, 560,565, 560,565, 560,565, 560,1690, 560,565,
// 下面一行表示數(shù)據(jù)反碼
560,565, 560,1690, 560,1690, 560,1690, 560,1690, 560,1690, 560,565, 560,1690,
560,20000}; // 末尾兩個(gè)數(shù)字表示結(jié)束碼
具體這些轉(zhuǎn)換工作用代碼寫出,如下:
package com.startimes.home.irManager.ircode;
import java.util.ArrayList;
import java.util.List;
/**
* Copyright : huangr
* Created by huangr on 2019/8/8.
* ClassName : NecPattern
* Description : 構(gòu)造NEC協(xié)議的pattern編碼
*/
public class NecPattern {
//引導(dǎo)碼
private static final int startH = 9000;
private static final int startL = 4500;
//結(jié)束碼
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<>();
/**
* 正常發(fā)碼:引導(dǎo)碼(9ms+4.5ms)+用戶編碼(高八位)+用戶編碼(低八位)+鍵數(shù)據(jù)碼+鍵數(shù)據(jù)反碼+結(jié)束碼
*/
public static int[] buildPattern(int userCodeH, int userCodeL, int keyCode) {
//用戶編碼高八位00
String userH = constructBinaryCode(userCodeH);
//用戶編碼低八位DF
String userL = constructBinaryCode(userCodeL);
//數(shù)字碼
String key = constructBinaryCode(keyCode);
//數(shù)字反碼
String keyReverse = constructBinaryCode(~keyCode);
list.clear();
//引導(dǎo)碼
list.add(startH);
list.add(startL);
//用戶編碼
changeAdd(userH);
changeAdd(userL);
//鍵數(shù)據(jù)碼
changeAdd(key);
//鍵數(shù)據(jù)反碼
changeAdd(keyReverse);
//結(jié)束碼
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;
}
/**
* 十六進(jìn)制鍵值轉(zhuǎn)化為二進(jìn)制串,并逆轉(zhuǎn)編碼
* @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();
}
/**
* 數(shù)字轉(zhuǎn)換為長(zhǎng)度為8位的二進(jìn)制字符串
* @return
*/
private static String convertToBinary(int num) {
String binary = Integer.toBinaryString(num);
StringBuffer sb8 = new StringBuffer();
//每個(gè)元素長(zhǎng)度為8位,不夠前面補(bǔ)充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;
}
}
/**
* 二進(jìn)制轉(zhuǎn)成電平
*
* @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);
}
}
}
4、使用
在需要發(fā)射紅外信號(hào)處調(diào)用如下:
/**
* 按下按鍵發(fā)射紅外信號(hào)
*/
public void transmit() {
ConsumerIrManagerApi.transmit(38000,NecPattern.buildPattern(NecPattern.buildPattern(0X08,0XE6, 0X41));
}